尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

对话式修改:Agent的人机协作模式 — Chat as Interface,对话不是聊天,是最高效的人机协作方式

对话式修改:Agent的人机协作模式 — Chat as Interface,对话不是聊天,是最高效的人机协作方式 先说结论生成一篇文章只需要一次 LLM 调用但改好一篇文章往往需要多轮对话。很多人做内容生成 Agent生成完就结束了 — 用户不满意只能重新生成结果每次都是一篇全新的文章之前的风格、结构、措辞全丢了。这不是协作是抽奖。在 self-media-agent 项目里chat/模块实现了完整的对话式修改链路用户提建议 → LLM 按建议编辑 → 保存修改记录 → 更新内容 → 下次生成自动进化三个模型一条链路让 Agent 从一次性生成器变成可协作的编辑助手。模型做什么存什么ChatSession绑定到某篇内容的对话会话消息列表 修改记录列表ChatMessage一条对话消息角色 内容 时间RevisionRecord一次修改的完整记录原文 修改后文 建议Chat as Interface — 对话不是聊天是最高效的人机协作方式。一、为什么对话修改比重新生成好重新生成的问题用户这篇文章太正式了 Agent重新生成→ 全新文章但风格可能又变了之前喜欢的部分也没了 用户不是让你全改是让你改语气结构别动 Agent重新生成→ 又是全新文章... 用户算了我自己改吧重新生成的三个致命问题问题说明后果风格漂移每次生成都是从零开始用户刚满意的风格下次又变了上下文丢失不记得上一版改了什么用户重复提同样的要求全有或全无要么全接受要么全重来无法局部修改对话修改的优势用户这篇文章太正式了活泼点 Agent按建议编辑保留原文结构和措辞只调整语气 用户很好但第三段太长了缩短一点 Agent在上一版基础上修改只动第三段 用户完美对话修改的核心原则保留原文风格只改建议的部分。重新生成 原文 → 丢弃 → 全新文章风格不可控 对话修改 原文 → 保留 → 局部修改风格延续精确控制对话修改的 Prompt 设计api/routes/chat.py的_regenerate_with_suggestion— 关键在 System Prompt 的一句话system_prompt ( 你是一位资深自媒体内容编辑。用户会给你一篇已有的文章和修改建议 你需要根据修改建议对文章进行调整保持文章的整体风格和结构 只修改用户建议的部分。\n\n 输出要求\n 1. 第一行输出修改后的标题以「标题」开头\n 2. 空一行后输出修改后的完整正文\n 3. 正文不要使用 markdown 格式标记\n 4. 保持原文的风格、语气和 emoji 使用习惯 )注意第3句只修改用户建议的部分。这句话是整个对话修改的灵魂 — 它告诉 LLM你不是在写新文章你是在编辑已有文章。对比生成正文时的 Promptcontent/body.py# 生成正文从零创作 请创作正文内容。 ​ # 对话修改在原文基础上编辑 请根据修改建议调整文章输出修改后的标题和正文。一个是创作一个是编辑— 同样是调 LLMPrompt 的定位完全不同效果也完全不同。修改的输入输出user_prompt ( f# 原始标题\n{content.title}\n\n f# 原始正文\n{content.body}\n\n f# 修改建议\n{suggestion}\n\n )输入 原始标题夏季防晒推荐 原始正文综上所述夏季防晒需要注意以下几点... 修改建议太正式了活泼点少用书面语 ​ 输出 标题夏天防晒这几个坑你一定要知道 正文姐妹们夏天到了防晒这事儿真不能马虎...LLM 同时看到原文和建议— 这样它才能在原文基础上修改而不是凭空写一篇新的。二、三个数据模型会话、消息、修改记录对话式修改的基础是数据模型。chat/schema.py定义了三个模型各司其职。模型关系图ChatSession绑定到某篇内容 ├── messages: list[ChatMessage] ← 对话消息列表 │ ├── ChatMessage(rolesystem) ← 欢迎消息 │ ├── ChatMessage(roleuser) ← 用户建议 │ ├── ChatMessage(roleassistant) ← AI 回复 │ └── ... └── revisions: list[RevisionRecord] ← 修改记录列表 ├── RevisionRecord(V1→V2) ← 第一次修改 ├── RevisionRecord(V2→V3) ← 第二次修改 └── ...一个会话绑定一篇内容— 每篇内容有自己的对话历史和修改历史互不干扰。ChatMessage对话消息class MessageRole(str, Enum): USER user # 用户消息修改建议 ASSISTANT assistant # AI 回复修改后的文章 / 确认信息 SYSTEM system # 系统消息 ​ class ChatMessage(BaseModel): id: str Field(default, description消息 ID) session_id: str Field(..., description所属会话 ID) role: MessageRole Field(..., description角色) content: str Field(..., description消息内容) created_at: datetime Field(default_factorydatetime.now, description创建时间)三种角色各管一段角色谁说的内容system系统已加载文章你可以提出修改建议user用户太正式了活泼点assistantAI已根据建议修改修改记录 #1 已保存...ChatMessage 只存对话文本不存修改前后的文章全文— 文章全文存在 RevisionRecord 里消息里只存摘要预览。这样对话列表轻量修改记录完整。RevisionRecord修改记录class RevisionRecord(BaseModel): id: str Field(default, description记录 ID) session_id: str Field(..., description所属会话 ID) content_id: str Field(..., description关联内容 ID) suggestion: str Field(..., description用户的修改建议) original_body: str Field(..., description修改前正文) revised_body: str Field(..., description修改后正文) original_title: str Field(default, description修改前标题) revised_title: str Field(default, description修改后标题) created_at: datetime Field(default_factorydatetime.now, description修改时间)RevisionRecord 是对话修改的核心— 它完整记录了一次修改的前世今生suggestion: 太正式了活泼点 ← 用户说了什么 original_body: 综上所述夏季防晒... ← 改之前长什么样 revised_body: 姐妹们夏天到了... ← 改之后长什么样为什么要把原文和修改后文都存下来— 三个原因版本回退用户觉得改坏了可以回到上一版偏好提取风格进化需要 diff第9篇讲过original_body和revised_body就是 diff 的来源审计追溯每一步修改都有据可查知道文章是怎么一步步变成现在的样子ChatSession会话class ChatSession(BaseModel): id: str Field(default, description会话 ID) content_id: str Field(..., description关联内容 ID) persona_id: str Field(default, description关联人设 ID) messages: list[ChatMessage] Field(default_factorylist, description消息列表) revisions: list[RevisionRecord] Field(default_factorylist, description修改记录) created_at: datetime Field(default_factorydatetime.now, description创建时间) updated_at: datetime Field(default_factorydatetime.now, description更新时间)ChatSession 是一个聚合根— 它把对话消息和修改记录组织在一起绑定到一篇内容和一个 人设。ChatSession ├── content_id → 绑定哪篇文章 ├── persona_id → 绑定哪个人设用于风格进化 ├── messages → 对话历史轻量只存文本 └── revisions → 修改历史重量存完整前后文为什么messages和revisions分开存— 它们的访问模式不同messages每次打开聊天界面就要全部加载需要轻量revisions只在查看修改历史或提取偏好时才加载可以重量分开存各按需加载互不拖累。三、修改流程建议→LLM编辑→版本管理→内容更新api/routes/chat.py的send_message是整个对话修改的入口 — 一个请求完成保存建议→编辑文章→保存记录→更新内容→提取偏好全流程。完整流程图用户发送建议 │ ▼ ① 获取/创建会话 ──→ 首次修改则创建 ChatSession 欢迎消息 │ ▼ ② 保存用户消息 ──→ ChatMessage(roleuser) │ ▼ ③ LLM 编辑文章 ──→ _regenerate_with_suggestion() │ 原文 建议 → 修改后文 ▼ ④ 保存修改记录 ──→ RevisionRecord(原文, 修改后文, 建议) │ ▼ ⑤ 更新内容 ──→ content.body revised_body │ save_content(content) ▼ ⑥ AI 回复消息 ──→ ChatMessage(roleassistant) │ ▼ ⑦ 自动提取偏好 ──→ StyleLearner.extract_preference_from_revision() │ 偏好写入人设第9篇讲过 ▼ ⑧ 持久化 ──→ save_chat_session() _maybe_persist()8 步一个请求完成修改版本管理风格进化。用户只发了一条消息后台做了这么多事。代码send_message 的核心逻辑# api/routes/chat.py router.post(/send, response_modelAPIResponse) async def send_message(req: ChatSendRequest) - APIResponse: state get_state() # ① 获取原始内容 content state.repo.store.get_content(req.content_id) # ② 获取或创建聊天会话 session state.repo.store.get_chat_session_by_content(req.content_id) if not session: session ChatSession( content_idreq.content_id, persona_idcontent.persona_id, ) # 添加系统欢迎消息 welcome ChatMessage( session_idsession.id, roleMessageRole.SYSTEM, contentf已加载文章「{content.title}」你可以提出修改建议..., ) session.messages.append(welcome) state.repo.store.save_chat_session(session) # ③ 保存用户消息 user_msg ChatMessage( session_idsession.id, roleMessageRole.USER, contentreq.message, ) session.messages.append(user_msg) # ④ 根据建议重新生成 if req.regenerate: revised_body, revised_title await _regenerate_with_suggestion( contentcontent, suggestionreq.message, configstate.config, ) # ⑤ 保存修改记录原文 修改后文 revision RevisionRecord( session_idsession.id, content_idreq.content_id, suggestionreq.message, original_bodycontent.body, # ← 修改前 revised_bodyrevised_body, # ← 修改后 original_titlecontent.title, revised_titlerevised_title, ) session.revisions.append(revision) # ⑥ 更新内容 content.body revised_body if revised_title: content.title revised_title content.compute_word_count() state.repo.store.save_content(content) # ⑦ AI 回复消息 ai_msg ChatMessage( session_idsession.id, roleMessageRole.ASSISTANT, contentf已根据你的建议修改文章修改记录 #{len(session.revisions)} 已保存。\n\n f修改后正文预览\n{revised_body[:200]}..., ) # ⑧ 自动提取偏好第9篇讲过这里不展开 ...注意req.regenerate这个开关— 用户可以只提建议不修改regenerateFalseAgent 会回复已收到你的建议。这个设计让对话更灵活用户可以先提多条建议最后一次性修改。请求参数ChatSendRequestclass ChatSendRequest(BaseModel): content_id: str Field(..., description关联内容 ID) message: str Field(..., description用户消息修改建议) regenerate: bool Field(defaultTrue, description是否根据建议重新生成文章)三个字段简单明了字段类型说明content_idstr改哪篇文章messagestr修改建议自然语言regeneratebool是否立即修改默认 True用户用自然语言提建议不需要指定改哪里、怎么改— LLM 自己理解建议并定位修改位置。这是 Chat as Interface 的核心优势用户说人话Agent 干人事。四、版本链V1→V2→V3每次修改产生一条RevisionRecord所有修改记录构成一条版本链。版本链的结构V1原始版本 │ 建议太正式了活泼点 ▼ V2第一次修改 │ 建议第三段太长缩短 ▼ V3第二次修改 │ 建议加一个 emoji ▼ V4第三次修改每条RevisionRecord记录了RevisionRecord #1: V1 → V2 suggestion: 太正式了活泼点 original_body: V1 的正文 revised_body: V2 的正文 RevisionRecord #2: V2 → V3 suggestion: 第三段太长缩短 original_body: V2 的正文 revised_body: V3 的正文 RevisionRecord #3: V3 → V4 suggestion: 加一个 emoji original_body: V3 的正文 revised_body: V4 的正文每条记录都是相邻两个版本之间的 diff— 串起来就是完整的修改历史。版本链的三个用途用途怎么用对应代码版本回退取某条的original_body恢复GET /chat/revisions/{content_id}偏好提取从 diff 提取风格偏好StyleLearner.extract_preference_from_revision()审计追溯查看完整修改历史session.revisions查看修改记录的 APIrouter.get(/revisions/{content_id}, response_modelAPIResponse) async def get_revisions(content_id: str) - APIResponse: 获取某篇内容的修改记录 state get_state() session state.repo.store.get_chat_session_by_content(content_id) if not session: return APIResponse(data[], message该内容暂无修改记录) return APIResponse(data[r.model_dump(modejson) for r in session.revisions])一个 GET 请求拿到完整版本链— 前端可以展示修改历史用户可以对比任意两个版本。版本号的演进早期修改#1、修改#2、修改#3 ← 只有序号不知道改了什么 现在V1→V2、V2→V3、V3→V4 ← 有方向感知道是版本演进从修改#N改为V1→V2— 不只是换个写法是认知的转变修改不是打补丁是版本演进。每次修改都是一个新版本有完整的前世今生。五、乐观更新体验更流畅什么是乐观更新悲观更新用户发消息 → 等 AI 回复 → 显示用户消息 AI 回复 乐观更新用户发消息 → 立即显示用户消息 → 等 AI 回复 → 显示 AI 回复乐观更新的核心先显示用户的消息不等 AI 回复 — 让用户感觉消息发出去了然后在后台等 AI 处理。为什么需要乐观更新用户发建议 → LLM 编辑文章2-5秒→ 保存记录 → 提取偏好又2-5秒整个修改流程可能要 5-10 秒— 如果用悲观更新用户点发送后界面卡住 10 秒没有任何反馈体验极差。乐观更新让用户立即看到自己的消息知道发送成功了然后耐心等 AI 回复。后端如何配合后端send_message是一个同步请求 — 收到请求处理完所有步骤一次性返回。前端配合乐观更新// 前端伪代码 async function sendMessage(message) { // 1. 乐观更新立即显示用户消息 chatMessages.push({ role: user, content: message }); render(); // 2. 发送请求等 AI 回复 const response await fetch(/api/chat/send, { method: POST, body: JSON.stringify({ content_id, message, regenerate: true }), }); const data await response.json(); // 3. 用服务器返回的数据替换包含 AI 回复 修改记录 chatMessages data.messages; render(); }前端先假装成功后端再真正处理— 两者配合体验流畅。AI 回复中包含预览ai_msg ChatMessage( session_idsession.id, roleMessageRole.ASSISTANT, contentf已根据你的建议修改文章修改记录 #{len(session.revisions)} 已保存。\n\n f修改后正文预览\n{revised_body[:200]}{... if len(revised_body) 200 else }, )AI 回复不只是改好了— 还包含修改后正文的前 200 字预览。用户不用切到文章页面就能看到修改效果在聊天界面就能确认改对了吗。revised_body[:200]— 只取前 200 字避免消息太长刷屏。要看完整文章切到内容页面。六、与风格进化的联动对话修改不是孤立的 — 每次修改都会触发风格进化第9篇讲过形成闭环。联动流程用户修改文章 │ ├──→ 保存 RevisionRecord原文 修改后文 │ └──→ StyleLearner.extract_preference_from_revision(revision) │ ▼ 提取偏好如语气:活泼 │ ▼ 合并到人设的 style_preferences │ ▼ 下次生成自动注入偏好 → 风格越来越准代码修改后自动提取偏好# api/routes/chat.py — 修改后自动提取偏好 try: from ...persona.style_learner import StyleLearner from ...llm.client import LLMClient llm_for_learn LLMClient( base_urlstate.config.llm.base_url, api_keystate.config.llm.api_key, modelstate.config.llm.model, ) learner StyleLearner(llmllm_for_learn, storestate.repo.store) new_prefs await learner.extract_preference_from_revision(revision) if new_prefs: persona state.repo.store.get_persona(content.persona_id) existing list(persona.style_preferences) existing.extend(new_prefs) merged StyleLearner._merge_preferences(existing) updated_persona persona.model_copy(update{ style_preferences: merged, updated_at: datetime.now(), }) state.repo.store.save_persona(updated_persona) except Exception as e: logger.warning(f即时偏好提取失败不影响主流程: {e})两个关键设计偏好提取失败不影响主流程—try/except兜底提取失败只是不进化不影响修改本身。主流程是修改进化是附赠。用独立的 LLMClient 实例— 偏好提取和文章编辑用同一个 LLM 配置但创建独立实例避免状态污染。完整闭环第1次用户太正式了活泼点 → 修改文章V1→V2 → 提取偏好语气:活泼 → 写入人设 第2次生成新文章 → 自动注入语气:活泼 → 直接活泼风格不用用户再说 第3次用户emoji少一点 → 修改文章V3→V4 → 提取偏好emoji:低频 → 写入人设 第4次生成新文章 → 自动注入语气:活泼 emoji:低频 → 风格越来越精准对话修改 风格进化 越用越懂你。用户每改一次Agent 就学一点下次生成更好。这不是两个独立功能是同一个闭环的两面。七、错误处理修改失败怎么办LLM 调用可能失败 — 网络超时、API 限流、输出格式异常。修改流程需要优雅降级。修改失败的降级if req.regenerate: try: revised_body, revised_title await _regenerate_with_suggestion(...) # ... 正常流程 except Exception as e: logger.error(f重新生成失败: {e}) ai_msg ChatMessage( session_idsession.id, roleMessageRole.ASSISTANT, contentf抱歉根据建议重新生成时出错{e}。请尝试换一种表述方式。, )修改失败时不抛异常给前端用户看不懂 traceback返回友好的错误消息请尝试换一种表述方式用户消息已保存不会丢失建议内容不更新保持原文不变偏好提取失败的降级try: new_prefs await learner.extract_preference_from_revision(revision) # ... 写入人设 except Exception as e: logger.warning(f即时偏好提取失败不影响主流程: {e})偏好提取失败时只记 warning 日志不影响修改结果文章已经改好了不影响内容更新用户已经看到修改后的文章下次修改再尝试提取两层降级各保各的— 修改是主流程必须保偏好提取是副流程可以丢。主副分离互不拖累。踩坑总结坑根因修复修改后原文丢失只存修改后的文章加了RevisionRecord同时存original_body和revised_body版本号不直观用修改#1、修改#2改为V1→V2、V2→V3有方向感重新生成风格漂移Prompt 说重新生成改为根据建议调整文章只修改建议的部分修改失败丢建议异常中断整个请求用户消息先保存修改失败只影响 AI 回复偏好提取失败影响修改没有隔离主副流程try/except隔离偏好提取失败不影响修改界面卡顿悲观更新等 AI 回复才显示乐观更新先显示用户消息AI 回复太长刷屏返回完整修改后文章只返回前 200 字预览revised_body[:200]对话和修改记录混存都放在 messages 里分开存messages存对话文本revisions存完整记录修改后不进化只改了文章没提取偏好修改后自动调extract_preference_from_revision经验总结对话修改的核心是编辑不是生成— Prompt 里只修改用户建议的部分这句话决定了 LLM 是在原文基础上调整而不是从零写一篇新的三个模型各司其职—ChatMessage存轻量对话文本RevisionRecord存重量完整记录ChatSession是聚合根按需加载互不拖累版本链是修改的前世今生— 每条RevisionRecord记录相邻版本的 diff串起来就是完整修改历史支持回退、偏好提取、审计追溯乐观更新让体验流畅— 先显示用户消息再等 AI 回复5-10 秒的处理时间用户不会觉得卡顿主副流程隔离— 修改是主流程必须保偏好提取是副流程可以丢try/except隔离互不拖累对话修改 风格进化 越用越懂你— 每次修改触发偏好提取写入人设下次生成自动注入形成闭环下篇预告下一篇讲质检系统Agent的自我审查— 质检是 Agent 的良知不自检的 Agent 就像没有编辑的报社。多维度质检口语化、去重、逻辑检查、敏感词 orchestrator 编排 分数阈值 质检与生成的闭环。
返回列表