我的第一个工具调用Agent:从零构建“知识库+计算器+时间+文件”四合一智能体

发布时间:2026/11/2 11:21:27

我的第一个工具调用Agent:从零构建“知识库+计算器+时间+文件”四合一智能体 从RAG到Agent我用一个周末实现了AI从知道到做到的跃迁一、引言为什么从RAG升级到Agent作为制造业数字化转型从业者2026年上半年我完成了几个本地化AI应用实践——环境搭建、多模态图搜、RAG知识库问答甚至还把橘子洲头做成了AI客服。这些应用让AI知道了很多事但总感觉缺了点什么AI只能被动回答问题不能主动调用工具去解决问题。直到我开始接触AI Agent才真正理解了这个核心差异RAG检索增强生成AI 大脑模型 资料库知识库能知道并回答Agent智能体AI 大脑模型 资料库知识库 手脚工具能做到并完成本案例的目标是构建一个具备知识库检索、数学计算、时间查询、文件读写四项能力的Agent通过实践完整理解Agent的核心机制。这对于我们后续在制造企业落地数据质量巡检、主数据处理、数仓分层设计等场景具有直接的参考价值。运行效果图二、技术方案与选型我的开发环境比较特殊可能也代表了一部分开发者的现状Windows 11 WSL2Ubuntu 22.04 32GB内存 无独显大模型通过Ollama安装在Windows侧提供API服务代码在WSL中运行。组件选型说明大模型Qwen2.5:7BOllama中文友好7B参数量在CPU上可运行Agent框架LangChain 0.3.x社区成熟ReAct模式原生支持向量数据库Chroma轻量级与之前RAG案例无缝衔接嵌入模型all-MiniLM-L6-v2384维CPU友好中文效果尚可Web框架Flask轻量级快速构建交互界面工程化原则贯穿整个专栏配置与代码分离config.py工具函数独立封装tools/模块完整的错误处理与日志记录单元测试覆盖每个工具函数项目结构清晰可一键运行三、项目结构工程化的起点case1_tool_agent/ ├── config.py # 配置集中管理路径、模型、端口 ├── requirements.txt # 依赖锁定版本 ├── .env.example # 环境变量模板 ├── tools/ # 工具模块可复用 │ ├── __init__.py │ ├── calculator.py # 计算器工具 │ ├── time_tool.py # 时间工具 │ ├── file_tool.py # 文件读写工具 │ └── rag_retriever.py # 知识库检索工具 ├── agent/ # Agent核心 │ ├── __init__.py │ └── builder.py # Agent构建工厂 ├── web/ # Web服务 │ ├── app.py # Flask主应用 │ └── templates/ │ └── index.html # 极简Chat界面 ├── scripts/ # 辅助脚本 │ ├── test_tools.py # 工具单元测试 │ └── test_cli.py # 命令行交互测试 ├── chroma_db/ # 知识库复用已有 │ └── chroma.sqlite3 └── workspace/ # 文件工具工作目录工程心得我一直信奉好的项目结构是成功的一半。清晰的模块划分让工具可以在后续案例中直接复用配置集中管理避免硬编码单元测试保障每次修改不引入新Bug。四、核心实现四个工具的定义与编码4.1 计算器工具tools/calculator.py# -*- coding: utf-8 -*- # author: javy21 # blog: https://blog.csdn.net/javy21 计算器工具执行数学表达式计算 import logging from langchain.tools import tool logger logging.getLogger(__name__) tool def calculate(expression: str) - str: 执行数学计算输入数学表达式返回计算结果。 Args: expression: 数学表达式如 35 或 10*2/3 Returns: 计算结果字符串或错误信息 # 安全检查只允许数字、运算符、括号、空格 allowed_chars set(0123456789-*/().% ) if not all(c in allowed_chars for c in expression): error_msg f表达式包含非法字符{expression} logger.warning(error_msg) return f计算错误{error_msg} try: result eval(expression) logger.info(f计算成功: {expression} {result}) return f计算结果{result} except ZeroDivisionError: return 计算错误除数不能为零 except Exception as e: return f计算错误表达式格式错误 - {str(e)}设计要点使用tool装饰器将函数转换为LangChain工具输入校验防止代码注入生产环境建议用numexpr完整的异常处理返回友好信息而非堆栈4.2 时间工具tools/time_tool.py# -*- coding: utf-8 -*- # author: javy21 # blog: https://blog.csdn.net/javy21 时间工具获取当前日期和时间 import logging from datetime import datetime from langchain.tools import tool logger logging.getLogger(__name__) tool def get_current_time(format: str %Y-%m-%d %H:%M:%S) - str: 获取当前日期和时间可指定输出格式。 Args: format: 时间格式字符串默认 %Y-%m-%d %H:%M:%S Returns: 格式化后的当前时间字符串 now datetime.now() result now.strftime(format) logger.info(f获取当前时间: {result}) return result4.3 文件读写工具tools/file_tool.py# -*- coding: utf-8 -*- # author: javy21 # blog: https://blog.csdn.net/javy21 文件读写工具在workspace目录下读写文件防路径穿越 import logging from pathlib import Path from langchain.tools import tool from config import WORKSPACE_DIR logger logging.getLogger(__name__) tool def read_file(filename: str) - str: 读取指定文件的内容。 # 安全检查防止路径穿越攻击 if .. in filename or filename.startswith(/): return 读取失败非法文件名 filepath WORKSPACE_DIR / filename if not filepath.exists(): return f读取失败文件 {filename} 不存在 try: content filepath.read_text(encodingutf-8) logger.info(f读取文件成功: {filename}) return f文件内容\n{content} except Exception as e: return f读取失败{str(e)} tool def write_file(filename: str, content: str) - str: 将内容写入指定文件。 if .. in filename or filename.startswith(/): return 写入失败非法文件名 filepath WORKSPACE_DIR / filename try: filepath.write_text(content, encodingutf-8) logger.info(f写入文件成功: {filename}) return f成功写入文件{filename} except Exception as e: return f写入失败{str(e)}⚠️安全提醒文件工具是企业场景中最容易出安全问题的环节一定要做好路径穿越防护。4.4 知识库检索工具tools/rag_retriever.py# -*- coding: utf-8 -*- # author: javy21 # blog: https://blog.csdn.net/javy21 知识库检索工具基于Chroma向量库的语义检索 import logging from langchain.tools import tool from langchain_community.vectorstores import Chroma from langchain_community.embeddings import HuggingFaceEmbeddings from config import CHROMA_DIR, EMBEDDING_MODEL, RETRIEVAL_TOP_K logger logging.getLogger(__name__) _vectordb None def _get_vectordb(): 懒加载向量数据库单例模式 global _vectordb if _vectordb is None: logger.info(f正在加载向量数据库: {CHROMA_DIR}) embeddings HuggingFaceEmbeddings(model_nameEMBEDDING_MODEL) _vectordb Chroma( persist_directorystr(CHROMA_DIR), embedding_functionembeddings, ) logger.info(向量数据库加载完成) return _vectordb tool def search_knowledge(query: str) - str: 从知识库中检索与查询相关的文档片段。 try: vectordb _get_vectordb() docs vectordb.similarity_search(query, kRETRIEVAL_TOP_K) if not docs: return 未找到相关信息。 contents [doc.page_content for doc in docs] result \n\n---\n\n.join(contents) logger.info(f检索成功: {query} - {len(docs)} 个片段) return f检索结果\n{result} except Exception as e: return f检索失败{str(e)}五、Agent构建让AI学会使用工具Agent构建是本案例的核心环节代码位于agent/builder.py# -*- coding: utf-8 -*- # author: javy21 # blog: https://blog.csdn.net/javy21 Agent构建器创建和配置LangChain Agent import logging from langchain_community.llms import Ollama from langchain.agents import initialize_agent, AgentType from langchain.memory import ConversationBufferMemory from config import OLLAMA_BASE_URL, MODEL_NAME, TEMPERATURE from tools import calculate, get_current_time, read_file, write_file, search_knowledge logger logging.getLogger(__name__) def create_agent(verbose: bool True): 创建并返回一个配置好的Agent实例 # 1. 初始化大模型 logger.info(f初始化模型: {MODEL_NAME} {OLLAMA_BASE_URL}) llm Ollama( modelMODEL_NAME, base_urlOLLAMA_BASE_URL, temperatureTEMPERATURE, ) # 2. 注册工具顺序影响决策优先级 tools [ search_knowledge, # 知识库检索 calculate, # 计算器 get_current_time, # 时间查询 read_file, # 文件读 write_file, # 文件写 ] # 3. 设置记忆对话缓冲 memory ConversationBufferMemory( memory_keychat_history, return_messagesTrue, ) # 4. 创建Agent使用支持多参数工具的Agent类型 agent initialize_agent( tools, llm, agentAgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, memorymemory, verboseverbose, handle_parsing_errorsTrue, max_iterations10, early_stopping_methodgenerate, ) logger.info(Agent创建成功) return agent关键决策我选择了STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION而非CONVERSATIONAL_REACT_DESCRIPTION因为前者支持多参数工具如write_file需要 filename content 两个参数后者只支持单参数工具。六、Web交互界面极简Chat风格Flask应用提供简洁的聊天界面用户在浏览器中输入问题Agent自动决策并调用相应工具核心路由逻辑web/app.pyapp.route(/api/chat, methods[POST]) def chat(): data request.get_json() user_input data.get(message, ).strip() if not user_input: return jsonify({error: 消息不能为空}), 400 try: response agent.run(user_input) return jsonify({response: response}) except Exception as e: logger.error(f处理失败{str(e)}) return jsonify({error: f处理失败{str(e)}}), 500七、运行效果展示7.1 单元测试通过$ python scripts/test_tools.py 开始工具单元测试 测试计算器... ✅ 计算器测试通过 测试时间工具... ✅ 当前时间: 2026-07-05 22:32:26 测试文件工具... ✅ 文件工具测试通过 测试知识库检索... ✅ 检索结果长度: 1024 字符 ✅ 所有测试通过 7.2 Web交互实测用户输入Agent行为结果现在几点了调用时间工具返回当前时间帮我算一下 123 * 456调用计算器返回 56088十五五规划中关于人工智能的内容调用知识库检索返回相关文档片段把 Hello Agent 写入 test.txt调用文件写工具返回成功信息读取 test.txt 的内容调用文件读工具返回文件内容明天上午10点我要开会帮我算一下还有多少秒组合调用时间计算器计算差值并返回八、踩坑记录与解决方案坑1ModuleNotFoundError: No module named tools.calculator现象运行测试脚本时Python找不到自定义模块。原因直接运行python scripts/test_tools.py时项目根目录不在sys.path中。解决在每个脚本开头添加路径处理python复制下载import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent))坑2向量库为空检索无结果现象search_knowledge总是返回未找到相关信息。原因从之前项目复制的chroma_db只包含空表没有实际向量数据。解决重新构建知识库用PDF文档填充向量数据python复制下载vectordb Chroma.from_documents( documentschunks, embeddingembeddings, persist_directorystr(CHROMA_DIR) )坑3ConversationalAgent does not support multi-input tool现象启动Web服务时报错提示write_file是多参数工具不被支持。原因ConversationalAgent要求所有工具都是单输入只有一个参数。解决更换为AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION该类型支持多参数工具。坑4Ollama连接失败Connection refused现象Agent调用大模型时返回HTTPConnectionPool(hostlocalhost, port11434)... Connection refused。原因我的Ollama安装在Windows上而代码在WSL中运行WSL中的localhost指向WSL自身而非Windows宿主机。解决在Windows上配置Ollama监听所有接口set OLLAMA_HOST0.0.0.0:11434 ollama serve修改config.py中的OLLAMA_BASE_URL为Windows的IP地址九、工程化改进方向让Demo走向生产改进方向实现建议优先级工具描述优化精炼每个工具的description提高模型调用准确率P0日志与监控集成结构化日志如JSON格式便于采集分析P0超时与重试对Ollama调用增加超时和重试机制P1环境变量管理使用.envpython-dotenv管理配置P1容器化部署编写Dockerfile实现一键部署P2十、小结与下篇预告通过本案例我们完整实现了从0到1构建一个工具调用Agent的全过程涵盖了工具定义、Agent构建、Web交互、单元测试、工程化组织等多个维度。在这个过程中我深刻体会到Agent的核心不是调用工具而是决定何时调用什么工具。ReAct模式让AI具备了自主决策的能力这是与传统自动化脚本的本质区别。下篇预告下一篇理论文章《从RAG到AgentAI应用范式的演进——兼谈工具调用在制造业数据管理的落地价值》我将从理论角度深入剖析RAG与Agent的本质区别与演进逻辑ReAct模式的深层理解工具调用的关键设计原则Agent在制造业数据管理数据质量、数仓分层、数据标准中的落地场景敬请期待欢迎在评论区交流您的实践心得或遇到的问题一起探讨、共同成长。作者javy21博客javy21-CSDN博客专栏AI Agent从入门到落地本文案例代码可在评论区获取完整项目源码如果您觉得本文有帮助欢迎交流分享您的支持是我持续输出的动力。
延伸阅读

