目录

当 Agent 遇上高危场景

调优第一步:用状态机(State Machine)重构流程

代码架构示例 (基于 LangGraph)

调优思路解析:

调优第二步:结构化记忆与状态管理

调优实践:记忆更新机制

调优第三步:容错与安全兜底(Safety Rails)

1. 工具调用的弹性设计

2. 确定性安全审查(Guardrails)

总结

很多开发者发现,用 LangChain搭一个能调工具的 Agent 只需一下午,但要让它在生产环境中稳定运行却需要几个月。本文以一个复杂的医疗预问诊 Agent 为案例,探讨如何跳出“疯狂修改 Prompt”的怪圈,通过引入状态机架构(LangGraph)、结构化记忆和确定性护栏,系统地调试和优化你的智能体。

当 Agent 遇上高危场景

在电商客服场景中,Agent 说错一句话可能只是引发投诉;但在医疗、金融或工业控制领域,Agent 的一次“幻觉”或逻辑断层可能导致严重后果。

我们经常遇到的 Agent“调试之痛”包括:

  1. 流程迷失:在长达数十轮的问诊中,Agent 忘了自己还没问过敏史,就急着下结论。

  2. 状态丢失:患者在第3轮提到“有高血压”,在第15轮 Agent 开药建议时却完全忘了这一点。

  3. 不可控的工具调用:在需要紧急干预时,Agent 还在尝试调用一个超时的外部知识库 API,而不是立即建议患者拨打急救电话。

核心观点:Agent 的稳定性不是“提示(Prompted)”出来的,而是“架构(Architected)”出来的。

本文将构建一个“智能预问诊 Agent (Dr. Bot)”,它的任务是:收集患者主诉、询问相关病史、检查药物过敏,并给出初步就医建议(去急诊还是约专科)。我们将展示如何通过架构调整来解决上述痛点。

调优第一步:用状态机(State Machine)重构流程

最原始的 Agent 通常是一个死循环(While Loop):思考 -> 选工具 -> 执行 -> 观察 -> 再思考。这种扁平结构处理简单任务尚可,但在医疗场景下,它缺乏宏观的“规划感”。

痛点:Agent 不知道自己处于问诊的哪个阶段,容易在收集信息和给出建议之间反复横跳。

解决方案:引入 LangGraph,将流程显性化。

我们不再让 LLM 自己决定下一步做什么大方向,而是定义好明确的阶段(Nodes):

  1. Intake Node(接诊):只负责寒暄并确认主诉。

  2. Information Gathering Node(信息收集):负责循环追问症状、病史、过敏史。

  3. Triage Node(分诊决策):基于收集的信息,做出最终建议。

代码架构示例 (基于 LangGraph)

我们将使用 LangGraph 来强制 Agent 遵循医疗规范流程,而不是让它自由发挥。

from typing import TypedDict, Annotated, List
from langgraph.graph import StateGraph, END
# 假设已导入必要的 LLM 和 Tool 定义

# 1. 定义核心状态 Schema(这是 Agent 的“短期记忆”)
# 在医疗场景,强烈建议使用强类型而非一大段 Chat History 字符串
class MedicalState(TypedDict):
    patient_id: str
    chief_complaint: str | None  # 主诉
    symptoms_collected: List[str] # 已收集症状
    allergies: str | None        # 过敏史
    current_stage: str           # 当前处于哪个阶段
    dialogue_history: List[str]  # 对话记录

# 2. 定义节点逻辑 (Nodes)

def intake_node(state: MedicalState):
    """接诊节点:只关注确认主诉"""
    # ... LLM 调用逻辑,Prompt 聚焦于:“请热情接待患者,并询问他们哪里不舒服。”
    # 如果确认了主诉,更新 state['chief_complaint']
    print("---进入接诊阶段---")
    # 模拟 LLM 行为,假设已获取主诉
    state['chief_complaint'] = "胸痛,伴随呼吸困难"
    return state

def info_gathering_node(state: MedicalState):
    """信息收集节点:ReAct 循环,调用工具查询医学知识或追问细节"""
    print("---进入信息收集阶段,尝试调用医学知识库工具---")
    # 这里的核心调优点:
    # 这个节点的 Prompt 必须包含强约束:“你现在的任务是收集信息。
    # 在你确认了所有必要的症状和过敏史之前,绝对不要给出医疗建议。”
    # ... Agent 执行 ReAct 循环 ...
    # 假设收集完毕
    state['allergies'] = "青霉素过敏"
    state['symptoms_collected'].extend(["持续时间2小时", "无放射痛"])
    return state

def triage_node(state: MedicalState):
    """分诊节点:只根据 State 做总结建议,禁止调用查询工具"""
    print("---进入分诊决策阶段---")
    # Prompt 聚焦:“根据以下结构化信息:{state},给出就医建议。”
    return state

# 3. 定义路由逻辑 (Edges) - 这是调优的关键!
# 相比于让 LLM 自己决定,我们用代码写死关键跳转逻辑。

def router(state: MedicalState) -> str:
    """决定下一步去哪的'守门员'函数"""
    # 确定性规则:如果没有主诉,必须回到接诊
    if not state.get('chief_complaint'):
        return "intake"
    
    # 确定性规则:关键信息缺失,必须继续收集
    # 医疗场景下,不能让 LLM 自己觉得“信息够了”
    if state.get('allergies') is None:
        return "info_gathering"
        
    # 确定性规则:如果识别到高危关键词(如胸痛),直接进入分诊,跳过繁琐询问
    if "胸痛" in state['chief_complaint']:
        return "triage"

    # 默认路径
    return "triage"

# 4. 构建图
workflow = StateGraph(MedicalState)

