《大家都在聊LangGraph,企业真正需要的却不是更多 Demo》看起来是个大话题,但真落到项目里,常常就是几个具体选择。下面我尽量按实际开发时会遇到的问题来讲。

摘要

摘要:上周复盘一个 Agent 项目,业务方一句话"跨文档关联"就把整个团队打回原形。我们之前用 LangChain 写的脚本式 Agent 在 Demo 里跑得挺顺,但真正要接入生产环境时,权限校验、日志追踪、人工审批这些环节全乱了。这篇文章复盘我们用 LangGraph 重构工作流的过程,聊聊从 Demo 到生产系统真正需要补的功课。

---

目录

  • 为什么脚本式 Agent 在生产环境会崩
  • State 与 Node:把状态显式化
  • Edge 与条件分支:让流程可控
  • 人工审批节点:权限校验的正确姿势
  • 工程化落地:日志、追踪和可观测
  • 总结

---

为什么脚本式 Agent 在生产环境会崩

文章插图 1

我先说一个真实场景。

去年我们团队接了一个内部知识库问答的 Agent 需求,用的是 LangChain 的 ChatChain 写法,大概 200 行代码就搭出了一个能跑的 Demo。业务方试用后说"挺好用",我们就以为能上线了。

结果上线前一周,安全团队来评审,问了三个问题:

1. Agent 调用内部 API 时,有没有做权限校验?
2. 每次请求的日志能不能追踪到具体是哪个用户、什么时间、问了什么?
3. 涉及敏感数据的查询,需不需要人工审批?

我们当时愣住,因为 Demo 里根本没考虑这些东西。

回看代码,问题出在架构上。我们用的是典型的脚本式写法:


# 典型的脚本式 Agent(有问题)
def run_agent(user_question):
    # 1. 直接调用 LLM
    response = llm.invoke(user_question)

    # 2. 根据回复决定下一步
    if "查询" in response:
        result = call_internal_api(user_question)
    elif "审批" in response:
        result = send_approval_request(user_question)
    else:
        result = response

    return result

这段代码在 Demo 里能跑,但有几个致命问题:

状态不可追踪。每次调用都是独立的,中间过程没有记录,出问题的时候根本不知道 Agent 走到哪一步了。

流程不可控。条件分支硬编码在函数里,想加一个新类型的问题处理,就得改代码、重新部署。

权限和日志是事后补的。等安全团队来问,才发现根本没设计。

这就是为什么很多团队说"Agent 能跑 Demo,但上不了线"。不是模型不行,是架构不行。

---

State 与 Node:把状态显式化

文章插图 2

LangGraph 的核心思想是把 Agent 的工作流显式化成一个图。每个节点(Node)是一个函数,图的状态(State)是节点之间传递的数据。

我们重构后的代码长这样:

from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
import operator

# 定义 State,所有状态都显式声明
class AgentState(TypedDict):
    user_question: str
    user_id: str
    intent: str  # 识别出的意图
    response: str  # LLM 回复
    api_result: dict  # API 调用结果
    approval_status: str  # 审批状态
    final_answer: str  # 最终答案
    logs: list  # 日志记录

# 每个节点只负责一件事
def intent_recognition_node(state: AgentState) -> AgentState:
    """识别用户意图"""
    intent = classify_intent(state["user_question"])
    state["intent"] = intent
    state["logs"].append({
        "step": "intent_recognition",
        "intent": intent,
        "timestamp": datetime.now().isoformat()
    })
    return state

def llm_response_node(state: AgentState) -> AgentState:
    """调用 LLM 生成回复"""
    prompt = build_prompt(state["user_question"], state["intent"])
    response = llm.invoke(prompt)
    state["response"] = response
    state["logs"].append({
        "step": "llm_response",
        "response_length": len(response),
        "timestamp": datetime.now().isoformat()
    })
    return state

def api_call_node(state: AgentState) -> AgentState:
    """调用内部 API"""
    result = call_internal_api(state["user_question"], state["user_id"])
    state["api_result"] = result
    state["logs"].append({
        "step": "api_call",
        "success": result.get("success"),
        "timestamp": datetime.now().isoformat()
    })
    return state

关键点:

State 是显式的。所有中间状态都在 TypedDict 里声明,谁都能看懂 Agent 当前走到哪一步、数据是什么。

节点是纯函数。输入 State,输出 State,没有副作用。这样的好处是节点可以独立测试,出了问题也容易定位。

日志内建在 State 里。每次节点执行都会往 logs 列表里追加一条记录,上线后直接查 State 就能拿到完整执行轨迹。

---

CSDN资料领取方式

Edge 与条件分支:让流程可控

脚本式 Agent 的流程是硬编码的,LangGraph 用 Edge 把流程变成可配置的图。


# 构建图
graph = StateGraph(AgentState)

# 添加节点
graph.add_node("intent_recognition", intent_recognition_node)
graph.add_node("llm_response", llm_response_node)
graph.add_node("api_call", api_call_node)
graph.add_node("approval", approval_node)
graph.add_node("format_answer", format_answer_node)

# 设置入口
graph.set_entry_point("intent_recognition")

# 条件分支:根据意图决定下一步
def route_by_intent(state: AgentState) -> str:
    if state["intent"] == "query":
        return "api_call"
    elif state["intent"] == "approval_required":
        return "approval"
    else:
        return "format_answer"

graph.add_conditional_edges(
    "llm_response",
    route_by_intent,
    {
        "api_call": "api_call",
        "approval": "approval",
        "format_answer": "format_answer"
    }
)

# 普通边
graph.add_edge("intent_recognition", "llm_response")
graph.add_edge("api_call", "format_answer")
graph.add_edge("approval", "format_answer")
graph.add_edge("format_answer", END)

