实现一个原生版本的 LangGraph 的 `create_agent` 功能,使用 Python 和通用的 LLM MaaS API

157 阅读9分钟

在这里插入图片描述

LangChain的create_agent函数基于LangGraph构建了图形化的智能体运行时。在这个架构中,智能体被构建为一个由节点(步骤)和边(连接)组成的图,定义了信息处理的流程。

那么,背后的实现原理和执行过程究竟是怎样的呢?

这里,我将为您实现一个原生版本的 create_agent 功能,使用 Python 和通用的 LLM MaaS API。这个实现将模拟 LangChain 的代理功能,但使用原生 API 调用。

import json
import requests
from typing import List, Dict, Any, Union, Callable, Optional
from dataclasses import dataclass
import time

@dataclass
class Tool:
    """表示一个可用的工具"""
    name: str
    description: str
    func: Callable
    args_schema: Optional[Dict] = None

class LLMModel:
    """LLM 模型的抽象类"""
    def __init__(self, model_name: str, **kwargs):
        self.model_name = model_name
        self.kwargs = kwargs
    
    def invoke(self, messages: List[Dict[str, str]]) -> str:
        """调用模型并返回响应"""
        raise NotImplementedError

class OpenAIModel(LLMModel):
    """OpenAI 模型的实现"""
    def __init__(self, model: str, api_key: str, temperature: float = 0.7, 
                 max_tokens: int = 1000, timeout: int = 30, **kwargs):
        super().__init__(model, **kwargs)
        self.api_key = api_key
        self.temperature = temperature
        self.max_tokens = max_tokens
        self.timeout = timeout
        self.base_url = kwargs.get("base_url", "https://api.openai.com/v1")
    
    def invoke(self, messages: List[Dict[str, str]]) -> str:
        headers = {
            "Authorization": f"Bearer {self.api_key}",
            "Content-Type": "application/json"
        }
        
        data = {
            "model": self.model_name,
            "messages": messages,
            "temperature": self.temperature,
            "max_tokens": self.max_tokens
        }
        
        try:
            response = requests.post(
                f"{self.base_url}/chat/completions",
                headers=headers,
                json=data,
                timeout=self.timeout
            )
            response.raise_for_status()
            return response.json()["choices"][0]["message"]["content"]
        except Exception as e:
            raise Exception(f"Error calling OpenAI API: {str(e)}")

class Agent:
    """智能代理类"""
    def __init__(self, model: LLMModel, tools: List[Tool]):
        self.model = model
        self.tools = {tool.name: tool for tool in tools}
        self.system_prompt = self._build_system_prompt()
    
    def _build_system_prompt(self) -> str:
        """构建系统提示,包含工具信息"""
        tools_info = "\n".join([
            f"- {tool.name}: {tool.description}" 
            for tool in self.tools.values()
        ])
        
        return f"""你是一个智能助手,可以使用以下工具来帮助用户回答问题:

{tools_info}

当需要使用工具时,请使用以下JSON格式:

{
  "tool": "工具名称",
  "args": {
    "参数名": "参数值"
  }
}


如果不需要使用工具,请直接回答问题。"""




   
    def _parse_tool_call(self, response: str) -> Optional[Dict[str, Any]]:
        """解析模型响应中的工具调用"""
        try:
            # 尝试提取JSON格式的工具调用
            start_idx = response.find("{")
            end_idx = response.rfind("}") + 1
            
            if start_idx != -1 and end_idx != 0:
                json_str = response[start_idx:end_idx]
                return json.loads(json_str)
        except json.JSONDecodeError:
            pass
        
        return None
    
    def _execute_tool(self, tool_name: str, args: Dict[str, Any]) -> str:
        """执行指定的工具"""
        if tool_name not in self.tools:
            return f"错误: 未找到工具 '{tool_name}'"
        
        tool = self.tools[tool_name]
        try:
            result = tool.func(**args)
            return str(result)
        except Exception as e:
            return f"执行工具 '{tool_name}' 时出错: {str(e)}"
    
    def run(self, query: str) -> str:
        """运行代理,处理用户查询"""
        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": query}
        ]
        
        max_iterations = 5  # 防止无限循环
        iteration = 0
        
        while iteration < max_iterations:
            iteration += 1
            
            # 调用模型
            response = self.model.invoke(messages)
            
            # 检查是否需要调用工具
            tool_call = self._parse_tool_call(response)
            
            if tool_call and "tool" in tool_call:
                tool_name = tool_call["tool"]
                tool_args = tool_call.get("args", {})
                
                # 执行工具
                tool_result = self._execute_tool(tool_name, tool_args)
                
                # 将工具调用和结果添加到对话历史
                messages.append({"role": "assistant", "content": response})
                messages.append({
                    "role": "system", 
                    "content": f"工具 '{tool_name}' 的执行结果: {tool_result}"
                })
                
                # 继续对话,让模型基于工具结果回答
                continue
            else:
                # 模型直接回答,不需要工具
                return response
        
        return "抱歉,处理您的请求时遇到了问题。"

