第四次作业

简历修改智能体-历历在练

这个作业属于哪个班级 https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/Class56-Grade2024-CS/homework/16760
这个作业的目标 构建一个能说会做的智能体
Github仓库 https://github.com/w7885/resume-rehearsal-agent.git
参与成员 温美如-3224004081、陈婉怡-3224004078、兰羽-3224004079

PSP表格

PSP Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 10
· Estimate · 估计这个任务需要多少时间 10 10
Development 开发 300 335
· Analysis · 需求分析 (包括学习新技术) 40 45
· Design Spec · 生成设计文档 20 40
· Design Review · 设计复审 20 40
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 20 30
· Design · 具体设计 60 60
· Coding · 具体编码 50 60
· Code Review · 代码复审 30 20
· Test · 测试(自我测试,修改代码,提交修改) 60 40
Reporting 报告 90 145
· Test Repor · 测试报告 30 40
· Size Measurement · 计算工作量 20 25
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 40 80
· 合计 800 960

一、项目介绍

​ 求职者在简历投递时,总是会遇到简历质量不过关和不同岗位反复修改简历的问题。本项目推出一种简历修改智能体(Agent)——历历在练。历历在练主要以交互对话模式呈现,会针对用户提供的简历和JD(可选的),根据用户简历中项目,询问更具体、贴合JD的信息,结合用户实际回答,实现简历文字的修改与优化。其中禁止agent凭空捏造不实经历。

​ 本项目主要面向寻找实习的学生,支持.md、.docx、.pdf、.png格式的简历上传,由于暂时的技术限制只能生成.markdown格式的产出内容。

Github:https://github.com/w7885/resume-rehearsal-agent.git

1.1 项目背景与设计目标

​ 传统简历修改有着不知道怎么突出个人贡献、难以针对JD调整、容易遗漏经历中的有效信息等痛点,本项目构建了一个能分析、懂追问、提建议、会执行的简历修改Agent,用来辅助用户完善简历,而非自动编造经历或直接覆盖原始简历。

历历在练面向准备实习或求职简历的学生,关注以下场景:

  • 用户做过项目,但难以清楚表达个人职责、技术应用和实际结果。
  • 简历描述过于笼统或冗长,需要在保留事实的基础上整理语言。
  • 面对不同岗位,需要结合职位描述(JD)调整表达重点。
  • 一次性润色缺少补充信息的机会,生成内容也不容易逐条核对和采用。
1.2 界面展示与流程演示

前端界面展示如下图:

706

主流程:创建会话 → 提交简历与可选 JD → 保存原件和消息 → 提取内容并确认材料用途 → 分析当前内容 → 追问补充事实 → 生成待确认建议 → 用户决定 → 更新当前稿 → 接续处理或导出。

1.3 创新点

(1)交互式、证据导向的简历优化机制。 项目将简历内容分析、针对性追问与修改建议生成相结合,引导用户补充项目职责、技术应用和成果等信息,减少仅依赖单轮文本生成进行简历润色的局限。

(2)建议与正式简历分离的人机协同机制。 系统将 Agent 生成的修改内容作为待确认建议保存,用户可接受、拒绝或要求调整。只有在用户明确接受且通过版本与来源校验后,系统才更新正式简历,并记录相应决定,从而保障用户对个人经历表达和最终修改结果的控制权。

(3)基于来源关联与规则校验的修改约束机制。 系统将修改建议与相关材料来源关联,并在建议执行及接受阶段校验来源有效性、原文一致性和目标版本等条件,降低无依据修改及过期建议误应用的风险,增强简历优化过程的可追溯性。

(4)面向 Agent 工作流的可靠性与成本控制设计。 项目结合请求幂等、数据库事务、版本校验、流式事件校验和模型预算管理等机制,处理重复提交、状态冲突、异常中断及模型调用成本等问题,提升系统在多轮交互场景下的数据一致性与运行可靠性。

