Skip to content

Recover complete object from concatenated JSON in sampling responses#53

Merged
m-nash merged 9 commits into
mainfrom
fix/sampling-concatenated-objects
Jul 6, 2026
Merged

Recover complete object from concatenated JSON in sampling responses#53
m-nash merged 9 commits into
mainfrom
fix/sampling-concatenated-objects

Conversation

@m-nash

@m-nash m-nash commented Jul 2, 2026

Copy link
Copy Markdown
Owner

Summary

Follow-up to #52. Fixes another variant of the sampling failure that surfaces as Sampling failed to explain comment after 2 attempts / 'e' is invalid after a value (Path: $).

The sampling backend can return a truncated false-start object and the complete object concatenated in a single content block (blocks=1):

{"explanation": "...no longer enfor
{"explanation": "The reviewer flags...", "recommendation": "...", "recommendationType": "pushback"}

#52 handled the case where these arrive as separate content blocks (blocks=2) by trying each block. But when both objects are in one block, per-block selection has nothing to split, so a direct parse still fails (the first unterminated string swallows up to the second object's key-quote, landing on the e of the second "explanation").

Changes

  • TryDeserialize<T>: on direct-parse failure, fall back to TryExtractBestObject<T>, which scans each {, deserializes one object via Utf8JsonReader (ignoring trailing content), and keeps the object that consumes the most bytes. The truncated false-start fails to parse; the complete object wins.
  • Bumped the failure-log truncation 500 -> 2000 so future malformations are fully visible in the debug log (the 500 cap hid this one).

Tests

  • SingleBlockTwoObjects_UsesCompleteObject — the exact blocks=1 two-object failure.
  • TrailingProseAfterObject_StillParses — bonus: a complete object followed by trailing prose.

All 352 tests pass; both new tests verified to fail against the previous code. Composes with #52, covering blocks=1 and blocks=2. Validated live on a dev build before opening.

The sampling backend can return a truncated false-start object and the
complete object concatenated in a single content block, which a direct
parse rejects. On parse failure, scan for the largest parseable top-level
object (via Utf8JsonReader, ignoring trailing content) and use it. Also
bump the failure-log truncation to 2000 chars for diagnosability.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens SamplingHelper.SampleStructuredAsync<T> against a sampling-backend variant where a truncated false-start JSON object and the complete object are concatenated within a single content block, causing direct JSON deserialization to fail.

Changes:

  • Adds a TryDeserialize<T> fallback that scans for candidate { starts and selects the deserializable object that consumes the most bytes (recovering the complete object when multiple objects are concatenated).
  • Increases failure-log truncation for raw sampling blocks from 500 to 2000 characters to make malformed responses more visible.
  • Adds regression tests covering “single block, two objects” and “valid JSON followed by trailing prose”.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
PrCopilot/src/PrCopilot/Tools/SamplingHelper.cs Adds fallback object-extraction logic for concatenated JSON and increases debug-log truncation size.
PrCopilot/tests/PrCopilot.Tests/SamplingHelperTests.cs Adds tests for concatenated-objects-in-one-block and trailing-prose parsing behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread PrCopilot/src/PrCopilot/Tools/SamplingHelper.cs
Comment thread PrCopilot/src/PrCopilot/Tools/SamplingHelper.cs
m-nash and others added 3 commits July 2, 2026 12:50
Add the legitimate term 'parseable' to cspell.json and replace the
placeholder word fragments 'trunc'/'enfor' in a comment example and a test
string with real words, so cspell strict mode passes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Break once a candidate consumes the entire remaining suffix (no later start
can consume more), avoiding O(n^2) allocations on brace-heavy text, and
explain why the catch blocks intentionally skip invalid candidate starts.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread PrCopilot/src/PrCopilot/Tools/SamplingHelper.cs Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread PrCopilot/src/PrCopilot/Tools/SamplingHelper.cs Outdated
Replace string.Join(...).Truncate(...) (which materializes the full joined
string first) with JoinCapped, which stops appending at the cap so the log
allocation stays bounded even when a content block is very large.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comment thread PrCopilot/src/PrCopilot/Tools/SamplingHelper.cs
Comment thread PrCopilot/tests/PrCopilot.Tests/SamplingHelperTests.cs
Only append the separator when the next part still fits under maxLength,
so the separator can't push sb.Length past the cap and drive remaining
negative (which threw ArgumentOutOfRangeException in the failure-logging
path). Add a regression test for the first-part-leaves-<-separator case.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread PrCopilot/src/PrCopilot/Tools/SamplingHelper.cs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

Comment thread PrCopilot/src/PrCopilot/Tools/SamplingHelper.cs
The separator-doesn't-fit break exited without the '...' marker, so a
truncated diagnostic log looked complete. Append the marker before breaking,
and add tests asserting it appears on between-parts truncation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@m-nash m-nash merged commit ecff5d7 into main Jul 6, 2026
8 checks passed
@m-nash m-nash deleted the fix/sampling-concatenated-objects branch July 6, 2026 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants