• 微信:WANCOME
  • 扫码加微信,提供专业咨询
  • 服务热线
  • 13215191218
    13027920428

  • 微信扫码访问本页
智能体的动态人设
LangGraph 运行时大变身:如何给 AI 智能体在运行中动态更换「人设」?

LangGraph 运行时大变身:如何给 AI 智能体在运行中动态更换「人设」?

想要在 graph.compile() 之后动态更换系统提示词,是LangGraph进阶开发里的经典需求。这通常意味着我们需要一个能在每次 invoke() 调用时,把不同的指令或“人设”注入到现有对话里的运行机制。

LangGraph 的思路是 把“提示词”也当成运行时状态的一部分,在图的任意节点里动态创建,而不是编译时就固定好。具体来说,有下面这几种实现方式:

🧠 方法一:将 system_prompt 纳入自定义 State

这个方法的思路是把系统提示词也放进全局状态(State)里,这样任何节点都能根据 State 里的值来修改模型行为。架构图大致如下:

flowchart LR
    subgraph State[State Management]
        S[State Schema<br/>system_prompt: str]
    end

    subgraph Nodes[Nodes]
        P1[prepare_prompt_node<br/>修改 State.system_prompt]
        AG[agent_node<br/>读取 State.system_prompt]
    end

    subgraph Invoke[Invoke]
        I[graph.invoke()<br/>传入初始 State]
    end

    Invoke -->|传入 State| P1
    P1 -->|更新 system_prompt| S
    S -->|被读取| AG

一种常见且整洁的做法,是用一个叫做 prepare_prompt_node 的专门节点来处理所有与提示词相关的逻辑:

from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from typing import Annotated

# 定义全局状态State,添加system_prompt字段
class GraphState(TypedDict):
    messages: Annotated[list, add_messages]
    system_prompt: str  # ① 关键:在State中定义一个字段来动态存放系统提示词

# --- 节点1:专门负责构建/更新系统提示词的节点 ---
def prepare_prompt_node(state: GraphState):
    # 这里就可以包含各种复杂的逻辑了
    # 比如访问数据库、上下文变化、多轮对话修正等等
    user_input = state["messages"][-1].content

    # ② 根据用户最新输入,动态构建系统提示词
    new_system_prompt = f"你是一个专业的Python编程专家。当前用户的问题是: {user_input}。请用专业术语回答。"
    print(f"🔄 动态生成了新的系统提示词: {new_system_prompt[:50]}...")
    # 返回字段来更新状态
    return {"system_prompt": new_system_prompt}

# --- 节点2:主要的Agent节点(例如调用LLM)---
def agent_node(state: GraphState):
    # ③ 在调用模型前,从State中拿到最新的系统提示词
    current_system_prompt = state.get("system_prompt", "你是一个乐于助人的助手。")
    print(f"🤖 Agent节点正在使用提示词: {current_system_prompt[:50]}...")

    # 构建传递给模型的完整消息
    final_messages = [{"role": "system", "content": current_system_prompt}] + state["messages"]
    # 模拟调用模型的逻辑(实际使用中请替换为真实的LLM调用)
    # response = your_llm.invoke(final_messages)
    response_content = f"根据系统提示 '{current_system_prompt[:20]}...' 生成的回复。"
    return {"messages": [{"role": "assistant", "content": response_content}]}

# --- 构建图 ---
builder = StateGraph(GraphState)
builder.add_node("prepare_prompt", prepare_prompt_node)
builder.add_node("agent", agent_node)

builder.add_edge(START, "prepare_prompt")  # ④ 先运行提示词准备节点
builder.add_edge("prepare_prompt", "agent") # ⑤ 再运行代理节点
builder.add_edge("agent", END)

graph = builder.compile()

# 测试运行
print("🚀 第一次调用:")
final_state_1 = graph.invoke({"messages": [{"role": "user", "content": "什么是闭包?"}]})
print(f"最终回复: {final_state_1['messages'][-1].content}\n")

print("🚀 第二次调用,系统提示词会再次动态改变:")
final_state_2 = graph.invoke({"messages": [{"role": "user", "content": "如何实现装饰器?"}]})

优点:逻辑解耦,prepare_prompt_node 可以是一个很复杂的子系统,处理各种动态生成提示词的逻辑,非常灵活。

🛠️ 方法二:在 Runtime 配置中传入动态提示词

LangGraph 提供了 Runtime 对象,允许我们在调用 invoke() 的时候直接传入一个 context 对象。这个对象可以在图的任何节点中被访问,类似于一种“请求上下文”。

from dataclasses import dataclass
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.runtime import Runtime
from typing import Annotated, TypedDict

# 1. 定义一个上下文类,定义哪些配置可以被动态传递
@dataclass
class RuntimeContext:
    system_prompt: str

# 2. 定义主State(不再包含system_prompt,让运行时上下文来管理)
class GraphState(TypedDict):
    messages: Annotated[list, add_messages]

# 3. 在Agent节点中,通过runtime参数获取动态传入的system_prompt
def agent_node(state: GraphState, runtime: Runtime[RuntimeContext]):
    # 直接从运行时上下文中获取,无需通过State传递
    dynamic_system_prompt = runtime.context.system_prompt
    print(f"🎲 从Runtime获取系统提示词: {dynamic_system_prompt[:50]}...")

    # 构建消息
    final_messages = [{"role": "system", "content": dynamic_system_prompt}] + state["messages"]
    # 模拟LLM调用
    response_content = f"根据动态提示词生成的回复。"
    return {"messages": [{"role": "assistant", "content": response_content}]}

# 4. 构建图时,关联上我们的RuntimeContext
builder = StateGraph(GraphState, context_schema=RuntimeContext)
builder.add_node("agent", agent_node)
builder.add_edge(START, "agent")
builder.add_edge("agent", END)
graph = builder.compile()