1.4 成员分工
成员 主要分工 具体工作
温美如 前后端开发、系统集成与 Agent 优化 对话界面与业务 API 开发、SQLite 持久化、文件解析、建议确认与版本保护、稿件编辑与导出;负责接口联调,并推进上下文衔接、追问策略和改写效果优化
陈婉怡 Agent 核心能力开发与优化 Agent 决策流程、模型适配、工具调用、会话记忆、意图路由,以及调用预算、运行取消与重复执行控制
兰羽 测试评估、演示与文档整理 准备测试样例和预期结果,组织功能验证与问题复现,整理演示材料、项目博客及交付说明

二、整体设计与架构

2.1 系统架构与模块划分

本项目“历历在练”采用前后端分离的本地应用结构,面向实习求职场景提供对话式简历修改功能。前端使用 React、TypeScript 和 Vite,负责聊天界面、附件操作、当前稿展示与编辑;后端使用 Python、FastAPI 和 Uvicorn,负责接口校验、业务处理、Agent 调用和数据持久化;SQLite 用于保存会话、消息、材料、来源、稿件、建议、运行事件及费用记录。模型凭证只在后端读取,浏览器不直接持有密钥。

​ 系统按照职责划分为接口层、业务层、数据访问层和 Agent 分析层。main.py 负责应用初始化与服务装配,api.py 处理 HTTP 请求及参数校验,business_service.py 组织业务事务与请求幂等处理,repository.py 负责数据读写及版本校验。Agent 分析由 agent_host.py 统一协调,并通过 backend/app/agent/ 目录中的路由、推理循环、工具、记忆和模型客户端完成具体任务。各模块通过明确的数据结构和接口衔接,形成从用户输入、材料处理、智能分析到正文更新的完整业务链路。

2.2 用户交互与请求处理

前端主要通过 ChatPage.tsx、chatSend.ts 和 chatAgent.ts 实现聊天主流程。用户提交消息或附件后,系统首先保存用户消息及相关数据,再根据已保存的消息编号启动 Agent 分析,避免将未持久化的前端文本直接作为模型运行依据。分析过程中,前端通过服务端发送事件(SSE)接收运行状态,并根据持久化结果更新界面。

后端通过 api.py 提供会话、消息、材料、当前稿、修改建议及 Agent 运行等业务接口。请求中携带请求编号和预期状态版本,用于识别重复提交并检查数据是否已经发生变化。对于网络中断或请求重放,系统能够读取已保存的处理状态和事件,避免重复执行已经完成的模型调用。消息保存与 Agent 分析分别处理,因此消息成功保存并不代表模型分析已经完成。

2.3 材料导入与来源管理

系统支持用户直接粘贴简历文本,也支持 Markdown、DOCX、PDF 及 PNG/JPEG 等材料。document_parser.py 等材料处理模块负责文件解析、文字提取及相关状态记录;agent_material_intake.py 与 agent_text_intake.py 分别参与附件用途处理和聊天文本的简历正文确认。对于 PDF 和图片,系统还支持本地图页准备与预览,以便用户检查原始材料。

材料上传、内容提取和用途确认是相互独立的处理环节。新上传材料初始处于待确认状态,系统不会仅凭附件存在就将其认定为个人经历,也不会直接覆盖已有来源快照。用户确认材料用途后,系统再建立相应来源版本,并将其用于后续分析。DOCX 和文字型 PDF 采用本地文本提取;PNG/JPEG 可在相应视觉配置启用后调用模型进行文字转录,而扫描型 PDF 当前主要提供本地预览,尚未接入自动 OCR 识别。

系统同时区分个人经历材料与岗位描述(JD)。JD 用于识别岗位要求和调整简历表达重点,不作为证明用户个人经历的依据。来源版本及原文引用为后续建议核对提供数据基础。

前端与后端之间的职责边界

前端负责呈现和发起用户操作,但不自行决定 Agent 是否有权改写简历,它先保存用户消息,再根据后端返回的能力状态决定是否启动 Agent;后端负责最终校验会话范围、消息归属、来源引用、稿件版本、用户决定和费用额度。

前后端交互与数据持久化流程图如下:

mermaid-diagram_1

2.4 Agent 分析与建议确认

Agent 的运行由 agent_host.py 协调。它首先读取当前会话中有效用户回合、简历来源、岗位描述、当前稿和历史问答,再把这些内容交给 router.py 与 loop.py 完成路由和分步分析。