# 添加节点
workflow.add_node("intake", intake_node)
workflow.add_node("info_gathering", info_gathering_node)
workflow.add_node("triage", triage_node)

# 设置入口
workflow.set_entry_point("intake")

# 添加条件边 (Conditional Edges)
# 在接诊和信息收集完成后,都由 router 函数决定下一步去哪
workflow.add_conditional_edges("intake", router)
workflow.add_conditional_edges("info_gathering", router)
workflow.add_edge("triage", END)

# 编译图
app = workflow.compile()

调优思路解析:

  • 从“提示”到“约束”:我们不再在 Prompt 里哀求 LLM “请先问完症状再下结论”,而是通过 router 函数的 Python 代码强制要求:只要 allergies 字段为空,就必须回到 info_gathering 节点。这就是确定性护栏

  • 职责分离triage_node 的 Prompt 可以写得很简单,因为它不需要处理复杂的工具调用,只负责总结。这大大降低了模型出错的概率。


调优第二步:结构化记忆与状态管理

在基础的 Agent 中,我们习惯把所有的对话历史塞给模型。对于医疗场景,这简直是灾难。患者可能在第3句说“我高血压”,第20句说“我最近没吃药”。模型很容易在长上下文中遗漏关键信息。

痛点:信息淹没在噪音中,重要医疗事实(Fact)丢失。

解决方案:显式维护结构化状态 (Structured State)。

在上面的代码中,我们定义了 MedicalState(TypedDict)。这就是我们的“真相来源(Single Source of Truth)”。

调优实践:记忆更新机制

你不能指望 LLM 自动把对话里的信息完美同步到 State 中。你需要一个专门的机制来做这件事。

  1. 工具作为状态更新器:定义一个特殊的工具,比如 update_patient_record(key, value)。强制 Agent 在获取到关键信息时显式调用该工具。

  2. 后处理提取器(Post-processor):在 info_gathering_node 的每一轮对话结束后,运行一个更小、更便宜的模型(如 GPT-3.5 或专门的提取模型),专门负责从最近的对话中提取关键事实并更新 State

# 伪代码:在信息收集节点内部的状态更新逻辑
def info_gathering_node_internal_loop(state):
    # 1. Agent 执行动作生成回复
    response = agent_executor.invoke(state)
    
    # 2. 【调优关键】记忆整理者介入
    # 使用一个专门的 Prompt,让模型从刚才的对话中提取事实
    extraction_prompt = f"""
    基于刚才的对话:{response['output']}
    请提取出患者新提到的症状或过敏史,并以JSON格式返回。如果没有新信息,返回空JSON。
    """
    extracted_data = cheap_llm.invoke(extraction_prompt)
    
    # 3. 显式更新结构化状态,而不是依赖对话历史
    if extracted_data.get('new_allergy'):
        state['allergies'] = extracted_data['new_allergy']
        print(f"【记忆更新】已记录过敏史:{state['allergies']}")
        
    return state

调优第三步:容错与安全兜底(Safety Rails)

在医疗场景,Agent 调用工具失败(例如医学知识库 API 超时)是不可接受的直接报错。更可怕的是,Agent 因为幻觉推荐了错误的药物。

痛点:工具链脆弱,且缺乏对高危输出的拦截机制。

解决方案:确定性兜底与输出审查。

1. 工具调用的弹性设计

不要只写 tool.run(args)。要包裹在 try-catch 块中,并给 Agent 提供“反思”的机会。

  • 错误回传:如果 API 超时,将错误信息包装成自然语言:“知识库暂时无法连接,请尝试询问患者更多细节,或者建议患者稍后再试。” 将其作为 Observation 返回给 LLM。

2. 确定性安全审查(Guardrails)

triage_node 输出最终建议之前,增加一个强制的审查步骤。这个步骤甚至可以不使用 LLM,而是基于规则

def safety_check(advice_text: str, state: MedicalState) -> str:
    """安全审查函数"""
    dangerous_keywords = ["服用阿司匹林", "自行停药"]
    
    # 规则1:如果患者声明过敏,且建议中包含相关药物关键词(需要复杂的实体链接,这里简化演示)
    if state['allergies'] == "青霉素" and "阿莫西林" in advice_text:
         return "警告:监测到严重的药物过敏冲突。请立刻停止当前建议,提示患者其过敏风险,并建议咨询面诊医生。"

    # 规则2:关键词黑名单
    for keyword in dangerous_keywords:
        if keyword in advice_text:
             return "警告:检测到高风险医疗建议关键词。作为预问诊AI,请勿提供具体用药指导。请修改建议,引导患者就医。"

    return advice_text # 通过审查

你可以在 LangGraph 中将这个审查作为一个独立的 Node 插入在最终输出之前。


总结

调试医疗等复杂领域的 Agent,本质上是一场从**“依赖概率(Prompting)”走向“追求确定性(Engineering)”**的战役。

当你的 Agent 不好用时,请尝试以下重构路径:

  1. 画流程图:别急着写代码,先画出业务专家的决策流程图。

  2. 图代码化:使用 LangGraph 将流程图转化为强制性的状态机结构,把“什么阶段做什么事”定死。

  3. 结构化记忆:放弃只用 Chat History,定义强类型的 Schema (Pydantic/TypedDict) 来存储关键事实,并设计专门的机制去更新它。

  4. 加入代码级护栏:对于关键的跳转和安全检查,用 Python if/else 代替 LLM 的判断。

只有建立了坚实的架构基础,你的 Agent 才能在复杂的现实世界中,像一位专业、稳健的医生助手一样工作,而不是一个只会瞎聊天的玩具。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