# 编译图
app = graph.compile()

条件分支的好处是:加新流程不需要改代码。比如业务方突然说"查询类请求需要二次确认",我们只需要:

1. 加一个 secondary_confirm 节点
2. 修改条件路由函数
3. 加一条条件边

State 和 Node 的设计让流程变更变得可预测,而不是到处打补丁。

---

人工审批节点:权限校验的正确姿势

回到最初的问题:权限校验怎么做?

我们之前的做法是在 API 调用前加一个 if 判断,但这有几个问题:判断逻辑散落在代码里,不好统一管理;审批流程不可追踪;无法处理需要人工介入的场景。

用 LangGraph 的方式,我们把审批做成一个独立的节点:

def approval_node(state: AgentState) -> AgentState:
    """人工审批节点"""
    # 1. 生成审批请求
    approval_request = {
        "user_id": state["user_id"],
        "question": state["user_question"],
        "intent": state["intent"],
        "risk_level": assess_risk(state)
    }

    # 2. 发送审批请求(比如发到企业微信/钉钉)
    send_approval_request(approval_request)

    # 3. 等待审批结果(这里用轮询,生产环境建议用消息队列)
    approval_result = wait_for_approval(approval_request["id"])

    # 4. 根据审批结果更新 State
    if approval_result["status"] == "approved":
        state["approval_status"] = "approved"
        state["logs"].append({
            "step": "approval",
            "status": "approved",
            "approver": approval_result["approver"],
            "timestamp": datetime.now().isoformat()
        })
    else:
        state["approval_status"] = "rejected"
        state["final_answer"] = "您的请求已被拒绝,原因:" + approval_result["reason"]
        state["logs"].append({
            "step": "approval",
            "status": "rejected",
            "reason": approval_result["reason"],
            "timestamp": datetime.now().isoformat()
        })

    return state

# 权限校验也可以做成节点
def permission_check_node(state: AgentState) -> AgentState:
    """权限校验节点"""
    user_permissions = get_user_permissions(state["user_id"])
    required_permission = get_required_permission(state["intent"])

    if required_permission not in user_permissions:
        state["final_answer"] = "您没有权限执行此操作"
        state["logs"].append({
            "step": "permission_check",
            "passed": False,
            "user_id": state["user_id"],
            "timestamp": datetime.now().isoformat()
        })
        return state

    state["logs"].append({
        "step": "permission_check",
        "passed": True,
        "user_id": state["user_id"],
        "timestamp": datetime.now().isoformat()
    })
    return state

关键设计:

审批和权限校验都是节点。这意味着它们可以被独立测试、独立监控、独立调整。

审批结果写入 State。后续节点可以根据审批状态决定怎么做,整个流程是透明的。

日志内建。谁在什么时候审批了什么,全部记录在 State 里,上线后直接查。

---

工程化落地:日志、追踪和可观测

有了图工作流,工程化问题就清晰了。我们当时踩的坑主要是三个:

1. 日志分散,排查困难

脚本式 Agent 的日志散落在各个函数里,出了问题要翻好几层代码。用 LangGraph 后,所有日志都在 State 的 logs 字段里,一条查询就能拿到完整执行轨迹:


# 执行 Agent
result = app.invoke({
    "user_question": "查询上月销售数据",
    "user_id": "user_123",
    "intent": "",
    "response": "",
    "api_result": {},
    "approval_status": "",
    "final_answer": "",
    "logs": []
})

# 直接查日志
for log in result["logs"]:
    print(f"[{log['timestamp']}] {log['step']}: {log}")

2. 追踪断点

脚本式 Agent 跑挂了,不知道是 LLM 的问题还是 API 的问题。用 LangGraph 后,每个节点的状态都在 State 里,可以直接看到 Agent 走到哪一步、哪一步失败了:


# 检查 State 中的中间状态
print(f"意图识别: {result['intent']}")
print(f"审批状态: {result['approval_status']}")
print(f"API结果: {result['api_result']}")

3. 可观测性

上线后我们需要监控 Agent 的健康状况。用 LangGraph 后,可以在每个节点前后加 hook,统一收集指标:

from langgraph.prebuilt import create_react_agent
import time

# 在节点执行前后记录耗时
def timed_node(node_func):
    def wrapper(state):
        start = time.time()
        result = node_func(state)
        elapsed = time.time() - start
        result["logs"].append({
            "step": f"{node_func.__name__}_duration",
            "duration_seconds": elapsed,
            "timestamp": datetime.now().isoformat()
        })
        return result
    return wrapper

# 给节点加计时
graph.add_node("llm_response", timed_node(llm_response_node))

---

总结

从 Demo 到生产,Agent 项目真正要补的不是模型调用能力,而是工程化能力。具体来说:

状态要显式化。用 State 而不是局部变量,所有中间状态都能追踪。

流程要图化。用 Edge 而不是 if-else,流程变更可配置、可测试。

权限和审批要节点化。做成独立节点,统一管理和监控。

日志要内建。每个节点执行都记录日志,出问题能快速定位。

我们团队用 LangGraph 重构后,上线前安全评审一次性通过。不是因为模型变强了,是因为架构变清晰了。

如果你也在做 Agent 项目,建议尽早用图工作流的思路重构。Demo 能跑只是第一步,能上线、可维护、可观测才是真本事。

资料展示

下面是我整理的AI大模型学习资料和工具包预览,适合收藏后按主题逐步学习。

AI大模型资料展示 1

AI大模型资料展示 2

AI大模型资料展示 3

AI大模型资料展示 4

AI大模型资料展示 5

如果你想看完整资料目录,可以在评论区留言「资料」;也欢迎告诉我你更关注AI大模型里的哪类内容。

CSDN官方大礼包

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