在分析过程中,Agent 通过 tools.py 中定义的受限工具读取材料、检索依据、查询经历记忆、保存用户补充事实、记录信息缺口以及提交改写建议。context.py负责组织和区分当前修改目标、个人来源和 JD 要求,为 Agent 提供相应的上下文依据。

系统采用改写建议确认机制,将建议与目标段落、版本及来源关联,经后端校验后,仅将用户明确接受的有效建议应用于当前简历稿,相关逻辑由 repository.py、draft_actions.py、assistant_service.py 和 chat_decisions.py 实现。

最终通过 guard.py 等模块校验来源依据、段落版本、数字及职级表述,以降低无依据内容的生成风险。但这些约束无法完全保证事实准确性,最终内容仍需用户核对确认。

Agent 分析、材料识别与建议确认流程图如下:

mermaid-diagram_2

2.5 持久化、版本控制与费用边界

项目采用 SQLite 保存业务数据,并通过 db.py 与 backend/app/migrations/ 管理数据库连接、事务及结构迁移。系统将会话状态版本、当前稿版本、简历代次、来源版本和段落版本分开管理,以便检测过期操作,并使旧简历内容和建议可追溯。

business_service.py 负责组织用户业务操作,并基于请求编号及请求内容指纹实现幂等处理。用户重试相同操作时,后端可以返回已有结果,而不是重复写入。

真实模型调用由 llm.py 完成,项目费用的统一预留和结算由 agent_budget.py 负责。启用真实分析需要模型密钥、Agent 开关和预算确认条件同时满足;模型请求可能产生费用。项目账本用于限制本机项目预算,但不能统计其他机器或绕过项目入口的调用。

SQLite 持久化、版本控制与费用管理流程图如下:

mermaid-diagram_3

2.6 重要模块清单
模块 核心文件 主要职责
应用启动与服务装配 main.py 创建 FastAPI 应用,加载本地环境配置,初始化数据库和 AgentHost,提供健康检查。
HTTP 接口与请求校验 api.py 定义用户业务接口、请求数据模型、字段约束、本机请求校验和错误响应。
业务事务与幂等 business_service.py 组织会话、消息、材料、稿件和建议的业务写入;按请求编号处理安全重试。
数据访问与业务状态 repository.py 管理会话、消息、来源、附件、当前稿、建议、版本及状态校验。
数据库结构与迁移 db.py、migrations 创建 SQLite 连接、执行事务、验证数据库完整性并执行追加式结构迁移。
前端主流程 ChatPage.tsx、chatSend.ts、chatAgent.ts 实现聊天界面、先保存消息、再触发 Agent,以及消费处理事件。
前端当前稿 ChatDraftPanel.tsx、useChatDraft.ts、draftBuffer.ts 展示和编辑当前稿,并协调自动保存、版本冲突和恢复。
材料解析与导入 document_parser.py、agent_material_intake.py、agent_text_intake.py 提取文档文字、判断附件用途、确认聊天中粘贴的简历正文范围。
Agent 与业务系统连接 agent_host.py 创建运行、验证 Agent 能力、装配上下文、协调工具与模型、保存助手回复、发送 SSE 事件。
Agent 推理循环 loop.py、router.py 路由用户意图,执行分步分析,在追问、建议和结束之间推进运行。
Agent 工具与安全检查 tools.py、guard.py 定义模型可以调用的工具,并检查追问形式、证据引用、建议目标和部分事实风险。
上下文与会话记忆 context.py、memory.py、agent_state.py 组织模型上下文,并保存经历事实、问题记录和 Agent 状态。
建议确认与稿件编辑 draft_actions.py、chat_decisions.py、assistant_service.py 将 Agent 建议作为待确认数据保存,校验用户决定,并在事务中更新正式稿。
聊天保存与恢复 chat_service.py 保存原始聊天消息、附件关系并读取聊天会话。
模型客户端与费用 llm.py、agent_budget.py 连接文本模型、解析模型响应、预留和结算项目调用费用。

其余如 page_service.py、page_renderer.py、recognition_service.py 和 agent_image_intake.py,主要支持附件分页预览、图片处理和识别流程;阅读主聊天链路后再看这些辅助功能会更容易理解。

