Resume Agent Harness P0 开发

0 阅读11分钟

建议查看相关链接获取项目核心知识

GIT仓库 | 项目概述 | OpenHarness 源码 | OpenHarness 架构说明

版本:v0.1.0 | 阶段:P0 — Agent 核心可用版 | 日期:2026-04


运行截图演示

---

1. P0 核心目标

P0 阶段的核心命题是:验证 Resume Agent 的技术路线可行性。具体而言,需要证明以下三个关键假设:

假设一:精简裁剪可行

OpenHarness 是一个面向本地 CLI 的重量级 Agent 框架(43+ 工具、多 Provider、MCP stdio、Sandbox、Swarm),能否精准裁剪出适合云端简历场景的最小可用集?

P0 验证结论:可行。RuntimeBundle 成功将 OpenHarness 裁剪为仅保留 OpenAICompatibleClient + 3 个领域工具 + AUTO 权限模式的精简架构,同时保留了 Agent Loop 的完整能力。

假设二:领域提示词 + 记忆注入可产生高质量简历

通过"角色提示词 + 用户记忆 + 领域 Skill"三层组装的系统提示词,LLM 能否稳定输出结构化的 Markdown 简历?记忆系统能否实现"越用越好用"?

P0 验证结论:可行。三层组装机制使 LLM 能稳定输出包含个人简介、工作经历(STAR 法则)、技能标签的结构化简历;memory_write 工具使 LLM 可主动持久化用户偏好,跨会话生效。

假设三:SSE 流式对话可跑通端到端闭环

从 Web 页面输入 → FastAPI SSE 端点 → Agent Loop → DeepSeek API → 流式输出 → 页面渲染,这条链路能否完整跑通?

P0 验证结论:可行。极简 HTML 页面通过 EventSource 实现 SSE 流式对话,text_delta 逐字渲染,assistant_turn_complete 标记完成,端到端闭环已验证。


2. P0 实现逻辑

P0 的实现围绕一条核心链路展开:

用户输入 → SessionPool 获取/创建会话 → 提示词三层组装 → Agent Loop → SSE 流式输出

下面按数据流经的模块顺序,阐述每个环节的实现逻辑。

2.1 入口:FastAPI + SSE 端点

实现文件backend/app.pybackend/routes/chat.py

实现逻辑

  1. FastAPI 应用启动时,在 lifespan() 中校验 API 配置并初始化 SessionPool
  2. 用户发送 POST /api/chat {prompt, session_id},chat 路由从 SessionPool 获取 RuntimeBundle
  3. 调用 bundle.engine.submit_message(prompt) 获取 AsyncIterator[StreamEvent]
  4. 将每个 StreamEvent 通过 format_sse_data() 序列化为 data: {...}\n\n 格式推送给客户端

P0 简化:开发模式下跳过认证,所有请求使用默认 user_id="dev_user"

2.2 会话管理:SessionPool

实现文件resume_agent/session_pool.py

实现逻辑

get_or_create(user_id, session_id)
  ├── 命中缓存 → 更新 last_access → 返回已有 RuntimeBundle
  └── 未命中 → build_resume_runtime() → 存入 _entries → 返回新 RuntimeBundle
  • 会话 Key 设计:{channel}:{user_id}:{session_id or 'default'},为后续多渠道接入预留
  • 淘汰策略:5 分钟定时扫描,30 分钟空闲淘汰,容量满时强制 LRU 淘汰
  • 并发安全:asyncio.Lock 保护 _entries 的读写

P0 简化_save_snapshot() 为空实现,服务重启后对话历史丢失。

2.3 运行时构建:RuntimeBundle

实现文件resume_agent/runtime.py

实现逻辑

build_resume_runtime(user_id, session_id)
  ├── validate_api_config()              → 缺 Key 则拒绝启动
  ├── build_resume_system_prompt(user_id) → 三层组装(见 2.4)
  ├── _get_shared_api_client()           → 进程级单例,连接池复用
  ├── _get_shared_tool_registry()        → 进程级单例,工具定义共享
  ├── _build_auto_permission_checker()   → 固定 FULL_AUTO
  ├── _get_shared_hook_executor()        → 进程级单例,P0 空实现
  └── QueryEngine(...)                   → 每会话独立,对话历史隔离

共享 vs 隔离的设计逻辑

对象生命周期理由
OpenAICompatibleClient进程级共享连接池复用,10 并发共享同一连接
ToolRegistry进程级共享工具定义不变,执行时从 metadata 获取 user_id
HookExecutor进程级共享P0 空实现,后续加载全局钩子
QueryEngine每会话独立对话历史必须隔离,避免跨会话污染
System Prompt每会话独立包含用户专属记忆,按 user_id 差异化注入

