LangGraph Task Graph 任务规划Agent工作流系统

核心目标

将用户的一句自然语言查询,自动分解成多个可并行执行的任务,通过 LLM 智能规划,高效完成复杂的信息聚合需求。


📐 系统架构(4 层)

复制代码
┌─────────────────────────────────────────────┐
│ 层1: 工具层 (@tool 装饰器)                   │
│ ├─ weather、news、stock、search...          │
│ └─ calculator、summarize、translate、compare │
└────────────┬────────────────────────────────┘
             │
┌────────────▼────────────────────────────────┐
│ 层2: 规划层 (Planner 节点)                   │
│ • 解析用户查询                              │
│ • 调用 Qwen LLM 分析任务                    │
│ • 生成任务依赖图 (JSON)                     │
└────────────┬────────────────────────────────┘
             │
┌────────────▼────────────────────────────────┐
│ 层3: 执行层 (Executor 节点)                  │
│ • 拓扑排序任务                              │
│ • asyncio.gather 并发执行                  │
│ • 处理依赖和数据传递                        │
└────────────┬────────────────────────────────┘
             │
┌────────────▼────────────────────────────────┐
│ 层4: 汇总层 (Joiner 节点)                    │
│ • 收集所有结果                              │
│ • 判断是否需要重新规划                      │
│ • 生成最终答案                              │
└─────────────────────────────────────────────┘

🛠️ 核心组件详解

1️⃣ 工具定义层
python 复制代码
@tool
def weather(location: str) -> str:
    """获取天气"""
    time.sleep(0.3)
    return f"天气:{location} 15-22°C..."

特点:

  • ✅ 使用 @tool 装饰器(符合 LangChain 标准)
  • ✅ 自动从类型提示 + 文档字符串提取 schema
  • ✅ LLM 可以自动理解每个工具的用途和参数

代码映射:

python 复制代码
ALL_TOOLS = [weather, news, stock, ...]  # 工具池
TOOLS_MAP = {tool.name: tool for tool in ALL_TOOLS}  # 快速查找表

2️⃣ 规划层 (Planner Node)

输入: 用户查询

复制代码
"北京天气怎么样?有什么科技新闻?"

处理流程:

python 复制代码
def planner_node(state):
    # 1. 把所有工具的 schema 转为 JSON 字符串
    tools_schema = get_tools_schema()
    
    # 2. 构造提示词
    system_prompt = "你是任务规划助手..."
    user_prompt = f"查询:{query}\n工具:{tools_schema}"
    
    # 3. 调用 Qwen LLM
    response = LLM.invoke([system_prompt, user_prompt])
    
    # 4. 解析 JSON 任务图
    task_graph = json.loads(response)

输出: 结构化任务图

json 复制代码
{
  "reasoning": "用户询问两个独立的问题:1. 北京天气 2. 科技新闻。这两个任务没有依赖关系,可以并行执行",
  "tasks": [
    {
      "id": "t1",
      "tool": "weather",
      "description": "查询北京天气",
      "depends_on": [],
      "args": {"location": "北京"}
    },
    {
      "id": "t2",
      "tool": "news",
      "description": "查询科技新闻",
      "depends_on": [],
      "args": {"category": "科技"}
    }
  ],
  "edges": [["t1", "JOIN"], ["t2", "JOIN"]]
}

3️⃣ 执行层 (Executor Node) - 核心并发机制

第一步:拓扑排序

python 复制代码
execution_order = topological_sort(task_graph["tasks"])
# 输出:[["t1", "t2"], ["t3"]]
# 含义:第一批并行执行 t1、t2;第二批执行 t3

第二步:分批并发执行

python 复制代码
for level_idx, level in enumerate(execution_order):
    # level = ["t1", "t2"]
    
    # 构建可执行任务列表
    executable_tasks = [task1, task2]
    task_ids_to_execute = ["t1", "t2"]
    
    # 🔑 核心:使用 asyncio.gather 并发执行
    batch_results = await asyncio.gather(
        *[execute_task_async(task, results_dict) for task in executable_tasks]
    )
    # 结果:[TaskResult(t1), TaskResult(t2)](同时完成!)

第三步:异步执行单个任务

python 复制代码
async def execute_task_async(task, previous_results):
    # 1. 获取工具
    tool_func = TOOLS_MAP[task["tool"]]
    
    # 2. 准备参数(支持依赖传递)
    args = task.get("args", {})
    if task["tool"] == "summarize":
        args["texts"] = [previous_results[dep]["output"] 
                        for dep in task["depends_on"]]
    
    # 3. 在线程池执行(避免阻塞事件循环)
    output = await loop.run_in_executor(
        None,
        lambda: tool_func.invoke(args)
    )
    
    return TaskResult(...)

时间对比:

复制代码
┌─────────────────────┐
│ 顺序执行 (sync)      │
├─────────────────────┤
│ t1: ░░░░░░░░░░ 0.3s │  ← 阻塞
│ t2:           ░░░░░░░░░░ 0.3s │  ← 阻塞
│ t3:                   ░░░░░░░░ 0.3s │
└─────────────────────┘
总耗时:0.9s ❌

┌─────────────────────┐
│ 并发执行 (async)     │
├─────────────────────┤
│ t1: ░░░░░░░░░░      │
│ t2: ░░░░░░░░░░      │ ← 同时进行
│ t3:           ░░░░░░░░ 
└─────────────────────┘
总耗时:0.6s ✅ (快 1.5 倍)

4️⃣ 汇总层 (Joiner Node)

职责:

python 复制代码
def joiner_node(state):
    results = state["task_results"]  # 所有任务结果
    
    # 1. 统计成功/失败
    failed = [r for r in results if r["status"] == "failed"]
    success = [r for r in results if r["status"] == "success"]
    
    # 2. 提取最终输出
    final_outputs = [r["output"] for r in success if r["task_id"] in final_task_ids]
    
    # 3. 判断是否重规划
    needs_replan = (len(failed) > 0 and len(failed)/len(results) > 0.5)
    
    return {"final_answer": "...", "needs_replan": needs_replan}

🎯 完整工作流示例

输入查询: "北京天气怎么样?有什么科技新闻?"

步骤 1: Planner 生成任务图
复制代码
📝 Qwen 思考:
  用户问题分解:
  • 问题1:北京天气(无依赖)
  • 问题2:科技新闻(无依赖)
  → 可以并行执行

📊 生成任务图:
  └─ t1: weather(location="北京")
  └─ t2: news(category="科技")
步骤 2: Executor 拓扑排序
复制代码
📋 执行顺序:[["t1", "t2"]]
   解释:只有一个批次,t1 和 t2 无依赖关系,可并行
步骤 3: Executor 并发执行
复制代码
🔄 执行批次 1: ['t1', 't2']
  
  时间线:
  t1 开始 → [0.3s] ✅ 完成 (天气:北京 15-22°C)
  t2 开始 → [0.3s] ✅ 完成 (新闻:科技板块 - AI大模型...)
  
  ⏱️  总耗时:0.3s(不是 0.6s!)
步骤 4: Joiner 汇总
复制代码
🔗 汇总结果:
  ✅ 成功:2 个任务
  ❌ 失败:0 个任务
  
📌 最终答案:
  1. 天气:北京 15-22°C,小雨,建议携带雨具
  2. 新闻:科技板块 - AI大模型技术突破,阿里云推出Qwen-Max
  
✅ 共执行 2 个任务,成功 2 个,失败 0 个

🔄 更复杂的场景:依赖关系

输入: "查询腾讯股票,然后与阿里股票对比"

任务图:
json 复制代码
{
  "tasks": [
    {"id": "t1", "tool": "stock", "args": {"symbol": "腾讯"}},
    {"id": "t2", "tool": "stock", "args": {"symbol": "阿里"}},
    {"id": "t3", "tool": "compare", "depends_on": ["t1", "t2"]}
  ]
}
执行流程:
复制代码
📋 执行顺序:[["t1", "t2"], ["t3"]]

🔄 第一批:
  t1 (腾讯股票) → ✅ {price: ¥123.45, change: +2.3%}
  t2 (阿里股票) → ✅ {price: ¥98.76, change: -1.2%}

🔄 第二批:
  t3 (对比) → 【接收 t1、t2 的输出作为输入】
       ✅ 对比分析:腾讯涨幅更大...

时间轴:
  ┌─t1─┐
  │    ├─t3─
  └─t2─┘
  
总耗时:0.7s(而不是 1.0s)

💡 关键设计亮点

特性 实现方式 优势
自动 Schema 提取 @tool + 类型提示 无需手动维护,LLM 自动理解
智能规划 Qwen LLM 自动识别任务依赖和并行机会
真正并发 asyncio.gather() 充分利用多核,提速 2-3 倍
依赖传递 previous_results 下游任务自动接收上游输出
自适应重规划 失败检测 + 迭代 自动重试,最多 3 次
类型安全 TypedDict 状态 完整的类型提示,IDE 支持

🚀 性能指标

假设有 8 个独立的 I/O 任务,每个耗时 0.3 秒:

方案 耗时 加速倍数
顺序执行 2.4s ❌ 1x
简单并发 (thread pool) 0.4s ✅ 6x
asyncio 并发 0.3s ✅✅ 8x

📊 状态管理

整个系统通过 PlanExecuteState TypedDict 管理状态流转:

python 复制代码
{
    "user_query": "原始查询",
    "task_graph": {...},           # Planner 生成
    "task_results": [...],         # Executor 填充
    "completed_tasks": ["t1", "t2"],
    "final_answer": "最终答案",
    "needs_replan": False,         # Joiner 判断
    "planning_iterations": 1
}

总结

这是一个智能、高效的分布式任务编排系统

  1. 🧠 LLM 理解 - 自然语言 → 任务依赖图
  2. 并发执行 - 拓扑排序 + asyncio 真正并行
  3. 🔗 数据流 - 自动传递依赖结果
  4. 🔄 自适应 - 失败重规划,确保成功率

适用于任何需要智能规划 + 高并发执行的场景!

代码

LangGraph Task Graph 任务规划Agent工作流系统

python 复制代码
"""
LangGraph Task Graph 模式 - 使用 @tool 装饰器 + Qwen
====================================================================
核心改进:
1. 使用 @tool 装饰器定义工具(符合 LangChain 标准)
2. Qwen 自动获取工具的 schema
3. 更好的类型提示和文档字符串
"""

import asyncio
import json
import os
from typing import List, Dict, TypedDict, Annotated, Literal, Optional
from datetime import datetime
import operator
import time

from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_core.tools import tool
from langgraph.graph import StateGraph, START, END

# ===================== 1. 状态定义 =====================

class TaskDefinition(TypedDict):
    """单个任务定义"""
    id: str
    tool: str
    description: str
    depends_on: List[str]
    args: Optional[Dict]

class TaskGraph(TypedDict):
    """LLM 生成的任务图"""
    tasks: List[TaskDefinition]
    edges: List[List[str]]
    reasoning: str

class TaskResult(TypedDict):
    """任务执行结果"""
    task_id: str
    tool: str
    status: Literal["success", "failed"]
    output: any
    error: Optional[str]
    execution_time: float

class PlanExecuteState(TypedDict):
    """Plan-and-Execute 工作流状态"""
    user_query: str
    task_graph: Optional[TaskGraph]
    planning_iterations: int
    task_results: Annotated[List[TaskResult], operator.add]
    completed_tasks: List[str]
    final_answer: str
    needs_replan: bool

# ===================== 2. 使用 @tool 装饰器定义工具 =====================

@tool
def weather(location: str) -> str:
    """获取指定城市的天气信息。
    
    Args:
        location: 城市名称,例如 "北京"、"上海"、"深圳"
        
    Returns:
        天气信息字符串,包含温度、天气状况和建议
    """
    # 模拟天气查询
    time.sleep(0.3)
    return f"天气:{location} 15-22°C,小雨,建议携带雨具。空气质量良好。"


@tool
def news(category: str = "科技") -> str:
    """获取指定类别的最新新闻头条。
    
    Args:
        category: 新闻类别,可选值:科技、财经、体育、娱乐、社会
        
    Returns:
        该类别的最新新闻摘要
    """
    time.sleep(0.3)
    news_data = {
        "科技": "AI大模型技术突破,阿里云推出Qwen-Max;量子计算取得新进展",
        "财经": "A股三大指数集体上涨,科创板领涨;美联储维持利率不变",
        "体育": "中国队在亚运会上再夺3金;NBA常规赛精彩对决",
        "娱乐": "春节档电影票房创新高;某明星新专辑发布",
        "社会": "全国多地气温回升;新政策惠及千万家庭"
    }
    return f"新闻:{category}板块 - {news_data.get(category, '暂无相关新闻')}"


