在AI Agent快速发展的当下,“任务追踪”是智能代理落地的核心能力之一。TodoWrite作为一款演示型AI编码代理,通过TodoManager实现任务进度可视化,让AI能自主管理多步骤任务(如写代码、执行命令),但其设计存在一个致命痛点——极易出现LLM幻觉,导致任务状态错乱、内容失真。

本文将从TodoWrite的核心设计缺陷出发,拆解幻觉问题的产生根源,再给出4级递进式优化方案,从简单适配到工业级稳健,帮你把一个演示Demo改成可实际使用的AI任务代理。

一、先回顾:TodoWrite 原设计的“致命短板”

在深入优化前,我们先快速复盘TodoWrite的核心逻辑——这是理解幻觉问题的关键。

TodoWrite的任务更新机制非常简单:LLM每次需要更新任务状态时,必须重写整个任务列表,后端通过TodoManager的update方法直接全量覆盖原有列表,不做任何对比、校验或状态追溯。

举个直观的例子:初始任务列表为「[ ] #1: 写main.py」「[ ] #2: 写test.py」,当LLM想把#1标记为进行中时,它需要重新构造整个列表并发送给后端,后端直接用新列表替换旧列表,完成“更新”。

为什么这种设计容易出幻觉?

核心问题在于“全量重写+后端无脑信任”:

  1. LLM依赖自身记忆重写列表,而LLM的上下文记忆并非100%可靠,容易出现漏写任务、修改任务文本、错乱任务ID、凭空新增/删除任务等问题;

  2. 后端不做任何校验,只要LLM发送的列表格式合法(有id、text、合法status),就直接覆盖原有状态,幻觉一旦产生,就会彻底破坏任务列表的正确性;

  3. 没有状态追溯和纠错机制,一旦任务被幻觉篡改,无法恢复到上一轮正确状态。

这在演示场景中无关紧要,但如果用于实际开发(如让AI自主完成一个多步骤编码任务),幻觉会导致任务失控——比如原本要写main.py,LLM幻觉成写index.py,后端直接覆盖后,原有任务彻底丢失。

二、4级优化方案:从简单到稳健,逐步解决幻觉

优化的核心思路只有一个:不让LLM重写整个任务列表,将任务状态的管理权从LLM转移到后端,让LLM只负责发送“细粒度操作指令”。下面给出4级优化方案,可根据自身需求灵活选择,也可逐步升级。

优化1:最小成本适配——禁止LLM修改任务核心信息

这是最简单、见效最快的优化,无需大幅改动原有代码,核心是“锁定任务的id和text,只允许LLM修改status”。

具体实现:
  1. 修改TodoManager的update方法,增加校验逻辑:后端保存原有任务的id和text,LLM发送的新列表中,若某条任务的id已存在,其text必须与原有一致,否则直接报错拒绝更新;

  2. 限制LLM的操作范围:明确告知LLM,只能修改任务的status,不能修改id和text,若需修改任务文本,需调用专门的“rename_todo”工具。

核心代码改动(关键片段):

def update(self, items: list) -> str: if len(items) > 20: raise ValueError("Max 20 todos allowed") validated = [] in_progress_count = 0 # 新增:校验任务text是否被篡改 old_id_to_text = {item["id"]: item["text"] for item in self.items} for i, item in enumerate(items): text = str(item.get("text", "")).strip() status = str(item.get("status", "pending")).lower() item_id = str(item.get("id", str(i + 1))) # 新增:若任务id已存在,text必须与原有一致 if item_id in old_id_to_text and old_id_to_text[item_id] != text: raise ValueError(f"禁止修改任务文本!任务#{item_id}原有文本:{old_id_to_text[item_id]}") if not text: raise ValueError(f"Item {item_id}: text required") if status not in ("pending", "in_progress", "completed"): raise ValueError(f"Item {item_id}: invalid status '{status}'") if status == "in_progress": in_progress_count += 1 validated.append({"id": item_id, "text": text, "status": status}) if in_progress_count > 1: raise ValueError("Only one task can be in_progress at a time") self.items = validated return self.render()

优势与局限:

优势:几乎零开发成本,能快速解决“任务文本被篡改、id错乱”的核心幻觉问题;

局限:仍允许LLM重写整个列表,若LLM漏写任务,后端仍会覆盖,无法完全避免漏项问题。

优化2:强化校验——后端对比新旧列表,拦截幻觉

在优化1的基础上,进一步强化后端校验,不仅锁定任务text,还校验任务的完整性,防止LLM漏写、新增无关任务。

