news 2026/10/14 13:13:01

Python Ollama 本地 Agent 入门与订单查询实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python Ollama 本地 Agent 入门与订单查询实践

问模型“订单 2001 一共多少钱”,它可以给出一句像客服的回答,却不一定知道订单里实际有哪些商品。只要答案依赖业务数据,就需要让程序查询真实记录,再把结果交给模型组织语言。

这个教程面向会写基础 Python 的开发者。我们用 Ollama、SQLite 和一个查询函数,搭起一次工具调用的完整流程:模型识别需求,Python 校验并执行,模型根据返回值回答。示例采用虚构书店订单,不连接真实业务系统,也不需要云端模型账号。

先区分模型请求与程序执行

工具调用(Tool Calling)是模型生成的一份操作请求,例如“调用 query_order_amount,参数为 order_id=2001”。真正连接数据库、执行 SQL、返回结果的是 Python。模型看到的是工具名称、用途和参数结构,而不是函数内部的 SQL。[1]

这也是入门 Agent 值得先拆开学习的地方:模型负责选择动作,应用负责执行动作,并把结果放回对话。本例只完成一轮“请求工具—执行工具—返回结果”,最多发出两次模型请求。它有一个工具,但不等于整个程序只调用一次模型。

先约定可检查的目标:订单 2001 包含两本单价 39.90 元的书和一个 12.50 元的书签,总计 92.30 元。我们同时检查工具有没有选对、订单号有没有取对,以及最终回答有没有改动金额。

准备可运行的本地环境

下面的命令使用 Windows PowerShell。需要事先安装 uv 和 Ollama;安装入口见文末官方资料。本文选择 Python 3.12 作为项目解释器,不要求追随最新 Python 版本。

先下载模型并建立独立项目:

ollama pull qwen3.5:4b

uv init --python 3.12 bookshop-agent

cd bookshop-agent

uv add ollama

qwen3.5:4b在 Ollama 模型页标注了工具调用能力,但这不保证它每次都能正确选取工具。[2] 模型下载需要网络;下载完成后,本例使用本地 Ollama 服务推理。磁盘占用和运行内存是不同指标,选模型时还要考虑上下文长度和设备余量。

uv add管理项目依赖,uv run在项目环境中运行命令,因此不用手动激活虚拟环境。[3] SQLite 属于 Python 标准库。项目里只需自己创建两个文件:init_db.py和agent.py,数据库文件会由初始化脚本生成。

用整数金额建立测试数据

先完成与模型无关的数据库部分。金额以“分”保存为整数,避免把二进制浮点数当成精确的货币表示。下面的脚本创建虚构订单,重复运行时保留已有表和记录,不删除文件。

保存为init_db.py:

import sqlite3

from contextlib import closing

from pathlib import Path

DB = Path(__file__).with_name("bookshop.db")

with closing(sqlite3.connect(DB)) as conn:

execute("PRAGMA foreign_keys = ON")

executescript("""

CREATE TABLE IF NOT EXISTS orders (

order_id INTEGER PRIMARY KEY

);

CREATE TABLE IF NOT EXISTS items (

item_id INTEGER PRIMARY KEY,

order_id INTEGER NOT NULL REFERENCES orders(order_id),

name TEXT NOT NULL,

quantity INTEGER NOT NULL CHECK (quantity > 0),

price_fen INTEGER NOT NULL CHECK (price_fen >= 0)

);

""")

execute("INSERT OR IGNORE INTO orders VALUES (?)", (2001,))

executemany(

"INSERT OR IGNORE INTO items VALUES (?, ?, ?, ?, ?)",

[(1, 2001, "Python book", 2, 3990),

(2, 2001, "Bookmark", 1, 1250)],

)

commit()

print(f"Ready: {DB}")

运行uv run python init_db.py,确认同目录出现bookshop.db。如果自行修改了示例数据,INSERT OR IGNORE不会覆盖原来的记录;请在新的练习目录初始化,或自行明确更新目标记录。

给模型一项范围明确的能力

查询函数只接受订单号,只返回订单金额。不要给第一个示例开放任意 SQL 或通用命令执行接口,否则学习工具调用的同时,还得处理更大的权限范围。

先把下面这一段保存为agent.py,后面的两段依次追加到同一个文件中:

import json

import os

import sqlite3

import sys

from contextlib import closing

from pathlib import Path

from ollama import chat

DB = Path(__file__).with_name("bookshop.db")

MODEL = os.getenv("OLLAMA_MODEL", "qwen3.5:4b")

def query_order_amount(order_id: int) -> str:

"""Return the recorded order amount in CNY.

Args:

order_id: Positive integer order number.

"""

