Skip to content

feat(agent-testing): add a Template case type and let turns attach files - #1473

Merged
iceljc merged 10 commits into
SciSharp:masterfrom
yileicn:master
Oct 31, 2026
Merged

iceljc merged 10 commits into
SciSharp:masterfrom
yileicn:master

Conversation

@yileicn

@yileicn yileicn commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Summary

Agent-testing plugin: a new Template case type with turn files, plus a round of fixes to run state, cost reporting and AI case authoring.

Template cases and turn files

  • A Template case calls one of the entry agent's templates directly, the way code such as a photo check calls it outside any conversation, so a template can be tested without routing, function calls or dialog history.
  • Turns can attach files, which both Template and Agent cases pass on to the model, so vision templates can be tested with real photos. The Anthropic provider now sends URL-only images as URL image sources instead of dropping them.
  • Saving a Template case requires entryAgentId and templateName, checks that the agent has that template (exact, case-sensitive match, as the runtime looks it up), and rejects history.

Run state

  • Cancel sets CancelRequested with a single-field update on Pending or Running runs instead of replacing the run document, which could revert a finished run to Running or lose case counts. It returns 409 when the run already finished.
  • UpdateRunAsync only replaces while the stored CancelRequested matches, so the executor's whole-document writes never undo a cancel; the executor also honours a cancel sent while the run was Pending.
  • A case PUT without enabled keeps the current value instead of enabling an unreviewed draft.
  • Abandoned runs are failed when read, not swept on startup. The startup sweep failed every Running run, including runs another host was still executing (several pods, or a local host sharing the database). Runs now carry UpdateDate, set on every write and by a 30s executor heartbeat while cases run; listing, reading or deleting a run fails it when it is Running with no heartbeat for 3 minutes. The heartbeat is independent of case completions, since PrepareAsync and the judge's provider call are not cut off by the case timeout. Failing is an atomic update conditioned on the run still being Running with the UpdateDate that was read, and sets only Status, Error and CompletedAt, so a stale read never overwrites newer progress or a finished status. Pending runs lost with a host stay Pending until triggered again.
  • A cancel carried over by the executor's per-case write stops the next case; it was checked before that write and missed.
  • Runs record PlannedCount (cases x models) when they start Running, so progress reads as finished / planned.

Cost reporting

  • The queued run executor now gets ILlmProviderService, so the pricing snapshot is filled and the run page no longer shows unit cost as unknown when model pricing is configured.
  • llmJudge spend is no longer billed to the model under test: the judge resolves its provider from the case's scope, so its tokens landed in the case's TotalTokens/Cost. It is now recorded as JudgeTokens/JudgeCost on each case result and summed per model in the performance summary.
  • Each run records the judge model and its unit costs as JudgePricing, taken from the suite it executed with, since the suite's judge setting can change afterwards.
  • The latency, token and cost summary is recomputed after every case instead of only at the end, so the run page shows spend while a run goes and a run whose host died keeps the figures for the cases that finished.

AI case authoring

  • Mocks and tool assertions naming a function the entry agent lacks are kept with a warning instead of dropped: mocks match for every agent in the conversation, so a routed-to agent's function is valid.
  • When the model rewrites turns without their files, the files are restored by position if the turn count is unchanged; otherwise a warning reports the loss.
  • The author can declare templateName, so it can draft and repair a Template case.

UI

Matching UI changes: SciSharp/BotSharp-UI#494.

Test plan

  • Unit tests: BotSharp.Core.UnitTests AgentTesting (369 passed)
  • Cancel race, conditional replace, heartbeat and conditional abandon checked against a real MongoDB
  • Anthropic URL image source against the live Claude API
  • Run a Template case with image files end to end
  • Run a suite with an llmJudge assertion; check JudgeTokens/JudgeCost on results and the summary

🤖 Generated with Claude Code

yileicn and others added 2 commits October 31, 2026 11:00
- A Template case calls one of the entry agent's templates directly, the
  way code such as a photo check calls it outside any conversation, so a
  template can be tested without routing, function calls or dialog history
- Turns can now attach files, which both Template and Agent cases pass on
  to the model, so vision templates can be tested with real photos
- Saving a Template case requires entryAgentId and templateName, checks
  that the agent has that template, and rejects history
- The queue built AgentTestRunExecutor without ILlmProviderService, so
  SnapshotPricing always returned empty and the run page showed unit
  cost as unknown even with model pricing configured. Case cost was
  unaffected: TokenStatistics resolves the service on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@qodo-code-review

Copy link
Copy Markdown
Contributor

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

yileicn and others added 5 commits October 31, 2026 12:41
- Cancel now sets CancelRequested with a single-field update on Pending or
  Running runs instead of replacing the run document, which could revert
  a finished run to Running or lose case counts.
- UpdateRunAsync only replaces while the stored CancelRequested matches,
  so the executor's whole-document writes never undo a cancel; the
  executor also honours a cancel sent while the run was Pending.
- A case PUT without "enabled" keeps the current value instead of
  enabling an unreviewed draft.