具体实现:
  1. 新增“任务id白名单”:后端保存当前所有任务的id,LLM发送的新列表中,id必须全部在白名单内(禁止新增任务),且不能遗漏任何一个id(禁止漏写任务);

  2. 若需新增/删除任务,需调用专门的“add_todo”“delete_todo”工具,禁止LLM通过重写列表的方式新增/删除任务;

  3. 校验不通过时,返回明确的错误信息(如“漏写任务#2,请补充完整”),让LLM修正后重新发送。

优势:

彻底解决“漏写任务、凭空新增任务”的幻觉问题,后端对任务列表的控制权进一步提升,幻觉被拦截在更新之前,不会导致状态错乱。

优化3:工业级稳健——后端托管状态,LLM只发操作指令

这是最推荐的优化方案,也是企业级AI Agent的标准做法:彻底禁止LLM重写整个列表,后端成为任务状态的“唯一真相源”,LLM只能通过细粒度工具发送操作指令,实现“指令式更新”。

具体实现:
  1. 拆分原有“todo”工具,替换为4个细粒度工具,彻底杜绝全量重写:

    1. add_todo:新增任务(参数:text,id由后端自动生成,避免LLM错乱id);

    2. update_todo_status:更新任务状态(参数:id、status,只能修改status);

    3. rename_todo:修改任务文本(参数:id、new_text,单独授权,避免误改);

    4. delete_todo:删除任务(参数:id,单独授权);

    5. list_todos:查询当前所有任务(供LLM查看最新状态,避免记忆偏差)。

  2. TodoManager新增对应方法,接收LLM的指令,单独处理单条任务的操作,不涉及全量覆盖;

  3. 后端保存完整的任务状态,支持状态追溯(如记录每一次操作日志),即使出现错误,也能回滚到上一轮正确状态。

核心代码改动(工具拆分):

# 新增细粒度工具 TOOLS = [ # 原有工具(bash、read_file等)保留 {"name": "add_todo", "description": "新增任务,id由系统自动生成", "input_schema": {"type": "object", "properties": {"text": {"type": "string"}}, "required": ["text"]}}, {"name": "update_todo_status", "description": "更新任务状态,只能修改status", "input_schema": {"type": "object", "properties": {"id": {"type": "string"}, "status": {"type": "string", "enum": ["pending", "in_progress", "completed"]}}, "required": ["id", "status"]}}, {"name": "rename_todo", "description": "修改任务文本,只能修改text", "input_schema": {"type": "object", "properties": {"id": {"type": "string"}, "new_text": {"type": "string"}}, "required": ["id", "new_text"]}}, {"name": "delete_todo", "description": "删除指定任务", "input_schema": {"type": "object", "properties": {"id": {"type": "string"}}, "required": ["id"]}}, {"name": "list_todos", "description": "查询当前所有任务,返回完整状态", "input_schema": {"type": "object", "properties": {}}, "required": []}, ]

优势:

彻底解决幻觉问题——LLM不再需要记忆整个任务列表,也不需要重写列表,只需要发送明确的操作指令,后端精准执行,任务状态全程可控、可追溯,完全适配实际生产场景。

优化4:辅助增强——注入当前状态,减少LLM记忆负担

无论采用上述哪种优化方案,都可以搭配这一辅助手段,进一步降低LLM的记忆负担,减少幻觉概率。

具体实现:

每次调用LLM时,自动将当前最新的任务列表(通过TodoManager.render()获取)注入到system prompt或对话历史中,让LLM随时能看到最新的任务状态,无需依赖自身记忆。

核心代码改动:

def agent_loop(messages: list): rounds_since_todo = 0 while True: # 新增:注入当前任务列表,帮助LLM回忆 current_todos = TODO.render() system_prompt = f"{SYSTEM}\n\n当前任务列表:\n{current_todos}" response = client.messages.create( model=MODEL, system=system_prompt, messages=messages, tools=TOOLS, max_tokens=8000, ) # 后续逻辑不变...

优势:

无需额外开发成本,能有效减少LLM因记忆偏差导致的幻觉,尤其适合多轮对话、长时间任务场景。

三、最优实践:组合优化方案

在实际开发中,推荐将“优化3+优化4”组合使用,兼顾稳健性和易用性,具体架构如下:

  1. 后端:TodoManager全权管理任务状态,作为唯一真相源,支持新增、修改状态、修改文本、删除、查询等细粒度操作,记录操作日志;

  2. 工具:提供5个细粒度工具(add_todo、update_todo_status、rename_todo、delete_todo、list_todos),禁止任何全量覆盖操作;

  3. LLM:每次对话自动注入当前任务列表,只需要发送细粒度操作指令,无需重写列表;

  4. 校验:后端对每一个工具调用进行合法性校验(如id是否存在、status是否合法),校验不通过则返回错误提示,让LLM修正。

四、总结:从Demo到可用的核心转变

TodoWrite的原设计,核心是演示“AI如何调用工具、追踪进度”,追求简单直观,因此牺牲了稳健性;而优化的本质,是完成从“LLM托管状态”到“后端托管状态”的转变——让LLM专注于“做决策、发指令”,让后端专注于“管状态、做校验”。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