在这里插入图片描述

AI Agent 的核心不是“让大模型聊天”,而是让大模型根据任务判断是否需要调用工具。本文用 Ollama + Python 搭建一个本地 AI Agent 雏形,实现“用户提问 → 模型判断 → 调用工具 → 汇总回答”的完整流程,适合想入门 Agent、MCP 和本地大模型应用开发的程序员。


用 Ollama + Python 搭一个本地 AI Agent 雏形:让大模型学会调用工具

很多开发者第一次接触 AI Agent 时,容易把它理解成“更聪明的 ChatGPT”。

但真正的 Agent,不只是回答问题,而是能根据目标拆解任务、调用工具、读取数据、执行流程,再把结果反馈给用户。

这也是为什么 MCP、Agent、工具调用这些概念最近在开发者社区越来越热。MCP 官方文档把它描述为一种连接 AI 应用与外部数据源、工具和工作流的开放标准,核心目的就是让模型不只停留在对话,而是能和外部系统协作。([Model Context Protocol][1])

这篇文章不做完整 MCP Server 实现,而是先用更容易理解的方式,手写一个“Agent 雏形”。

你会看到一个最小闭环:

用户问题
↓
本地大模型理解
↓
判断是否需要工具
↓
Python 调用工具函数
↓
把工具结果交回模型
↓
生成最终回答

这个流程跑通之后,再去理解 MCP、LangChain、Spring AI、Claude Code 这类工具和框架,就会清楚很多。


1. 环境准备

本文示例使用:

Python 3.10+
Ollama
本地模型:llama3.2 / qwen2.5 / gemma3 均可

Ollama 官方提供了本地 REST API,可以通过 http://localhost:11434/api/chat 与本地模型对话。官方 API 示例也是基于 /api/chat 发送 messages 数组来完成对话。([GitHub][2])

先安装 Ollama,并拉取一个模型:

ollama pull llama3.2

确认 Ollama 是否正常运行:

curl http://localhost:11434/api/chat -d '{
  "model": "llama3.2",
  "messages": [
    {
      "role": "user",
      "content": "你好,请用一句话介绍你自己"
    }
  ],
  "stream": false
}'

如果能返回模型回答,说明本地模型已经可以调用。


2. 项目目录结构

我们先做一个最小可运行版本。

local-agent-demo/
├── agent.py
├── tools.py
└── README.md

其中:

tools.py
负责存放工具函数

agent.py
负责模型对话、意图判断、工具调用和结果汇总

这个结构虽然简单,但已经具备 Agent 的核心雏形。


3. 先写几个工具函数

这里模拟三个常见工具:

  1. 获取当前时间;
  2. 计算两个数字之和;
  3. 查询本地知识库。

新建 tools.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 是一个可以在本地运行大语言模型的工具,适合开发本地 AI 应用。",
        "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 "本地知识库中没有找到相关信息。"

这三个函数就是我们的“工具库”。

在真实项目中,这些工具可以替换成:

数据库查询
HTTP API 请求
文件读取
向量检索
Shell 命令
订单查询
日志分析
自动化脚本

Agent 的价值就在这里:模型负责理解任务,工具负责执行真实操作。


4. 定义工具注册表

如果工具很多,不能靠 if else 到处散落。

更好的做法是建立一个工具注册表。

tools.py 继续加入:

# tools.py

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"]
    }
}

这样做有两个好处:

第一,工具统一管理。

第二,后续可以把工具说明交给大模型,让模型自己判断该调用哪个工具。

这也是理解 MCP 的关键。MCP 规范里服务端能力包括 tools、resources、prompts 等,工具只是其中一个能力类型。([Model Context Protocol][3])


5. 编写 Ollama 调用函数

新建 agent.py

# agent.py

import json
import requests

from tools import TOOLS


OLLAMA_URL = "http://localhost:11434/api/chat"
MODEL_NAME = "llama3.2"


def call_ollama(messages):
    """
    调用本地 Ollama 模型。
    """
    payload = {
        "model": MODEL_NAME,
        "messages": messages,
        "stream": False
    }

    response = requests.post(OLLAMA_URL, json=payload, timeout=120)
    response.raise_for_status()

    data = response.json()
    return data["message"]["content"]

这里使用的是 Ollama 的 /api/chat 接口。

如果你使用的是其他模型,只需要改:

MODEL_NAME = "llama3.2"

例如:

MODEL_NAME = "qwen2.5"

