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:
+5
-1
@@ -34,7 +34,7 @@ MONKEY_OCR_URLS=http://10.77.0.20:7866,http://10.77.0.20:7867
|
|||||||
EMBED_API_KEY=
|
EMBED_API_KEY=
|
||||||
EMBED_API_URL=
|
EMBED_API_URL=
|
||||||
|
|
||||||
# ── Redis(响应缓存 + ARQ 任务队列)──
|
# ── Redis(响应缓存)──
|
||||||
REDIS_URL=redis://localhost:6379/0
|
REDIS_URL=redis://localhost:6379/0
|
||||||
|
|
||||||
# ── LLM 韧性参数 ──
|
# ── LLM 韧性参数 ──
|
||||||
@@ -43,3 +43,7 @@ LLM_MAX_RETRIES=3
|
|||||||
LLM_RETRY_BASE_DELAY=2.0
|
LLM_RETRY_BASE_DELAY=2.0
|
||||||
LLM_CIRCUIT_BREAKER_THRESHOLD=5
|
LLM_CIRCUIT_BREAKER_THRESHOLD=5
|
||||||
LLM_CIRCUIT_BREAKER_COOLDOWN=60
|
LLM_CIRCUIT_BREAKER_COOLDOWN=60
|
||||||
|
LLM_TTFT_TIMEOUT=30
|
||||||
|
LLM_INTER_TOKEN_TIMEOUT=15
|
||||||
|
LLM_RETRY_MAX_DELAY=30.0
|
||||||
|
REDIS_CACHE_TTL=86400
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ MODE=mock N_SAMPLES=10 bash scripts/<experiment>.sh # smoke test
|
|||||||
|
|
||||||
### 4.8 Agent 遥测
|
### 4.8 Agent 遥测
|
||||||
|
|
||||||
每次 LLM/VLM 调用必须经过 `TelemetryRecorder` Protocol(`core/evolution/protocols.py`)记录。完整规范见 `research-wiki/ARCHITECTURE.md §4`。
|
每次 LLM/VLM 调用必须经过 `TelemetryRecorder` Protocol(`core/protocols.py`)记录。完整规范见 `research-wiki/ARCHITECTURE.md §4`。
|
||||||
|
|
||||||
| 必录字段 | 类型 | 说明 |
|
| 必录字段 | 类型 | 说明 |
|
||||||
|----------|------|------|
|
|----------|------|------|
|
||||||
@@ -254,8 +254,11 @@ MODE=mock N_SAMPLES=10 bash scripts/<experiment>.sh # smoke test
|
|||||||
| `provider` | str | API 端点标识 |
|
| `provider` | str | API 端点标识 |
|
||||||
| `messages` | str (JSON) | 原始输入 |
|
| `messages` | str (JSON) | 原始输入 |
|
||||||
| `response` | str | 原始输出 |
|
| `response` | str | 原始输出 |
|
||||||
|
| `thinking` | str | thinking/reasoning 内容 |
|
||||||
| `prompt_tokens` / `completion_tokens` | int | token 用量 |
|
| `prompt_tokens` / `completion_tokens` | int | token 用量 |
|
||||||
| `latency_ms` | int | 延迟毫秒 |
|
| `latency_ms` | int | 延迟毫秒 |
|
||||||
|
| `ttft_ms` | float? | 首 token 延迟(流式测量) |
|
||||||
|
| `max_inter_token_ms` | float? | 最大 token 间隔(流式测量) |
|
||||||
| `cache_hit` | bool | 是否命中 Redis 缓存 |
|
| `cache_hit` | bool | 是否命中 Redis 缓存 |
|
||||||
| `error` | str? | 异常信息(正常为 null) |
|
| `error` | str? | 异常信息(正常为 null) |
|
||||||
|
|
||||||
@@ -263,17 +266,16 @@ MODE=mock N_SAMPLES=10 bash scripts/<experiment>.sh # smoke test
|
|||||||
|
|
||||||
### 4.9 LLM 韧性
|
### 4.9 LLM 韧性
|
||||||
|
|
||||||
所有 LLM/VLM 调用必须经过 `GovernedLLMClient`(`adapters/llm.py`)治理,禁止裸调 OpenAI SDK。治理栈五层(详见 `research-wiki/ARCHITECTURE.md §5`):
|
所有 LLM/VLM 调用必须经过 `GovernedLLMClient`(`adapters/llm.py`)治理,禁止裸调 OpenAI SDK。治理栈四层(详见 `research-wiki/ARCHITECTURE.md §5`):
|
||||||
|
|
||||||
| 层 | 机制 | 说明 |
|
| 层 | 机制 | 说明 |
|
||||||
|---|------|------|
|
|---|------|------|
|
||||||
| 1 | 硬超时 | `asyncio.wait_for(call, timeout=config.llm_timeout)` |
|
| 1 | 熔断器 | 连续 N 失败 → 短路 M 秒 → 探针恢复(`adapters/breaker.py`) |
|
||||||
| 2 | 指数退避重试 | `max_retries`、`base_delay`、`max_delay`(可配置) |
|
| 2 | Redis 响应缓存 | content-addressed:`hash(model + messages)` → response |
|
||||||
| 3 | 熔断器 | 连续 N 失败 → 短路 M 秒 → 探针恢复 |
|
| 3 | 流式三层看门狗 | TTFT / inter_token / total 超时保护(`adapters/streaming.py`) |
|
||||||
| 4 | Redis 响应缓存 | content-addressed:`hash(model + messages)` → response |
|
| 4 | 指数退避重试 | `max_retries`、`base_delay`、`max_delay`(可配置) |
|
||||||
| 5 | ARQ 任务队列 | 长时间推理任务异步执行 |
|
|
||||||
|
|
||||||
> 熔断参数(`LLM_CIRCUIT_BREAKER_THRESHOLD`、`LLM_CIRCUIT_BREAKER_COOLDOWN`)和超时(`LLM_TIMEOUT`)通过 `.env` 工程配置管理。
|
> 熔断参数(`LLM_CIRCUIT_BREAKER_THRESHOLD`、`LLM_CIRCUIT_BREAKER_COOLDOWN`)和超时(`LLM_TIMEOUT`、`LLM_TTFT_TIMEOUT`、`LLM_INTER_TOKEN_TIMEOUT`)通过 `.env` 工程配置管理。
|
||||||
|
|
||||||
## 5. 项目结构规范
|
## 5. 项目结构规范
|
||||||
|
|
||||||
@@ -289,6 +291,7 @@ project_root/
|
|||||||
├── .claude/ # Claude 配置(skills, hooks, tools)
|
├── .claude/ # Claude 配置(skills, hooks, tools)
|
||||||
│
|
│
|
||||||
├── core/ # 可提取内核(不依赖 app/、adapters/)
|
├── core/ # 可提取内核(不依赖 app/、adapters/)
|
||||||
|
│ ├── protocols.py # 共享端口(LLMProvider, VLMProvider, TelemetryRecorder)
|
||||||
│ ├── agent/ # AgentLoop 引擎(loop, types, protocols)
|
│ ├── agent/ # AgentLoop 引擎(loop, types, protocols)
|
||||||
│ ├── evolution/ # 诊断+进化引擎(diagnose, evolve, gate)
|
│ ├── evolution/ # 诊断+进化引擎(diagnose, evolve, gate)
|
||||||
│ └── types.py # 跨模块共享类型
|
│ └── types.py # 跨模块共享类型
|
||||||
|
|||||||
@@ -103,17 +103,18 @@ flowchart TD
|
|||||||
```text
|
```text
|
||||||
project_root/
|
project_root/
|
||||||
├── core/ # 可提取内核(不依赖 app/、adapters/)
|
├── core/ # 可提取内核(不依赖 app/、adapters/)
|
||||||
|
│ ├── protocols.py # 共享端口:LLMProvider, VLMProvider, TelemetryRecorder
|
||||||
│ ├── agent/ # 【可提取包】AgentLoop 引擎
|
│ ├── agent/ # 【可提取包】AgentLoop 引擎
|
||||||
│ │ ├── loop.py # Thinking+JSON 推理循环,pluggy hook
|
│ │ ├── loop.py # Thinking+JSON 推理循环,pluggy hook
|
||||||
│ │ ├── types.py # Step, LoopResult
|
│ │ ├── types.py # Step, LoopResult
|
||||||
│ │ └── protocols.py # LLMProvider, ToolDispatcher Protocol
|
│ │ └── protocols.py # Agent 专属:ToolDispatcher, AgentLoopSpec
|
||||||
│ ├── evolution/ # 【可提取包】诊断+进化引擎
|
│ ├── evolution/ # 【可提取包】诊断+进化引擎
|
||||||
│ │ ├── diagnose.py # 两阶段诊断管线
|
│ │ ├── diagnose.py # 两阶段诊断管线
|
||||||
│ │ ├── evolve.py # patch/rewrite 进化
|
│ │ ├── evolve.py # patch/rewrite 进化
|
||||||
│ │ ├── gate.py # CE-Gate e-process
|
│ │ ├── gate.py # CE-Gate e-process
|
||||||
│ │ ├── validate.py # 块顺序验证
|
│ │ ├── validate.py # 块顺序验证
|
||||||
│ │ ├── types.py # DiagnosisResult, EvolutionRecord, ...
|
│ │ ├── types.py # DiagnosisResult, EvolutionRecord, ...
|
||||||
│ │ └── protocols.py # SkillStore, RunLog, TelemetryRecorder Protocol
|
│ │ └── protocols.py # Evolution 专属:SkillStore, PromptStore, RunLog
|
||||||
│ └── types.py # 跨模块共享类型
|
│ └── types.py # 跨模块共享类型
|
||||||
│
|
│
|
||||||
├── app/ # 应用层(组合 core + adapters,领域特化)
|
├── app/ # 应用层(组合 core + adapters,领域特化)
|
||||||
@@ -148,7 +149,9 @@ project_root/
|
|||||||
│ └── ports.py # 应用层特有端口
|
│ └── ports.py # 应用层特有端口
|
||||||
│
|
│
|
||||||
├── adapters/ # 外部实现层
|
├── adapters/ # 外部实现层
|
||||||
│ ├── llm.py # GovernedLLMClient(遥测+熔断+Redis缓存)
|
│ ├── llm.py # GovernedLLMClient(四层治理栈)
|
||||||
|
│ ├── streaming.py # 流式三层看门狗(TTFT/inter_token/total)
|
||||||
|
│ ├── breaker.py # CircuitBreaker(内存级熔断器)
|
||||||
│ ├── vlm.py # VLM 客户端
|
│ ├── vlm.py # VLM 客户端
|
||||||
│ ├── embedding.py # Embedding 服务实现
|
│ ├── embedding.py # Embedding 服务实现
|
||||||
│ ├── redis_cache.py # Redis 响应缓存
|
│ ├── 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) |
|
| `app/` | `core/`、标准库 | `adapters/`(只通过 Protocol) |
|
||||||
| `adapters/` | `core/`、`app/ports.py`、第三方库 | `app/` 内部模块 |
|
| `adapters/` | `core/`、`app/ports.py`、第三方库 | `app/` 内部模块 |
|
||||||
|
|
||||||
@@ -188,15 +191,28 @@ project_root/
|
|||||||
|
|
||||||
### 3.1 核心端口(`core/` 内,可提取)
|
### 3.1 核心端口(`core/` 内,可提取)
|
||||||
|
|
||||||
| Protocol | 所在文件 | 关键方法 | 职责 |
|
**共享端口(`core/protocols.py`,跨子包):**
|
||||||
|----------|---------|---------|------|
|
|
||||||
| `LLMProvider` | `core/agent/protocols.py` | `chat()`, `chat_async()` | LLM 文本调用 |
|
| Protocol | 关键方法 | 职责 |
|
||||||
| `VLMProvider` | `core/agent/protocols.py` | `chat_with_images()`, `chat_with_images_async()` | VLM 图文调用 |
|
|----------|---------|------|
|
||||||
| `ToolDispatcher` | `core/agent/protocols.py` | `dispatch(tool_name, args, context)` | Agent 工具调度 |
|
| `LLMProvider` | `chat()` | LLM 文本调用(全异步) |
|
||||||
| `SkillStore` | `core/evolution/protocols.py` | `read_skill()`, `write_skill()`, `list_versions()` | 版本化技能存储 |
|
| `VLMProvider` | `chat_with_images()` | VLM 图文调用(全异步) |
|
||||||
| `PromptStore` | `core/evolution/protocols.py` | `read_prompt()`, `write_prompt()` | 版本化提示词存储 |
|
| `TelemetryRecorder` | `record_llm_call()` | LLM 调用遥测 |
|
||||||
| `RunLog` | `core/evolution/protocols.py` | `insert()`, `query()` | 实验日志 |
|
|
||||||
| `TelemetryRecorder` | `core/evolution/protocols.py` | `record_llm_call()` | Agent 遥测 |
|
**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`)
|
### 3.2 应用层端口(`app/ports.py`)
|
||||||
|
|
||||||
@@ -239,9 +255,12 @@ project_root/
|
|||||||
| `provider` | str | API 端点标识 |
|
| `provider` | str | API 端点标识 |
|
||||||
| `messages` | str (JSON) | 原始输入 |
|
| `messages` | str (JSON) | 原始输入 |
|
||||||
| `response` | str | 原始输出 |
|
| `response` | str | 原始输出 |
|
||||||
|
| `thinking` | str | thinking/reasoning 内容 |
|
||||||
| `prompt_tokens` | int | 输入 token 数 |
|
| `prompt_tokens` | int | 输入 token 数 |
|
||||||
| `completion_tokens` | int | 输出 token 数 |
|
| `completion_tokens` | int | 输出 token 数 |
|
||||||
| `latency_ms` | int | 延迟毫秒 |
|
| `latency_ms` | int | 延迟毫秒 |
|
||||||
|
| `ttft_ms` | float? | 首 token 延迟(流式测量) |
|
||||||
|
| `max_inter_token_ms` | float? | 最大 token 间隔(流式测量) |
|
||||||
| `cache_hit` | bool | 是否命中 Redis 缓存 |
|
| `cache_hit` | bool | 是否命中 Redis 缓存 |
|
||||||
| `error` | str? | 异常信息(正常为 null) |
|
| `error` | str? | 异常信息(正常为 null) |
|
||||||
|
|
||||||
@@ -251,37 +270,39 @@ project_root/
|
|||||||
|
|
||||||
## §5 LLM 调用韧性
|
## §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)` |
|
| 1 | 熔断器 | 连续 N 失败 → 短路 M 秒 → 探针恢复(`adapters/breaker.py`) |
|
||||||
| 2 | 指数退避重试 | `max_retries`、`base_delay`、`max_delay`(可配置) |
|
| 2 | Redis 响应缓存 | content-addressed:`hash(model + messages)` → response |
|
||||||
| 3 | 熔断器 | 连续 N 失败 → 短路 M 秒 → 探针恢复 |
|
| 3 | 流式三层看门狗 | TTFT / inter_token / total 超时保护(`adapters/streaming.py`) |
|
||||||
| 4 | Redis 响应缓存 | content-addressed:`hash(model + messages)` → response |
|
| 4 | 指数退避重试 | `max_retries`、`base_delay`、`max_delay`(可配置) |
|
||||||
| 5 | ARQ 任务队列 | 长时间推理任务异步执行 |
|
|
||||||
|
|
||||||
熔断参数(`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) →
|
chat(messages) →
|
||||||
if breaker.is_open: raise CircuitOpen
|
if breaker.is_open: raise CircuitOpen # 层1: 熔断
|
||||||
key = hash(model, messages)
|
call_id = uuid4()
|
||||||
if redis.exists(key): return cached # 层4: 缓存
|
if cache.get(model, messages): # 层2: 缓存
|
||||||
try:
|
telemetry.record(..., cache_hit=True)
|
||||||
resp = await wait_for(api(messages), # 层1: 硬超时
|
return cached
|
||||||
timeout)
|
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()
|
breaker.record_success()
|
||||||
redis.set(key, resp)
|
cache.set(model, messages, response)
|
||||||
telemetry.record(...) # 遥测
|
telemetry.record(...)
|
||||||
return resp
|
return response
|
||||||
except Transient:
|
except transient → backoff + breaker.record_failure()
|
||||||
retry with backoff # 层2: 退避重试
|
except 401/403 → breaker.force_open(); raise
|
||||||
except:
|
|
||||||
breaker.record_failure() # 层3: 熔断
|
|
||||||
raise
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
Reference in New Issue
Block a user