@tool
def stock(symbol: str) -> str:
    """获取股票的实时行情信息。
    
    Args:
        symbol: 股票名称或代码,例如 "阿里巴巴"、"腾讯"、"沪深300"
        
    Returns:
        股票的当前价格和涨跌幅信息
    """
    time.sleep(0.3)
    import random
    price = round(random.uniform(50, 200), 2)
    change = round(random.uniform(-5, 5), 2)
    return f"股票:{symbol} 当前价格 ¥{price},日涨幅 {change:+.2f}%"


@tool
def search(query: str) -> str:
    """在互联网上搜索指定关键词的信息。
    
    Args:
        query: 搜索关键词或问题
        
    Returns:
        搜索结果摘要
    """
    time.sleep(0.3)
    return f"搜索结果:关于「{query}」找到以下信息:大模型技术持续突破,多模态AI应用广泛,Agent技术成为新热点..."


@tool
def calculator(expression: str) -> Dict[str, any]:
    """执行数学表达式计算。
    
    Args:
        expression: 数学表达式,例如 "2.5 + 3.8"、"100 * 0.15"
        
    Returns:
        包含表达式和计算结果的字典
    """
    time.sleep(0.1)
    try:
        result = eval(expression)
        return {
            "expression": expression,
            "result": result,
            "formatted": f"{expression} = {result}"
        }
    except Exception as e:
        return {
            "expression": expression,
            "error": str(e),
            "result": None
        }


@tool
def summarize(texts: List[str]) -> str:
    """汇总多条信息生成综合摘要。
    
    Args:
        texts: 要汇总的文本列表
        
    Returns:
        综合摘要文本
    """
    time.sleep(0.5)
    if not texts:
        return "无内容需要汇总"
    
    return f"综合摘要:基于 {len(texts)} 条信息分析,主要内容包括:{';'.join(texts[:3])}等。"


@tool
def translate(text: str, target_lang: str = "英文") -> str:
    """将文本翻译成目标语言。
    
    Args:
        text: 要翻译的文本
        target_lang: 目标语言,例如 "英文"、"日文"、"韩文"
        
    Returns:
        翻译后的文本
    """
    time.sleep(0.3)
    # 模拟翻译
    translations = {
        "英文": f"[EN] Translation of: {text}",
        "日文": f"[JP] {text}の翻訳",
        "韩文": f"[KR] {text} 번역"
    }
    return translations.get(target_lang, f"翻译结果:{text} → {target_lang}")


@tool
def compare(items: List[str]) -> str:
    """比较多个项目并生成对比分析。
    
    Args:
        items: 要比较的项目列表
        
    Returns:
        对比分析结果
    """
    time.sleep(0.4)
    return f"对比分析:在 {len(items)} 个项目中,各有优劣。第一项表现突出,第二项性价比较高..."

# ===================== 3. 工具注册表 =====================

# 收集所有定义的工具
ALL_TOOLS = [
    weather,
    news,
    stock,
    search,
    calculator,
    summarize,
    translate,
    compare
]

# 创建工具名称到工具对象的映射
TOOLS_MAP = {tool.name: tool for tool in ALL_TOOLS}

def get_tools_schema() -> str:
    """获取所有工具的 schema 描述(用于 LLM)"""
    tools_info = []
    for tool in ALL_TOOLS:
        # 获取工具的参数信息
        params = []
        if hasattr(tool, 'args_schema') and tool.args_schema:
            schema = tool.args_schema.schema()
            for param_name, param_info in schema.get('properties', {}).items():
                params.append({
                    "name": param_name,
                    "type": param_info.get('type', 'string'),
                    "description": param_info.get('description', ''),
                    "required": param_name in schema.get('required', [])
                })
        
        tools_info.append({
            "name": tool.name,
            "description": tool.description,
            "parameters": params
        })
    
    return json.dumps(tools_info, ensure_ascii=False, indent=2)

# ===================== 4. 初始化 Qwen 模型 =====================

