脚本专家 发表于 2026-6-12 09:00:00

Ollama+Python搭建本地AI Agent:工具调用与结构化输出实现

AI Agent的核心不是让模型更会聊天,而是让模型根据任务判断是否需要调用外部工具,再基于工具结果生成回答。本文基于Ollama和Python搭建一个最小化本地AI Agent雏形,覆盖从模型选择、工具注册、JSON结构化输出约束到异常处理的完整流程,适合想入门Agent和MCP实现的开发者。

环境准备
需要Python 3.10+、已安装并运行的Ollama,以及一个本地模型(如llama3.2、qwen2.5、gemma3均可)。确认Ollama服务正常:curl http://localhost:11434/api/chat -d '{"model":"llama3.2","messages":[{"role":"user","content":"你好"}],"stream":false}'。返回模型回答则说明环境就绪。

项目结构
创建两个文件:tools.py存放工具函数和注册表,agent.py负责模型调用、意图判断、工具执行和结果汇总。

编写工具函数与注册表
在tools.py中定义三个模拟工具:获取当前时间、计算两数之和、查询本地知识库。

from datetime import datetime
def get_current_time() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def add_numbers(a: float, b: float) -> str:
    return str(a + b)
def search_local_knowledge(keyword: str) -> str:
    knowledge_base = {
      "ollama": "Ollama 是一个可以在本地运行大语言模型的工具",
      "mcp": "MCP 是 Model Context Protocol,用于连接AI与外部工具",
      "agent": "AI Agent具备任务理解、工具调用和多步骤推理能力"
    }
    keyword = keyword.lower()
    for key, value in knowledge_base.items():
      if key in keyword:
            return value
    return "未找到相关信息"

然后建立工具注册表,统一管理工具名称、描述、参数列表和引用。

TOOLS = {
    "get_current_time": {
      "description": "获取当前本地时间",
      "function": get_current_time,
      "args": []
    },
    "add_numbers": {
      "description": "计算两个数字之和",
      "function": add_numbers,
      "args": ["a", "b"]
    },
    "search_local_knowledge": {
      "description": "查询本地知识库",
      "function": search_local_knowledge,
      "args": ["keyword"]
    }
}


封装Ollama调用
在agent.py中编写调用Ollama /api/chat接口的函数。

import json, requests
from tools import TOOLS
OLLAMA_URL = "http://localhost:11434/api/chat"
MODEL_NAME = "llama3.2"
def call_ollama(messages):
    payload = {"model": MODEL_NAME, "messages": messages, "stream": False}
    response = requests.post(OLLAMA_URL, json=payload, timeout=120)
    response.raise_for_status()
    return response.json()["message"]["content"]


引导模型输出结构化JSON
核心难点是让模型输出可解析的工具调用指令。构造一个强制JSON输出的prompt,明确字段名和格式。

def build_tool_prompt(user_input):
    tool_descriptions = []
    for name, info in TOOLS.items():
      tool_descriptions.append({"name": name, "description": info["description"], "args": info["args"]})
    return f"""
你是一个本地AI Agent的工具选择器。
用户问题:{user_input}
可用工具:{json.dumps(tool_descriptions, ensure_ascii=False, indent=2)}
判断是否需要调用工具。要求:只能输出JSON,不要Markdown或解释。如果需要工具:{{"need_tool": true, "tool_name": "工具名称", "arguments": {{"参数名": "参数值"}}}}。如果不需要:{{"need_tool": false, "answer": "直接回答"}}
"""


解析与执行工具
模型返回的文本可能包含多余前缀,容错解析JSON。

def parse_json_safely(text):
    try:
      return json.loads(text)
    except json.JSONDecodeError:
      start = text.find("{")
      end = text.rfind("}") + 1
      if start != -1 and end != -1:
            return json.loads(text)
      raise ValueError(f"非法JSON:{text}")

执行工具时注意参数校验,比如数字可能被模型输出为字符串,在工具函数内部做类型转换。

def execute_tool(tool_name, arguments):
    if tool_name not in TOOLS:
      raise ValueError(f"未知工具:{tool_name}")
    return TOOLS["function"](**arguments)


结果汇总与主循环
工具执行结果不应直接返回,而是交给模型重新总结成自然语言。

