📋 本文目录
一、前言
1.1 本节内容简介
我们已经有了5个好用的工具,但问题来了:用户怎么知道该调用哪个工具?
本文将对比两种使用工具的方式:
-
方式1:写死的脚本,按固定流程调用所有工具
-
方式2:用Agent,让它根据问题自动选择合适的工具
看完你会明白,为什么Agent能让工具的价值翻倍!
1.2 为什么要学习本节内容?
-
理解差异:明白脚本和Agent各自适合什么场景
-
智能选择:让Agent根据用户需求自动选择工具
-
实战完整:从工具开发到智能应用的完整闭环
二、两种方式对比
在开始写代码之前,先让我们看看这两种方式的区别。
2.1 方式1:脚本调用(固定流程)
就像我们的 tool_chain_demo.py 那样:
用户说:"帮我分析一下"
↓
脚本说:"好的,我按顺序来一遍"
↓
[工具1] → [工具2] → [工具3] → [工具4] → [工具5]
↓
输出:5个文件
特点:
-
✅ 流程固定, predictable
-
✅ 简单直接,不容易出错
-
❌ 不管用户需要什么,都跑完所有工具
-
❌ 浪费资源,效率不高
-
❌ 用户需要自己知道该用什么工具
适用场景:
-
固定的批量处理任务
-
每天例行的报表生成
-
标准化的数据处理流程
2.2 方式2:Agent智能调用
就像我们即将看到的那样:
用户说:"帮我看看有多少错误"
↓
Agent思考:"这个问题需要分析错误,用工具3"
↓
[工具3]
↓
输出:错误统计结果
用户说:"生成一份完整报告"
↓
Agent思考:"这个需要先结构化,再生成报告"
↓
[工具1] → [工具5]
↓
输出:完整报告
特点:
-
✅ 按需调用,不浪费
-
✅ 自然语言交互,用户友好
-
✅ 可以处理复杂、模糊的问题
-
⚠️ 需要大模型,有一定成本
-
⚠️ 可能选择错误的工具(需要调试)
适用场景:
-
用户自助查询系统
-
智能助手、客服机器人
-
需要灵活应对各种问题的场景
2.3 对比总结表
| 维度 | 脚本调用 | Agent调用 |
|---|---|---|
| 灵活性 | 低(固定流程) | 高(按需选择) |
| 易用性 | 需要技术知识 | 自然语言即可 |
| 资源使用 | 每次都跑完所有工具 | 只用需要的工具 |
| 开发成本 | 低 | 中(需要大模型) |
| 适用场景 | 固定任务 | 多变的用户需求 |
三、工具链完整演示
3.1 工具链流程图
原始日志
↓
[工具1] parse_log → 结构化JSON
↓
├─→ [工具3] analyze_errors → 错误统计
├─→ [工具5] generate_report → 完整报告
├─→ [工具2] convert_timestamps → 人类可读格式
└─→ [工具4] summarize_communication → 通信汇总
3.2 完整工具链代码
"""
工具链完整演示脚本
展示5个工具如何联动工作
"""
import sys
import os
# 添加当前目录到路径,方便导入工具
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from tool_1_parse_log import parse_log
from tool_2_convert_timestamps import convert_timestamps
from tool_3_analyze_errors import analyze_errors
from tool_4_summarize_communication import summarize_communication
from tool_5_generate_report import generate_report
def print_section(title):
"""打印分节标题"""
print("\n" + "="*80)
print(f" {title}")
print("="*80)
def main():
print_section("LangChain 工具链完整演示")
print("展示5个工具如何联动分析嵌入式设备日志")
# 步骤1: 读取原始日志文件
print_section("步骤 1: 读取原始日志")
log_file = "sample_device.log"
print(f"读取文件: {log_file}")
with open(log_file, 'r', encoding='utf-8') as f:
raw_log = f.read()
print(f"日志行数: {len(raw_log.splitlines())}")
# 步骤2: 使用工具1 - 结构化解析
print_section("步骤 2: 工具1 - 日志结构化解析")
print("调用 parse_log()...")
parsed_json = parse_log.invoke(raw_log)
parsed_file = "output_parsed.json"
with open(parsed_file, 'w', encoding='utf-8') as f:
f.write(parsed_json)
print(f"结果已保存到: {parsed_file}")
print(f"结果预览:\n{parsed_json[:300]}...")
# 步骤3: 使用工具2 - 时间戳转换(给人看的格式)
print_section("步骤 3: 工具2 - 时间戳格式转换")
print("调用 convert_timestamps()...")
readable_log = convert_timestamps.invoke(raw_log)
readable_file = "output_readable.log"
with open(readable_file, 'w', encoding='utf-8') as f:
f.write(readable_log)
print(f"结果已保存到: {readable_file}")
print(f"结果预览:\n{readable_log[:300]}...")
# 步骤4: 使用工具3 - 错误统计(使用工具1的输出)
print_section("步骤 4: 工具3 - 错误统计分析")
print("调用 analyze_errors() (使用工具1的输出)...")
error_report = analyze_errors.invoke(parsed_json)
error_file = "output_errors.txt"
with open(error_file, 'w', encoding='utf-8') as f:
f.write(error_report)
print(f"结果已保存到: {error_file}")
print(f"结果:\n{error_report}")
# 步骤5: 使用工具4 - 通信汇总
print_section("步骤 5: 工具4 - 通信记录汇总")
print("调用 summarize_communication()...")
comm_report = summarize_communication.invoke(raw_log)
comm_file = "output_communication.txt"
with open(comm_file, 'w', encoding='utf-8') as f:
f.write(comm_report)
print(f"结果已保存到: {comm_file}")
print(f"结果:\n{comm_report}")
# 步骤6: 使用工具5 - 生成完整报告(使用工具1的输出)
print_section("步骤 6: 工具5 - 生成完整分析报告")
print("调用 generate_report() (使用工具1的输出)...")
final_report = generate_report.invoke(parsed_json)
report_file = "output_final_report.md"
with open(report_file, 'w', encoding='utf-8') as f:
f.write(final_report)
print(f"结果已保存到: {report_file}")
print(f"结果:\n{final_report}")
# 完成总结
print_section("工具链演示完成")
print("生成的文件列表:")
output_files = [
parsed_file,
readable_file,
error_file,
comm_file,
report_file
]
for f in output_files:
print(f" - {f}")
print("\n工具链流程回顾:")
print(" 原始日志 -> [工具1] -> 结构化JSON")
print(" |")
print(" +----> [工具3] -> 错误统计")
print(" |")
print(" +----> [工具5] -> 完整报告")
print(" |")
print(" +----> [工具2] -> 人类可读格式")
print(" |")
print(" +----> [工具4] -> 通信汇总")
if __name__ == "__main__":
main()
3.3 运行工具链
cd 01_tool_use
python tool_chain_demo.py
3.4 生成的输出文件
-
output_parsed.json(工具1的输出)
-
output_readable.log(工具2的输出)
-
output_errors.txt(工具3的输出)
-
output_communication.txt(工具4的输出)
-
output_final_report.md(工具5的输出)
四、Agent 整合实战
4.1 前置要求
-
安装并启动 Ollama:访问 https://ollama.com
-
拉取模型:
ollama pull qwen2.5:3b-instruct
4.2 交互式 Agent 完整代码(简化可运行版)
"""
交互式 Agent - 简化可运行版
直接调用 Ollama API,不依赖复杂框架
"""
import requests
import sys
# 导入我们的工具
from tool_1_parse_log import parse_log
from tool_2_convert_timestamps import convert_timestamps
from tool_3_analyze_errors import analyze_errors
from tool_4_summarize_communication import summarize_communication
from tool_5_generate_report import generate_report
OLLAMA_URL = "http://localhost:11434/api/generate"
MODEL_NAME = "qwen2.5:3b-instruct"
# 工具定义
tools = {
"parse_log": {
"name": "parse_log",
"description": "解析日志文件,把原始日志转换成结构化的JSON格式",
"function": parse_log
},
"convert_timestamps": {
"name": "convert_timestamps",
"description": "把时间戳转换成人类可读的格式",
"function": convert_timestamps
},
"analyze_errors": {
"name": "analyze_errors",
"description": "分析日志中的错误,统计错误出现的次数",
"function": analyze_errors
},
"summarize_communication": {
"name": "summarize_communication",
"description": "汇总日志中的通信记录",
"function": summarize_communication
},
"generate_report": {
"name": "generate_report",
"description": "生成完整的分析报告",
"function": generate_report
}
}
def load_log():
"""加载日志文件"""
with open('sample_device.log', 'r', encoding='utf-8') as f:
return f.read()
def call_ollama(prompt, max_tokens=200):
"""调用 Ollama API"""
try:
response = requests.post(
OLLAMA_URL,
json={
"model": MODEL_NAME,
"prompt": prompt,
"stream": False,
"options": {
"temperature": 0.1,
"num_predict": max_tokens
}
},
timeout=60
)
if response.status_code == 200:
return response.json().get('response', '')
else:
return f"Error: {response.status_code}"
except Exception as e:
return f"Error: {e}"
def select_tool(user_question):
"""根据问题选择合适的工具"""
tool_names = list(tools.keys())
tool_descriptions = "\n".join([f"- {name}: {tools[name]['description']}" for name in tool_names])
prompt = f"""你是一个日志分析助手。用户问了一个问题,请根据问题选择最合适的工具。
可用工具:
{tool_descriptions}
用户问题:{user_question}
请只返回工具名称(不要返回其他内容),例如:analyze_errors"""
response = call_ollama(prompt, max_tokens=30)
response = response.strip()
# 确保返回的是有效的工具名
for tool_name in tool_names:
if tool_name in response:
return tool_name
# 默认返回错误分析
return "analyze_errors"
def main():
print("="*80)
print("交互式 Agent - 简化可运行版")
print("="*80)
print("\n可用工具:")
for name, info in tools.items():
print(f" - {name}: {info['description']}")
print("\n" + "="*80)
print("加载日志文件...")
log_content = load_log()
print(f"[OK] 日志加载成功,共 {len(log_content.splitlines())} 行")
print("\n" + "="*80)
print("开始对话!")
print("="*80)
print("--- 你可以尝试 ---")
print("1. 帮我分析一下日志中的错误")
print("2. 把时间戳转换成人类可读格式")
print("3. 统计一下通信记录")
print("4. 生成一份完整的分析报告")
print("5. 退出: q 或 quit")
print("="*80)
while True:
try:
user_input = input("\n请输入你的问题: ").strip()
if user_input.lower() in ['q', 'quit', 'exit']:
print("再见!")
break
if not user_input:
print("请输入一个问题")
continue
print(f"\n[1/3] 理解问题...")
selected_tool = select_tool(user_input)
print(f"[OK] 选择工具: {selected_tool}")
print(f"\n[2/3] 调用工具...")
tool_func = tools[selected_tool]['function']
# 根据工具决定输入
if selected_tool in ['analyze_errors', 'generate_report']:
# 需要结构化输入
parsed = parse_log.invoke(log_content)
result = tool_func.invoke(parsed)
else:
# 直接用原始日志
result = tool_func.invoke(log_content)
print(f"[OK] 工具调用完成")
print(f"\n[3/3] 整理结果...")
summary_prompt = f"""用户问:{user_input}
工具执行结果:
{result}
请用自然语言总结这个结果。"""
summary = call_ollama(summary_prompt, max_tokens=300)
print("\n" + "="*80)
print("Agent 回答:")
print("="*80)
print(summary)
print("\n--- 原始结果 ---")
print(result[:500])
if len(result) > 500:
print("... (结果太长,已截断)")
except KeyboardInterrupt:
print("\n再见!")
break
except Exception as e:
print(f"\n执行出错: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()
4.3 运行交互式 Agent
步骤1:启动 Ollama 服务
ollama serve
步骤2:运行交互式 Agent(推荐)
cd 01_tool_use
python working_interactive_agent.py
你可以尝试的问题:
-
"帮我分析一下日志中的错误"
-
"把时间戳转换成人类可读格式"
-
"统计一下通信记录"
-
"生成一份完整的分析报告"
运行演示:
请输入你的问题: 帮我分析下日志中的错误
[1/3] 理解问题...
[OK] 选择工具: analyze_errors
[2/3] 调用工具...
[OK] 工具调用完成
[3/3] 整理结果...
================================================================================
Agent 回答:
================================================================================
在分析的日志中,共检测到5个错误,其中 "scheme type[1] id[1] not found!!" 出现了4次,"scheme type[1] id[2] not found!!" 出现了1次。
--- 原始结果 ---
错误统计:
总计:5个错误
4次:scheme type[1] id[1] not found!!
1次:scheme type[1] id[2] not found!!
快速测试(不用交互):
python quick_demo.py
五、Agent 的价值在哪里
看到这里,你可能会问:Agent不就是自动调用工具吗?有什么了不起的?
让我用一个真实的场景来说明Agent的价值:
5.1 场景对比:客服处理问题
传统方式(没有Agent):
用户:"我的设备连接不上了"
↓
客服:"好的,请按以下步骤操作:
1. 先把日志发给我
2. 我用工具1解析一下
3. 然后用工具3看看有没有错误
4. 再用工具4检查通信
..."
↓
(需要10分钟,客服需要懂技术)
有Agent的方式:
用户:"我的设备连接不上了"
↓
Agent:[自动调用工具1 → 工具3 → 工具4]
↓
Agent:"我帮你分析了日志,发现3个错误:
1. scheme type[1] id[1] not found(出现3次)
2. 建议检查设备ID配置"
↓
(需要30秒,用户不需要懂技术)
5.2 Agent 的核心价值
| 价值点 | 说明 |
|---|---|
| 降低使用门槛 | 用户不需要知道该调用哪个工具,用自然语言提问就行 |
| 按需调用 | 不会浪费资源调用不需要的工具,只做必要的事 |
| 自然交互 | 可以处理模糊、不完整的问题,不需要严格格式 |
| 上下文理解 | 可以基于对话历史,持续跟进处理复杂问题 |
| 解释能力 | 可以解释为什么选择这个工具,让结果更可信 |
5.3 什么时候该用Agent?
✅ 适合用Agent的场景:
-
面向非技术用户的系统
-
问题多样,无法预知所有情况
-
需要自然语言交互
-
工具之间有多种组合方式
❌ 不适合用Agent的场景:
-
非常固定的批量处理任务
-
对性能有极致要求(Agent有额外开销)
-
完全不能容忍错误的场景
六、总结与展望
6.1 本系列要点总结
| 要点 | 说明 |
|---|---|
| ✅ 理解了 Agent 工具模式 | 工具是 Agent 的核心扩展能力 |
| ✅ 掌握了工具四要素 | 装饰器、文档字符串、类型注解、实现逻辑 |
| ✅ 实现了 5 个实用工具 | 从日志解析到报告生成 |
| ✅ 掌握了工具链整合 | 多个工具联动工作 |
| ✅ 理解了两种方式的区别 | 脚本 vs Agent,各有所长 |
| ✅ 实现了 Agent 调用工具 | 让 Agent 智能选择和调用工具 |
6.2 系列结束
感谢你学习完本系列!希望这些内容对你有帮助。记住:工具本身只是基础,如何用Agent让它们智能化,才是真正的价值所在。
📚 参考资源
| 资源 | 链接 |
|---|---|
| LangChain Tools 文档 | https://python.langchain.com/docs/modules/tools/ |
| Ollama 官方网站 | https://ollama.com |
点赞 + 关注,更新不迷路!🚀