三、关键代码

3.1 Agent 运行协调与推理循环

(1)用户回合进入Agent

def start(self, sid, payload):
    digest = hashlib.sha256(json.dumps(payload, sort_keys=True).encode()).hexdigest()
    with self.repo._operation(RequestContext(sid), write=True) as (connection, session):
        old = connection.execute('SELECT * FROM agent_run_requests WHERE session_id=? AND request_id=?', (sid,payload['requestId'])).fetchone()
        if old:
            if old['request_hash'] != digest:
                raise RepositoryError('request_conflict', '同一分析请求不能更换消息或版本。')
            return dict(old), False
        old = connection.execute('SELECT * FROM agent_run_requests WHERE session_id=? AND user_message_id=?', (sid,payload['userMessageId'])).fetchone()
        if old:
            return dict(old), False
        if not self.enabled:
            raise RepositoryError('agent_unavailable', '分析尚未启用,消息与当前稿仍已保存在本机。')
        if session['mode'] != 'local':
            raise RepositoryError('scope_violation', '模拟历史不能启动个人 Agent。', False)
        context = RequestContext(sid,payload['expectedStateRevision'])
        self.repo._check_context(connection,context)
        user_turn(connection,context,session,payload['userMessageId'])
        budget=self.budget()
        account=budget._account(connection)
        reserved=connection.execute("SELECT coalesce(sum(reserved_cents),0) FROM model_budget_reservations WHERE status='reserved'").fetchone()[0]
        if account['spent_cents']+reserved>=account['limit_cents']:
            raise RepositoryError('agent_unavailable','项目额度不足,已保存的消息和当前稿保持可用。')
        ...
        if connection.execute("SELECT 1 FROM agent_run_requests WHERE session_id=? AND status='running'", (sid,)).fetchone():
            raise RepositoryError('run_busy', '本会话仍在处理中,请等待或停止本次处理。')
        run_id,generation = self._new_run(connection,session)
        ...
    self.active[(sid,row['request_id'])] = runtime
    return row, True

AgentHost 负责统一管理分析任务的启动。系统先识别重复请求,再检查运行条件、会话版本及预算等约束,符合要求后才创建运行记录,避免同一请求被重复启动或在条件不满足时调用模型。

(2)单步决策的守卫与执行

if decision is None:
    note = "上一次输出不是合法的动作 JSON,请只输出一个符合格式的 JSON 对象。"
    continue

    load_ref_sources(decision)
    result = check(decision, guard_state())
    if result.violation is None:
        gaps = result.gaps
        break

        sink.emit("warning", code=result.violation.code, message=result.violation.message)
        if not result.violation.retryable:
            return limited(result.violation.code, result.violation.message)
        note = (f"上一次动作被拦截({result.violation.code}):"
                f"{result.violation.message}。请换一个合规动作。")
        decision = None
        ...
        if isinstance(decision, ToolStep):
            sink.emit("tool_progress", state="requested", seq=step_number,
                      tool=decision.tool, args=decision.args)
            try:
                observation = await execute_tool(decision, ToolContext(
                    session_id=session_id, repo=repo, store=store, message_id=message_id))
            except RepoError as exc:
                observation = {"error": exc.code, "message": str(exc)}
                sink.emit("tool_progress", state="failed", seq=step_number,
                          tool=decision.tool, error=exc.code)
            else:
                sink.emit("tool_progress", state="completed", seq=step_number, tool=decision.tool)

                if decision.tool == "propose_change" and "suggestion_id" in observation:
                    sink.emit("suggestion",
                              suggestion_id=observation["suggestion_id"],
                              experience_id=decision.args.get("target"),
                              operation=decision.args.get("operation", "replace"),
                              before=decision.args.get("before"),
                              after=decision.args.get("after", ""),
                              reason=decision.args.get("reason", ""),
                              source_refs=decision.args.get("source_refs", []),
                              gaps=decision.args.get("gaps", []))

Agent 每步输出的动作需先经过守卫校验,只有符合约束的动作才能执行;可重试的违规动作会反馈原因并重新生成。对于简历修改,Agent 通过工具生成待确认建议,而不是直接覆盖正式简历。

