Skip to content

Guard LocalLLM's provider identity and add its GenAI telemetry - #691

Merged
xmap merged 2 commits into
mainfrom
feat/local-llm-provider-guard-telemetry
Aug 20, 2026
Merged

Guard LocalLLM's provider identity and add its GenAI telemetry#691
xmap merged 2 commits into
mainfrom
feat/local-llm-provider-guard-telemetry

Conversation

@xmap

@xmap xmap commented Aug 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • LocalLLM.chat now refuses a request whose model_ref.provider isn't "local", mirroring AnthropicLLM's existing guard: cost resolves from the Agent's declared (provider, model) while the serving route comes from LLM_PROVIDER configuration, so a mismatch would silently misattribute spend between the bought and built arms.
  • LocalLLM.chat now emits the same OpenTelemetry GenAI signals AnthropicLLM does: track_in_flight_call around the backend call (feeds the cora.agent.llm.concurrent_calls counter), an llm.chat span, and record_llm_call on the success path only.

The provider identity is a fixed module constant (_PROVIDER_NAME = "local") rather than an injected constructor argument like AnthropicLLM.provider_name. AnthropicLLM needs the injection because ArgoLLM composes it and overrides the identity to "argo"; no gateway composes LocalLLM the same way today, so there is nothing yet to override it for.

docs/deployments/2-bm/llm_debrief.md already documented this refusal as applying to the local arm; no doc change was needed since the implementation now matches what it already claimed.

Test plan

  • uv run pytest tests/unit/agent -q --timeout=60 --timeout-method=thread (938 passed)
  • uv run pytest tests/architecture -q --timeout=60 --timeout-method=thread (31181 passed, 629 skipped)
  • uv run ruff format . && uv run ruff check .
  • uv run pyright src/cora/agent/adapters/local_llm.py tests/unit/agent/test_local_llm.py
  • New coverage: mismatched provider refused before the backend/meter are touched; matching provider passes; two overlapping local calls register as concurrent; a solo call does not; record_llm_call fires only on the success path; the GPU occupancy meter still measures correctly with the new telemetry nesting.

🤖 Generated with Claude Code

xmap and others added 2 commits August 19, 2026 22:28
AnthropicLLM.chat already refuses a request whose model_ref.provider
disagrees with the adapter serving it, because cost resolves from the
Agent's declared (provider, model) while the serving route comes from
LLM_PROVIDER configuration. LocalLLM had no such check: with
LLM_PROVIDER=local and an Agent declaring anthropic, the call would be
served free on the facility GPU and priced at vendor rates, silently
misattributing spend and defeating the buy-vs-build comparison the two
routes exist to support.

The provider identity is a fixed module constant here, not an injected
constructor argument like AnthropicLLM's provider_name. AnthropicLLM
needs the injection because ArgoLLM composes it and overrides the
identity to "argo"; no gateway composes LocalLLM the same way, so there
is nothing yet to override it for. Add the injection if that changes.

docs/deployments/2-bm/llm_debrief.md already documented this refusal
as applying to the local arm; that claim is now true rather than
aspirational.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
LocalLLM emitted no OpenTelemetry GenAI signal at all: neither
track_in_flight_call nor record_llm_call was imported, unlike
AnthropicLLM which wraps every call in both. The in-flight counter is
load-bearing beyond observability, since a sustained nonzero reading is
the stated trigger for building a heavier budget-enforcement tier, and
its absence was a hole exactly where concurrency is most likely: a
batching GPU server is the one serving route that answers many calls
at once.

Mirrors AnthropicLLM.chat's shape: track_in_flight_call and an
"llm.chat" span wrap the backend await, and record_llm_call fires on
the success path only, fed from the completion the backend returns
(response_model_id, usage, stop_reason) and request.max_output_tokens.
record_llm_call's returned cost is discarded here exactly as it is in
AnthropicLLM: it feeds the telemetry histogram, not the durable spend
ledger, which the caller writes from the returned LLMResponse.

The telemetry context nests inside the existing GPU occupancy meter's
try/finally so that meter keeps opening and closing around every call,
success or failure, unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Coverage report

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  apps/api/src/cora/agent/adapters
  local_llm.py
Project Total  

This report was generated by python-coverage-comment-action

@xmap
xmap merged commit 17fc714 into main Aug 20, 2026
19 checks passed
@xmap
xmap deleted the feat/local-llm-provider-guard-telemetry branch August 20, 2026 11:35
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