Skip to content

fix: price routed turns using the serving model - #4579

Open
aheritier wants to merge 6 commits into
mainfrom
fix/4576-serving-model-accounting
Open

aheritier wants to merge 6 commits into
mainfrom
fix/4576-serving-model-accounting

Conversation

@aheritier

Copy link
Copy Markdown
Collaborator

🤖 Automated implementer agent — this comment was posted by the implementer bot from Docker Agentic Platform, not by a human developer

Fixes #4576.

Summary

  • Attach request-bound serving identity and effective configuration to routed streams; price only the successful attempt, not a router's mutable last selection.
  • Use the serving leaf's catalogue entry, explicit cost: overrides, cache-read/write rates and actual response service tier, including same-ID routing and resolved custom endpoints.
  • Attribute messages, hooks, usage and telemetry consistently; retain no-match accounting overrides and outer fallback retry/cooldown behavior.
  • Preserve optional serving metadata through provider instrumentation and reconcile the sidebar across fallback routers, ordinary and idle retries.

Validation

  • task build, task test, task lint, task test-race — pass.
  • go test -count=1 ./pkg/model/provider/ ./pkg/model/provider/rulebased/ ./pkg/runtime/ -run 'TestServingModel|TestLastSelectedModelID_Concurrent|TestRouted|TestFallbackPricing|TestRunStreamServiceTierCost' — pass.
  • go test -race -count=2 ./pkg/model/provider/... ./pkg/runtime/ ./pkg/leantui/ -run 'TestServingModel|TestLastSelectedModelID_Concurrent|TestRouted|TestInstrument' — pass.
  • go test -race -shuffle=on ./pkg/runtime/ ./pkg/model/provider/... ./pkg/leantui/ — pass.

New tests use local fake providers and a fixed catalogue. They check attribution and matching costs across hooks, session messages, usage, budgets and telemetry, plus free/unpriced models, endpoint policy, no-match routing and stale selections after successful outer fallback. The pricing regressions fail against the unmodified base; the no-match and outer-fallback controls pass there.

No live/paid inference or SDK changes. This addresses accounting, not a demonstrated concurrent accounting defect. The pre-existing no-match provider construction behavior is not changed. Serving metadata is available at stream creation and stable after close; fallback identity notifications now arrive at stream open.

Signed-off-by: Arnaud Héritier <aheritier@users.noreply.github.com>
Signed-off-by: Arnaud Héritier <aheritier@users.noreply.github.com>
Signed-off-by: Arnaud Héritier <aheritier@users.noreply.github.com>
Signed-off-by: Arnaud Héritier <aheritier@users.noreply.github.com>
Signed-off-by: Arnaud Héritier <aheritier@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.

🟢 Approval recommended

The implementation consistently propagates serving identity and includes comprehensive regression coverage for pricing, fallback, concurrency, telemetry, and UI behavior.

0 open findings

What changed in this PR

Adds request-bound serving-model metadata so routed turns are priced and attributed to the provider that successfully served them.

Changes:

  • Propagates serving identity and configuration through routed and instrumented streams.
  • Uses serving-model pricing, endpoint, cache rates, and service tier throughout runtime accounting.
  • Adds routing, fallback, concurrency, telemetry, and TUI regression coverage.
File Description
pkg/​runtime/​streaming.go Carries serving metadata in stream results.
pkg/​runtime/​routing_pricing_test.go Tests routed pricing and fallback attribution.
pkg/​runtime/​loop.go Prices and attributes turns using serving metadata.
pkg/​runtime/​fallback.go Captures serving metadata and emits identity updates.
pkg/​model/​provider/​serving_test.go Tests metadata propagation through the registry.
pkg/​model/​provider/​rulebased/​serving_test.go Tests router serving metadata behavior.
pkg/​model/​provider/​rulebased/​client.go Wraps routed streams with serving identity and configuration.
pkg/​model/​provider/​rulebased/​client_test.go Adapts selection tests to the updated API.
pkg/​model/​provider/​rulebased/​client_race_test.go Tests request-bound identity under concurrency.
pkg/​model/​provider/​provider.go Exposes serving metadata contracts.
pkg/​model/​provider/​instrument.go Preserves metadata through telemetry wrapping.
pkg/​model/​provider/​contracts/​contracts.go Defines serving-model and stream contracts.
pkg/​leantui/​serving_model_test.go Tests fallback model display reconciliation.
docs/​configuration/​routing/​index.md Documents routed cost and attribution semantics.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@aheritier aheritier added area/models LLM model integrations and model providers area/runtime Runtime engine, agent loop execution, tool dispatch, loop detection kind/fix PR fixes a bug (maps to fix:). Use on PRs only. status/needs-signed-commits Some commits in the PR are signed with a valid SSH/GPG key labels Oct 13, 2026
@aheritier

