fix(llm): Anthropic 结构化输出记真实 usage——修计费记 0 成本(CR-H3)

This commit is contained in:
Yaojia Wang
2026-07-08 10:21:18 +02:00
parent c69e613c97
commit cb8fddfc3b
2 changed files with 54 additions and 5 deletions

View File

@@ -164,6 +164,51 @@ async def test_gemini_structured_output() -> None:
assert "config" in models.last_kwargs
async def test_anthropic_structured_output_threads_usage() -> None:
# 结构化输出必须记真实 usageCR-H3instructor 的 create_with_completion 回
# (parsed, raw)raw.usage 带真实 token 数网关据此记账§4.8)。
class _RawUsage:
input_tokens = 120
output_tokens = 80
cache_read_input_tokens = 30
class _Raw:
usage = _RawUsage()
class _FakeStructured:
def __init__(self) -> None:
self.last_kwargs: dict[str, Any] = {}
async def create_with_completion(
self, *, response_model: type[BaseModel], **kwargs: Any
) -> tuple[BaseModel, Any]:
self.last_kwargs = kwargs
return response_model(score=7), _Raw()
# 修前路径调 `.create`(仅回 parsed——保留它令 RED 是值不符0!=120
# 而非 AttributeError坐实旧代码硬编码零 usage 的缺陷。
async def create(self, *, response_model: type[BaseModel], **kwargs: Any) -> BaseModel:
return response_model(score=7)
fake = _FakeStructured()
client = _FakeAnthropicClient(_FakeAnthropicMessages("", 0, 0, 0))
adapter = AnthropicAdapter("anthropic", client, structured_client=fake)
req = LlmRequest(
tier="analyst",
input="打分",
output_schema=Out,
scope=Scope(user_id=uuid.UUID(int=1)),
)
result = await adapter.complete(req, "claude-3-5-sonnet")
assert isinstance(result.parsed, Out)
assert result.parsed.score == 7
assert result.usage.input_tokens == 120
assert result.usage.output_tokens == 80
assert result.usage.cache_read_tokens == 30
async def test_anthropic_stream_yields_text_then_usage() -> None:
class _StreamEvent:
def __init__(self, **kw: Any) -> None: