LangGraph构建Ai智能体-8-计划和执行架构-中文版本+详细解析

前言

在之前的内容中,我们使用了英文提示词。虽然这种方式对于人工智能来说更容易被正确理解,但对于很多学习者来说,可能会感到有些困惑。毕竟,不是所有人都对英文编程术语非常熟悉,尤其是初学者,可能会在理解这些提示词时遇到困难。

为了让大家更好地理解和使用这些提示词,我决定编写一份中文版本的提示词。这份中文提示词的效果将与英文版本完全一致,但会更加贴近我们的日常语言习惯,方便大家学习和使用。希望这份中文提示词能够帮助大家更轻松地掌握相关知识。

中文版本代码

python 复制代码
# 中文版本
import operator
import os
from typing import Annotated, List, Tuple, Union

from dotenv import load_dotenv
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import create_react_agent
from pydantic import BaseModel, Field
from typing_extensions import TypedDict

#
load_dotenv()
llm = ChatOpenAI(model="qwen-plus",
                 base_url=os.getenv("BASE_URL"),
                 api_key=os.getenv("OPENAI_API_KEY"),
                 temperature=0,
                 streaming=True)

tools = []
prompt = ChatPromptTemplate.from_messages(
    [
        ("system", """你是一位乐于助人的助手。"""),
        ("placeholder", "{messages}")
    ]
)
agent_executor = create_react_agent(llm, tools, state_modifier=prompt, debug=False)


# Define the Plan and Execution structure
class PlanExecute(TypedDict):
    input: str
    plan: List[str]
    past_steps: Annotated[List[Tuple], operator.add]
    response: str


class Plan(BaseModel):
    steps: List[str] = Field(description="序号唯一的步骤,按照数字升序")


class Response(BaseModel):
    response: str = Field(description="对用户的回应。")


class Act(BaseModel):
    action: Union[Response, Plan] = Field(
        description="要执行的操作。如果你想回复用户,请使用Response。"
                    "如果你需要进一步使用工具来获取答案,请使用Plan。")


# Planning step
planner_prompt = ChatPromptTemplate.from_messages(
    [
        (
            "system",
            """为实现既定目标,制定一个简单的分步计划。\
               该计划应包含按顺序编号的单独任务,如果正确执行这些任务,将得出正确答案。不要添加任何多余的步骤。\
               最后一步的结果应为最终答案。确保每个步骤都包含所需的所有信息,不要遗漏任何步骤。
              您必须按照以下 JSON 格式输出:
               {{
                 "steps": [
                   "Step 1 描述",
                   "Step 2 描述",
                   "Step 3 描述"
                 ]
               }}
               
               确保只包含带有步骤数组的 JSON 对象,不要添加其他内容。
               """,
        ),
        ("placeholder", "{messages}"),
    ]
)
planner = planner_prompt | llm.with_structured_output(Plan)

# Re-planning step
replanner_prompt = ChatPromptTemplate.from_template(
    """为实现既定目标,制定一个简单的分步计划。\
该计划应包含按顺序编号的单独任务,如果正确执行这些任务,将得出正确答案。不要添加任何多余的步骤。\
最后一步的结果应为最终答案。确保每个步骤都包含所需的所有信息,不要遗漏任何步骤。

你的目标是这样的:
{input}

你的原始计划是这样的:
{plan}

你目前已经完成了以下步骤:
{past_steps}

相应地更新你的计划。如果不需要更多步骤,且你可以回复用户,那么就这样回复。否则,完善计划。只在计划中添加那些仍需完成的步骤。不要把已经完成的步骤再纳入计划。

你必须严格按照下面其中一种JSON格式响应:

第一种 - 如果提供最终回复:
{{
  "action": {{
    "response": "你给用户的最终回复"
  }}
}}

第二种 - 如果提供新的步骤:
{{
  "action": {{
    "steps": [
      "Step 1 描述",
      "Step 2 描述"
    ]
  }}
}}
"""
)
replanner = replanner_prompt | llm.with_structured_output(Act)