更多相关文章

2026/11/1 19:13:49

从硬件限制到自由安装:Rufus如何重新定义Windows启动盘制作

从硬件限制到自由安装:Rufus如何重新定义Windows启动盘制作 【免费下载链接】rufus The Reliable USB Formatting Utility 项目地址: https://gitcode.com/GitHub_Trending/ru/rufus 当用户尝试在老旧的电脑上安装Windows 11时,经常会遇到一个令人…

2026/11/2 11:21:02

一物一码红包营销落地实战指南

在快消品行业,促销活动往往是拉动销量的关键引擎,但传统的“普惠式”打折或固定金额红包,不仅让营销成本居高不下,还难以激发用户的复购热情。更棘手的是,一旦活动火爆,瞬间涌入的高并发流量极易导致系统崩…

2026/11/2 11:21:02

02 词法与语法分析(前端)

02 词法与语法分析(前端) 本册目标:搞清楚源代码文本如何变成语法树——Lexer 如何扫描 Token、LanguageParser 如何用递归下降 优先级爬升建树、红绿树为何能做到编辑时 O(1) 复用、Syntax API 长什么样、语法错误如何恢复。 一、要点速览 …

2026/11/2 11:21:02

从初稿到终稿,降AIGC软件全维度测评

AI检测全面趋严,降AI率工具选择成论文刚需 当前知网、维普、万方、格子达等主流学术平台已全面上线AI内容检测机制,AI识别率成为继重复率之后又一道学术门槛。市面上各类降AI率工具层出不穷,但定价标准混乱、功能参差不齐、内容质量难以保障&…