P0 裁剪对照

环节OpenHarness 原版P0 精简版
API Client多 Provider(OpenAI + Anthropic)仅 OpenAICompatibleClient → DeepSeek
MCP Managerstdio + HTTP不创建
Tool Registry43+ 内置工具仅 domain 工具
PermissionDEFAULT 需交互确认固定 AUTO
Sandbox / Swarm可选创建不创建

2.4 提示词组装:三层注入

实现文件resume_agent/prompts/system_prompt.py

实现逻辑

build_resume_system_prompt(user_id)
  │
  ├── 第一层:RESUME_AGENT_SYSTEM_PROMPT
  │   角色定义 + 核心能力 + 输出格式约束 + 工作原则
  │   → 赋予 LLM "简历优化顾问" 身份,约束输出为 Markdown 简历格式
  │
  ├── 第二层:load_memory_documents(user_id)
  │   按 user_id 加载记忆目录下的 .md 文件
  │   → 注入用户的简历原文、职业偏好、技能标签、优化历史
  │   → 容量控制:简历原文 16KB,其余 4KB,超出截断
  │
  └── 第三层:load_resume_skill()
      加载 resume_agent/skills/resume-skill.md
      → 注入简历优化领域知识:ATS 友好、STAR 法则、量化成果、关键词匹配

为什么是三层而非一次性写入

  • 第一层是稳定的角色约束,不随用户变化
  • 第二层是用户专属上下文,按 user_id 差异化注入
  • 第三层是领域知识,可被 skill_loader 工具动态重新加载

2.5 Agent Loop:对话引擎

实现文件resume_agent/engine/query_engine.pyresume_agent/engine/query.py

实现逻辑

QueryEngine.submit_message(prompt)
  ↓ 追加用户消息到 _messages
  ↓ 构建 QueryContext(包含所有依赖)
  ↓
run_query(context, messages) — Agent Loop
  │
  ├── 1. auto_compact_if_needed()
  │     检查上下文 token 数,超出阈值则自动压缩历史消息
  │
  ├── 2. OpenAICompatibleClient.stream_message()
  │     → DeepSeek API 流式请求
  │     → yield ApiTextDeltaEvent(逐字文本)
  │     → yield ApiMessageCompleteEvent(完整消息 + usage)
  │
  ├── 3. 解析响应
  │     ├── stop_reason == "stop" → 输出完成
  │     └── stop_reason == "tool_use" → 提取 ToolUseBlock
  │
  ├── 4. execute_tool()
  │     从 ToolRegistry 查找工具 → 执行 → 返回 ToolResult
  │     → memory_write: 持久化用户偏好/技能/优化历史
  │     → skill_loader: 重新加载 resume-skill.md
  │     → web_fetch: 抓取 JD 链接内容
  │
  ├── 5. 追加工具结果到 messages,回到步骤 2
  │     (最多循环 max_turns=8 轮)
  │
  └── 6. yield StreamEvent 流
        → TextDelta / ToolExecutionStarted / ToolExecutionCompleted / AssistantTurnComplete

2.6 API 对接:DeepSeek 客户端

实现文件resume_agent/api/openai_client.py

实现逻辑

  1. 消息转换:将内部 ConversationMessage 转换为 OpenAI Chat Format(_convert_messages_to_openai),包含 system / user / assistant / tool 四种角色
  2. 工具格式转换:将 ToolRegistry 中的工具定义转换为 OpenAI function-calling 格式(_convert_tools_to_openai
  3. 流式请求AsyncOpenAI.chat.completions.create(stream=True) 逐 chunk 解析
  4. 重试机制:指数退避(1s → 2s → 4s,最大 30s),自动重试 429/500/502/503
  5. 错误分类:401/403 → AuthenticationFailure,429 → RateLimitFailure,其他 → RequestFailure

多 Key 轮询池resume_agent/api_key_pool.py):

class ApiKeyPool:
    """按 Key 维护令牌桶,轮询策略公平分配请求。"""
    async def acquire(timeout=30.0) → str     # 获取可用 Key
    def report_429(key, suspend_seconds=10.0)  # 报告 429,暂停该 Key

⚠️ P0 状态:ApiKeyPool 已实现但未集成到 OpenAICompatibleClient,当前仅使用第一个 Key。

2.7 Agent 工具集

实现文件resume_agent/tools/memory_write.pyskill_loader.pyweb_fetch.py

三个工具构成 Agent 的"感知-行动"闭环:

工具逻辑关键约束
memory_writecontext.metadata 获取 user_id,调用 write_memory_file() 写入指定记忆文件白名单控制(仅 职业偏好/技能标签/优化历史 可写),append/replace 模式,容量控制
skill_loader读取 resume_agent/skills/resume-skill.md,返回完整内容只读,仅支持 resume-skill
web_fetchhttpx 异步 GET 请求,提取 HTML 正文文本仅 HTTP/HTTPS,10s 超时,5 分钟缓存,截断 4000 字符

"越用越好用"的实现路径

第 1 次:用户输入信息 → Agent 生成简历 → 调用 memory_write 记录优化历史
第 2 次:系统提示词包含记忆 → Agent 自动遵循偏好 → 主动调用 memory_write 更新
第 3 次:记忆持续积累 → 生成质量持续提升 → 无需重复说明偏好

⚠️ P0 已知问题:三个工具已实现但未在 _get_shared_tool_registry() 中注册,需在 runtime.py 中添加注册逻辑后激活。

2.8 SSE 事件序列化

实现文件resume_agent/models/sse_events.py

实现逻辑

StreamEvent (内部事件)
   sse_event_to_dict()
   按事件类型提取字段
dict
   json.dumps(ensure_ascii=False)
   format_sse_data()
"data: {\"type\": \"text_delta\", \"text\": \"...\"}\n\n"

P0 定义了 9 种 SSE 事件类型:

事件类型触发时机客户端处理
text_deltaLLM 逐字输出追加到助手消息气泡
tool_execution_started工具开始执行显示工具调用提示
tool_execution_completed工具执行完毕显示工具结果
status系统状态变更更新状态栏
error发生错误显示错误提示
assistant_turn_complete本轮对话完成标记完成,启用输入框
ping心跳保活忽略
resume_generated简历生成完成显示下载提示(P1 启用)
connection_timeout连接超时提示重连

3. 配置体系

3.1 配置加载优先级

.env 文件 (python-dotenv 自动加载)
  ↓ 覆盖
环境变量 (DEEPSEEK_API_KEY / DEEPSEEK_BASE_URL / DEEPSEEK_MODEL)
  ↓ 覆盖
全局配置文件 (~/.resume_agent/settings.json)
  ↓ 覆盖
代码内默认值

3.2 快速配置

cp .env.example .env
# 编辑 .env,填写 DEEPSEEK_API_KEY=sk-your-api-key-here

3.3 核心配置项

配置项环境变量默认值说明
API KeyDEEPSEEK_API_KEY必填,支持逗号分隔多 Key
Base URLDEEPSEEK_BASE_URLhttps://api.deepseek.comAPI 地址
ModelDEEPSEEK_MODELdeepseek-chat模型名称
Max Tokens4096单次最大输出 token 数
Max Turns8Agent Loop 最大循环轮次
Context Window64000上下文窗口大小
Max Sessions20SessionPool 最大会话数
Idle Timeout1800会话空闲超时(秒)

4. 代码目录结构

ResumeHarness/
├── backend/                          # FastAPI Web 服务层
│   ├── app.py                        # 应用入口 + 生命周期管理
│   └── routes/
│       └── chat.py                   # POST /api/chat SSE 端点
├── frontend/
│   ├── index.html                    # 极简验证页面
│   └── 测试数据.txt                   # 测试用例文档
├── resume_agent/                     # 核心 Agent 逻辑包
│   ├── __init__.py
│   ├── runtime.py                    # RuntimeBundle 构建 + 进程级单例
│   ├── session_pool.py               # 多租户会话池 (LRU 淘汰)
│   ├── api_key_pool.py               # DeepSeek 多 Key 轮询池
│   ├── exceptions.py                 # 统一错误码与异常定义
│   ├── api/
│   │   ├── client.py                 # SupportsStreamingMessages 协议
│   │   ├── openai_client.py          # OpenAI 兼容客户端
│   │   ├── errors.py                 # API 错误分类
│   │   └── usage.py                  # Token 用量追踪
│   ├── config/
│   │   └── settings.py               # 全局配置加载
│   ├── engine/
│   │   ├── query_engine.py           # 对话引擎(每会话独立)
│   │   ├── query.py                  # Agent Loop 核心循环
│   │   ├── messages.py               # 消息模型
│   │   ├── stream_events.py          # 内部流式事件
│   │   └── cost_tracker.py           # 用量统计
│   ├── hooks/
│   │   ├── executor.py               # 钩子执行器
│   │   └── loader.py                 # 钩子注册表
│   ├── memory/
│   │   ├── manager.py                # 记忆加载/写入/容量控制
│   │   └── paths.py                  # 用户记忆目录路径管理
│   ├── models/
│   │   ├── sse_events.py             # SSE 事件类型定义
│   │   └── api_schemas.py            # 请求/响应 Pydantic 模型
│   ├── permissions/
│   │   ├── checker.py                # 权限检查器
│   │   └── modes.py                  # 权限模式枚举
│   ├── prompts/
│   │   └── system_prompt.py          # 系统提示词模板 + 三层组装逻辑
│   ├── services/
│   │   ├── session_storage.py        # 会话快照持久化(已实现,未对接)
│   │   └── compact.py                # 上下文压缩
│   ├── skills/
│   │   └── resume-skill.md           # 简历优化领域知识
│   ├── templates/                    # 简历 CSS 模板 (P1 阶段启用)
│   └── tools/
│       ├── base.py                   # BaseTool + ToolRegistry + ToolResult
│       ├── memory_write.py           # 记忆写入工具
│       ├── skill_loader.py           # 技能加载工具
│       └── web_fetch.py              # 网页抓取工具
├── tests/                            # 单元测试
├── .env.example                      # 环境变量配置模板
├── pyproject.toml                    # 项目配置与依赖
├── requirements.txt                  # Python 依赖列表
└── README.md                         # 项目说明文档