def execute_step(state: PlanExecute):
    print("执行步骤")
    plan = state["plan"]
    plan_str = "\n".join(f"{i + 1}. {step}" for i, step in enumerate(plan))
    task = plan[0]
    task_formatted = f"对于以下计划:\n{plan_str}\n\n你负责执行第一步, {task}."
    agent_response = agent_executor.invoke({"messages": [("user", task_formatted)]})
    return {
        "past_steps": [(task, agent_response["messages"][-1].content)],
    }


# Planning step function
def plan_step(state: PlanExecute):
    print("plan_step")
    plan = planner.invoke({"messages": [("user", state["input"])]})
    # print(f"plan_step {plan}")
    return {"plan": plan.steps}


# Re-planning step function
def replan_step(state: PlanExecute):
    print("replan_step")
    output = replanner.invoke(state)
    if isinstance(output.action, Response):  # Final response provided
        return {"response": output.action.response}  # Return the response to the user
    else:
        return {"plan": output.action.steps}


# Conditional check for ending
def should_end(state: PlanExecute):
    print("should_end")
    if "response" in state and state["response"]:
        return END
    else:
        return "agent"


# Build the workflow
workflow = StateGraph(PlanExecute)

# Add nodes to the workflow
workflow.add_node("planner", plan_step)
workflow.add_node("agent", execute_step)
workflow.add_node("replan", replan_step)

# Add edges to transition between nodes
workflow.add_edge(START, "planner")
workflow.add_edge("planner", "agent")
workflow.add_edge("agent", "replan")
workflow.add_conditional_edges("replan", should_end, ["agent", END])

# Compile the workflow into an executable application
app = workflow.compile()


# Function to run the Plan-and-Execute agent
def run_plan_and_execute():
    inputs = {
        "input": "格蕾丝体重 125 磅。亚历克斯的体重比格蕾丝体重的 4 倍少 2 磅。他们两人的体重总和是多少磅?"
    }

    # Configuration for recursion limit
    config = {"recursion_limit": 10}

    # Run the Plan-and-Execute agent asynchronously
    for event in app.stream(inputs, config=config):
        for k, v in event.items():
            if k != "__end__":
                print(v)


# Run the async function
if __name__ == "__main__":
    run_plan_and_execute()

运行结果

arduino 复制代码
plan_step
{'plan': ['Step 1: 确定格蕾丝的体重,为 125 磅。', 'Step 2: 计算亚历克斯的体重,等于格蕾丝体重的 4 倍减去 2 磅,即 125 × 4 - 2 = 498 磅。', 'Step 3: 计算两人体重总和,等于 125 + 498 = 623 磅。']}
执行步骤
{'past_steps': [('Step 1: 确定格蕾丝的体重,为 125 磅。', '好的,我们完成了第一步:\n\n**Step 1: 格蕾丝的体重是 125 磅。**\n\n接下来可以继续执行后续步骤。是否需要我帮你继续?')]}
replan_step
should_end
{'plan': ['Step 2: 计算亚历克斯的体重,等于格蕾丝体重的 4 倍减去 2 磅,即 125 × 4 - 2 = 498 磅。', 'Step 3: 计算两人体重总和,等于 125 + 498 = 623 磅。']}
执行步骤
{'past_steps': [('Step 2: 计算亚历克斯的体重,等于格蕾丝体重的 4 倍减去 2 磅,即 125 × 4 - 2 = 498 磅。', '好的,我们来执行 **Step 2**:\n\n格蕾丝的体重是 125 磅。  \n亚历克斯的体重等于格蕾丝体重的 4 倍减去 2 磅:\n\n$$\n125 \times 4 - 2 = 500 - 2 = 498 \text{ 磅}\n$$\n\n✅ **亚历克斯的体重是 498 磅**。\n\n接下来可以继续执行 Step 3 来计算两人总和。需要我继续吗?')]}
replan_step
should_end
{'plan': ['Step 3: 计算格蕾丝和亚历克斯的体重总和,等于 125 + 498 = 623 磅。']}
执行步骤
{'past_steps': [('Step 3: 计算格蕾丝和亚历克斯的体重总和,等于 125 + 498 = 623 磅。', '看起来这里有些混淆。你提到"你负责执行第一步",但给出的步骤描述是"Step 3: 计算格蕾丝和亚历克斯的体重总和,等于 125 + 498 = 623 磅。"\n\n我们先澄清一下:\n\n如果 Step 3 是:"计算格蕾丝和亚历克斯的体重总和,等于 125 + 498 = 623 磅。"  \n那这个步骤已经完成了,因为等式已经给出并且是正确的:\n\n**125 + 498 = 623**\n\n所以,格蕾丝和亚历克斯的体重总和是 **623 磅**。\n\n如果你需要我执行的是其他步骤(比如第一步),请提供完整的步骤说明或任务内容,我会继续帮助你。')]}
replan_step
should_end
{'response': '格蕾丝和亚历克斯的体重总和是 **623 磅**。'}

执行步骤完整解析

下面的解析步骤会非常的长,请耐心阅读。

第一轮

1.1 planner 节点

使用AI将用户输入规划成计划数组,最终的提示词如下

bash 复制代码
System: 为实现既定目标,制定一个简单的分步计划。该计划应包含按顺序编号的单独任务,如果正确执行这些任务,将得出正确答案。不要添加任何多余的步骤。最后一步的结果应为最终答案。确保每个步骤都包含所需的所有信息,不要遗漏任何步骤。
您必须按照以下 JSON 格式输出:
{
 "steps": [
   "Step 1 描述",
   "Step 2 描述",
   "Step 3 描述"
 ]
}
确保只包含带有步骤数组的 JSON 对象,不要添加其他内容。
Human: 格蕾丝体重 125 磅。亚历克斯的体重比格蕾丝体重的 4 倍少 2 磅。他们两人的体重总和是多少磅?"

planner响应如下

json 复制代码
{
  "steps": [
    "Step 1: 确定格蕾丝的体重为 125 磅。",
    "Step 2: 计算亚历克斯的体重,即 4 × 125 - 2 = 498 磅。",
    "Step 3: 计算两人体重总和,即 125 + 498 = 623 磅。"
  ]
}

1.2 Agent 节点

也就是计划执行节点,最终提示词为

vbnet 复制代码
Human: 对于以下计划:
1. Step 1: 确定格蕾丝的体重为 125 磅。
2. Step 2: 计算亚历克斯的体重,即 4 × 125 - 2 = 498 磅。
3. Step 3: 计算两人体重总和,即 125 + 498 = 623 磅。

你负责执行第一步, Step 1: 确定格蕾丝的体重为 125 磅。.

agent 响应如下

vbnet 复制代码
好的,我们开始执行第一步:

**Step 1: 确定格蕾丝的体重为 125 磅。**

✅ 格蕾丝的体重已确定为 **125 磅**。

接下来可以继续进行 Step 2 和 Step 3 的计算。如果你需要继续,请告诉我!

此时需要处理past_step,把处理的步骤和刚刚的响应加入到past_step数组中,past_step结果如下

json 复制代码
[
    [
      "Step 1: 确定格蕾丝的体重为 125 磅。",
      "好的,我们开始执行第一步:

**Step 1: 确定格蕾丝的体重为 125 磅。**

✅ 格蕾丝的体重已确定为 **125 磅**。

接下来可以继续进行 Step 2 和 Step 3 的计算。如果你需要继续,请告诉我!"
    ]
  ]

1.3 replanner 节点

此时需要重新把计划数组重新处理下,剔除完成的计划列表,生成新的计划列表,这里也是交给AI来处理,最后的提示词如下

css 复制代码
Human: 为实现既定目标,制定一个简单的分步计划。该计划应包含按顺序编号的单独任务,如果正确执行这些任务,将得出正确答案。不要添加任何多余的步骤。最后一步的结果应为最终答案。确保每个步骤都包含所需的所有信息,不要遗漏任何步骤。

你的目标是这样的:
格蕾丝体重 125 磅。亚历克斯的体重比格蕾丝体重的 4 倍少 2 磅。他们两人的体重总和是多少磅?

你的原始计划是这样的:
['Step 1: 确定格蕾丝的体重为 125 磅。', 'Step 2: 计算亚历克斯的体重,即 4 × 125 - 2 = 498 磅。', 'Step 3: 计算两人体重总和,即 125 + 498 = 623 磅。']

你目前已经完成了以下步骤:
[('Step 1: 确定格蕾丝的体重为 125 磅。', '好的,我们开始执行第一步:\
\
**Step 1: 确定格蕾丝的体重为 125 磅。**\
\
✅ 格蕾丝的体重已确定为 **125 磅**。\
\
接下来可以继续进行 Step 2 和 Step 3 的计算。如果你需要继续,请告诉我!')]

相应地更新你的计划。如果不需要更多步骤,且你可以回复用户,那么就这样回复。否则,完善计划。只在计划中添加那些仍需完成的步骤。不要把已经完成的步骤再纳入计划。

你必须严格按照下面其中一种JSON格式响应:

第一种 - 如果提供最终回复:
{
  "action": {
    "response": "你给用户的最终回复"
  }
}

第二种 - 如果提供新的步骤:
{
  "action": {
    "steps": [
      "Step 1 描述",
      "Step 2 描述"
    ]
  }
}

Ai的响应如下,很明显Ai帮我们把已经完成的步骤给剔除了

json 复制代码
{
  "action": {
    "steps": [
      "Step 2: 计算亚历克斯的体重,即 4 × 125 - 2。",
      "Step 3: 计算两人体重总和,即 125 + 498。"
    ]
  }
}

1.4 终止判定

由于没有得到最终的答案,需要继续执行到Agent节点,于是进入了第二轮

第二轮

2.1 Agent节点

把刚刚的计划继续执行,提示词为

bash 复制代码
对于以下计划:
1. Step 2: 计算亚历克斯的体重,即 4 × 125 - 2。
2. Step 3: 计算两人体重总和,即 125 + 498。

你负责执行第一步, Step 2: 计算亚历克斯的体重,即 4 × 125 - 2。.

Ai响应如下

bash 复制代码
好的,我们来执行 Step 2:计算亚历克斯的体重,即  
**4 × 125 - 2**。

### 计算步骤:
1. 先算乘法:  
   $ 4 \\times 125 = 500 $

2. 再减去 2:  
   $ 500 - 2 = 498 $

### 结果:
亚历克斯的体重是 **498**。

此时需要处理past_step,把处理的步骤和刚刚的响应加入到past_step数组中,past_step结果如下

json 复制代码
[
    [
      "Step 1: 确定格蕾丝的体重为 125 磅。",
      "好的,我们开始执行第一步:

**Step 1: 确定格蕾丝的体重为 125 磅。**

✅ 格蕾丝的体重已确定为 **125 磅**。

接下来可以继续进行 Step 2 和 Step 3 的计算。如果你需要继续,请告诉我!"
    ],
    
    
    [
      "Step 2: 计算亚历克斯的体重,即 4 × 125 - 2。",
      "好的,我们来执行 Step 2:计算亚历克斯的体重,即  
**4 × 125 - 2**。

### 计算步骤:
1. 先算乘法:  
   $ 4 \\times 125 = 500 $

2. 再减去 2:  
   $ 500 - 2 = 498 $

### 结果:
亚历克斯的体重是 **498**。"
    ]
  ]

2.2 replanner 节点

继续使用replanner进行重新规划,提示词为

bash 复制代码
Human: 为实现既定目标,制定一个简单的分步计划。该计划应包含按顺序编号的单独任务,如果正确执行这些任务,将得出正确答案。不要添加任何多余的步骤。最后一步的结果应为最终答案。确保每个步骤都包含所需的所有信息,不要遗漏任何步骤。

你的目标是这样的:
格蕾丝体重 125 磅。亚历克斯的体重比格蕾丝体重的 4 倍少 2 磅。他们两人的体重总和是多少磅?