def init_llm():
    """初始化阿里百炼 Qwen 模型"""
    api_key = os.getenv("BAILIAN_API_KEY", "")
    base_url = os.getenv("BAILIAN_BASE_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
    model_name = os.getenv("BAILIAN_MODEL_NAME", "qwen-max")
    
    llm = ChatOpenAI(
        api_key=api_key,
        base_url=base_url,
        model=model_name,
        temperature=0,
    )
    
    print(f"✅ LLM 初始化成功: {model_name}")
    print(f"✅ 已注册 {len(ALL_TOOLS)} 个工具: {', '.join([t.name for t in ALL_TOOLS])}")
    return llm

LLM = init_llm()

# ===================== 5. Planner 节点 (Qwen 生成任务图) =====================

def planner_node(state: PlanExecuteState) -> dict:
    """使用 Qwen 生成任务执行图"""
    print("\n" + "="*80)
    print("🧠 [PLANNER] 开始规划任务...")
    print("="*80)
    
    query = state["user_query"]
    print(f"📝 用户查询: {query}")
    
    # 获取工具 schema
    tools_schema = get_tools_schema()
    
    system_prompt = """你是一个智能任务规划助手。根据用户查询和可用工具,生成最优的任务执行计划。

**规划原则:**
1. 任务应该原子化(一个任务只做一件事)
2. 识别任务之间的依赖关系(哪些任务需要等待其他任务完成)
3. 尽可能并行执行无依赖的任务
4. 如果需要汇总多个结果,使用 summarize 工具
5. 工具参数必须严格匹配工具定义

**输出格式(严格 JSON):**
```json
{
  "reasoning": "你的逐步思考过程",
  "tasks": [
    {
      "id": "t1",
      "tool": "工具名称",
      "description": "任务描述",
      "depends_on": [],
      "args": {"参数名": "参数值"}
    }
  ],
  "edges": [
    ["t1", "t2"],
    ["t2", "join"]
  ]
}

重要:

  • 只输出 JSON,不要有任何额外文字

  • 工具名称必须完全匹配(区分大小写)

  • 参数名必须完全匹配工具定义"""

    user_prompt = f"""用户查询: {query}

可用工具(JSON Schema):

{tools_schema}

请生成任务执行计划(纯 JSON 格式):"""

复制代码
try:
    print("🤖 正在调用 Qwen 生成规划...")
    messages = [
        SystemMessage(content=system_prompt),
        HumanMessage(content=user_prompt)
    ]
    
    response = LLM.invoke(messages)
    response_text = response.content.strip()
    
    # 清理可能的 Markdown 代码块
    if response_text.startswith("```"):
        lines = response_text.split("\n")
        response_text = "\n".join(lines[1:-1])
        if response_text.startswith("json"):
            response_text = response_text[4:].strip()
    
    # 解析 JSON
    task_graph = json.loads(response_text)
    
    # 验证格式
    if not all(key in task_graph for key in ["reasoning", "tasks", "edges"]):
        raise ValueError("JSON 格式不完整")
    
    # 验证工具名称
    for task in task_graph["tasks"]:
        if task["tool"] not in TOOLS_MAP:
            print(f"⚠️  警告:未知工具 {task['tool']},将跳过")
    
    print(f"\n💡 Qwen 规划思考:\n{task_graph['reasoning']}")
    print(f"\n📊 生成任务图:")
    print(f"  - 任务数: {len(task_graph['tasks'])}")
    print(f"  - 依赖边: {len(task_graph['edges'])}")
    
    for task in task_graph['tasks']:
        deps = f" (依赖: {', '.join(task['depends_on'])})" if task.get('depends_on') else ""
        args_str = json.dumps(task.get('args', {}), ensure_ascii=False)
        print(f"  • {task['id']}: {task['tool']}({args_str}) - {task['description']}{deps}")
    
    return {
        "task_graph": task_graph,
        "planning_iterations": state.get("planning_iterations", 0) + 1
    }

except Exception as e:
    print(f"❌ Planner 错误: {e}")
    print(f"原始响应: {response_text if 'response_text' in locals() else 'N/A'}")
    
    # 降级到简单规划
    fallback_graph = TaskGraph(
        reasoning=f"Qwen 规划失败,使用默认方案: {str(e)}",
        tasks=[
            TaskDefinition(
                id="t1",
                tool="search",
                description=f"搜索: {query}",
                depends_on=[],
                args={"query": query}
            )
        ],
        edges=[["t1", "join"]]
    )
    
    return {
        "task_graph": fallback_graph,
        "planning_iterations": state.get("planning_iterations", 0) + 1
    }

===================== 6. Executor 节点 (执行任务图) =====================

async def executor_node(state: PlanExecuteState) -> dict:

"""执行任务图中的所有任务(使用 asyncio 并发执行)"""

print("\n" + "="*80)

print("⚙️ [EXECUTOR] 开始执行任务图(并发模式)...")

print("="*80)

复制代码
task_graph = state["task_graph"]
completed = set(state.get("completed_tasks", []))
results_dict = {}

# 构建依赖图
tasks_dict = {t["id"]: t for t in task_graph["tasks"]}

# 拓扑排序执行
execution_order = topological_sort(task_graph["tasks"])
print(f"\n📋 执行顺序: {' → '.join([str(level) for level in execution_order])}\n")

all_results = []

for level_idx, level in enumerate(execution_order):
    print(f"🔄 执行批次 {level_idx + 1}: {level}")
    
    # 同一层级可以并行执行 - 构建可执行任务列表
    executable_tasks = []
    task_ids_to_execute = []
    
    for task_id in level:
        if task_id in completed:
            print(f"  ⏭️  {task_id}: 已完成,跳过")
            continue
        
        task = tasks_dict[task_id]
        
        # 检查依赖是否满足
        deps_ready = all(dep in completed for dep in task.get("depends_on", []))
        if not deps_ready:
            print(f"  ⏸️  {task_id}: 依赖未满足,等待")
            continue
        
        executable_tasks.append(task)
        task_ids_to_execute.append(task_id)
    
    # 使用 asyncio.gather 并发执行同一层级的任务
    if executable_tasks:
        batch_results = await asyncio.gather(
            *[execute_task_async(task, results_dict) for task in executable_tasks]
        )
        
        # 更新结果和完成状态
        for task_id, result in zip(task_ids_to_execute, batch_results):
            results_dict[task_id] = result
            completed.add(task_id)
            
            status_icon = "✅" if result["status"] == "success" else "❌"
            output_preview = str(result.get('output', ''))[:60]
            print(f"  {status_icon} {task_id}: {output_preview}...")
        
        all_results.extend(batch_results)

return {
    "task_results": all_results,
    "completed_tasks": list(completed)
}

async def execute_task_async(task: TaskDefinition, previous_results: Dict) -> TaskResult:

"""异步执行单个任务(支持并发)"""

start_time = time.time()

复制代码
try:
    # 获取工具
    tool_name = task["tool"]
    if tool_name not in TOOLS_MAP:
        raise ValueError(f"未知工具: {tool_name}")
    
    tool_func = TOOLS_MAP[tool_name]
    
    # 准备参数
    args = task.get("args", {})
    
    # 特殊处理:summarize 需要收集依赖任务的输出
    if tool_name == "summarize" and task.get("depends_on"):
        texts = [
            str(previous_results[dep]["output"])
            for dep in task["depends_on"]
            if dep in previous_results and previous_results[dep]["status"] == "success"
        ]
        args["texts"] = texts
    
    # 特殊处理:compare 需要收集依赖任务的输出
    if tool_name == "compare" and task.get("depends_on"):
        items = [
            str(previous_results[dep]["output"])
            for dep in task["depends_on"]
            if dep in previous_results and previous_results[dep]["status"] == "success"
        ]
        args["items"] = items
    
    # 在线程池中执行工具调用(避免阻塞事件循环)
    # 使用 run_in_executor 将同步调用转为异步
    loop = asyncio.get_event_loop()
    output = await loop.run_in_executor(
        None,
        lambda: tool_func.invoke(args)
    )
    
    return TaskResult(
        task_id=task["id"],
        tool=tool_name,
        status="success",
        output=output,
        error=None,
        execution_time=time.time() - start_time
    )

except Exception as e:
    return TaskResult(
        task_id=task["id"],
        tool=task.get("tool", "unknown"),
        status="failed",
        output=None,
        error=str(e),
        execution_time=time.time() - start_time
    )

def execute_task_with_tool(task: TaskDefinition, previous_results: Dict) -> TaskResult:

"""同步执行单个任务(向后兼容)"""

start_time = time.time()

复制代码
try:
    # 获取工具
    tool_name = task["tool"]
    if tool_name not in TOOLS_MAP:
        raise ValueError(f"未知工具: {tool_name}")
    
    tool_func = TOOLS_MAP[tool_name]
    
    # 准备参数
    args = task.get("args", {})
    
    # 特殊处理:summarize 需要收集依赖任务的输出
    if tool_name == "summarize" and task.get("depends_on"):
        texts = [
            str(previous_results[dep]["output"])
            for dep in task["depends_on"]
            if dep in previous_results and previous_results[dep]["status"] == "success"
        ]
        args["texts"] = texts
    
    # 特殊处理:compare 需要收集依赖任务的输出
    if tool_name == "compare" and task.get("depends_on"):
        items = [
            str(previous_results[dep]["output"])
            for dep in task["depends_on"]
            if dep in previous_results and previous_results[dep]["status"] == "success"
        ]
        args["items"] = items
    
    # 调用工具(使用 invoke 方法)
    output = tool_func.invoke(args)
    
    return TaskResult(
        task_id=task["id"],
        tool=tool_name,
        status="success",
        output=output,
        error=None,
        execution_time=time.time() - start_time
    )

except Exception as e:
    return TaskResult(
        task_id=task["id"],
        tool=task.get("tool", "unknown"),
        status="failed",
        output=None,
        error=str(e),
        execution_time=time.time() - start_time
    )

def topological_sort(tasks: List[TaskDefinition]) -> List[List[str]]:

"""拓扑排序,返回执行层级"""

from collections import defaultdict, deque

复制代码
in_degree = defaultdict(int)
graph = defaultdict(list)

for task in tasks:
    task_id = task["id"]
    if task_id not in in_degree:
        in_degree[task_id] = 0
    
    for dep in task.get("depends_on", []):
        graph[dep].append(task_id)
        in_degree[task_id] += 1

queue = deque([tid for tid in in_degree if in_degree[tid] == 0])
levels = []

while queue:
    level = []
    for _ in range(len(queue)):
        node = queue.popleft()
        level.append(node)
        
        for neighbor in graph[node]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)
    
    levels.append(level)

