不使用智能体开发框架(如 LangChain, AutoGen, CrewAI 等),直接调用工具是完全可行的,并且在很多场景下是更直接、更可控的选择。
这种方法的核心是:你将承担智能体框架原本为你处理的"大脑"工作 ------也就是任务规划、工具选择、参数提取、结果解析和流程控制。你需要手动编写代码来实现这些逻辑。
下面我们通过一个清晰的框架和具体示例来说明如何实现。
核心思想:自己动手构建一个"微型智能体"
你可以把一个智能体想象成一个由以下几个部分组成的循环:
-
思考 (Think/Plan):理解用户意图,决定下一步该做什么。
-
选择工具 (Select Tool):根据思考的结果,选择一个合适的工具。
-
准备参数 (Prepare Arguments):为选定的工具准备好必需的输入参数。
-
执行工具 (Execute Tool):调用工具并获取结果。
-
观察结果 (Observe Result):分析工具返回的结果。
-
决定下一步 (Decide Next Step):根据结果决定是继续调用下一个工具,还是直接给出最终答案,或者重新规划。
不使用框架,就意味着你需要用代码手动实现这个循环。
方法一:简单的顺序调用(适用于线性任务)
这是最简单的形式,任务步骤是固定的,不需要复杂的判断和循环。
场景:用户想知道"北京今天的天气,并根据天气建议穿什么衣服"。
步骤:
-
调用天气API工具获取天气。
-
根据返回的天气数据(如"晴,25°C"),在代码里写一个
if-else逻辑来判断。 -
输出穿衣建议。
Python 示例:
import requests
# 1. 定义工具函数 (Tool Functions)
def get_weather(city: str):
"""一个模拟的天气查询工具"""
# 实际场景中这里会是调用天气API
weather_data = {
"北京": {"condition": "晴", "temperature": 25},
"上海": {"condition": "雨", "temperature": 18}
}
return weather_data.get(city, {"condition": "未知", "temperature": 0})
def generate_clothing_advice(condition, temperature):
"""一个根据天气生成建议的函数"""
if condition == "晴" and temperature > 20:
return f"天气晴朗,{temperature}°C,建议穿短袖T恤和薄外套。"
elif condition == "雨":
return f"今天有雨,{temperature}°C,记得带伞,穿防水的鞋子。"
else:
return "天气状况复杂,请注意保暖。"
# 2. 主逻辑(手动编排)
def main():
user_query = "北京今天天气怎么样?该穿什么?"
# 步骤1:提取关键信息(手动解析)
city = "北京" # 这里我们直接从问题中硬编码或简单解析出城市
# 步骤2:调用第一个工具
weather_info = get_weather(city)
print(f"查询到{city}的天气:{weather_info}")
# 步骤3:处理结果并调用第二个工具
advice = generate_clothing_advice(weather_info["condition"], weather_info["temperature"])
# 步骤4:给出最终答案
print(f"回答:{advice}")
if __name__ == "__main__":
main()
方法二:基于大模型的动态调用(更接近智能体)
这种方法利用大模型的理解、规划和代码生成能力,但由你来控制整个流程。这是"不用框架"但又想实现智能体核心能力的常用方法。
核心流程:
-
提示大模型:将用户问题和可用工具的列表(包括名称、描述和参数)一起发给大模型。
-
模型决策 :大模型分析后,决定是否需要调用工具。如果需要,它会生成一个结构化的输出(如 JSON 或一个函数调用对象),指明要调用的工具和参数。
-
解析与执行 :你的代码解析模型的输出,提取出工具名和参数,然后在你的程序中真正执行对应的工具函数。
-
循环与终止:将工具执行结果返回给大模型,让它判断是否需要继续调用下一个工具,或者根据所有收集到的信息生成最终的自然语言回复。
场景:用户问"苹果公司的股价和CEO是谁?"(需要调用两个独立的工具)
Python 示例:
import requests
import json
# 1. 定义工具函数
def get_stock_price(ticker: str):
"""获取股票价格的工具"""
# 模拟API调用
prices = {"AAPL": 190.50, "GOOGL": 175.20}
return {"ticker": ticker, "price": prices.get(ticker, "未找到")}
def get_company_ceo(company_name: str):
"""获取公司CEO的工具"""
ceos = {"苹果": "蒂姆·库克", "谷歌": "桑达尔·皮查伊"}
return {"company": company_name, "ceo": ceos.get(company_name, "未知")}
# 2. 定义可用工具的元数据(用于告诉大模型)
tools_metadata = [
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "获取上市公司的股票价格",
"parameters": {
"type": "object",
"properties": {
"ticker": {"type": "string", "description": "公司的股票代码,例如 'AAPL'"}
},
"required": ["ticker"]
}
}
},
{
"type": "function",
"function": {
"name": "get_company_ceo",
"description": "获取一家公司的首席执行官(CEO)",
"parameters": {
"type": "object",
"properties": {
"company_name": {"type": "string", "description": "公司的中文全称,例如 '苹果'"}
},
"required": ["company_name"]
}
}
}
]
# 3. 主逻辑(手动实现 ReAct 循环)
def run_agent(user_query):
# 初始消息
messages = [{"role": "user", "content": user_query}]
# 为了简化,我们假设模型一次就能规划好所有步骤
# 更复杂的实现会是一个 while 循环,直到模型决定不再调用工具
# a. 将工具定义和用户问题发送给支持 function calling 的模型 (如 GPT-4, Qwen, etc.)
# 这里用伪代码表示模型调用
# model_response = call_llm_api(messages=messages, tools=tools_metadata, tool_choice="auto")
# b. 【模拟模型响应】模型分析后,决定调用两个工具
# 在实际中,这一步是由大模型生成的。
simulated_tool_calls = [
{
"id": "call_1",
"function": {"name": "get_stock_price", "arguments": json.dumps({"ticker": "AAPL"})}
},
{
"id": "call_2",
"function": {"name": "get_company_ceo", "arguments": json.dumps({"company_name": "苹果"})}
}
]
print("模型决定调用以下工具:")
for tool_call in simulated_tool_calls:
print(f"- {tool_call['function']['name']}: {tool_call['function']['arguments']}")
# c. 在我们的代码中执行这些工具调用
tool_results = []
for tool_call in simulated_tool_calls:
func_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
# 根据函数名,在我们的字典中找到并执行对应的函数
if func_name == "get_stock_price":
result = get_stock_price(**arguments)
elif func_name == "get_company_ceo":
result = get_company_ceo(**arguments)
else:
result = {"error": "Function not found"}
tool_results.append({"tool_call_id": tool_call["id"], "result": result})
print("\n工具执行结果:")
for res in tool_results:
print(res)
# d. 将工具结果返回给模型,让它生成最终的自然语言回复
# messages.append({"role": "assistant", "tool_calls": simulated_tool_calls}) # 添加助手的调用指令
# for tr in tool_results: # 添加工具结果
# messages.append({"role": "tool", "tool_call_id": tr["tool_call_id"], "content": json.dumps(tr["result"])})
# final_response = call_llm_api(messages=messages)
# print("\n最终回复:", final_response.choices[0].message.content)
# 【模拟最终回复】
print("\n最终回复: 苹果公司(AAPL)的股价是190.50美元,其CEO是蒂姆·库克。")
# 运行
run_agent("苹果公司的股价和CEO是谁?")
如何选择与总结
| 特性 | 简单顺序调用 | 基于大模型的动态调用 |
|---|---|---|
| 优点 | 简单直观,逻辑清晰,易于调试 | 灵活,能处理未知和复杂任务,接近智能体能力 |
| 缺点 | 僵化,只能处理预定义的线性流程 | 实现复杂,需要理解模型 function calling 机制,有成本 |
| 适用场景 | 自动化脚本、固定流程的ETL、简单的API串联 | 问答系统、个人助理、需要多步推理的复杂任务 |
结论:
-
如果你的任务是线性的、固定的 ,直接手动顺序调用函数是最清晰、最高效的方式。
-
如果你的任务需要根据用户输入动态决定步骤 ,那么利用大模型的 Function Calling 能力,并手动实现调用循环,是一条强大且灵活的"裸金属"智能体开发之路。它让你既能享受到大模型的能力,又能保持对流程和逻辑的完全掌控。