你的原始计划是这样的:
['Step 2: 计算亚历克斯的体重,即 4 × 125 - 2。', 'Step 3: 计算两人体重总和,即 125 + 498。']

你目前已经完成了以下步骤:
[('Step 1: 确定格蕾丝的体重为 125 磅。', '好的,我们开始执行第一步:\
\
**Step 1: 确定格蕾丝的体重为 125 磅。**\
\
✅ 格蕾丝的体重已确定为 **125 磅**。\
\
接下来可以继续进行 Step 2 和 Step 3 的计算。如果你需要继续,请告诉我!'), ('Step 2: 计算亚历克斯的体重,即 4 × 125 - 2。', '好的,我们来执行 Step 2:计算亚历克斯的体重,即  \
**4 × 125 - 2**。\
\
### 计算步骤:\
1. 先算乘法:  \
   $ 4 \\\\times 125 = 500 $\
\
2. 再减去 2:  \
   $ 500 - 2 = 498 $\
\
### 结果:\
亚历克斯的体重是 **498**。')]

相应地更新你的计划。如果不需要更多步骤,且你可以回复用户,那么就这样回复。否则,完善计划。只在计划中添加那些仍需完成的步骤。不要把已经完成的步骤再纳入计划。

你必须严格按照下面其中一种JSON格式响应:

第一种 - 如果提供最终回复:
{
  "action": {
    "response": "你给用户的最终回复"
  }
}

第二种 - 如果提供新的步骤:
{
  "action": {
    "steps": [
      "Step 1 描述",
      "Step 2 描述"
    ]
  }
}

Ai响应为

json 复制代码
{
  "action": {
    "steps": [
      "Step 3: 计算两人体重总和,即 125 + 498。"
    ]
  }
}

2.3 终止判断

很明显不能终止,继续下一轮

第三轮

3.1 agent节点

上面的计划只有一步了,提示词为

vbnet 复制代码
对于以下计划:
1. Step 3: 计算两人体重总和,即 125 + 498。

你负责执行第一步, Step 3: 计算两人体重总和,即 125 + 498。.

Ai响应为

ini 复制代码
好的,我们来计算两人的体重总和:

**125 + 498 = 623**

所以,两人的体重总和是 **623**。

此时需要处理past_step,把处理的步骤和刚刚的响应加入到past_step数组中,past_step结果如下

json 复制代码
[
    [
      "Step 1: 确定格蕾丝的体重为 125 磅。",
      "好的,我们开始执行第一步:

**Step 1: 确定格蕾丝的体重为 125 磅。**

✅ 格蕾丝的体重已确定为 **125 磅**。

接下来可以继续进行 Step 2 和 Step 3 的计算。如果你需要继续,请告诉我!"
    ],
    
    
    [
      "Step 2: 计算亚历克斯的体重,即 4 × 125 - 2。",
      "好的,我们来执行 Step 2:计算亚历克斯的体重,即  
**4 × 125 - 2**。

### 计算步骤:
1. 先算乘法:  
   $ 4 \\times 125 = 500 $

2. 再减去 2:  
   $ 500 - 2 = 498 $

### 结果:
亚历克斯的体重是 **498**。"
    ],
    
    
    [
      "Step 3: 计算两人体重总和,即 125 + 498。",
      "好的,我们来计算两人的体重总和:

**125 + 498 = 623**

所以,两人的体重总和是 **623**。"
    ]
  ]

3.2 replanner 节点

从上面的数据可以看出计划中的每一步都已经执行完毕了,那么replanner应该会给出最终的答案,那么会不会呢?先看下提示词

css 复制代码
Human: 为实现既定目标,制定一个简单的分步计划。该计划应包含按顺序编号的单独任务,如果正确执行这些任务,将得出正确答案。不要添加任何多余的步骤。最后一步的结果应为最终答案。确保每个步骤都包含所需的所有信息,不要遗漏任何步骤。

你的目标是这样的:
格蕾丝体重 125 磅。亚历克斯的体重比格蕾丝体重的 4 倍少 2 磅。他们两人的体重总和是多少磅?