- The startup sweep also fails Pending runs created before the process
  started; they were lost with the old in-process queue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… drafts

- Mocks and tool assertions naming a function the entry agent lacks are
  kept with a warning: mocks match for every agent in the conversation,
  so a routed-to agent's function is valid and was being deleted.
- When the model rewrites turns without their files, the files are
  restored by position if the turn count is unchanged, otherwise a
  warning reports the loss.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…test

- The judge resolves its completion provider from the case's scope, so
  its tokens and cost landed in the case's TotalTokens/Cost and skewed
  model comparisons. The runner now measures each judge call and takes
  it back out.
- Judge spend is recorded on its own as JudgeTokens/JudgeCost on each
  case result and summed per model in the performance summary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n startup

- The startup sweep failed every Running run, including runs another
  host was still executing: dev runs several pods, and a local host can
  share the same database. Those runs finished but kept the "host
  restarted" error text.
- Runs now carry UpdateDate, set on every write. Listing, reading or
  deleting a run fails it when it is Running with no write for twice
  the suite's CaseTimeoutSeconds; a live executor writes after every
  case, and each case is cut off at that timeout.
- Pending runs lost with a host are no longer swept; they stay Pending
  until triggered again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The run report showed judge spend next to the tested model's unit cost, with no way to tell which model the judge ran on
- Taken from the suite the run executed with, since the suite's judge setting can change afterwards

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@yileicn
yileicn requested review from iceljc and a balanced review from Copilot October 31, 2026 07:35

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.

🟡 Changes recommended

Abandonment checks can overwrite newer run progress or misclassify still-executing cases as failed, potentially allowing live runs to be deleted.

7 open issues
What changed in this PR

Extends the agent-testing plugin with direct Template cases and file attachments, alongside improvements to run management, cost reporting, and AI-assisted case authoring.

Changes:

  • Adds Template case validation and execution, with attached files supported in both Template and Agent turns.
  • Revises cancellation and abandoned-run handling, preserves omitted case-enabled values, and separates judge usage from model-under-test costs.
  • Preserves routed-agent mocks and assertions with warnings, restores omitted turn files, and adds regression tests.
File Description
CaseAuthoringTests.cs Tests warning-based retention and turn-file restoration.
AgentTestRunExecutorTests.cs Tests pending cancellation and separate judge totals.
AgentTestJudgeTests.cs Updates the driver stub for files and Template calls.
AgentTestControllerTests.cs Tests abandoned-run detection and deletion.
AgentTestCaseRunnerTests.cs Tests Template execution, attachments, and judge accounting.
LlmCaseAuthor.cs Restores files and retains unfamiliar tool names with warnings.
IAgentConversationDriver.cs Adds file parameters and direct Template execution.
CaseValidation.cs Validates required Template fields and rejects history.
BotSharpAgentConversationDriver.cs Forwards attachments and invokes templates through InstructService.
AgentTestRunQueue.cs Removes startup reconciliation and supplies pricing services.
AgentTestRunExecutor.cs Honors pending cancellation and summarizes judge pricing and spend.
AgentTestCaseRunner.cs Dispatches Template calls and separates judge usage.
AgentTestRepository.cs Adds targeted cancellation writes and progress timestamps.
AgentTestRun.cs Adds judge pricing, judge totals, and update timestamps.
AgentTestDtos.cs Adds template names and nullable enabled updates.
AgentTestCaseResult.cs Records judge tokens and cost separately.
AgentTestCase.cs Defines Template cases and turn attachments.
AgentTestController.cs Validates templates and updates cancellation and abandonment handling.

Review effort: Balanced

yileicn and others added 3 commits October 31, 2026 15:54
- TotalCount only counts finished executions, so a run never said how
  many it set out to do. PlannedCount (cases x models) is written with
  the Running status.
- The latency, token and cost summary was computed only at the end, so
  the run page had no cost table mid-run and a run whose host died had
  none at all. It is now recomputed after every case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Liveness came from case completions, but PrepareAsync and the judge's
  provider call are not cut off by the case timeout, and the suite's
  timeout can change mid-run, so a live run could go quiet for longer
  than the threshold, be failed and then deleted. The executor now
  stamps UpdateDate every 30s while cases run; a Running run is failed
  after 3 minutes without one.
- Failing an abandoned run was a whole-document replace of the copy
  just read, which could overwrite newer progress or a finished status.
  It is now an atomic update that applies only while the run is still
  Running with the UpdateDate that was read, and sets only Status,
  Error and CompletedAt; otherwise the current run is reloaded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…image URLs

- Template names are checked with an ordinal match on save, as
  AgentService.RenderTemplate looks them up with ==; a name matching
  only ignoring case saved fine and then rendered an empty instruction.
- The case author can declare templateName, so it can draft and repair
  a Template case.
- A cancel carried over by the per-case write now stops the next case;
  it was checked before the write and missed.
- The Anthropic provider sends URL-only images as URL image sources
  instead of dropping them, so vision cases with file URLs reach Claude.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@iceljc
iceljc merged commit 4cef505 into SciSharp:master Oct 31, 2026
4 checks passed
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.

3 participants