uri = DB.resolve().as_uri() + "?mode=ro"

with closing(sqlite3.connect(uri, uri=True)) as conn:

row = conn.execute("""

SELECT o.order_id,

COALESCE(SUM(i.quantity * i.price_fen), 0)

FROM orders AS o

LEFT JOIN items AS i ON i.order_id = o.order_id

WHERE o.order_id = ?

GROUP BY o.order_id

""", (order_id,)).fetchone()

if row is None:

data = {"error": "ORDER_NOT_FOUND", "order_id": order_id}

else:

total = row[1]

data = {

"order_id": row[0], "currency": "CNY",

"amount": f"{total // 100}.{total % 100:02d}",

}

return json.dumps(data, ensure_ascii=False)

这里有三处值得保留:路径相对于脚本定位;连接使用只读模式,数据库不存在时不会偷偷创建空库;订单号通过?占位符传入,不拼接 SQL。closing会明确关闭连接,避免把事务上下文误当成连接关闭机制。

LEFT JOIN用来区分“不存在的订单”和“存在但没有商品的订单”。前者返回错误,后者金额为 0.00。金额返回为字符串,是为了保留两位小数;真实业务还应明确优惠、运费、税费和退款的计价规则。

在应用侧校验工具请求

函数的类型注解会帮助 SDK 生成参数说明,但它不会自动阻止错误类型进入函数。下面把名称白名单、字段检查和整数检查放在执行之前。继续追加到agent.py:

def execute_tool(call) -> str:

if call.function.name != "query_order_amount":

raise ValueError("Unknown tool")

args = call.function.arguments

if not isinstance(args, dict) or set(args) != {"order_id"}:

raise ValueError("Expected only order_id")

order_id = args["order_id"]

if type(order_id) is not int or order_id <= 0:

raise ValueError("order_id must be a positive integer")

print("Tool request:", call.function.name, args)

result = query_order_amount(order_id)

print("Tool result:", result)

return result

这里使用type(order_id) is int,会拒绝字符串、浮点数和布尔值。Python 中bool是int的子类,仅用isinstance(value, int)会把True也当作合法整数。

对这个练习来说,遇到非法请求直接停止就够了。后续需要自动纠错时,可以把结构化校验错误返回给模型,但应设置重试上限。也不要从模型返回的名称中动态查找或执行任意函数。

把执行结果放回对话

最后追加入口函数。第一轮允许模型请求工具,第二轮只要求它根据已取得的结果答复:

def main():

question = " ".join(sys.argv[1:]) or "订单 2001 一共多少钱?"

messages = [

{"role": "system", "content": (

"Answer in Chinese. Use query_order_amount for order amounts. "

"Do not invent shop facts. Preserve the returned amount and "

"currency. If data is unavailable, explain that limitation."

)},

{"role": "user", "content": question},

]

response = chat(model=MODEL, messages=messages,

tools=[query_order_amount])

calls = response.message.tool_calls or []

if len(calls) != 1:

raise RuntimeError("Expected exactly one tool call")

result = execute_tool(calls[0])

append(response.message)

append({

"role": "tool", "tool_name": calls[0].function.name,

"content": result,

})

final = chat(model=MODEL, messages=messages)

if final.message.tool_calls or not final.message.content.strip():

raise RuntimeError("Expected a final text answer")

print("Agent:", final.message.content)

if __name__ == "__main__":

main()

Ollama Python SDK 支持把函数直接放入tools,根据函数签名和文档字符串生成工具描述。[1] 我们保留第一轮的 assistant 消息,让模型在第二轮看到自己发出的请求,再用tool消息提供执行结果。

这个入口刻意规定只能调用一次工具。它适合观察一次完整交互,不是通用多步 Agent。第二轮不再提供工具列表,也没有无限循环;如果以后支持多步任务,应另外限制轮次、超时和累计执行成本。

验收时分别检查数据和模型行为

运行uv run python agent.py。正常请求应产生下面的工具记录,最后一句为示意,实际模型措辞可能不同:

Tool request: query_order_amount {'order_id': 2001}

Tool result: {"order_id": 2001, "currency": "CNY", "amount": "92.30"}

Agent: 订单 2001 的总金额是 92.30 元人民币。

工具记录正确,只能证明 Python 查到了正确值,不能保证模型一定忠实转述。如果金额不能容许任何偏差,更可靠的做法是由程序根据结构化结果直接生成金额句子,或在输出前核对模型答案。模型可以补充解释,但结算金额应以业务系统为准。

接着运行两类失败场景:

uv run python agent.py "订单 9999 一共多少钱?"