return levels

===================== 7. Joiner 节点 (汇总结果) =====================

def joiner_node(state: PlanExecuteState) -> dict:

"""汇总所有任务结果"""

print("\n" + "="*80)

print("🔗 [JOINER] 汇总执行结果...")

print("="*80)

复制代码
results = state["task_results"]

failed = [r for r in results if r["status"] == "failed"]
success = [r for r in results if r["status"] == "success"]

if failed:
    print(f"\n⚠️  发现 {len(failed)} 个失败任务:")
    for r in failed:
        print(f"  • {r['task_id']} ({r['tool']}): {r['error']}")

# 获取最终任务的输出
task_graph = state["task_graph"]
all_task_ids = {t["id"] for t in task_graph["tasks"]}
has_dependents = set()
for edge in task_graph.get("edges", []):
    if len(edge) > 0:
        has_dependents.add(edge[0])

final_task_ids = all_task_ids - has_dependents
if not final_task_ids:
    final_task_ids = {r["task_id"] for r in success}

final_outputs = [
    r["output"] for r in success
    if r["task_id"] in final_task_ids
]

# 生成最终答案
if final_outputs:
    final_answer = "\n\n".join([
        f"📌 针对查询「{state['user_query']}」的执行结果:\n",
        *[f"  {i+1}. {output}" for i, output in enumerate(final_outputs)],
        f"\n✅ 共执行 {len(results)} 个任务,成功 {len(success)} 个,失败 {len(failed)} 个"
    ])
