- 薄自建 LLM 网关:OpenAI 兼容适配器(DeepSeek) + instructor 结构化输出 + usage_ledger 记账 + 档位路由 - 记忆服务 assemble:确定性选择(显式+主角+近况) + 渲染卡 + 缓存断点(中性文本) - LangGraph 写章节点 + Postgres checkpointer + SSE 归一(token/done/error) - API:立项 + 写章 draft(SSE) + PUT 自动保存 + 提供商凭据(Fernet 加密/测试连接) - 前端:AppShell + 作品库 + 5 步立项向导 + 写作工作台(流式打字机+自动保存) + 设置页 - M1 E2E:真实 DB + mock 网关零 token 走通闭环
57 lines
1.8 KiB
Python
57 lines
1.8 KiB
Python
"""网关单测替身(mock provider + 内存 ledger)——不碰真实 API / DB。
|
||
|
||
放在独立模块(非 conftest)以便测试用绝对导入 `from fakes import ...`;
|
||
本目录无 __init__.py(避免与顶层 tests 包同名冲突),故走 pytest 路径注入。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from collections.abc import AsyncIterator
|
||
|
||
from ww_llm_gateway.adapters.base import (
|
||
Capabilities,
|
||
ProviderResult,
|
||
ProviderUsage,
|
||
StreamChunk,
|
||
)
|
||
from ww_llm_gateway.routing import Route
|
||
from ww_llm_gateway.types import LlmRequest, Scope, Tier, Usage
|
||
|
||
|
||
class FakeAdapter:
|
||
provider = "deepseek"
|
||
|
||
def __init__(self, text: str = "hello world", deltas: list[str] | None = None) -> None:
|
||
self.text = text
|
||
self.deltas = deltas if deltas is not None else ["hel", "lo ", "world"]
|
||
self.complete_calls = 0
|
||
self.stream_calls = 0
|
||
|
||
def capabilities(self) -> Capabilities:
|
||
return Capabilities(structured_output=True, prefix_cache=True)
|
||
|
||
async def complete(self, req: LlmRequest, model: str) -> ProviderResult:
|
||
self.complete_calls += 1
|
||
return ProviderResult(
|
||
text=self.text,
|
||
usage=ProviderUsage(input_tokens=100, output_tokens=50),
|
||
)
|
||
|
||
async def stream(self, req: LlmRequest, model: str) -> AsyncIterator[StreamChunk]:
|
||
self.stream_calls += 1
|
||
for d in self.deltas:
|
||
yield StreamChunk(text=d)
|
||
yield StreamChunk(usage=ProviderUsage(input_tokens=100, output_tokens=50))
|
||
|
||
|
||
class FakeLedger:
|
||
def __init__(self) -> None:
|
||
self.records: list[Usage] = []
|
||
|
||
async def record(self, scope: Scope, usage: Usage) -> None:
|
||
self.records.append(usage)
|
||
|
||
|
||
def fake_route(tier: Tier) -> Route:
|
||
return Route(provider="deepseek", model="deepseek-chat")
|