2026/11/2 11:21:02

论文降AI、查AI率一站汇总:10个降重降AIGC工具,免费降AI 1000字

相信很多小伙伴在写完文章之后都会有着或多或少的烦恼 有的可能在烦自己的文章ai率太高,有的在烦自己文章的查重率太高,有的更苦恼的是自己文章的ai率和查重率都很高。还有的小伙伴可能在烦恼文章写完了之后怎么查呢?用什么工具查呢&#xff…

2026/11/2 11:21:02

三层数据模式:外模式、模式与内模式如何映射到游戏服务端的存储分层

一、从一个具体动作切入:打开背包时到底发生了什么 你开发了一款游戏。玩家点开背包,界面上显示「治疗药水 3」「铁矿石 10」。 这个瞬间看起来简单,但它背后同时存在三个完全不同的问题: 界面该显示哪些字段、叫什么名字、按什么顺序排? 这是「给谁看」的问题。 全服…

2026/11/2 11:16:01

Three.js 批量渲染 GLB:InstancedMesh、矩阵归一化与资源释放

最近在做一个三维钓鱼网页,岸边需要重复摆放树木、石头和芦苇。同一份模型会出现在不同位置,大小和朝向也不一样。 项目里采用了 InstancedMesh。把 GLB 转成实例批次时,有几件事需要一起处理:模型可能包含多个 Mesh;…

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/2 6:39:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

工业级SPI隔离设计:硬件抗扰与信号完整性实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

PLFM_RADAR:主动探测数据通道,构建低误报的数据感知系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 1:04:04

工业SPI隔离设计:MAX14431与STM32F746硬件抗干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/2 6:08:01

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/2 6:39:12

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