3.2 建议确认与简历正文更新
with repo._operation(context, write=True) as (
    connection, session
):
    row = connection.execute(
        "SELECT * FROM suggestions "
        "WHERE session_id=? AND suggestion_id=?",
        (context.session_id, suggestion_id)
    ).fetchone()

    if row is None:
        raise RepositoryError("source_not_found", ...)

    if (row["status"] != "pending"
            or row["resume_generation"]
            != session["current_resume_generation"]):
        raise RepositoryError("stale_state", ...)

    # 校验目标段落版本、旧文内容及来源引用
    ...

    if decision == "accept":
        # 写入新版本段落,并处理同目标旧建议
        ...

    connection.execute(
        "UPDATE suggestions SET status=?, decided_at=? "
        "WHERE ...",
        (...)
    )
    connection.execute(
        "INSERT INTO suggestion_decisions(...) VALUES (...)",
        (...)
    )

系统采用“建议—确认—更新”的修改机制。用户作出决定后,后端重新校验建议状态、简历版本、目标旧文及来源引用;只有通过校验且用户接受时,才更新简历正文,并在同一事务中记录决定,降低过期建议和无依据修改带来的风险。

3.3 数据库事务与请求幂等
fingerprint = hashlib.sha256(
    json.dumps(
        {"operation": operation, "payload": payload},
        sort_keys=True,
        separators=(",", ":"),
        ensure_ascii=False,
        allow_nan=False
    ).encode("utf-8")
).hexdigest()

with database(self.repository.path) as connection, \
        transaction(connection):
    old = connection.execute(
        "SELECT * FROM request_log "
        "WHERE scope_key=? AND request_id=?",
        (scope, request_id)
    ).fetchone()

    if old is not None:
        if (old["operation"] != operation
                or old["request_hash"] != fingerprint):
            raise RepositoryError("request_conflict", ...)
        return json.loads(old["response_json"])

    data = callback(
        Repository(self.repository.path, _connection=connection)
    )
    response = {"ok": True, "data": data}
    # 保存本次请求及响应
    ...

系统根据操作类型和请求内容生成指纹,并通过请求编号识别重复提交。相同请求直接返回已保存的响应,不同内容复用同一请求编号则拒绝处理;业务操作与请求记录在同一事务中提交,减少网络重试造成的重复写入。

3.4 模型调用预算控制
with database(self.path) as connection, transaction(connection):
    account = self._account(connection)
    reserved = connection.execute(
        "SELECT coalesce(sum(reserved_cents), 0) "
        "FROM model_budget_reservations "
        "WHERE status='reserved'"
    ).fetchone()[0]

    if account["spent_cents"] + reserved + cents \
            > account["limit_cents"]:
        return False

    # 在账本中创建预留记录
    ...

def _settle(self, actual):
    if not self.pending:
        return

    ...
    if row and row["status"] == "reserved":
        charge = (
            row["reserved_cents"]
            if actual is None else actual
        )
        # 更新预留状态与累计支出
        ...

模型调用前,系统在事务内检查已支出金额、已预留金额和项目预算上限,额度不足时拒绝预留;调用结束后再根据实际用量结算。若用量未知,则按预留金额保守计费,以降低并发调用或异常中断导致的预算失控风险。

四、测试运行

T0:基础功能测试

目标: 不调用 Agent 模型时,验证文件解析、原文提取、来源定位、Markdown 生成与导出功能的正确性。

测试结果表明:Markdown、DOCX 和文字型 PDF 的解析结果符合预期,损坏、加密、空白及扫描类材料均能得到相应状态或警告。常规单栏 PDF 字符召回率为 88%–97%,复杂排版能够提示阅读顺序风险,DOCX 特殊结构具有对应提取结果或警告。来源定位、正文范围及 Markdown 结构用例通过,导出内容与界面当前正文一致。对于不支持的文件类型及格式不匹配情况,接口均按约定拒绝处理。

T1:后端与数据库集成测试

目标: 验证接口契约、数据持久化、建议确认、版本控制、会话隔离、预算管理及异常回滚等功能。

