1141 lines
38 KiB
Markdown
1141 lines
38 KiB
Markdown
# 个人任务 Agent 设计方案 v2
|
|
|
|
> 一个面向"写汇报 PPT、写科研申报书、写代码"三类任务的轻量级 agent 框架。
|
|
> 完全自实现,综合 nanobot / CoreCoder / better-claw / smolagents 的优点,模型自由,**为模型持续升级做了演化性设计**。
|
|
|
|
> **v2 更新要点(2026-05)**:
|
|
> 1. 模型策略基于 DeepSeek V4 实际能力(2026-04-24 发布)重新设计
|
|
> 2. Skill 系统对齐 Anthropic 开放标准(2025-12 发布,行业已成共识)
|
|
> 3. 工具范式改为 Hybrid:JSON tool call + run_python 混合
|
|
> 4. 新增第 6 章「演化性设计」—— 让 agent 跟着模型升级而升级
|
|
> 5. 新增 Eval Suite 框架,作为模型升级的决策依据
|
|
|
|
---
|
|
|
|
## 1. 设计目标与边界
|
|
|
|
### 1.1 要做什么
|
|
|
|
构建一个本地运行的任务型 agent,能稳定完成三类工作:
|
|
|
|
| 任务模式 | 输入示例 | 输出 | 主要能力 |
|
|
|---------|---------|------|---------|
|
|
| **PPT 模式** | "把这份会议纪要做成 5 页汇报 PPT" | `.pptx` 文件 | 大纲提炼、版式设计、图表生成 |
|
|
| **科研申报模式** | "写一份国自然青年基金的立项依据" | `.docx` 文件(分章节) | 长文写作、文献检索、格式套模板 |
|
|
| **编码模式** | "修这个文件的 bug" / "实现这个函数" | 修改后的代码 | 文件编辑、shell 执行、迭代验证 |
|
|
|
|
### 1.2 明确不做什么
|
|
|
|
- ❌ 子 agent / subagent
|
|
- ❌ IM 渠道(Telegram / WeChat / Discord 等)
|
|
- ❌ 多用户系统
|
|
- ❌ Web UI(初期 CLI 即可)
|
|
- ❌ 自定义 RAG / 向量检索
|
|
- ❌ Anthropic 锁定(必须模型自由)
|
|
|
|
### 1.3 关键约束
|
|
|
|
- **模型自由**:支持 DeepSeek V4 / Kimi / Qwen / GPT / Claude 等任意 OpenAI-compatible API
|
|
- **代码可控**:总代码量 1100-1500 行,自己能完全读懂
|
|
- **任务持久化**:任意时刻关机,下次能恢复
|
|
- **长任务稳定**:单任务可跑数小时不崩
|
|
- **演化性**:模型升级时 agent 能力随之升级,不需要大改架构
|
|
|
|
---
|
|
|
|
## 2. 各家方案借鉴清单
|
|
|
|
| 借鉴自 | 借鉴的设计 | 为什么抄 |
|
|
|--------|-----------|---------|
|
|
| **CoreCoder** | 主 agent loop 简洁实现 | ~150 行写完核心,可读性极高 |
|
|
| **CoreCoder** | Edit 工具的"唯一匹配"约束 | 防止 LLM 改错地方,业界最佳实践 |
|
|
| **CoreCoder** | 三层 context 压缩(简化版,V4 时代不太用) | 兜底方案 |
|
|
| **Anthropic Agent Skills** | **SKILL.md + 渐进披露 标准** | 行业标准,2025-12 开放,跨平台兼容 |
|
|
| **nanobot** | Workspace + 任务隔离的目录结构 | 多任务并行不互相污染 |
|
|
| **nanobot** | 双层记忆(core + extended) | core 注入 prompt,extended 按需读 |
|
|
| **better-claw** | Mid-query 轮转 + carryover | 长任务工程兜底 |
|
|
| **better-claw** | 任务状态持久化(state.json) | 支持中断恢复 |
|
|
| **smolagents** | LiteLLM 做模型层 | 一行切换 25+ provider |
|
|
| **smolagents** | `@tool` 装饰器风格 | 工具定义最简洁 |
|
|
| **smolagents** | **CodeAgent 思路(部分采用)** | 通过 `run_python` 工具实现 hybrid 范式 |
|
|
| **CodeAct 论文** | 代码作为 action 的范式 | 在数据/计算/批处理任务上比 JSON 强 20% |
|
|
|
|
---
|
|
|
|
## 3. 整体架构
|
|
|
|
### 3.1 分层结构
|
|
|
|
```
|
|
┌──────────────────────────────────────────────┐
|
|
│ 入口层 │
|
|
│ - CLI(初期):interactive REPL │
|
|
│ - 后期可加:Streamlit / Gradio Web UI │
|
|
└──────────────────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────┐
|
|
│ 任务路由层 │
|
|
│ - /ppt → PPT 模式 + ppt skill │
|
|
│ - /doc → 科研写作模式 + proposal skill │
|
|
│ - /code → 编码模式 + coding skill │
|
|
│ - 默认 → 通用模式 │
|
|
└──────────────────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────┐
|
|
│ Agent 核心 │
|
|
│ - Loop:ReAct 循环(LLM ↔ Tool) │
|
|
│ - Capability Manager:模型能力探测与适配 │
|
|
│ - Context Manager:三层压缩(必要时) │
|
|
│ - Session:对话历史持久化 │
|
|
│ - Memory:双层记忆系统 │
|
|
└──────────────────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────┐
|
|
│ 工具层(Hybrid 范式) │
|
|
│ 通用工具(JSON tool call): │
|
|
│ - read / write / edit / glob / grep / shell │
|
|
│ - web_search / web_fetch │
|
|
│ - load_skill │
|
|
│ - run_python(沙盒执行,Hybrid 关键) │
|
|
│ │
|
|
│ Skill 提供的工具(标准格式): │
|
|
│ - skills/<name>/scripts/*.py │
|
|
└──────────────────────────────────────────────┘
|
|
↓
|
|
┌──────────────────────────────────────────────┐
|
|
│ LLM 层(LiteLLM + Model Profile) │
|
|
│ - 默认:DeepSeek V4-Flash │
|
|
│ - 升级:DeepSeek V4-Pro / Claude Opus 4.7 │
|
|
│ - Profile 化配置,新模型 5 分钟接入 │
|
|
└──────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 3.2 目录结构
|
|
|
|
```
|
|
your_agent/
|
|
├── core/
|
|
│ ├── __init__.py
|
|
│ ├── loop.py # 主 agent loop ~150 行
|
|
│ ├── llm.py # LiteLLM 封装 ~120 行
|
|
│ ├── capabilities.py # 模型能力探测与适配 ~100 行 ⭐新增
|
|
│ ├── context.py # 三层 context 压缩 ~150 行
|
|
│ ├── session.py # 会话持久化 ~100 行
|
|
│ └── memory.py # 双层记忆 ~80 行
|
|
├── tools/
|
|
│ ├── __init__.py
|
|
│ ├── base.py # Tool 基类 ~50 行
|
|
│ ├── fs.py # read/write/edit/glob/grep ~250 行
|
|
│ ├── shell.py # bash 执行 + 安全检查 ~80 行
|
|
│ ├── web.py # web_search + fetch ~80 行
|
|
│ ├── run_python.py # 沙盒 Python 执行器 ~100 行 ⭐新增
|
|
│ └── skill_tool.py # load_skill 工具 ~60 行
|
|
├── skills/ # ⭐ 标准 Anthropic Agent Skills 格式
|
|
│ ├── ppt/
|
|
│ │ ├── SKILL.md # 主指引(短)
|
|
│ │ ├── references/ # 详细资料(按需读)
|
|
│ │ ├── scripts/ # 可执行脚本
|
|
│ │ └── assets/ # 模板等资源
|
|
│ ├── proposal/
|
|
│ │ ├── SKILL.md
|
|
│ │ ├── references/
|
|
│ │ ├── scripts/
|
|
│ │ └── assets/
|
|
│ └── coding/
|
|
│ ├── SKILL.md
|
|
│ └── references/
|
|
├── prompts/ # ⭐ 版本化系统提示词
|
|
│ ├── system/
|
|
│ │ ├── general_v1.md
|
|
│ │ └── general_active.md → general_v1.md
|
|
│ └── modes/
|
|
│ ├── ppt.md
|
|
│ ├── proposal.md
|
|
│ └── coding.md
|
|
├── config/
|
|
│ ├── agent.yaml # 主配置
|
|
│ └── models/ # ⭐ 模型档案库
|
|
│ ├── _template.yaml
|
|
│ ├── deepseek_v4.yaml
|
|
│ ├── claude_4_7.yaml
|
|
│ └── gpt_5.yaml
|
|
├── evals/ # ⭐ 评估任务集
|
|
│ ├── coding/
|
|
│ ├── ppt/
|
|
│ ├── proposal/
|
|
│ └── runner.py # eval 执行器
|
|
├── workspace/ # 用户数据(gitignore)
|
|
│ ├── tasks/
|
|
│ ├── memory/
|
|
│ │ ├── core.md
|
|
│ │ └── extended/
|
|
│ └── logs/
|
|
├── cli.py # CLI 入口 ~150 行
|
|
├── main.py # 装配 + 启动 ~50 行
|
|
├── requirements.txt
|
|
└── README.md
|
|
```
|
|
|
|
**总代码量预估:1300-1600 行 Python**(比 v1 多 ~200 行,因为加了 capabilities 和 run_python)
|
|
|
|
---
|
|
|
|
## 4. 核心组件设计
|
|
|
|
### 4.1 Agent Loop(`core/loop.py`)
|
|
|
|
```python
|
|
class AgentLoop:
|
|
def __init__(self, llm, tools, capabilities, context_manager, session,
|
|
max_iterations=None):
|
|
self.llm = llm
|
|
self.tools = tools
|
|
self.caps = capabilities # ⭐ 模型能力档案
|
|
self.ctx = context_manager
|
|
self.session = session
|
|
# 迭代次数从 capabilities 读取,不同模型不同
|
|
self.max_iterations = max_iterations or self.caps.max_iterations
|
|
|
|
def run(self, user_message: str) -> str:
|
|
self.session.append({"role": "user", "content": user_message})
|
|
|
|
for iteration in range(self.max_iterations):
|
|
messages = self.ctx.check_and_compress(self.session.messages)
|
|
|
|
response = self.llm.chat(
|
|
messages=messages,
|
|
tools=[t.schema for t in self.tools.values()],
|
|
# ⭐ 高级特性按 capabilities 启用
|
|
parallel_tool_calls=self.caps.parallel_tools,
|
|
reasoning_effort=self.caps.default_reasoning_effort
|
|
)
|
|
msg = response.choices[0].message
|
|
self.session.append(msg)
|
|
|
|
if not msg.tool_calls:
|
|
return msg.content
|
|
|
|
# ⭐ 并行 vs 串行执行根据能力决定
|
|
if self.caps.parallel_tools and len(msg.tool_calls) > 1:
|
|
results = self._execute_tools_parallel(msg.tool_calls)
|
|
else:
|
|
results = self._execute_tools_serial(msg.tool_calls)
|
|
|
|
for tool_call, result in results:
|
|
self.session.append({
|
|
"role": "tool",
|
|
"tool_call_id": tool_call.id,
|
|
"content": result
|
|
})
|
|
|
|
return "[已达到最大迭代次数]"
|
|
```
|
|
|
|
### 4.2 Model Profile + Capabilities(`core/capabilities.py`)⭐ 新增
|
|
|
|
**核心思想**:每个模型有自己的能力档案,agent 行为根据档案动态调整。新模型出来,加一个 yaml 即可。
|
|
|
|
```python
|
|
@dataclass
|
|
class ModelCapabilities:
|
|
"""模型能力档案"""
|
|
model_id: str
|
|
|
|
# 基础能力
|
|
max_context: int # 最大上下文(tokens)
|
|
reliable_context: int # 实测可靠上下文
|
|
max_output: int # 最大输出
|
|
|
|
# Tool calling
|
|
parallel_tools: bool # 是否支持并行 tool call
|
|
tool_calling_quality: str # "excellent" / "good" / "fair"
|
|
|
|
# 思考模式
|
|
thinking_mode: bool # 是否支持思考模式
|
|
reasoning_effort_levels: list # ["low","medium","high","max"]
|
|
default_reasoning_effort: str
|
|
|
|
# 推理与代码
|
|
code_quality: str # CodeAct 范式适配度
|
|
enable_run_python: bool # 是否启用 run_python 工具
|
|
|
|
# 工程参数
|
|
max_iterations: int # 最大迭代次数
|
|
optimal_temperature: float
|
|
|
|
# 特殊功能
|
|
prompt_caching: bool # Anthropic 特有
|
|
extended_thinking: bool # Claude 4.x 特有
|
|
|
|
@classmethod
|
|
def from_yaml(cls, path: Path) -> "ModelCapabilities":
|
|
"""从 config/models/*.yaml 加载"""
|
|
...
|
|
|
|
@classmethod
|
|
def detect(cls, model_id: str) -> "ModelCapabilities":
|
|
"""根据 model_id 自动找到对应档案
|
|
deepseek-v4-flash → config/models/deepseek_v4.yaml (variant=flash)
|
|
claude-opus-4-7 → config/models/claude_4_7.yaml
|
|
"""
|
|
...
|
|
```
|
|
|
|
**模型档案示例** (`config/models/deepseek_v4.yaml`):
|
|
|
|
```yaml
|
|
family: deepseek_v4
|
|
variants:
|
|
flash:
|
|
model_id: deepseek/deepseek-v4-flash
|
|
max_context: 1048576
|
|
reliable_context: 262144
|
|
max_output: 384000
|
|
parallel_tools: true
|
|
tool_calling_quality: good
|
|
thinking_mode: true
|
|
reasoning_effort_levels: [non_thinking, thinking]
|
|
default_reasoning_effort: non_thinking
|
|
code_quality: good
|
|
enable_run_python: true
|
|
max_iterations: 50
|
|
optimal_temperature: 0.3
|
|
prompt_caching: false
|
|
extended_thinking: false
|
|
|
|
pro:
|
|
model_id: deepseek/deepseek-v4-pro
|
|
max_context: 1048576
|
|
reliable_context: 524288
|
|
max_output: 384000
|
|
parallel_tools: true
|
|
tool_calling_quality: excellent
|
|
thinking_mode: true
|
|
reasoning_effort_levels: [low, medium, high, max]
|
|
default_reasoning_effort: medium
|
|
code_quality: excellent
|
|
enable_run_python: true
|
|
max_iterations: 100
|
|
optimal_temperature: 0.2
|
|
prompt_caching: false
|
|
extended_thinking: false
|
|
```
|
|
|
|
**Claude 4.7 档案** (`config/models/claude_4_7.yaml`):
|
|
|
|
```yaml
|
|
family: claude_4_7
|
|
variants:
|
|
opus:
|
|
model_id: anthropic/claude-opus-4-7
|
|
max_context: 200000
|
|
reliable_context: 200000
|
|
max_output: 8192
|
|
parallel_tools: true
|
|
tool_calling_quality: excellent
|
|
thinking_mode: true
|
|
reasoning_effort_levels: [low, medium, high]
|
|
default_reasoning_effort: medium
|
|
code_quality: excellent
|
|
enable_run_python: true
|
|
max_iterations: 100
|
|
optimal_temperature: 0.2
|
|
prompt_caching: true # Claude 特有
|
|
extended_thinking: true
|
|
```
|
|
|
|
**新模型怎么加(以未来 V5 为例)**:
|
|
|
|
```bash
|
|
# 1. 复制模板
|
|
cp config/models/_template.yaml config/models/deepseek_v5.yaml
|
|
|
|
# 2. 填能力(从模型发布博客 + 跑一次 capability probe)
|
|
vim config/models/deepseek_v5.yaml
|
|
|
|
# 3. 跑 eval suite 验证
|
|
python evals/runner.py --model deepseek-v5-pro
|
|
|
|
# 4. 切换默认模型
|
|
vim config/agent.yaml # default_model: deepseek-v5-pro
|
|
```
|
|
|
|
整个流程不需要改任何 agent 核心代码。
|
|
|
|
### 4.3 LLM 封装(`core/llm.py`)
|
|
|
|
```python
|
|
class LLM:
|
|
def __init__(self, capabilities: ModelCapabilities, api_key: str, base_url: str = None):
|
|
self.caps = capabilities
|
|
self.api_key = api_key
|
|
self.base_url = base_url
|
|
self.token_counter = TokenCounter()
|
|
|
|
def chat(self, messages, tools=None, parallel_tool_calls=None,
|
|
reasoning_effort=None, max_retries=3):
|
|
# 用 capabilities 自动填充默认值
|
|
kwargs = {
|
|
"model": self.caps.model_id,
|
|
"messages": messages,
|
|
"tools": tools,
|
|
"temperature": self.caps.optimal_temperature,
|
|
"api_key": self.api_key,
|
|
"base_url": self.base_url,
|
|
}
|
|
|
|
# 按能力启用
|
|
if self.caps.parallel_tools and parallel_tool_calls is not False:
|
|
kwargs["parallel_tool_calls"] = True
|
|
if self.caps.thinking_mode and reasoning_effort:
|
|
kwargs["reasoning_effort"] = reasoning_effort
|
|
if self.caps.prompt_caching:
|
|
kwargs["extra_headers"] = {"anthropic-beta": "prompt-caching-2024-07-31"}
|
|
|
|
for attempt in range(max_retries):
|
|
try:
|
|
response = litellm.completion(**kwargs)
|
|
self.token_counter.add(response.usage, self.caps.model_id)
|
|
return response
|
|
except (RateLimitError, APIConnectionError):
|
|
if attempt == max_retries - 1:
|
|
raise
|
|
time.sleep(2 ** attempt)
|
|
```
|
|
|
|
### 4.4 工具系统(Hybrid 范式)
|
|
|
|
#### 4.4.1 通用工具(JSON tool call)
|
|
|
|
文件操作工具的核心仍然是 **Edit 工具的"唯一匹配"约束**(借鉴 CoreCoder):
|
|
|
|
```python
|
|
class EditTool(Tool):
|
|
name = "edit"
|
|
description = "Replace a unique string in a file with another string."
|
|
|
|
def execute(self, path: str, old_str: str, new_str: str) -> str:
|
|
content = Path(path).read_text()
|
|
count = content.count(old_str)
|
|
if count == 0:
|
|
return f"[Error] old_str not found in {path}"
|
|
if count > 1:
|
|
return f"[Error] old_str appears {count} times, must be unique"
|
|
Path(path).write_text(content.replace(old_str, new_str))
|
|
return self._make_diff(content, ...)
|
|
```
|
|
|
|
#### 4.4.2 `run_python` 工具 ⭐ 新增
|
|
|
|
**Hybrid 范式的关键**:agent 主要用 JSON tool call,但需要时可以写代码作为 action:
|
|
|
|
```python
|
|
class RunPythonTool(Tool):
|
|
name = "run_python"
|
|
description = """Execute Python code in a sandboxed environment.
|
|
|
|
Use for:
|
|
- Data analysis, statistics, calculations
|
|
- Batch file operations (process many files)
|
|
- Document generation (PPT, Word, charts)
|
|
- Tasks where code is more natural than tool composition
|
|
|
|
Available libraries: pandas, numpy, matplotlib, python-pptx, python-docx,
|
|
arxiv, requests, pypdf, pdfplumber.
|
|
|
|
Working directory is the current task's tasks/<task_id>/.
|
|
Files created here are automatically available to the user.
|
|
"""
|
|
|
|
def execute(self, code: str, timeout: int = 60) -> str:
|
|
# 阶段 1(本地用):subprocess + venv + 工作目录限制
|
|
# 阶段 2(更安全):Docker container
|
|
# 阶段 3(公开服务):E2B / Modal
|
|
|
|
with tempfile.NamedTemporaryFile(suffix=".py", mode="w", delete=False) as f:
|
|
f.write(code)
|
|
script_path = f.name
|
|
|
|
try:
|
|
result = subprocess.run(
|
|
[sys.executable, script_path],
|
|
cwd=self.task_dir, # 限制工作目录
|
|
capture_output=True,
|
|
timeout=timeout,
|
|
env=self._safe_env() # 过滤敏感环境变量
|
|
)
|
|
return self._format_result(result)
|
|
finally:
|
|
os.unlink(script_path)
|
|
```
|
|
|
|
**为什么这是关键设计**:
|
|
- **JSON tool call 处理离散操作**(读文件、跑命令、查文献)
|
|
- **Code execution 处理连续逻辑**(算数据、批处理、生成文档)
|
|
- 模型自己决定什么时候用什么,不是你硬编码
|
|
|
|
#### 4.4.3 工具粒度原则 ⭐ 新增
|
|
|
|
工具切分按"原子操作"原则,不做高级封装:
|
|
|
|
```python
|
|
# ❌ 反模式:工具做太多,模型用不灵活
|
|
class GenerateProposalTool:
|
|
def execute(self, topic):
|
|
# 内部硬编码 8 章节流程
|
|
|
|
# ✅ 正模式:原子操作,组合策略给模型
|
|
class WriteSectionTool: # 写一节
|
|
class CompileDocxTool: # 合并成 docx
|
|
class SearchPapersTool: # 查文献
|
|
class FormatBibtexTool: # BibTeX 格式化
|
|
```
|
|
|
|
**理由**:模型变强后会有更好的组合策略。封装太死接收不到模型升级的红利。
|
|
|
|
### 4.5 Skill 系统(对齐 Anthropic 开放标准)⭐ 重大调整
|
|
|
|
#### 4.5.1 标准目录结构
|
|
|
|
每个 skill 是一个目录,包含:
|
|
|
|
```
|
|
skills/proposal/
|
|
├── SKILL.md # 主指引(短,~3000 tokens 内)
|
|
├── references/ # 详细资料(按需加载)
|
|
│ ├── nsfc_format.md
|
|
│ ├── citation_style.md
|
|
│ └── section_examples.md
|
|
├── scripts/ # 可执行脚本(可作为工具)
|
|
│ ├── search_papers.py
|
|
│ ├── format_bibtex.py
|
|
│ └── compile_docx.py
|
|
└── assets/ # 模板、字体等
|
|
└── templates/
|
|
├── nsfc_youth.docx
|
|
├── nsfc_general.docx
|
|
└── nsfc_key.docx
|
|
```
|
|
|
|
#### 4.5.2 SKILL.md 标准格式
|
|
|
|
```markdown
|
|
---
|
|
name: proposal
|
|
description: 撰写科研申报书(国自然/省基金/横向项目)。当用户需要写课题申请、立项依据、项目书时使用。
|
|
---
|
|
|
|
# 科研申报书
|
|
|
|
## 资源
|
|
- `references/nsfc_format.md`:国自然格式细节
|
|
- `references/citation_style.md`:引文规范
|
|
- `references/section_examples.md`:各章节范例
|
|
- `scripts/search_papers.py`:可执行,文献检索
|
|
- `scripts/compile_docx.py`:可执行,合并章节为 docx
|
|
- `assets/templates/`:不同基金类型的 docx 模板
|
|
|
|
## 原则
|
|
- 文献必须真实(用 search_papers,绝不编造)
|
|
- 分章节写,不一次性生成全文
|
|
- 先与用户对齐课题信息卡片
|
|
|
|
## 工作目录
|
|
所有产出在 `tasks/<task_id>/`:
|
|
- `project.md` - 课题信息卡片
|
|
- `sections/<section_name>.md` - 各章节草稿
|
|
- `proposal.docx` - 最终输出
|
|
|
|
## 字数参考
|
|
立项依据 5000-8000 字,研究内容 3000-5000 字。
|
|
具体格式参见 references/nsfc_format.md。
|
|
```
|
|
|
|
注意:**不再写"Step 1/2/3"流程**,只写资源、原则、目标。让模型自己规划。
|
|
|
|
#### 4.5.3 Progressive Disclosure(渐进披露)的三层加载
|
|
|
|
按 Anthropic 标准:
|
|
|
|
| 层 | 时机 | 内容 | Token 占用 |
|
|
|---|------|------|----------|
|
|
| **Discovery** | Agent 启动 | 仅 `name + description`,所有 skill 都加载 | 几百 tokens |
|
|
| **Activation** | 任务匹配某个 skill | 完整 SKILL.md 主体 | 1000-5000 tokens |
|
|
| **Execution** | SKILL.md 引用某个 reference 时 | 单个 reference 文件 | 视情况 |
|
|
|
|
具体实现:
|
|
|
|
```python
|
|
# 启动时:Discovery
|
|
def build_initial_system_prompt(skills) -> str:
|
|
skill_descriptions = []
|
|
for name, skill in skills.items():
|
|
meta = parse_frontmatter(skill["SKILL.md"])
|
|
skill_descriptions.append(f"- {name}: {meta['description']}")
|
|
|
|
return f"""
|
|
You are a task agent. Available skills:
|
|
{chr(10).join(skill_descriptions)}
|
|
|
|
Use `load_skill(name)` to load full instructions when relevant.
|
|
"""
|
|
|
|
# Agent 调用 load_skill 后:Activation
|
|
class LoadSkillTool(Tool):
|
|
def execute(self, name: str) -> str:
|
|
return (skills_dir / name / "SKILL.md").read_text()
|
|
|
|
# Agent 在 SKILL.md 里看到 references/xxx.md,主动调 read_file:Execution
|
|
# 这一层不需要专门工具,就是普通 read 工具
|
|
```
|
|
|
|
#### 4.5.4 Skill 设计原则(基于 Anthropic 官方 + 行业经验)
|
|
|
|
1. **Description 是关键** —— 决定模型能否触发,要明确具体
|
|
2. **SKILL.md 主体不超过 5000 tokens / 500 行** —— 超过就拆 references/
|
|
3. **写 WHY+WHAT,不写 HOW** —— 描述目标和资源,不写步骤
|
|
4. **代码即工具又是文档** —— scripts/ 里的脚本可以执行,也可以读到 context 当文档
|
|
5. **保持 Skill 数 ≤ 20**,工具数 ≤ 10 同时可见(超过后准确率下降)
|
|
|
|
### 4.6 Context 管理(简化版)
|
|
|
|
V4 时代 long context 性能好了很多,**大部分任务不再需要复杂压缩**。但保留三层兜底:
|
|
|
|
```python
|
|
class ContextManager:
|
|
def __init__(self, capabilities, llm):
|
|
# ⭐ 阈值从 capabilities 读取,不同模型不同
|
|
self.max_tokens = capabilities.reliable_context
|
|
self.soft = self.max_tokens * 0.6 # V4 长 context 强,提高阈值
|
|
self.force = self.max_tokens * 0.85
|
|
self.collapse = self.max_tokens * 0.95
|
|
self.llm = llm
|
|
|
|
def check_and_compress(self, messages):
|
|
tokens = count_tokens(messages)
|
|
if tokens < self.soft:
|
|
return messages
|
|
if tokens < self.force:
|
|
return self._snip_old_tool_results(messages)
|
|
if tokens < self.collapse:
|
|
return self._microcompact(messages)
|
|
return self._collapse(messages, llm=self.llm)
|
|
```
|
|
|
|
**实测预期**:DeepSeek V4-Pro 在 256K 内基本不触发任何压缩,写一份完整申报书(7-8 万 token)用 V4-Flash 也只触发到 soft 层。
|
|
|
|
### 4.7 Session 与 Memory
|
|
|
|
(沿用 v1 设计,无重大变化)
|
|
|
|
---
|
|
|
|
## 5. 模型路由策略(基于 V4 实际能力)⭐ 重大调整
|
|
|
|
### 5.1 默认配置:V4-Flash 当主力
|
|
|
|
```yaml
|
|
# config/agent.yaml
|
|
default_model: deepseek_v4.flash
|
|
|
|
# 模式覆盖
|
|
by_mode:
|
|
general: deepseek_v4.flash
|
|
|
|
coding: deepseek_v4.flash # SWE-Bench 80.6,Flash 已够用
|
|
coding_hard: deepseek_v4.pro # 复杂 bug、架构设计
|
|
|
|
ppt: deepseek_v4.flash # PPT 生成不需要顶级模型
|
|
|
|
proposal_draft: deepseek_v4.flash
|
|
proposal_final:
|
|
profile: deepseek_v4.pro
|
|
reasoning_effort: max # 终稿用最强模式
|
|
|
|
# 工具用模型(便宜)
|
|
utility:
|
|
summarize: deepseek_v4.flash
|
|
title: deepseek_v4.flash
|
|
|
|
# 紧急升级路径(V4 不行时手动切)
|
|
fallback:
|
|
- claude_4_7.opus # 国基终稿如果质量不够,临时切 Claude
|
|
```
|
|
|
|
### 5.2 成本预估
|
|
|
|
| 任务 | V4-Flash | V4-Pro-Max | Claude Opus 4.7 |
|
|
|-----|---------|-----------|------------------|
|
|
| 修一个 bug(~10 轮) | $0.01 | $0.05 | $0.30 |
|
|
| 5 页汇报 PPT | $0.05 | $0.20 | $1.50 |
|
|
| 一份完整申报书(2-3 小时) | $0.30 | $1.50 | $10-15 |
|
|
|
|
**结论**:99% 任务 V4-Flash 已够用,关键终稿可升级 Pro,Claude 仅作 fallback。
|
|
|
|
---
|
|
|
|
## 6. 演化性设计 ⭐ 新增章节
|
|
|
|
> **核心问题**:模型每 3-6 个月迭代一次,agent 怎么不被甩在后面?
|
|
|
|
### 6.1 设计哲学
|
|
|
|
**Less Scaffolding, More Trust**(少脚手架,多信任)
|
|
|
|
老 agent 框架(LangChain 早期、AutoGPT)失败的核心原因:**给 LLM 太多脚手架,模型升级后这些脚手架成了枷锁**。
|
|
|
|
参考反例:
|
|
- 强制 ReAct 三段式输出 —— GPT-4 出来后这种格式反而降智
|
|
- PydanticOutputParser 死磕格式 —— Structured Output 内置后成了多此一举
|
|
- Prompt 里详细教"应该怎么思考" —— 强模型不需要被教
|
|
|
|
**正确做法**:把 LLM 当一个**会持续变强的同事**对待,告诉它目标,不告诉它步骤。
|
|
|
|
### 6.2 七条具体原则
|
|
|
|
#### 原则 1:Prompt 用 WHY+WHAT,不用 HOW
|
|
|
|
```
|
|
❌ HOW 型:
|
|
"修 bug 时:
|
|
1. 先用 read 工具读文件
|
|
2. 再用 grep 找相关位置
|
|
3. 然后用 edit 工具替换
|
|
4. 最后跑测试..."
|
|
|
|
✅ WHY+WHAT 型:
|
|
"目标:修复用户报告的 bug,做最小可逆修改。
|
|
工具:read, edit, grep, run, ...
|
|
原则:验证后再改、最小变更、有测试就跑。"
|
|
```
|
|
|
|
#### 原则 2:Skill 用渐进披露,不写完整流程
|
|
|
|
直接对齐 Anthropic 开放标准。Discovery 层只放 description,模型理解能力越强触发越准 —— 你不用回头给老 description 加 trigger 词。
|
|
|
|
#### 原则 3:工具按原子操作切分,不做高级封装
|
|
|
|
详见 4.4.3。粒度太粗,模型升级后没有施展空间。
|
|
|
|
#### 原则 4:Model Profile 化,不硬编码
|
|
|
|
详见 4.2。所有模型相关参数都在 yaml 里,新模型 5 分钟接入。
|
|
|
|
#### 原则 5:Capability Probing(启动时探测)
|
|
|
|
```python
|
|
def probe_capabilities(llm) -> dict:
|
|
"""启动时跑几个小测试,验证 yaml 里声称的能力"""
|
|
results = {}
|
|
|
|
# 测试 1:并行 tool call 是否真的工作
|
|
response = llm.chat([...], tools=[...test_tools])
|
|
results["parallel_tools_actual"] = len(response.tool_calls) > 1
|
|
|
|
# 测试 2:thinking mode 输出是否符合预期
|
|
response = llm.chat([...], reasoning_effort="medium")
|
|
results["thinking_works"] = hasattr(response.choices[0].message, "reasoning_content")
|
|
|
|
# 测试 3:long context recall(简化版 needle in haystack)
|
|
needle = f"The secret code is {random_code()}."
|
|
haystack = make_long_context(needle, target_tokens=100_000)
|
|
response = llm.chat([{"role": "user", "content": haystack + "\nWhat is the secret code?"}])
|
|
results["long_context_100k"] = random_code() in response.choices[0].message.content
|
|
|
|
return results
|
|
```
|
|
|
|
发现实际能力跟 yaml 不符 → 警告并自动调整。
|
|
|
|
#### 原则 6:版本化 Prompt,支持 A/B 切换
|
|
|
|
```
|
|
prompts/system/
|
|
├── coding_v1.md # 老模型用的详细版
|
|
├── coding_v2.md # 新模型用的精简版
|
|
└── coding_active.md → coding_v2.md
|
|
```
|
|
|
|
模型升级时:
|
|
1. 写一个新版本 prompt(更精简、更信任模型)
|
|
2. 在 eval suite 上对比 v1 vs v2
|
|
3. 数据说话,切换 active 软链接
|
|
|
|
#### 原则 7:Eval Suite —— 模型升级的决策基础
|
|
|
|
**最关键的一条**。没有 eval,你升级模型只能"凭感觉"。
|
|
|
|
详见下一节。
|
|
|
|
### 6.3 Eval Suite 框架
|
|
|
|
#### 6.3.1 目录结构
|
|
|
|
```
|
|
evals/
|
|
├── coding/
|
|
│ ├── fix_import_bug/
|
|
│ │ ├── input/ # 输入文件
|
|
│ │ │ └── main.py
|
|
│ │ ├── prompt.txt # 给 agent 的指令
|
|
│ │ ├── expected/ # 期望输出
|
|
│ │ │ └── main.py
|
|
│ │ └── rubric.yaml # 评分标准
|
|
│ ├── implement_function/
|
|
│ └── refactor/
|
|
├── ppt/
|
|
│ ├── meeting_to_slides/
|
|
│ │ ├── input/
|
|
│ │ │ └── notes.md
|
|
│ │ ├── prompt.txt
|
|
│ │ └── rubric.yaml # 主观评分(LLM-as-judge)
|
|
│ └── ...
|
|
├── proposal/
|
|
│ ├── write_intro_section/
|
|
│ ├── search_and_cite/
|
|
│ └── ...
|
|
└── runner.py # 执行器
|
|
```
|
|
|
|
#### 6.3.2 Rubric 示例
|
|
|
|
**客观评分**(coding 任务):
|
|
```yaml
|
|
# evals/coding/fix_import_bug/rubric.yaml
|
|
type: deterministic
|
|
checks:
|
|
- type: file_diff
|
|
path: main.py
|
|
expected_path: expected/main.py
|
|
- type: run_command
|
|
command: python -c "import main"
|
|
expect_exit_code: 0
|
|
- type: run_tests
|
|
command: pytest tests/
|
|
```
|
|
|
|
**主观评分**(ppt/proposal 任务):
|
|
```yaml
|
|
# evals/ppt/meeting_to_slides/rubric.yaml
|
|
type: llm_judge
|
|
judge_model: claude-opus-4-7 # 用强模型当裁判
|
|
criteria:
|
|
- "幻灯片数是否符合要求(5 页)"
|
|
- "每页 bullet 是否 ≤ 5 条"
|
|
- "信息密度是否合理"
|
|
- "是否有图表(如果数据 ≥ 3 个点)"
|
|
score_threshold: 7 # 满分 10
|
|
```
|
|
|
|
#### 6.3.3 Runner
|
|
|
|
```python
|
|
# evals/runner.py
|
|
def run_eval_suite(model_id: str, suite: str = "all"):
|
|
results = []
|
|
for case_dir in find_cases(suite):
|
|
# 起一个干净的 agent 实例
|
|
agent = build_agent(model=model_id, workspace=tmp_workspace())
|
|
|
|
# 跑测试
|
|
prompt = (case_dir / "prompt.txt").read_text()
|
|
result = agent.run(prompt)
|
|
|
|
# 评分
|
|
rubric = load_rubric(case_dir / "rubric.yaml")
|
|
score = grade(rubric, agent.workspace, result)
|
|
|
|
results.append({
|
|
"case": case_dir.name,
|
|
"score": score,
|
|
"tokens": agent.token_counter.total,
|
|
"cost": agent.token_counter.cost_usd,
|
|
"duration": agent.duration_seconds
|
|
})
|
|
|
|
return summarize(results)
|
|
|
|
if __name__ == "__main__":
|
|
# 模型升级时,跑这个
|
|
print(run_eval_suite("deepseek-v4-flash"))
|
|
print(run_eval_suite("deepseek-v4-pro"))
|
|
# 对比看哪个性价比最高
|
|
```
|
|
|
|
#### 6.3.4 Eval 的真实用途
|
|
|
|
每次模型升级,你能用数据回答这些问题:
|
|
|
|
> Q1:V5-Flash 出来了,值得升级吗?
|
|
> A:跑 eval suite,对比 V4-Flash vs V5-Flash 的 score 和 cost。
|
|
|
|
> Q2:Claude Opus 5.0 出来了,要不要换主力?
|
|
> A:跑 eval。如果 score 提升 < 10% 但 cost 涨 10x,继续用 DeepSeek。
|
|
|
|
> Q3:某个 prompt 改了之后,效果是好是坏?
|
|
> A:跑 eval。
|
|
|
|
**没有 eval suite,你的"升级"全靠想象。**
|
|
|
|
### 6.4 实操:模型升级 checklist
|
|
|
|
未来 V5、Opus 5、GPT-6 出来时,按这个流程:
|
|
|
|
```markdown
|
|
## 模型升级 Checklist
|
|
|
|
- [ ] 1. 写新模型档案 yaml (5 分钟,从 _template 起)
|
|
- [ ] 2. 跑 capability probe 验证 yaml(10 分钟)
|
|
- [ ] 3. 跑完整 eval suite 测试新模型(30 分钟,看任务量)
|
|
- [ ] 4. 对比 score / cost / latency,判断是否升级
|
|
- [ ] 5. 如果升级:
|
|
- [ ] 在 config 里调整 default_model
|
|
- [ ] 检查现有 prompt 是否可以精简(强模型不需要那么多脚手架)
|
|
- [ ] 跑 eval 回归一遍
|
|
- [ ] 6. 部分模式按需升级(比如只把 proposal_final 升级到新 Pro)
|
|
```
|
|
|
|
整个流程**不需要改 agent 核心代码**。
|
|
|
|
---
|
|
|
|
## 7. 关键工程细节
|
|
|
|
### 7.1 任务状态(`tasks/<task_id>/state.json`)
|
|
|
|
```json
|
|
{
|
|
"task_id": "proposal_20260102_1430",
|
|
"mode": "proposal",
|
|
"description": "国自然青年基金 - LLM agent 在医疗问诊",
|
|
"status": "active",
|
|
"model_used": "deepseek-v4-pro",
|
|
"reasoning_effort": "max",
|
|
"created_at": 1735800000,
|
|
"tokens_used": {"prompt": 145000, "completion": 38000},
|
|
"cost_usd": 0.42
|
|
}
|
|
```
|
|
|
|
### 7.2 中断恢复 / 成本控制 / 安全约束
|
|
|
|
(沿用 v1 设计,无重大变化)
|
|
|
|
---
|
|
|
|
## 8. 实施路线图
|
|
|
|
### Phase 1:最小可用骨架(2 天)
|
|
|
|
- [x] `core/llm.py` + Model Profile 雏形
|
|
- [x] `core/loop.py` - 主循环
|
|
- [x] `core/session.py`
|
|
- [x] `tools/base.py` + `tools/fs.py` + `tools/shell.py`
|
|
- [x] `cli.py` - 基础 REPL
|
|
- [x] `config/agent.yaml` + `config/models/deepseek_v4.yaml`
|
|
|
|
**验收**:`python cli.py chat` 能让 V4-Flash 修一个简单 Python bug。
|
|
|
|
### Phase 2:Skill 系统(标准格式)+ 三个 skill(2 天)
|
|
|
|
- [x] `tools/skill_tool.py`(LoadSkill)
|
|
- [x] 三个 skill 目录,对齐 Anthropic 格式
|
|
- [x] 任务模式路由
|
|
|
|
**验收**:三种模式都能进入,渐进披露正常工作。
|
|
|
|
### Phase 3:Hybrid 范式(1-2 天)
|
|
|
|
- [x] `tools/run_python.py` - subprocess 沙盒版
|
|
- [x] PPT/Word 通过 run_python 生成(不再做高级 API 封装)
|
|
- [x] PDF / 文献检索脚本到 skills/proposal/scripts/
|
|
|
|
**验收**:能产出 .pptx 和 .docx,文献检索真实。
|
|
|
|
### Phase 4:演化性能力(1-2 天)⭐ 新增
|
|
|
|
- [x] `core/capabilities.py` - Model Profile 加载
|
|
- [x] Capability Probing 启动检测
|
|
- [x] 版本化 prompts/ 目录结构
|
|
- [x] 配置热重载
|
|
|
|
**验收**:能切换 V4-Flash 和 V4-Pro 不用改代码,只改 config。
|
|
|
|
### Phase 5:Eval Suite(2 天)⭐ 新增
|
|
|
|
- [x] `evals/runner.py`
|
|
- [x] 每种任务 3-5 个测试 case
|
|
- [x] LLM-as-judge 评分
|
|
- [x] 报告输出(score / cost / latency)
|
|
|
|
**验收**:`python evals/runner.py --model deepseek-v4-flash` 能跑完所有任务并出报告。
|
|
|
|
### Phase 6:长任务工程化(2-3 天)
|
|
|
|
- [x] `core/context.py` - 三层压缩(兜底用)
|
|
- [x] `core/memory.py` - 双层记忆
|
|
- [x] 任务恢复机制
|
|
|
|
**验收**:写完整一份申报书不崩,中断后能恢复。
|
|
|
|
### Phase 7:打磨(持续)
|
|
|
|
- 双层记忆系统完善
|
|
- 更多 skill
|
|
- Web UI(可选)
|
|
- Docker 沙盒(替代 subprocess)
|
|
- 更多模型档案(Claude / GPT / Kimi 等)
|
|
|
|
---
|
|
|
|
## 9. 技术栈清单
|
|
|
|
```
|
|
# requirements.txt(核心)
|
|
|
|
# LLM
|
|
litellm>=1.50.0
|
|
tiktoken>=0.7.0
|
|
|
|
# 文档(给 run_python 用)
|
|
python-pptx>=0.6.21
|
|
python-docx>=1.1.0
|
|
pypdf>=3.17.0
|
|
pdfplumber>=0.10.0
|
|
matplotlib>=3.8.0
|
|
pandas>=2.0.0
|
|
|
|
# 文献
|
|
arxiv>=2.1.0
|
|
requests>=2.31.0
|
|
|
|
# CLI / 配置
|
|
click>=8.1.0
|
|
rich>=13.7.0
|
|
pydantic>=2.5.0
|
|
pyyaml>=6.0
|
|
python-frontmatter>=1.0.0
|
|
|
|
# Eval
|
|
deepdiff>=6.0 # 文件 diff 比对
|
|
|
|
# 开发
|
|
pytest>=7.4.0
|
|
ruff>=0.1.0
|
|
```
|
|
|
|
---
|
|
|
|
## 10. 风险与权衡
|
|
|
|
### 10.1 已知风险
|
|
|
|
| 风险 | 缓解 |
|
|
|-----|------|
|
|
| run_python 沙盒安全(subprocess 不够强) | 限制工作目录 + 环境变量过滤;后期升级 Docker |
|
|
| V4 在某些复杂任务上仍不如 Claude | Eval suite 帮判断;fallback 机制 |
|
|
| Skill description 不够好 → 触发不准 | 用 V4-Pro 优化 description;eval 测触发率 |
|
|
| Long context 退化 | Capability probe 探测 reliable_context;不要依赖宣称值 |
|
|
| Prompt 改了一次就不敢动 | 版本化 + eval 让改动有数据支撑 |
|
|
|
|
### 10.2 取舍说明
|
|
|
|
**为什么改用 Anthropic Skill 标准而不是自创**:
|
|
- 行业标准已成,跨平台兼容
|
|
- 直接拿到 Anthropic skills repo 的现成资源
|
|
- 未来想换底层 SDK 不用改 skill
|
|
|
|
**为什么用 Hybrid 范式而不是纯 CodeAgent**:
|
|
- DeepSeek V4 在 JSON tool calling 已足够稳定
|
|
- 沙盒成本更低(只在需要时执行代码)
|
|
- 可以兼容 thinking 模式(纯 CodeAgent 跟 thinking 不太合)
|
|
|
|
**为什么花精力做 Eval Suite**:
|
|
- 没有 eval,模型升级决策只能凭感觉
|
|
- 一次性投入,长期复用
|
|
- 跑 eval 的成本(~$10)远低于因为没 eval 选错模型的成本
|
|
|
|
**为什么不做 subagent**:
|
|
- 用户明确不需要
|
|
- 加了之后状态管理复杂度爆炸
|
|
- 单 agent + skill 已能覆盖 95% 场景
|
|
|
|
---
|
|
|
|
## 11. 与 v1 方案的差异
|
|
|
|
| 维度 | v1 | v2 |
|
|
|-----|----|----|
|
|
| 默认模型 | deepseek-chat (V3.2) | deepseek-v4-flash |
|
|
| Context 阈值 | 65K, 0.5/0.7/0.85 | 256K, 0.6/0.85/0.95 |
|
|
| 工具范式 | 纯 JSON tool call | **Hybrid:JSON + run_python** |
|
|
| Skill 格式 | 自创 | **Anthropic 开放标准** |
|
|
| Skill 描述风格 | "Step 1/2/3" 流程 | **WHY+WHAT 风格** |
|
|
| 模型配置 | 散在 config 里 | **Model Profile 化** |
|
|
| 升级机制 | 无 | **Capability Probing + Eval Suite** |
|
|
| Prompt 管理 | 散在代码里 | **版本化 + active 软链接** |
|
|
| 工具粒度 | 部分高级封装(如 make_pptx) | **原子化(用 run_python 调 python-pptx)** |
|
|
| 代码量 | 1100-1300 行 | 1300-1600 行 |
|
|
|
|
---
|
|
|
|
## 12. 下一步
|
|
|
|
确认方案后,从 Phase 1 开始落地:
|
|
|
|
1. 起项目骨架
|
|
2. 写 `core/llm.py` + `core/capabilities.py`
|
|
3. 写 `config/models/deepseek_v4.yaml`
|
|
4. 跑通最小 REPL
|
|
5. 用 V4-Flash 跑一个简单任务
|
|
|
|
Phase 1 预计 2 天完成,跑通后能立即用。
|
|
|
|
---
|
|
|
|
## 附录 A:Anthropic Skill 标准参考
|
|
|
|
官方资源:
|
|
- [Agent Skills 文档](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview)
|
|
- [Anthropic skills 仓库](https://github.com/anthropics/skills)(开源,可直接抄)
|
|
- 现成可用的 skill:pdf-processing、xlsx、docx、pptx、claude-api 等
|
|
|
|
行业落地:
|
|
- Claude Code: `~/.claude/skills/`
|
|
- OpenAI Codex CLI: `.agents/skills/`
|
|
- Google Gemini CLI: `.gemini/skills/`
|
|
- GitHub Copilot: 同日跟进
|
|
- **格式完全统一**,只是路径不同
|
|
|
|
## 附录 B:DeepSeek V4 关键事实(2026-04-24)
|
|
|
|
模型:
|
|
- V4-Pro:1.6T 总 / 49B 激活,1M context
|
|
- V4-Flash:284B 总 / 13B 激活,1M context
|
|
- 三种推理模式:non-thinking / thinking / thinking-max
|
|
|
|
Agent 能力(V4-Pro-Max):
|
|
- SWE-Bench Verified: 80.6%(对标 Claude Opus 4.6 的 80.8%)
|
|
- Terminal-Bench 2.0: 67.9%(超过 Claude 4.6 的 64.3%)
|
|
- MCPAtlas: 73.6%(对标 Claude 4.6 的 73.8%)
|
|
|
|
价格:
|
|
- 输入约 $0.145 / M tokens(Claude Opus 的 1/7)
|
|
- 输出约 $1.74 / M tokens(Claude Opus 的 1/6)
|
|
|
|
迁移:
|
|
- `deepseek-chat` / `deepseek-reasoner` 在 2026-07-24 后下线
|
|
- 必须迁到 `deepseek-v4-flash` / `deepseek-v4-pro`
|
|
|
|
## 附录 C:演化性设计的灵感来源
|
|
|
|
- **Anthropic "Equipping agents for the real world"** —— Skill 渐进披露的设计哲学
|
|
- **CodeAct 论文(Wang et al. 2024)** —— Code as action 范式
|
|
- **LangChain 早期教训** —— 过度脚手架的反例
|
|
- **Karpathy 的 nanoGPT 哲学** —— "可读性优先于功能完备"
|
|
|
|
---
|
|
|
|
*Last updated: 2026-05-02*
|
|
*v2 changes: DeepSeek V4 / Anthropic Skill 标准 / Hybrid 范式 / 演化性设计*
|