# 5. 调用时,在config中传入自定义的上下文
print("🚀 调用1: 以Python专家身份")
response_1 = graph.invoke(
    {"messages": [{"role": "user", "content": "什么是迭代器?"}]},
    config={"configurable": {"system_prompt": "你是一位资深的Python架构师,请详细解答。"}}
)

print("\n🚀 调用2: 以幽默老师身份")
response_2 = graph.invoke(
    {"messages": [{"role": "user", "content": "什么是迭代器?"}]},
    config={"configurable": {"system_prompt": "你是一个幽默风趣的老师,用比喻解释概念。"}}
)

这种方法的好处是 副作用隔离,特别适合构建多租户系统。每个 invoke 调用传入的配置都是独立的,不会互相污染。LangGraph 1.0+ 和 v0.6 版本中也推荐用这种方式来管理运行时配置。

⚙️ 方法三:利用 Middleware 拦截模型调用(LangGraph 1.0+)

如果你的 LangGraph 版本支持(1.0+),官方推荐的另一个高级玩法是使用 中间件(Middleware)。中间件可以在模型被调用前、调用时和调用后插入自定义逻辑,很适合用来做通用的提示词修改。

from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint import MemorySaver
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气"""
    return f"{city}的天气是晴朗的,温度24度。"

# 自定义中间件:在模型调用前动态修改系统提示词
class DynamicPromptMiddleware:
    def __init__(self, get_prompt_func):
        self.get_prompt_func = get_prompt_func  # 接收一个能动态获取提示词的可调用对象

    def before_model(self, state, runtime):
        # 这个钩子会在模型被调用前执行
        new_prompt = self.get_prompt_func(state, runtime)
        # 修改 state 中的系统消息或相关字段
        # 具体修改方式取决于你的 State 设计,下面是一种思路
        if state.get("system_prompt") != new_prompt:
            state["system_prompt"] = new_prompt
        return state

# 你的动态提示词逻辑
def get_dynamic_prompt(state, runtime):
    user_input = state["messages"][-1].content
    return f"你是天气助手,用非正式的语气回答。用户问的是:{user_input}"

# 创建Agent时,挂载中间件
agent = create_react_agent(
    model="your-model",
    tools=[get_weather],
    middleware=[DynamicPromptMiddleware(get_dynamic_prompt)],  # 使用中间件
    checkpointer=MemorySaver()
)

这种方法就像是给模型调用加上了AOP,动态提示词不再侵入业务节点,而是成为可插拔的基础能力。

💡 进阶技巧:利用 update_state 实现「时间旅行式」提示词修改

LangGraph 的检查点(Checkpointer)机制,允许我们保存图运行时的每一步状态。利用这个机制,你可以实现更高级的用法:在 graph.invoke() 运行后,通过 update_state() 方法修改某个历史检查点的提示词,然后从这个时间点重新运行。例如,你可以:

  1. 执行:运行 result = graph.invoke(initial_state, config)
  2. 回溯:使用 graph.get_state_history(config) 获取所有历史状态快照。
  3. 修改:找到某个特定的检查点 checkpoint,调用 graph.update_state(config, {"system_prompt": "新的提示词"}, checkpoint_id=checkpoint["id"])
  4. 重新执行:基于修改后的状态,重新执行图。

这种方法可以实现类似于 Git 的时间旅行和分支功能,让你在 Agent 执行出错时,能够修正提示词并从正确的节点继续。

🚀 终极方案:拥抱 v0.6+ 的 Context API

在 LangGraph 的最新版 (v0.6+) 中,你可以直接从 runtime 对象中解构上下文。这比 v0.5 中访问 config 的繁琐写法要优雅得多:

from dataclasses import dataclass
from langgraph.graph import StateGraph
from langgraph.runtime import Runtime

@dataclass
class AppContext:
    system_prompt: str

def agent_node(state: dict, runtime: Runtime[AppContext]) -> dict:
    # 直接访问,享受 IDE 的自动补全和类型检查!
    print(runtime.context.system_prompt)  # 而不是 runtime.context["system_prompt"]

🔍 延伸思考:什么时候应该动态改变系统提示词?

场景类型 示例 适用方法
多租户隔离 为不同企业客户定制 AI 风格 方法二(Runtime Context)
多轮对话修正 Agent 中途发现自己理解错了,需要换个思路 方法一(State 更新)
A/B 测试 & 实验 同一流程,随机分配不同提示词,观察效果差异 方法三(Middleware)
人机协同 (Human-in-the-loop) 人在流程中审核并修改提示词 update_state() 进阶方法
开发/调试阶段 快速测试不同提示词效果 方法二(Runtime Context)

💬 FAQ

Q1: configState 里存提示词有什么区别?
config 是静态的、只读的请求上下文,适合在调用时一次性注入。State 是可变的,在整个图执行期间可以被节点修改和传递。官方建议:不变的用 config,变化的用 State

Q2: v0.6 中,旧版的 state_modifier 还能用吗?
在 v0.2.46 之前 state_modifier 还能用,但现在已被官方移除,推荐统一使用 prompt 参数,但 prompt 参数主要面向预置 Agent 的场景。对于更通用的场景,建议使用上面几种方法。

Q3: 在多用户环境下这样用安全吗?
安全。关键在于确保每次调用 invokeainvoke 时,你的上下文是隔离的。方法二(Runtime Context)是专门为此设计的,因为它通过 configurable 传递信息,不会在全局变量或 Agent 实例中残留。

动态提示词的实现,本质上是一个“依赖注入”的过程。我们将“可变策略”(提示词)与“不变框架”(图结构)解耦,使得Agent具备了真正的“人格可塑性”与无限扩展的业务适配能力。