else:
    final_answer = f"❌ 任务执行失败,无有效结果。共 {len(failed)} 个任务失败。"

needs_replan = (
    len(failed) > 0 and 
    state["planning_iterations"] < 3 and
    len(failed) / len(results) > 0.5
)

if needs_replan:
    print("\n🔄 检测到大量失败任务,将重新规划...")

return {
    "final_answer": final_answer,
    "needs_replan": needs_replan
}

===================== 8. 条件路由 =====================

def should_replan(state: PlanExecuteState) -> Literal["replan", "end"]:

"""决定是否需要重新规划"""

return "replan" if state.get("needs_replan", False) else "end"

===================== 9. 构建工作流 =====================

def build_task_graph_workflow():

"""构建 Plan-and-Execute 工作流"""

graph = StateGraph(PlanExecuteState)

复制代码
graph.add_node("planner", planner_node)
graph.add_node("executor", executor_node)
graph.add_node("joiner", joiner_node)

graph.add_edge(START, "planner")
graph.add_edge("planner", "executor")
graph.add_edge("executor", "joiner")

graph.add_conditional_edges(
    "joiner",
    should_replan,
    {"replan": "planner", "end": END}
)

return graph.compile()

===================== 10. 测试示例 =====================

async def run_examples():

"""运行测试示例"""