uv run python agent.py "订单 2001 什么时候送到?"

第一条应查得ORDER_NOT_FOUND,最终回答应说明订单不存在。第二条超出了工具能力:金额数据没有物流字段。模型可能不调用工具而触发入口异常,也可能调用金额工具后解释缺少物流数据;这两种结果都不能当成已经完成配送查询。如果它猜出到货日期,验收应判为失败。

排错时按结果发生的位置检查:

无法连接 Ollama:先确认应用或服务正在运行,再用ollama list检查模型是否已下载。

unable to open database file:确认先运行了初始化脚本,数据库与agent.py位于同一目录,且进程有读权限。

Expected exactly one tool call:检查问题是否属于金额查询,再查看模型返回的工具请求;系统提示无法代替执行侧检查。

order_id must be a positive integer:参数类型不合格,应修正请求或采用有上限的重试。

工具结果正确但回答错误:优先改用确定性的结果模板,再评估是否需要更换模型。

更换已安装且支持工具调用的模型,可以在当前 PowerShell 会话中设置$env:OLLAMA_MODEL = "你的模型名称"。比较模型时,记录它是否选对工具和参数,而不仅仅是最终句子是否流畅。

从练习脚本走向真实服务

上线前需要增加用户身份和订单归属校验。只读 SQL 与工具白名单能够缩小操作范围,但不能证明提问者有权查看某个订单;业务权限必须由可信应用上下文决定,不能相信模型自报的用户身份。

如果希望多人使用或长期提供查询,需要把脚本封装成服务,并补上进程管理、请求超时、日志脱敏、鉴权和访问限制。应用与推理服务可以分别部署,模型选择则按内存或显存、并发和可接受延迟评估。

迁移到远程环境时,可以评估 Hostease 或其他服务器资源,并按应用负载与模型推理需求确认配置。本地跑通只是迁移的起点,服务器选型不会替代权限控制和输出验收。

下一步建议先增加第二个只读工具,例如查询订单状态,然后用“金额”和“配送”两类问题检查模型是否能选对能力。把单工具流程验证清楚,再引入多步调度,会更容易判断错误来自数据、执行还是模型。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 13:11:46

SpringBoot+Vue3农产品预售平台架构设计与实践

1. 项目背景与核心价值农产品预售平台是近年来农业电商领域的热门方向&#xff0c;它解决了传统农产品销售中的三大痛点&#xff1a;供需匹配难、中间环节多、农户收益低。这套基于SpringBoot2Vue3MyBatis-PlusMySQL8.0的技术方案&#xff0c;正是针对这些行业痛点给出的现代化…

作者头像 李华
网站建设 2026/10/14 13:11:30

IPv6环境下App测试实战:从环境搭建到故障排查的完整指南

1. 为什么IPv6环境下的App测试不能简单套用IPv4经验很多团队在做移动端测试时&#xff0c;习惯性地把手机连上Wi-Fi、确认能上网&#xff0c;就开始跑用例。这套流程在IPv4环境下确实够用&#xff0c;但一旦切换到IPv6网络&#xff0c;问题就会以各种意想不到的方式冒出来。我见…

作者头像 李华
网站建设 2026/10/14 13:11:28

SAP查询操作全指南:从T-code到SE16N,高效准确查数据

简介&#xff1a;SAP查询操作办法.docx是一份面向财务会计、SAP模块顾问及系统运维人员的实操型操作手册&#xff0c;系统梳理财务模块常用查询事务代码与报表调用路径&#xff0c;帮助使用者绕开菜单层层查找&#xff0c;直接通过事务代码快速获取总账、应收、应付、资产等关键…

作者头像 李华
网站建设 2026/10/14 13:10:55

PowerBuilder集成CEF嵌入现代Web浏览器实战指南

简介&#xff1a;本资源是一份基于PowerBuilder 9开发的Web浏览器组件集成实践方案&#xff0c;面向PB企业级应用开发者及维护老旧XP系统的工程师&#xff0c;解决在传统客户端中嵌入网页导航、加载本地HTML与动态Web内容的核心需求。压缩包共88个文件&#xff0c;含16个JavaSc…

作者头像 李华
网站建设 2026/10/14 13:10:24

概念设计必修:solidThinking Inspire拓扑优化基础教程

简介&#xff1a;一份面向设计工程师、结构优化及数字产品开发初学者的solidThinking Inspire基础教程PDF&#xff0c;旨在帮助零基础用户快速上手这款基于Altair OptiStruct优化求解器的仿真驱动设计软件。资源包为单个PDF文件&#xff0c;压缩后约4.32MB&#xff0c;便于离线…

作者头像 李华