docs: 同步 core/protocols.py 分层、四层治理栈、遥测新字段
ARCHITECTURE.md §2.3/§2.4/§3.1/§4/§5 + CLAUDE.md §4.8/§4.9/§5 + .env.example 对应设计 research-wiki/designs/2026-07-06-core-agent-adapters-llm-design.md §8 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -103,17 +103,18 @@ flowchart TD
|
||||
```text
|
||||
project_root/
|
||||
├── core/ # 可提取内核(不依赖 app/、adapters/)
|
||||
│ ├── protocols.py # 共享端口:LLMProvider, VLMProvider, TelemetryRecorder
|
||||
│ ├── agent/ # 【可提取包】AgentLoop 引擎
|
||||
│ │ ├── loop.py # Thinking+JSON 推理循环,pluggy hook
|
||||
│ │ ├── types.py # Step, LoopResult
|
||||
│ │ └── protocols.py # LLMProvider, ToolDispatcher Protocol
|
||||
│ │ └── protocols.py # Agent 专属:ToolDispatcher, AgentLoopSpec
|
||||
│ ├── evolution/ # 【可提取包】诊断+进化引擎
|
||||
│ │ ├── diagnose.py # 两阶段诊断管线
|
||||
│ │ ├── evolve.py # patch/rewrite 进化
|
||||
│ │ ├── gate.py # CE-Gate e-process
|
||||
│ │ ├── validate.py # 块顺序验证
|
||||
│ │ ├── types.py # DiagnosisResult, EvolutionRecord, ...
|
||||
│ │ └── protocols.py # SkillStore, RunLog, TelemetryRecorder Protocol
|
||||
│ │ └── protocols.py # Evolution 专属:SkillStore, PromptStore, RunLog
|
||||
│ └── types.py # 跨模块共享类型
|
||||
│
|
||||
├── app/ # 应用层(组合 core + adapters,领域特化)
|
||||
@@ -148,7 +149,9 @@ project_root/
|
||||
│ └── ports.py # 应用层特有端口
|
||||
│
|
||||
├── adapters/ # 外部实现层
|
||||
│ ├── llm.py # GovernedLLMClient(遥测+熔断+Redis缓存)
|
||||
│ ├── llm.py # GovernedLLMClient(四层治理栈)
|
||||
│ ├── streaming.py # 流式三层看门狗(TTFT/inter_token/total)
|
||||
│ ├── breaker.py # CircuitBreaker(内存级熔断器)
|
||||
│ ├── vlm.py # VLM 客户端
|
||||
│ ├── embedding.py # Embedding 服务实现
|
||||
│ ├── redis_cache.py # Redis 响应缓存
|
||||
@@ -172,7 +175,7 @@ project_root/
|
||||
|
||||
| 层 | 可依赖 | 禁止依赖 |
|
||||
|---|--------|---------|
|
||||
| `core/` | 标准库、typing、pluggy | `app/`、`adapters/`、任何框架 |
|
||||
| `core/` | 标准库、typing、pluggy、json_repair | `app/`、`adapters/`、任何框架 |
|
||||
| `app/` | `core/`、标准库 | `adapters/`(只通过 Protocol) |
|
||||
| `adapters/` | `core/`、`app/ports.py`、第三方库 | `app/` 内部模块 |
|
||||
|
||||
@@ -188,15 +191,28 @@ project_root/
|
||||
|
||||
### 3.1 核心端口(`core/` 内,可提取)
|
||||
|
||||
| Protocol | 所在文件 | 关键方法 | 职责 |
|
||||
|----------|---------|---------|------|
|
||||
| `LLMProvider` | `core/agent/protocols.py` | `chat()`, `chat_async()` | LLM 文本调用 |
|
||||
| `VLMProvider` | `core/agent/protocols.py` | `chat_with_images()`, `chat_with_images_async()` | VLM 图文调用 |
|
||||
| `ToolDispatcher` | `core/agent/protocols.py` | `dispatch(tool_name, args, context)` | Agent 工具调度 |
|
||||
| `SkillStore` | `core/evolution/protocols.py` | `read_skill()`, `write_skill()`, `list_versions()` | 版本化技能存储 |
|
||||
| `PromptStore` | `core/evolution/protocols.py` | `read_prompt()`, `write_prompt()` | 版本化提示词存储 |
|
||||
| `RunLog` | `core/evolution/protocols.py` | `insert()`, `query()` | 实验日志 |
|
||||
| `TelemetryRecorder` | `core/evolution/protocols.py` | `record_llm_call()` | Agent 遥测 |
|
||||
**共享端口(`core/protocols.py`,跨子包):**
|
||||
|
||||
| Protocol | 关键方法 | 职责 |
|
||||
|----------|---------|------|
|
||||
| `LLMProvider` | `chat()` | LLM 文本调用(全异步) |
|
||||
| `VLMProvider` | `chat_with_images()` | VLM 图文调用(全异步) |
|
||||
| `TelemetryRecorder` | `record_llm_call()` | LLM 调用遥测 |
|
||||
|
||||
**Agent 专属端口(`core/agent/protocols.py`):**
|
||||
|
||||
| Protocol | 关键方法 | 职责 |
|
||||
|----------|---------|------|
|
||||
| `ToolDispatcher` | `dispatch(tool_name, args, context)` | Agent 工具调度 |
|
||||
| `AgentLoopSpec` | `before_step/after_tool/after_step/on_finish` | pluggy 生命周期 hook |
|
||||
|
||||
**Evolution 专属端口(`core/evolution/protocols.py`):**
|
||||
|
||||
| Protocol | 关键方法 | 职责 |
|
||||
|----------|---------|------|
|
||||
| `SkillStore` | `read_skill()`, `write_skill()`, `list_versions()` | 版本化技能存储 |
|
||||
| `PromptStore` | `read_prompt()`, `write_prompt()` | 版本化提示词存储 |
|
||||
| `RunLog` | `insert()`, `query()` | 实验日志 |
|
||||
|
||||
### 3.2 应用层端口(`app/ports.py`)
|
||||
|
||||
@@ -239,9 +255,12 @@ project_root/
|
||||
| `provider` | str | API 端点标识 |
|
||||
| `messages` | str (JSON) | 原始输入 |
|
||||
| `response` | str | 原始输出 |
|
||||
| `thinking` | str | thinking/reasoning 内容 |
|
||||
| `prompt_tokens` | int | 输入 token 数 |
|
||||
| `completion_tokens` | int | 输出 token 数 |
|
||||
| `latency_ms` | int | 延迟毫秒 |
|
||||
| `ttft_ms` | float? | 首 token 延迟(流式测量) |
|
||||
| `max_inter_token_ms` | float? | 最大 token 间隔(流式测量) |
|
||||
| `cache_hit` | bool | 是否命中 Redis 缓存 |
|
||||
| `error` | str? | 异常信息(正常为 null) |
|
||||
|
||||
@@ -251,37 +270,39 @@ project_root/
|
||||
|
||||
## §5 LLM 调用韧性
|
||||
|
||||
所有 LLM/VLM 调用必须经过 `GovernedLLMClient`(`adapters/llm.py`)治理,禁止裸调 OpenAI SDK。治理栈包含五层:
|
||||
所有 LLM/VLM 调用必须经过 `GovernedLLMClient`(`adapters/llm.py`)治理,禁止裸调 OpenAI SDK。治理栈包含四层:
|
||||
|
||||
| 层 | 机制 | 说明 |
|
||||
|---|------|------|
|
||||
| 1 | 硬超时 | `asyncio.wait_for(call, timeout=config.llm_timeout)` |
|
||||
| 2 | 指数退避重试 | `max_retries`、`base_delay`、`max_delay`(可配置) |
|
||||
| 3 | 熔断器 | 连续 N 失败 → 短路 M 秒 → 探针恢复 |
|
||||
| 4 | Redis 响应缓存 | content-addressed:`hash(model + messages)` → response |
|
||||
| 5 | ARQ 任务队列 | 长时间推理任务异步执行 |
|
||||
| 1 | 熔断器 | 连续 N 失败 → 短路 M 秒 → 探针恢复(`adapters/breaker.py`) |
|
||||
| 2 | Redis 响应缓存 | content-addressed:`hash(model + messages)` → response |
|
||||
| 3 | 流式三层看门狗 | TTFT / inter_token / total 超时保护(`adapters/streaming.py`) |
|
||||
| 4 | 指数退避重试 | `max_retries`、`base_delay`、`max_delay`(可配置) |
|
||||
|
||||
熔断参数(`LLM_CIRCUIT_BREAKER_THRESHOLD`、`LLM_CIRCUIT_BREAKER_COOLDOWN`)和超时(`LLM_TIMEOUT`)通过 `.env` 工程配置管理。
|
||||
**流式三层看门狗**:GovernedLLMClient 内部始终使用 `stream=True` 消费 SSE,通过 `stream_with_liveness_timeouts()` 实现三层超时保护——TTFT(首 token,含 thinking token)、inter_token(token 间隔)、total(总时长)。thinking token 刷新看门狗但不计入 content,确保长时间思考不会被误判为连接挂死。
|
||||
|
||||
熔断参数(`LLM_CIRCUIT_BREAKER_THRESHOLD`、`LLM_CIRCUIT_BREAKER_COOLDOWN`)、超时(`LLM_TIMEOUT`、`LLM_TTFT_TIMEOUT`、`LLM_INTER_TOKEN_TIMEOUT`)通过 `.env` 工程配置管理。
|
||||
|
||||
**治理流程伪代码**:
|
||||
|
||||
```
|
||||
call(messages) →
|
||||
if breaker.is_open: raise CircuitOpen
|
||||
key = hash(model, messages)
|
||||
if redis.exists(key): return cached # 层4: 缓存
|
||||
try:
|
||||
resp = await wait_for(api(messages), # 层1: 硬超时
|
||||
timeout)
|
||||
chat(messages) →
|
||||
if breaker.is_open: raise CircuitOpen # 层1: 熔断
|
||||
call_id = uuid4()
|
||||
if cache.get(model, messages): # 层2: 缓存
|
||||
telemetry.record(..., cache_hit=True)
|
||||
return cached
|
||||
retry loop: # 层4: 重试
|
||||
stream = httpx.stream(POST, stream=True)
|
||||
guarded = stream_with_liveness_timeouts( # 层3: 三层看门狗
|
||||
sse_deltas, ttft, inter_token, total)
|
||||
content, thinking, ttft_ms = consume(guarded)
|
||||
breaker.record_success()
|
||||
redis.set(key, resp)
|
||||
telemetry.record(...) # 遥测
|
||||
return resp
|
||||
except Transient:
|
||||
retry with backoff # 层2: 退避重试
|
||||
except:
|
||||
breaker.record_failure() # 层3: 熔断
|
||||
raise
|
||||
cache.set(model, messages, response)
|
||||
telemetry.record(...)
|
||||
return response
|
||||
except transient → backoff + breaker.record_failure()
|
||||
except 401/403 → breaker.force_open(); raise
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user