测试结果表明: 会话、消息、附件和简历稿等核心接口运行符合 API 约定,幂等请求、版本冲突及跨会话访问控制均通过测试。故障注入结果表明,消息绑定、建议接受、稿件编辑、材料替换及请求缓存等操作失败时,相关数据能够整体回滚。会话删除不会影响其他会话及项目级费用账本,预算限制与并发预留机制运行正常。

配置与安全边界测试中,环境变量优先级、八种启用开关组合及非法预算配置均符合预期;材料中的指令性文本不会改变系统权限或生成操作决定。伪造来源、异常路径、非法文件名及控制字符均得到有效拦截,合法扩展名和 Windows 保留名可安全处理。

T2:Agent 模型能力测试

目标: 验证 Agent 在不同简历信息条件下的分析、追问、事实纠正、岗位匹配及改写建议生成能力。

测试结果表明: 在 EV-001 至 EV-008 场景中,Agent 能够利用已有材料、识别关键信息缺口、处理用户跳过或纠正信息的情况,并根据岗位要求调整分析重点。面对信息充分的材料,系统能够直接生成改写建议;面对信息不足、图片不可读或岗位能力缺口等情况,则能够进行针对性澄清或说明限制。非技术岗位场景下,建议能够结合实际工作职责生成,未机械套用开发岗位模板。

各项判定指标均达到要求:新增经历、技术、数字及结果具有材料依据;追问能够对应实际信息缺口;系统能够根据材料充分程度调整处理方式;建议均可追溯至来源材料、版本及原文片段。EV-001 至 EV-005 重复执行后,行为保持一致。

T3:完整流程测试与用户试用

目标: 验证系统端到端流程的完整性、数据恢复能力、建议可理解性及实际使用价值。

测试结果表明:项目组成员完成简历上传、目标方向填写、追问应答、依据查看、建议接受或拒绝、正文编辑及 Markdown 导出等完整流程。重新打开会话后,消息、附件和当前稿均可恢复,导出内容与当前稿一致。两名计划参与试用的学生均能独立完成核心流程,理解建议来源及修改原因,并各自采用至少一条建议。试用反馈显示,系统主要减少了措辞优化、量化信息补充及岗位重点调整方面的工作量,同时增加了回答追问所需的时间。

费用与记录

测试周期总预算控制在 30 元以内,并按 API 与工具调用、图片识别、Agent 行为测试、完整流程试用及失败复测分类记录费用。真实调用前统一预留预算;对于取消或费用未知的调用,按保守原则计入账本,重启或删除会话不会清零项目费用。各测试用例均保留编号、材料版本、预期结果、实际结果、测试结论及复测记录;试用前已取得参与者知情同意,测试记录未包含密钥及非必要个人信息。

除上述集成测试与流程测试外,项目还针对各功能模块编写了相应的单元测试,具体测试代码及用例见项目源代码中的测试文件。

五、问题解决与项目优化

5.1 后端启动后 Agent 无法回复

问题陈述: 项目启动后,前后端页面均可正常访问,健康检查也能返回正常结果,但用户发送消息后,Agent 始终没有回复,界面提示分析服务当前未启用。此外,即使之前通过带有密钥的命令启动过服务,重启后仍可能出现相同问题。

原因分析: 排查发现,后端主要从进程环境变量中读取配置,而 main.py 原先没有加载本地 .env 文件的逻辑,导致文件中已经配置好的密钥和功能开关没有生效。同时,3001 端口上还存在一个以 AGENT_ENABLED=0 启动的旧进程。由于健康检查只能反映模型配置是否存在,无法准确代表 Agent 是否真正可用,因此出现了服务看似正常、实际却无法启动分析的情况。

进行改进: 项目恢复了启动阶段的本地环境配置加载,并将其与应用生命周期关联。加载时优先保留进程中已有的环境变量,同时避免隔离测试应用读取本地配置文件。此外,项目统一通过 capabilities.agentAvailable 判断并向前端提供 Agent 的可用状态,避免仅凭健康检查判断服务能力。针对配置加载过程,还补充了回归测试,覆盖缺失配置补齐、进程变量优先、文件异常、不同格式的配置内容,以及功能开关组合和非法预算值等情况。