def create_agent(model: Union[str, LLMModel], tools: List[Tool], **kwargs) -> Agent:
    """
    创建一个智能代理
    
    参数:
        model: 模型标识符字符串或模型实例
        tools: 工具列表
        **kwargs: 额外的模型参数
    
    返回:
        Agent 实例
    """
    # 如果 model 是字符串,创建对应的模型实例
    if isinstance(model, str):
        # 解析模型标识符,格式如 "provider:model"
        if ":" in model:
            provider, model_name = model.split(":", 1)
        else:
            provider = "openai"
            model_name = model
        
        # 根据提供商创建模型实例
        if provider.lower() == "openai":
            api_key = kwargs.get("api_key")
            if not api_key:
                raise ValueError("OpenAI 模型需要提供 api_key")
            
            model = OpenAIModel(
                model=model_name,
                api_key=api_key,
                temperature=kwargs.get("temperature", 0.7),
                max_tokens=kwargs.get("max_tokens", 1000),
                timeout=kwargs.get("timeout", 30)
            )
        else:
            raise ValueError(f"不支持的模型提供商: {provider}")
    
    # 创建并返回代理
    return Agent(model, tools)

# 示例使用
if __name__ == "__main__":
    # 定义一些示例工具
    def calculator(expression: str) -> float:
        """计算数学表达式"""
        return eval(expression)
    
    def get_weather(city: str) -> str:
        """获取指定城市的天气信息"""
        # 这里只是模拟,实际应用中会调用天气API
        return f"{city}今天晴朗,温度25°C"
    
    def search_web(query: str) -> str:
        """在网络上搜索信息"""
        # 这里只是模拟,实际应用中会调用搜索API
        return f"关于'{query}'的搜索结果:这是一个模拟的搜索结果。"
    
    # 创建工具列表
    tools = [
        Tool(
            name="calculator",
            description="用于计算数学表达式",
            func=calculator,
            args_schema={"expression": "string"}
        ),
        Tool(
            name="get_weather",
            description="获取指定城市的天气信息",
            func=get_weather,
            args_schema={"city": "string"}
        ),
        Tool(
            name="search_web",
            description="在网络上搜索信息",
            func=search_web,
            args_schema={"query": "string"}
        )
    ]
    
    # 使用模型标识符字符串创建代理
    # 注意:这里需要替换为实际的API密钥
    api_key = "your-api-key-here"
    
    # 示例1:使用模型标识符字符串
    agent1 = create_agent(
        "openai:gpt-3.5-turbo",
        tools=tools,
        api_key=api_key,
        temperature=0.1,
        max_tokens=1000,
        timeout=30
    )
    
    # 示例2:使用模型实例
    model = OpenAIModel(
        model="gpt-3.5-turbo",
        api_key=api_key,
        temperature=0.1,
        max_tokens=1000,
        timeout=30
    )
    agent2 = create_agent(model, tools=tools)
    
    # 测试代理
    print("测试代理1:")
    response1 = agent1.run("计算 123 * 456 的结果")
    print(response1)
    
    print("\n测试代理2:")
    response2 = agent2.run("北京今天的天气怎么样?")
    print(response2)