workflow = build_task_graph_workflow()

复制代码
test_queries = [
    "北京今天天气怎么样?有什么科技新闻?",
    "查询腾讯和阿里的股票,然后比较它们的表现",
    "搜索人工智能的最新进展,并翻译成英文",
    "帮我查询上海的天气、最新科技新闻和股市行情,然后做个总结"
]

for i, query in enumerate(test_queries, 1):
    print("\n" + "🌟" * 40)
    print(f"测试查询 {i}/{len(test_queries)}")
    print("🌟" * 40)
    
    initial_state = PlanExecuteState(
        user_query=query,
        task_graph=None,
        planning_iterations=0,
        task_results=[],
        completed_tasks=[],
        final_answer="",
        needs_replan=False
    )
    
    start_time = time.time()
    final_state = await workflow.ainvoke(initial_state)
    elapsed = time.time() - start_time
    
    print("\n" + "="*80)
    print("📊 最终结果")
    print("="*80)
    print(final_state["final_answer"])
    print(f"\n📈 规划迭代次数: {final_state['planning_iterations']}")
    print(f"⏱️  总耗时: {elapsed:.2f}秒")
    print("\n" + "="*80)

===================== 11. 主程序 =====================

if name == "main ":

print("""

╔════════════════════════════════════════════════════════════════════════════╗

║ LangGraph Task Graph 模式 - 使用 @tool 装饰器 + Qwen-Max ║

╚════════════════════════════════════════════════════════════════════════════╝

🎯 核心特性:

✅ 使用 @tool 装饰器定义工具(符合 LangChain 标准)

✅ 自动生成工具 Schema(类型提示 + 文档字符串)

✅ Qwen 动态生成任务执行图

✅ 拓扑排序 + 并发执行

🔧 已注册工具:

• weather - 获取天气信息

• news - 获取最新新闻

• stock - 查询股票行情

• search - 互联网搜索

• calculator - 数学计算

• summarize - 汇总信息

• translate - 文本翻译

• compare - 对比分析

""")

复制代码
asyncio.run(run_examples())
复制代码
相关推荐
冬奇Lab9 小时前
一天一个开源项目(第31篇):awesome-openclaw-usecases - OpenClaw 真实用例集合
人工智能·开源·agent
jiangtao11 小时前
软件研发奇点时刻:从“AI 辅助对话”转向“AI 自动执行”。
人工智能·agent·openclaw
jiangtao12 小时前
多步骤 Agent + 工具调用:测评标准与数据集全景(2025-2026)
agent·测评
duration~13 小时前
Skills
ai·agent
Aric_Jones14 小时前
LLM、Agent、MCP、Skill 是什么?它们之间有什么关系?
ai·llm·agent·mcp·sikll
组合缺一14 小时前
赋予 AI 灵魂:如何在 Java AI 生态实现一个会“自我反思”的长期记忆系统
java·人工智能·ai·llm·agent·solon·mcp
Java_慈祥17 小时前
My First AI智能体!!!
python·agent·coze
XLYcmy17 小时前
chatgpt数据库检索文献 下
ai·chatgpt·llm·prompt·agent·检索·多轮对话
JaydenAI18 小时前
[拆解LangChain执行引擎]梳理Agent的执行流程
ai·langchain·agent·pregel