优化结果: 在不预先设置 Agent 环境变量的新终端中重新启动服务后,健康检查显示模型配置正常,会话能力接口也能正确报告 Agent 可用,且预算余额未发生异常变化。配置相关的 13 项测试全部通过,后端全量测试由 409 项增加至 429 项并全部通过。至此,Agent 无法回复的直接原因已经得到解决,启动配置与能力状态判断也更加可靠。

5.2 Agent 回复重复,反复确认且无法推进

问题陈述: 联调过程中,Agent 多次重复询问目标岗位、要求用户确认修改内容,或反复展示相似的简历改写建议。即使用户已经作出回答,系统仍可能回到之前的确认环节,无法顺利进入下一步。同时,生成的建议经常集中在简历开头的基本信息,对项目经历的分析不足。

原因分析: 这一问题主要涉及回复逻辑、会话状态和简历分段三个方面。首先,部分重复话术由后端直接拼接生成,并非模型每次独立生成,因此容易出现固定表达反复出现的情况。其次,当会话中存在尚未处理的建议时,后端会优先将用户的新消息解析为针对该建议的决定,导致用户表达继续分析或提出新要求时,仍被带回确认流程。最后,如果简历中的标题和项目边界未能正确识别,系统可能将整页内容视为一个分析段落,进而错误地将简历开头作为当前处理重点。

进行改进: 项目针对追问和路由逻辑补充了约束。当简历已经明确写出个人职责时,系统应避免重复询问相同内容,并优先追问尚未明确的成果信息,或直接提出改写建议。相关判断改为依据实际问题措辞进行识别,不再单纯依赖模型返回的 slot 字段。同时,项目进一步明确了局部修改与整体改写的区别,避免将用户提到的某一段或某个项目误判为整份简历的修改需求,并补充了 9 项确定性测试。

优化结果: 新增的离线测试均已通过,但这一问题尚未彻底解决。真实模型复测时,Agent 仍然出现询问用户本人负责内容的通用追问,服务端也没有观察到预期的守卫警告。这表明,虽然相关防护规则已经补充,但其在实际运行链路中的触发情况仍需进一步排查。下一步需要重点检查当前焦点文档是否正确传入,以及守卫校验函数是否在 Host 的实际运行路径中被调用。因此,目前只能认为重复追问问题已得到部分改善,不能认定已经完全修复。

5.3 文档与 PDF 解析能力优化

问题陈述: 项目早期主要处理纯文本简历,对 Word、PDF 和图片等材料的支持不足。即使用户上传了文件,也难以判断系统实际读取了哪些内容,以及提取结果是否完整,影响了后续分析和修改建议的可靠性。

原因分析: 真实简历通常包含分栏、浮动文本框、页眉页脚、图标和扫描页面等复杂排版,这些因素都会影响文本提取的完整性与顺序。尤其是在解析失败或内容缺失时,如果系统仍将结果当作正常文本交给 Agent,后续分析就可能建立在不完整甚至错误的材料之上。

进行改进: 项目对材料处理流程进行了梳理和完善。文件上传时会检查文件容器和 MIME 类型,解析任务放在独立子进程中执行,并设置运行时间限制。解析结果增加了明确的状态标记和警告信息,用于区分解析成功、部分成功、失败和等待处理等情况;原始文件则予以保留,方便用户查看和下载。对于图片材料,系统先完成保存和预览,图像识别则需要显式开启,避免将尚未启用的能力误认为默认功能。与此同时,项目选取仓库中的文本材料作为比对基准,对 17 个实际样例进行逐字比对,以评估不同格式的解析效果。

优化结果: 测试结果显示,常规单栏 PDF 的字符召回率分别达到 92.3% 和 97.1%,另一份 PDF 在剔除职位描述内容后达到 88.5%。对于复杂排版 PDF,字符召回率下降至 74%,提取字符数还比原文多出 24%,说明仍存在内容重复和顺序错乱的问题。4 份 DOCX 样例均解析失败,主要涉及图片和浮动文本框等复杂结构。不过,系统能够明确报告失败状态和相应警告,并保留原始文件,避免将解析失败伪装成成功。由此可见,本次优化首先解决了材料处理过程的状态透明和失败反馈问题,但不同格式下的稳定解析能力仍有待提升。