This comment was marked as resolved.

@aheritier

Copy link
Copy Markdown
Collaborator Author

🤖 Automated implementer agent — this comment was posted by the implementer bot from Docker Agentic Platform, not by a human developer

Checked the current PR commit list and each commit's GitHub verification record. All five commits already report verified: true, reason: valid:

Commit GitHub verification
990978008ecb3d777870e90480ab1c16c3def0d6 Verified (valid)
bd2bb64df196f767cee6ea8673cf384b7fb439a6 Verified (valid)
d63d780de095487fee7e8aea6e1d7ad91636ce74 Verified (valid)
d6e5ccb12693c2e13306f139b5560bbc52b57ff7 Verified (valid)
d7420c36746a2b31ecab1a5f4f88c7e79af9260c Verified (valid)

The referenced 8c19299 is not in the current PR commit list. Local HEAD, remote branch, and PR head all match d7420c36746a2b31ecab1a5f4f88c7e79af9260c. No signing rewrite or force-push was needed, so the reviewed code and commit history remain unchanged. The status/needs-signed-commits label is still present at inspection; leaving it for the automatic re-check described above.

@aheritier aheritier removed the status/needs-signed-commits Some commits in the PR are signed with a valid SSH/GPG key label Oct 13, 2026
@aheritier
aheritier requested a review from dgageot October 13, 2026 15:23
@aheritier

Copy link
Copy Markdown
Collaborator Author

please review @dgageot

@aheritier aheritier left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

No blocking findings. The request-bound serving metadata improves on mutable last-selection state, and the pricing changes preserve explicit/free overrides, cache rates, endpoint policy, and fallback cooldown behavior. Linked issue #4576 is addressed with regression coverage for serving-model pricing, routing, and fallback. No demonstrated security or material performance regression was found.

One nonblocking attribution inconsistency is noted inline. The required CI gate and reported build, lint, test, and CodeQL checks passed; the race-test job was skipped. No local tests or benchmarks were run. GitHub reports mergeable_state=blocked, so this is not a merge-ready declaration.

Comment thread pkg/runtime/fallback.go

idleTimeout := streamIdleTimeoutFor(modelEntry.provider)
res, err := handleStream(streamCtx, streamCancel, stream, a, attemptTools, sess, events, idleTimeout)
res.Serving = servingModelFrom(stream)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[should-fix] Use the serving identity for the fallback span's final-model attribute too.

This assignment captures the routed leaf, but the success branches at lines 438 and 456 still call fbSpan.SetFinalModel(modelEntry.provider.ID().Model). For openai/gpt-4o-mini routing to anthropic/claude-haiku-4-5, cagent.fallback.final_model therefore remains gpt-4o-mini, while messages, hooks, and token-usage accounting identify the serving leaf.

Use res.Serving.ID.Model when metadata is present, falling back to the dispatched provider's model, in both success branches. Keep retry/cooldown keys attached to the dispatched provider. Add an exported-span assertion to a routed regression test; the current recordingTelemetry checks cover token usage but not this OTel attribute.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

🤖 Automated implementer agent — this comment was posted by the implementer bot from Docker Agentic Platform, not by a human developer

Fixed in 0404f5c. Both normal success and delegated idle-retry success now use the successful stream's serving model for cagent.fallback.final_model, falling back to the dispatched provider for absent/empty model metadata. The attribute remains a bare model name; dispatched retry/cooldown ownership is unchanged.

Added six serial, exported-OTel-span regression cases for routed/plain success, same identity, idle-retry routed/plain success, and empty metadata, including attempts/outcome/primary attributes. Both routed cases fail against the prior implementation and pass with the fix.

Validation passed: task build, task test, task lint, full task test-race, focused race tests, and runtime/genai race + shuffle. Fresh independent review approved the exact pushed commit with no findings. Local and remote branch heads match; GitHub reports the new commit's signature valid. New-head CI is left to the normal checks.

Signed-off-by: Arnaud Héritier <aheritier@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/models LLM model integrations and model providers area/runtime Runtime engine, agent loop execution, tool dispatch, loop detection kind/fix PR fixes a bug (maps to fix:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rule-based routing: routed requests are priced with the router's model, not the model that served them

3 participants