这个实现包含以下关键组件:

  1. Tool 类:表示可用的工具,包含名称、描述和执行函数
  2. LLMModel 抽象类:定义了模型的接口
  3. OpenAIModel 类:实现了 OpenAI API 的调用
  4. Agent 类:核心代理逻辑,能够解析用户请求并选择合适的工具
  5. create_agent 函数:根据输入创建代理实例,支持字符串标识符或模型实例

这个实现模拟了 LangChain 的代理功能,但使用原生 API 调用。您可以根据需要扩展它,例如添加更多模型提供商的支持或增强工具调用逻辑。


要实现类似LangChain中create_agent的功能,我们需要原生封装LLM调用、工具管理和决策逻辑。以下是基于MaaS API(以OpenAI风格为例)的实现方案:

核心思路

Agent的核心功能是:接收用户输入 → 决定是否调用工具 → 执行工具(如需)→ 整理结果生成回答。实现需包含:

  1. 模型调用封装(对接MaaS API)
  2. 工具管理(定义工具结构与调用逻辑)
  3. 决策引导(通过提示词让模型判断是否使用工具)
  4. 流程控制(循环处理工具调用与结果整合)

完整代码实现

import json
import requests
from typing import List, Dict, Callable, Any


# 1. 模型调用封装(对接MaaS API)
class LLMChatModel:
    """封装LLM的MaaS API调用,支持模型参数配置"""
    def __init__(
        self,
        model: str,
        api_key: str,
        base_url: str = "https://api.openai.com/v1/chat/completions",  # 可替换为其他MaaS接口
        temperature: float = 0.1,
        max_tokens: int = 1000,
        timeout: int = 30
    ):
        self.model = model
        self.api_key = api_key
        self.base_url = base_url
        self.temperature = temperature
        self.max_tokens = max_tokens
        self.timeout = timeout

    def generate(self, messages: List[Dict[str, str]]) -> str:
        """调用MaaS API生成响应"""
        headers = {
            "Content-Type": "application/json",
            "Authorization": f"Bearer {self.api_key}"
        }
        payload = {
            "model": self.model,
            "messages": messages,
            "temperature": self.temperature,
            "max_tokens": self.max_tokens
        }

        try:
            response = requests.post(
                self.base_url,
                headers=headers,
                json=payload,
                timeout=self.timeout
            )
            response.raise_for_status()  # 抛出HTTP错误
            return response.json()["choices"][0]["message"]["content"]
        except Exception as e:
            raise RuntimeError(f"LLM调用失败: {str(e)}")


# 2. Agent核心逻辑(工具调用与决策)
class Agent:
    """原生实现的Agent,支持工具调用与结果处理"""
    def __init__(self, model: LLMChatModel, tools: List[Dict[str, Any]]):
        self.model = model
        self.tools = tools
        self.tool_map = {tool["name"]: tool for tool in tools}  # 工具名→工具的映射
        self.system_prompt = self._build_system_prompt()  # 构建引导模型的系统提示

    def _build_system_prompt(self) -> str:
        """生成引导模型使用工具的系统提示词"""
        tool_descriptions = "\n".join([
            f"- {tool['name']}: {tool['description']}(参数: {list(tool['parameters'].keys())})"
            for tool in self.tools
        ])
        
        return f"""你是一个可以使用工具的智能助手。任务:根据用户问题,判断是否需要调用工具,并生成最终回答。

可用工具:
{tool_descriptions}

工具调用规则:
1. 若需要调用工具,必须输出JSON格式:{{"action": {{"name": "工具名", "parameters": {{参数键值对}}}}}}
2. 若无需调用工具,直接输出自然语言回答。
3. 工具返回结果后,需基于结果整理成最终回答。"""

    def _call_tool(self, tool_name: str, parameters: Dict[str, Any]) -> Any:
        """调用指定工具并返回结果"""
        if tool_name not in self.tool_map:
            raise ValueError(f"工具不存在: {tool_name}")
        tool = self.tool_map[tool_name]
        try:
            return tool["func"](**parameters)
        except Exception as e:
            return f"工具调用错误: {str(e)}"

    def run(self, user_query: str) -> str:
        """处理用户查询,执行Agent流程"""
        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": user_query}
        ]

        while True:
            # 调用LLM获取响应
            llm_response = self.model.generate(messages)
            
            # 尝试解析是否为工具调用
            try:
                action = json.loads(llm_response)
                if "action" in action:
                    tool_name = action["action"]["name"]
                    params = action["action"]["parameters"]
                    
                    # 调用工具并获取结果
                    tool_result = self._call_tool(tool_name, params)
                    
                    # 将工具结果加入对话上下文,继续循环
                    messages.append({"role": "assistant", "content": llm_response})
                    messages.append({"role": "system", "content": f"工具返回: {str(tool_result)}"})
                    continue
            except (json.JSONDecodeError, KeyError):
                # 非工具调用格式,直接返回结果
                return llm_response