5.4 完成当前任务后自动接续下一段

问题陈述: Agent 完成一段简历的分析并给出修改建议后,往往停留在当前环节,需要用户反复确认或主动要求继续,才能开始处理下一段。这增加了多轮交互的操作成本,也使整份简历的连续优化不够顺畅。

原因分析: 多段简历优化不仅需要处理当前建议,还需要记录哪些段落已经复核、哪些仍待确认,以及下一步应该处理什么内容。如果缺少持久化的进度记录,系统就难以在切换段落或重新启动后恢复处理状态。此外,建议接受与下一段接续属于两个不同的操作,如果将它们混在同一个事务中,一旦后续接续失败,就可能影响已经完成的修改。

进行改进: 项目梳理并完善了现有的进度记录与任务接续机制。系统按照简历版本记录已经复核的段落,并区分待确认、可以继续处理和需要核对来源等状态。用户接受建议后,系统可以自动推进到下一段检查,同时提供继续、暂停和停止等明确操作。当后续接续失败时,系统保留已经采纳的修改,仅提示下一步尚未完成,避免将已完成的工作一并撤销。针对多段推进和重启恢复等场景,项目还通过构造客户端进行了流程验证。

优化结果: 测试表明,用户接受当前段落的建议后,系统能够自动进入下一段检查;暂停后恢复可以保留已有进度,后续接续失败也不会回滚已经采纳的文本,相关流程测试均已通过。不过,自动接续仍依赖系统对当前焦点段落的正确判断。如果简历分段不准确,系统仍可能将后续处理指向错误的内容,因此该机制的实际效果还受到材料解析与分段质量的影响。

加分项 完成情况 实现与演示方式
多轮对话记忆功能 已实现 保存会话消息、问答和处理状态,结合补充事实继续分析;演示连续回答、反馈调整和重新打开会话后的恢复。记忆限定在当前会话,不承诺任意自然语言都能正确定位
外部 API 或数据库交互 已实现 后端调用模型 API,并读写 SQLite,保存材料、建议和稿件;用户确认后产生可持久化的正文变化
简洁的前端界面 已实现 React 网页提供连续聊天、附件上传与预览、依据查看、建议确认、当前稿编辑和 Markdown 导出
语音输入 已实现 可进行语言输入即语音转文字功能

六、项目总结

温美如:在本次项目中,我主要负责前后端应用开发与集成,包括基于 React、TypeScript 的对话界面、Python 业务 API、SQLite 持久化、文件解析,以及建议确认、版本校验和稿件导出,并参与部分 Agent 优化,围绕上下文管理、意图识别、重复追问和改写质量持续迭代。项目实践让我更深入地理解了接口契约、事务一致性、请求幂等和事实溯源如何共同支撑可靠的交互体验,也逐步形成了从问题复现、根因定位到修复验证的工程闭环。最大的收获与突破,是能够将用户体验判断转化为数据模型、状态流转和可验证的验收标准,在生成效果、系统可靠性与用户控制权之间作出更具体的设计取舍。

陈婉怡:在这个项目中,我主要负责agent链路的设计。团队在整个agent的设计过程中进行充分的讨论。考虑到实际的简历保存格式,采用多模态的方式支持用户上传简历;我们进行了agent职责限定,对敏感信息不做修改... 最大的感受是明确什么可以交由LLM来决策,而什么必须让规则来判断。靠规则判断的就不问模型,识别不清就先追问澄清,模型输出不合规就打回重问等,这样才能让agent能应对大部分场景。

兰羽:在本项目中,我主要负责项目测试和博客撰写工作。在测试方面,我参与制定测试方案,设计边界测试与业务场景测试,验证前后端交互、数据库持久化、智能体对话及简历修改流程,在测试中出现了后端连接异常、智能体重复提问、文档解析不完整等问题,向另两位成员交接,共同解决并完善agent。在博客撰写方面,我梳理项目的整体架构、设计思路、实现过程与测试结果,力求清晰呈现团队的开发成果。此外,通过项目复现,我进一步熟悉了环境配置、代码运行与问题排查的基本流程。

posted @ 2026-10-25 22:25  zero_7(兰羽)  阅读(16)  评论(0)    收藏  举报