前提是你已经提前拉取对应模型。


6. 让模型输出结构化工具调用指令

现在的问题是:

模型怎么告诉 Python 它想调用哪个工具?

最简单的方式是让模型输出 JSON。

例如:

{
  "need_tool": true,
  "tool_name": "get_current_time",
  "arguments": {}
}

或者:

{
  "need_tool": true,
  "tool_name": "add_numbers",
  "arguments": {
    "a": 12,
    "b": 30
  }
}

如果不需要工具,就输出:

{
  "need_tool": false,
  "answer": "直接回答用户的问题"
}

继续在 agent.py 中加入:

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)}

请判断是否需要调用工具。

要求:
1. 只能输出 JSON
2. 不要输出 Markdown
3. 不要解释原因
4. 如果需要工具,输出:
{{
  "need_tool": true,
  "tool_name": "工具名称",
  "arguments": {{
    "参数名": "参数值"
  }}
}}
5. 如果不需要工具,输出:
{{
  "need_tool": false,
  "answer": "直接回答内容"
}}
"""

这里的重点是限制模型输出格式。

很多 Agent Demo 跑不稳定,核心问题不是模型不会,而是输出格式不可控。

所以必须给它明确约束:

只能输出 JSON
不要 Markdown
不要解释
字段名固定

7. 解析模型输出并调用工具

继续写工具调用逻辑:

def parse_json_safely(text):
    """
    尽量安全地解析模型返回的 JSON。
    """
    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[start:end])

        raise ValueError(f"模型输出不是合法 JSON:{text}")


def execute_tool(tool_name, arguments):
    """
    根据工具名称执行对应函数。
    """
    if tool_name not in TOOLS:
        raise ValueError(f"未知工具:{tool_name}")

    tool_info = TOOLS[tool_name]
    func = tool_info["function"]

    return func(**arguments)

这里做了一个简单容错。

因为本地模型有时可能会输出多余文字,比如:

好的,以下是 JSON:
{...}

所以我们通过 find("{")rfind("}") 尝试截取 JSON。

生产环境中建议使用更严格的结构化输出方案。


8. 把工具结果交回模型总结

工具执行后,不应该直接把结果原样扔给用户。

更好的方式是让模型基于工具结果生成自然语言回答。

继续加入:

def summarize_with_tool_result(user_input, tool_name, tool_result):
    """
    让模型基于工具调用结果生成最终回答。
    """
    messages = [
        {
            "role": "system",
            "content": "你是一个严谨的技术助手,请根据工具结果回答用户问题。"
        },
        {
            "role": "user",
            "content": f"""
用户原始问题:
{user_input}

调用的工具:
{tool_name}

工具返回结果:
{tool_result}