你的原始计划是这样的:
['Step 3: 计算两人体重总和,即 125 + 498。']

你目前已经完成了以下步骤:
[('Step 1: 确定格蕾丝的体重为 125 磅。', '好的,我们开始执行第一步:\
\
**Step 1: 确定格蕾丝的体重为 125 磅。**\
\
✅ 格蕾丝的体重已确定为 **125 磅**。\
\
接下来可以继续进行 Step 2 和 Step 3 的计算。如果你需要继续,请告诉我!'), ('Step 2: 计算亚历克斯的体重,即 4 × 125 - 2。', '好的,我们来执行 Step 2:计算亚历克斯的体重,即  \
**4 × 125 - 2**。\
\
### 计算步骤:\
1. 先算乘法:  \
   $ 4 \\\\times 125 = 500 $\
\
2. 再减去 2:  \
   $ 500 - 2 = 498 $\
\
### 结果:\
亚历克斯的体重是 **498**。'), ('Step 3: 计算两人体重总和,即 125 + 498。', '好的,我们来计算两人的体重总和:\
\
**125 + 498 = 623**\
\
所以,两人的体重总和是 **623**。')]

相应地更新你的计划。如果不需要更多步骤,且你可以回复用户,那么就这样回复。否则,完善计划。只在计划中添加那些仍需完成的步骤。不要把已经完成的步骤再纳入计划。

你必须严格按照下面其中一种JSON格式响应:

第一种 - 如果提供最终回复:
{
  "action": {
    "response": "你给用户的最终回复"
  }
}

第二种 - 如果提供新的步骤:
{
  "action": {
    "steps": [
      "Step 1 描述",
      "Step 2 描述"
    ]
  }
}

Ai的响应为

json 复制代码
{
  "action": {
    "response": "格蕾丝的体重是 125 磅,亚历克斯的体重是 498 磅。两人体重的总和是 **623 磅**。"
  }
}

可以看到,Ai这里给出了最终的答案,下面就交给终止判断节点了

3.3 终止判断

由于response中有了数据,那么就可以结束整个流程了,最后把response的内容作为答案返回给用户

总结

从上面的具体解析我们可以更加深刻的理解计划和执行架构是怎么运行的,其中核心组件负责的功能如下

  • 初始计划节点: 负责用户目标的细致拆解,给出可执行步骤的具体规划,是目标能够达成的关键!
  • 执行节点:这部分仅仅是从计划中取出一步进行执行,需要Ai具有较强的指令遵循能力,这部分也会涉及到工具的调用,不过本文中没有体现
  • 重新规划节点:负责整理上面用户和Ai的响应内容,重新规划出新的计划,或者总结用户的答案
  • 是否终止: 这部分在本文中比较简单,我们仅仅依靠重新规划节点的输出做出判定,真实业务中可能会较复杂。
相关推荐
小马敲马12 分钟前
[4.2-2] NCCL新版本的register如何实现的?
开发语言·c++·人工智能·算法·性能优化·nccl
阿里云大数据AI技术14 分钟前
【新模型速递】PAI-Model Gallery云上一键部署gpt-oss系列模型
人工智能·gpt·llm
martinzh16 分钟前
AI智能体如何从错误中学习:反思机制详解
人工智能
站大爷IP19 分钟前
Python多态实战:从基础到高阶的“魔法”应用指南
python
duration~32 分钟前
SpringAI实现Reread(Advisor)
java·人工智能·spring boot·spring
码界筑梦坊1 小时前
108-基于Python的中国古诗词数据可视化分析系统
python·信息可视化·数据分析·django·毕业设计·numpy
攻城狮7号1 小时前
MiniCPM-V 4.0开源,号称是手机上的GPT-4V
人工智能·开源大模型·openbmb·端侧大模型·minicpm-v-4
大志说编程1 小时前
LangChain框架入门12:深入解析文本嵌入组件
人工智能·langchain
紫金修道1 小时前
python安装部署rknn-toolkit2(ModuleNotFoundError: No module named ‘rknn_toolkit2‘)
开发语言·python