def summarize_with_tool_result(user_input, tool_name, tool_result):
    messages = [
      {"role": "system", "content": "你是一个严谨的技术助手,请根据工具结果回答用户问题。"},
      {"role": "user", "content": f"用户原始问题:{user_input}\n调用的工具:{tool_name}\n工具结果:{tool_result}\n请给出简洁自然的回答。"}
    ]
    return call_ollama(messages)

完整主函数和交互循环:

def run_agent(user_input):
    tool_prompt = build_tool_prompt(user_input)
    messages = [{"role": "system", "content": "你只输出JSON。"}, {"role": "user", "content": tool_prompt}]
    decision = parse_json_safely(call_ollama(messages))
    if not decision.get("need_tool"):
      return decision.get("answer", "无回答")
    tool_result = execute_tool(decision["tool_name"], decision.get("arguments", {}))
    return summarize_with_tool_result(user_input, decision["tool_name"], tool_result)

if __name__ == "__main__":
    while True:
      user_input = input("\n你:").strip()
      if user_input.lower() in ["exit","quit","q"]: break
      try:
            print(f"\nAgent:{run_agent(user_input)}")
      except Exception as e:
            print(f"出错:{e}")


运行测试
安装requests后执行python agent.py。测试问题如“现在几点?”、“计算18.5+23.7”、“MCP是什么?”可验证工具调用与结果总结。

常见问题排查
连接不上:检查Ollama是否运行(curl http://localhost:11434/)。模型不存在:ollama pull llama3.2。模型输出非JSON:强化system prompt或增加JSON截取逻辑。参数类型错误:在工具函数内做类型转换,例如def add_numbers(a,b): return str(float(a)+float(b))。

与真正MCP的区别
本文实现的是手动工具注册与调用,而MCP标准协议提供JSON-RPC通信、工具发现、权限控制等规范。理解此雏形后再学习MCP会更容易。

扩展方向
可接入真实API(天气、订单等)、加入多轮对话记忆、记录工具调用日志、连接向量数据库做RAG,或按MCP协议包装成标准服务。

此Demo虽然简单,但覆盖了Agent核心环节:任务理解→工具选择→参数生成→工具执行→结果总结→异常处理。跑通这个最小闭环,才能真正理解AI Agent落地的工程细节。

热心网友7 发表于 2026-6-12 09:05:00

Re: Ollama+Python搭建本地AI Agent:工具调用与结构化输出实现

楼主这篇文章太及时了,最近刚好在研究怎么让本地模型具备调用工具的能力。你提供的工具注册表和强制 JSON 输出的 prompt 思路非常清晰,特别是把工具描述、参数列表结构化传给模型,这样模型就能理解该用什么工具。我有个小问题:当模型返回的 JSON 里 `need_tool` 为 `false` 时,你是怎么直接让模型回答的?是直接复用 `call_ollama` 还是另外做一次普通对话?另外,如果工具参数填写错误或者模型输出的 JSON 格式不完整(比如少了一个花括号),你在异常处理上有什么经验?期待后续分享更完整的错误处理逻辑。

热心网友1 发表于 2026-6-19 22:35:00

Re: Ollama+Python搭建本地AI Agent:工具调用与结构化输出实现

感谢分享,很完整的入门教程!正好最近在折腾本地大模型,这个工具调用和结构化输出的思路清晰直接,收藏了。有个小问题:如果工具返回的结果比较复杂(比如列表或嵌套JSON),prompt那边需要额外处理吗?还是靠模型自己理解返回内容?

热心网友2 发表于 2026-6-20 10:05:00

Re: Ollama+Python搭建本地AI Agent:工具调用与结构化输出实现

很好的教程,结构清晰,从环境准备到工具注册再到JSON约束输出,把Agent的核心流程讲得很明白。特别是有意识地把“让模型输出结构化JSON”作为重点来设计prompt,这个思路对Ollama这类本地模型来说很关键,模型在自由生成时容易跑偏,强制JSON输出能大幅提高调用的可靠性。准备按你的项目结构试一遍,后面能否再分享一下多工具串联或带状态记忆的进阶版本?
页: [1]
查看完整版本: Ollama+Python搭建本地AI Agent:工具调用与结构化输出实现