请给用户一个简洁、准确、自然的回答。
"""
        }
    ]

    return call_ollama(messages)

这样 Agent 的链路就完整了:

用户问题
↓
模型判断工具
↓
Python 执行工具
↓
模型总结结果
↓
返回用户

9. 完整 Agent 主函数

继续在 agent.py 中加入:

def run_agent(user_input):
    """
    运行本地 Agent。
    """
    tool_prompt = build_tool_prompt(user_input)

    messages = [
        {
            "role": "system",
            "content": "你是一个只输出 JSON 的工具选择器。"
        },
        {
            "role": "user",
            "content": tool_prompt
        }
    ]

    decision_text = call_ollama(messages)
    decision = parse_json_safely(decision_text)

    if not decision.get("need_tool"):
        return decision.get("answer", "这个问题不需要调用工具,但模型没有给出回答。")

    tool_name = decision["tool_name"]
    arguments = decision.get("arguments", {})

    tool_result = execute_tool(tool_name, arguments)

    final_answer = summarize_with_tool_result(
        user_input=user_input,
        tool_name=tool_name,
        tool_result=tool_result
    )

    return final_answer


if __name__ == "__main__":
    while True:
        user_input = input("\n你:").strip()

        if user_input.lower() in ["exit", "quit", "q"]:
            print("退出 Agent。")
            break

        try:
            answer = run_agent(user_input)
            print(f"\nAgent:{answer}")
        except Exception as e:
            print(f"\nAgent 出错:{e}")

10. 运行测试

安装依赖:

pip install requests

启动:

python agent.py

测试 1:时间工具

你:现在几点?

预期流程:

模型判断需要 get_current_time
↓
Python 调用 get_current_time()
↓
模型基于结果回答

测试 2:计算工具

你:帮我计算 18.5 加 23.7 等于多少

预期工具调用:

{
  "need_tool": true,
  "tool_name": "add_numbers",
  "arguments": {
    "a": 18.5,
    "b": 23.7
  }
}

测试 3:知识库查询

你:MCP 是什么?

预期工具调用:

{
  "need_tool": true,
  "tool_name": "search_local_knowledge",
  "arguments": {
    "keyword": "MCP"
  }
}

11. 常见问题排查

问题 1:连接不上 Ollama

报错类似:

Connection refused

先检查 Ollama 是否运行:

curl http://localhost:11434/

如果返回:

Ollama is running

说明服务正常。

如果没有返回,先启动 Ollama。


问题 2:模型不存在

报错可能是:

model not found

检查你是否拉取了模型:

ollama list

如果没有,重新拉取:

ollama pull llama3.2

问题 3:模型输出不是 JSON

这是最常见问题。

可以从三方面优化:

第一,强化 system prompt:

你只能输出合法 JSON,不能输出其他任何内容。

第二,降低模型自由发挥空间。

第三,在代码中增加 JSON 截取和校验逻辑。


问题 4:工具参数类型错误

例如模型把数字输出成字符串:

{
  "a": "18.5",
  "b": "23.7"
}

可以在工具函数里做类型转换:

def add_numbers(a, b) -> str:
    return str(float(a) + float(b))

这个在真实项目里非常重要。

因为模型输出永远不能完全信任,所有参数都应该校验。


12. 这个 Demo 和真正 MCP 的区别

本文实现的是一个“类 Agent 工具调用雏形”,不是完整 MCP 实现。

区别主要在于:

能力 本文 Demo 完整 MCP
工具注册 Python 字典 标准协议
通信方式 函数调用 JSON-RPC / HTTP / stdio 等
工具发现 手动传给模型 客户端可发现
权限控制 可设计认证与权限
资源管理 简单模拟 标准 resources
提示词管理 手写 prompt 标准 prompts

MCP 规范的核心组件包括基础协议、版本协商、消息模式、授权、服务端能力等,远比本文 Demo 更完整。本文的价值在于先理解底层思想:模型判断任务,外部工具负责执行。([Model Context Protocol][4])

等你理解这个流程后,再去写 MCP Server,会顺很多。


13. 下一步可以怎么扩展?

这个 Demo 只是第一版,可以继续升级。

方向 1:接入真实 API

例如:

天气查询
GitHub Issue 查询
数据库查询
订单系统查询
日志平台查询

工具函数可以变成:

def query_order(order_id: str) -> str:
    ...

方向 2:加入多轮记忆

现在每次调用都是独立的。

可以把历史消息保存起来:

conversation_history = []

然后每轮传给模型。

方向 3:加入工具调用日志

生产环境必须记录:

用户问题
模型决策
调用工具
工具参数
返回结果
最终回答

否则出现错误很难排查。

方向 4:接入向量数据库

本地知识库可以升级为:

Markdown 文档
PDF 文档
数据库
向量库
RAG 检索系统

这样 Agent 就能回答项目文档、接口说明、业务知识等问题。

方向 5:改造成 MCP Server

等工具稳定后,可以按照 MCP 协议包装成标准工具服务,让支持 MCP 的客户端调用。


14. 写在最后

AI Agent 的核心,不是让大模型说得更像人,而是让它能做事。

这篇文章用 Ollama + Python 搭了一个最小可运行雏形,虽然简单,但已经覆盖了 Agent 最重要的几个环节:

任务理解
工具选择
参数生成
工具执行
结果总结
异常处理

如果你是开发者,建议不要只停留在“哪个 AI 编程工具更强”的讨论里,而是亲手跑一遍这种最小 Agent。

因为只有你真正写过工具调用、参数校验、异常处理和结果汇总,才会明白 Agent 落地最难的地方不是模型,而是工程细节。

如果你长期使用 ChatGPT Plus、Claude Pro、Grok、Gemini Advanced、Cursor、Kiro 等工具,也可以顺手了解一下 gpt108.com。它是 AI会员充值平台,解决的是订阅充值流程问题,不是替代工具本身;使用前建议看清楚套餐说明、账号要求和售后规则。

工具只是入口。

真正让开发者拉开差距的,是能不能把 AI 变成可运行、可维护、可扩展的系统。

Logo

免费领 200 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