5. 快速启动

# 1. 创建并激活虚拟环境
python -m venv .venv
# Windows CMD:
.venv\Scripts\activate.bat
# macOS / Linux:
source .venv/bin/activate

# 2. 安装依赖
pip install -r requirements.txt

# 3. 配置 API Key
cp .env.example .env
# 编辑 .env,填写 DEEPSEEK_API_KEY=sk-your-api-key-here

# 4. 启动服务
python -m uvicorn backend.app:app --host 0.0.0.0 --port 8000 --reload

# 5. 验证
# 浏览器访问 http://localhost:8000 → 极简验证页面
# 浏览器访问 http://localhost:8000/docs → Swagger API 文档

6. P0 已知限制与后续规划

6.1 已知限制

限制影响修复阶段修复方案
工具未注册到 ToolRegistry模型无法调用 memory_write/skill_loader/web_fetchP0 补丁runtime.py_get_shared_tool_registry() 中注册三个工具
ApiKeyPool 未集成多 Key 轮询不生效,仅使用第一个 KeyP1OpenAICompatibleClient 中集成 ApiKeyPool.acquire()/report_429()
会话快照未持久化服务重启后对话历史丢失P1_save_snapshot() 中调用 session_storage.save_session_snapshot()
无认证任何人可直接访问P2引入 JWT 认证中间件
无简历渲染下载只能查看 Markdown 内容P1实现 resume_renderer.py + weasyprint

6.2 P1 迭代重点

任务说明
简历渲染与下载Markdown → PDF (weasyprint),渲染队列,简历快照持久化
记忆管理 API记忆 CRUD + 简历上传 + 用户级 Settings
工具注册激活将三个领域工具注册到 ToolRegistry,激活 Agent 工具调用能力
工具与系统 API工具列表/会话列表/MCP 状态查询
ApiKeyPool 集成多 Key 轮询生效,429 自动切换

7. 测试验证

7.1 自动化测试

python -m pytest tests/ -v

覆盖范围:包导入、配置加载、API Key 合并、工具定义、提示词模板、消息模型等。

7.2 手动验证场景

详见 frontend/测试数据.txt,包含 8 个测试场景:

  1. 服务启动与健康检查
  2. API 文档访问
  3. SSE 流式对话(curl)
  4. 极简页面对话
  5. 简历生成对话
  6. 配置校验(无 API Key 时拒绝启动)
  7. 单测运行
  8. 工具调用验证

附录 A:关键依赖

依赖版本用途
fastapi>=0.110.0Web 框架
uvicorn>=0.29.0ASGI 服务器
openai>=1.23.0DeepSeek API SDK
pydantic>=2.0数据验证与模型
httpx>=0.27.0异步 HTTP 客户端
python-dotenv>=1.0.0.env 文件加载

附录 B:错误码参考

错误码含义HTTP 状态码
1001用户未认证401
1002Token 过期401
2001DeepSeek API 调用失败502
2002速率限制429
3001会话不存在404
3002会话已过期410
4001MCP 服务不可用503
4002简历渲染失败500
4003简历不存在404
5001记忆文档不存在404
6001网页抓取失败502

交流讨论 扫码加入 Resume Harness 技术交流群

替代文本