# 3. 创建Agent的工厂函数(类似LangChain的create_agent)
def create_agent(
    model_input: str | LLMChatModel,
    tools: List[Dict[str, Any]],
    **kwargs  # 传递模型参数(如api_key、temperature等)
) -> Agent:
    """
    创建Agent实例
    
    参数:
        model_input: 模型标识符(如"gpt-5")或LLMChatModel实例
        tools: 工具列表,每个工具格式: 
            {
                "name": 工具名,
                "description": 工具描述(帮助模型判断何时使用),
                "parameters": 参数描述(键为参数名,值为说明),
                "func": 工具调用函数(接收parameters中定义的参数)
            }
        **kwargs: 模型参数(当model_input为字符串时)
    """
    if isinstance(model_input, str):
        # 从字符串创建模型实例(默认使用OpenAI风格API)
        model = LLMChatModel(model=model_input,** kwargs)
    elif isinstance(model_input, LLMChatModel):
        # 直接使用已创建的模型实例
        model = model_input
    else:
        raise TypeError("model_input必须是字符串或LLMChatModel实例")
    
    return Agent(model, tools)


# ------------------------------
# 示例使用
# ------------------------------
if __name__ == "__main__":
    # 示例工具1:获取当前时间
    def get_current_time() -> str:
        import datetime
        return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    # 示例工具2:计算加法
    def add(a: float, b: float) -> float:
        return a + b

    # 定义工具列表
    tools = [
        {
            "name": "get_current_time",
            "description": "获取当前的日期和时间",
            "parameters": {},  # 无参数
            "func": get_current_time
        },
        {
            "name": "add",
            "description": "计算两个数字的和",
            "parameters": {"a": "第一个数字", "b": "第二个数字"},
            "func": add
        }
    ]

    # 方式1:使用模型标识符创建Agent(需提供api_key)
    # agent = create_agent(
    #     model_input="gpt-5",
    #     tools=tools,
    #     api_key="你的MaaS API密钥",
    #     temperature=0.1
    # )

    # 方式2:使用模型实例创建Agent(更灵活)
    model = LLMChatModel(
        model="gpt-5",
        api_key="你的MaaS API密钥",
        temperature=0.1,
        max_tokens=1000
    )
    agent = create_agent(model, tools)

    # 测试Agent
    print(agent.run("现在是什么时间?"))  # 应调用get_current_time
    print(agent.run("100加200等于多少?"))  # 应调用add
    print(agent.run("介绍一下你自己"))  # 无需调用工具,直接回答

关键说明

  1. 模型封装LLMChatModel类对接MaaS API,支持配置温度、最大 tokens 等参数,兼容OpenAI风格接口(可替换为其他平台API)。
  2. 工具定义:每个工具需包含名称、描述(帮助模型判断是否调用)、参数说明和实际调用函数,便于模型理解和使用。
  3. 决策逻辑:通过系统提示词严格规定工具调用格式(JSON),Agent循环处理:调用模型→解析是否需工具→执行工具→整合结果,直到生成最终回答。
  4. 灵活性create_agent函数支持通过模型标识符快速创建,或通过模型实例实现更精细的配置(如自定义API地址)。

使用时需替换api_key为实际的MaaS平台密钥,并根据目标平台调整base_url(如Azure OpenAI、Anthropic等的API地址)。