Skip to content

feat(probe): surface canonical token usage, lift tool calls, prune dead code - #1496

Open
FFengIll wants to merge 9 commits into
mainfrom
refactor/probe0806
Open

feat(probe): surface canonical token usage, lift tool calls, prune dead code#1496
FFengIll wants to merge 9 commits into
mainfrom
refactor/probe0806

Conversation

@FFengIll

@FFengIll FFengIll commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

After the probe rename, the implementation still had the original gaps it inherited: token usage was declared on Result but never actually populated (every probe JSON-marshaled the whole response into Content and returned zero usage), tool calls were never surfaced, several Result fields were dead or irregular, and the E2E/Light paths duplicated logic.

This PR fixes the substance so the probe returns trustworthy, canonical data the rest of TB and the frontend already understand.

Key Changes

  • Token usage is real and canonical: Result.Usage is now a single *protocol.TokenUsage in the canonical shape (input_tokens/output_tokens/cache_read_tokens/cache_write_tokens/reasoning_tokens), populated by reusing the existing
    internal/protocol/usage extractors rather than re-implementing parsing. The probe no longer emits a parallel prompt_tokens/completion_tokens/total_tokens vocabulary that diverged from everything else — consumers read one consistent shape. Streaming probes
    now surface real usage (OpenAI Chat requests stream_options.include_usage; Responses reads response.completed; Anthropic uses the accumulator). Google is intentionally out of scope.

  • Tool-mode probes show what the model called: tool calls are lifted out of the response into Result.ToolCalls ({id, name, input}) via per-provider extractors, so a tool probe reports the actual function call instead of burying it in the raw response blob.

  • Result fields mean what they say: Success is now set on success (it was a never-true bug — only the cache-hit path set it); LatencyMs is the pure upstream round-trip time owned by the SDK probe rather than a handler-overwritten end-to-end number. Removed
    dead surface: ModelsCount on the E2E result, the unused E2EResponseChunk, and three orphaned request/response types plus the ProbeProviderResponse envelope that nothing referenced.

  • Connectivity matrix is correct for every provider: the light probe's summary no longer hardcodes "/4 endpoints" — non-OpenAI providers (which skip chat/responses) now report a true denominator instead of "2/4". The four near-identical endpoint helpers were
    collapsed into focused ones sharing one body.

  • Frontend renders the canonical usage: the probe dialog's token chip derives its total from usage.input_tokens + usage.output_tokens (mirroring TokenUsage.TotalTokens()), and the hand-written types/probe.ts + mock + demo data follow the canonical shape.

Notes

  • Codegen required: backend model changes need task codegen to regenerate schema.d.ts/swagger; types/probe.ts is updated as a placeholder until then.
  • Google unchanged by design: no usage/tool-call extraction in this pass — Google probes keep their current raw-blob behavior.
  • Verified green: go build ./internal/..., go vet, go test ./internal/probe/... (62 cases) pass; frontend typecheck introduces no new errors (7 pre-existing legacy quota errors unchanged).
  • Also fixes a "type:" typo (colon inside the JSON key) in the OpenAI probe tool's schema that misnamed the field.

Token usage was declared on Result but never populated — every SDK probe
JSON-marshaled the whole response into Content and returned, leaving the
usage fields at zero. Tool calls were never lifted either.

Resolve by reusing the existing internal/protocol/usage extractors rather
than re-implementing parsing:

- Result now carries a single Usage *protocol.TokenUsage in the canonical
  shape (input_tokens/output_tokens/cache_read_tokens/cache_write_tokens/
  reasoning_tokens) — the same vocabulary the rest of TB emits and the
  frontend renders. No parallel flat token fields.
- Non-stream probes call FromOpenAIChatCompletion/FromOpenAIResponses/
  FromAnthropicMessage on resp.Usage. Stream probes take usage from the
  final chunk (OpenAI, with stream_options.include_usage now requested) /
  response.completed event (Responses) / AnthropicAccumulator (Anthropic).
- Tool mode lifts calls into ToolCalls via per-provider extractors.
- Success is now set in toProbeResult (was a never-true bug); LatencyMs is
  owned by the SDK probe (pure upstream time) — the handler no longer
  overwrites it.

Also remove meaningless/dead implementation surfaced during review:

- defaultModelForAPIStyle + model guessing: a probe tests a specific model
  the user picked; an empty model now errors explicitly instead of silently
  inventing claude-3-haiku/gpt-3.5-turbo.
- ProbeStream + probeProviderStream: 1-line passthroughs; Probe handles all
  modes (stream decision lives in the SDK helpers via testMode).
- ProbeProviderWithSDK -> unexported probeProviderWithSDK (the
  testProviderConnectivity reuse it was exported for no longer exists);
  routing-capture wiring deduped via a maybeCapture closure.
- Dead types Request, ProviderRequest, ProbeProviderResponseData, and the
  orphaned ProbeProviderResponse envelope (zero refs/swagger/frontend).

Google is out of scope: no usage/tool-call extraction, probes unchanged.
Drop the non-canonical prompt_tokens/completion_tokens/total_tokens fields
from the probe result types and read the canonical usage object instead
(protocol.TokenUsage shape: input_tokens/output_tokens/cache_read_tokens/
cache_write_tokens). The token chip now derives its total from
usage.input_tokens + usage.output_tokens, matching TokenUsage.TotalTokens()
on the backend. Updates the MSW mock and the in-dialog demo data to match.

Backend model change requires `task codegen` to regenerate schema.d.ts /
swagger; the hand-written types/probe.ts is updated as a placeholder.
Refresh the stale code-layout block (result.go/e2e.go/sdkprobe.go/
lightweight.go were all renamed) and document the Result field semantics:
canonical protocol.TokenUsage (no parallel flat fields), SDK-owned
LatencyMs, lifted tool calls, and the routing-trace fields. Note that
probeProviderWithSDK reuses internal/protocol/usage rather than
re-implementing parsing.
…enominator

The light probe had four near-identical probeXxxEndpoint methods returning
parallel endpointReport/modelsReport structs that Probe() then field-copied
into LightweightProbeResponseData one by one. Collapse to focused helpers
that write outcomes directly into the target fields:

- runOptionsEndpoint (HTTP-level OPTIONS, no SDK)
- runChatEndpoint/runResponsesEndpoint over a shared runOpenAIEndpoint body
  (only the SDK call + success label differ)
- runModelsEndpoint with a clean switch over error cases

Also fixes a latent bug: the summary message hardcoded "/4 endpoints", so
non-OpenAI providers (which skip chat/responses) reported e.g. "2/4". The
denominator now reflects the endpoints actually run.
…/vmodel paths

The three loopback resolution paths (resolveProviderTarget's loopback half,
resolveRuleTarget, resolveVModelLoopbackTarget) each built the same
provider_config target by hand: {Name, APIBase: loopback, APIStyle,
Token: model token, Model}. Extract loopbackConfigTarget so the loopback
construction lives in one place; each call site now passes only the
name/apiStyle/model that actually differ.
The OpenAI probe tool's function parameters used the JSON key "type:"
(colon inside the key) instead of "type", so the schema field was
misnamed. Aligns with the other two tool definitions in the same file.
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.

1 participant