这里写目录标题条件实现原理代码特别说明1. 安全地“拆快递”防御性编程2. 核心动作拼接文本条件需要能支持tool calling的模型比如我用的qwen3.5实现原理发请求把 messages 发给 AI。看返回如果返回里有 tool_calls 说明 AI 需要帮忙代码去执行真实函数把结果追加到 messages继续下一次循环。如果返回里只有 content没有 tool_calls说明 AI 已经拿到了所有信息并整理好了答案退出循环打印结果。代码importrequestsimportjson# # 1. 预制“真家伙”真实的工具函数# defget_weather(city:str)-str: 这是一个模拟查询天气的函数。 在真实场景中这里会是调用第三方天气API的代码。 # 实际开发中这里可以替换为真实的 HTTP 请求returnf{city}现在的天气是晴天气温 25℃。# 工具注册表把函数名和函数本身关联起来方便后面通过名字调用TOOL_FUNCS{get_weather:get_weather}# # 2. 预制“说明书”Ollama 格式的工具 Schema# # 这是给 AI 看的“工具使用手册”告诉它有哪些工具可以用以及怎么用。OLLAMA_TOOLS[{type:function,# 工具类型函数function:{name:get_weather,# 函数名必须和 TOOL_FUNCS 里的键一致description:获取指定城市的当前天气情况,# 函数功能描述AI 会根据这个决定何时调用parameters:{# 函数的参数定义遵循 JSON Schema 规范type:object,properties:{city:{type:string,description:需要查询天气的城市名称}},required:[city]# 指明哪些参数是必须的}}}]# # 3. 核心Agent 循环逻辑# defrun_ollama_agent(user_query:str):# 初始化对话上下文messages# 这是 Agent 的“记忆”它会带着这份记忆和 AI 进行多轮对话messages[{role:system,content:你是一个有用的助手需要时使用工具获取信息。},{role:user,content:user_query}]# Ollama API 地址urlhttp://xxxxx/api/chat# 开启 Agent Loop防止死循环最多循环10次forstepinrange(10):print(f\n--- 第{step1}步调用 Ollama 模型 ---)# 构建发送给 Ollama 的请求体payload{model:qwen3:8b,# 指定使用的模型messages:messages,# 把完整的对话历史发给模型让它基于上下文思考keep_alive:1h,tools:OLLAMA_TOOLS,# 把“工具说明书”也发给模型stream:False# 设置为 False让 API 一次性返回完整结果但 Ollama 底层仍是流式}# 发送请求给 Ollamatry:responserequests.post(url,jsonpayload,timeout(60,1800))response.raise_for_status()# 检查 HTTP 请求是否成功# --- 开始处理 Ollama 的流式响应 ---# 即使 streamFalseOllama 的 /api/chat 接口返回的仍是一个 JSON 流# 我们需要逐行读取并拼接直到收到最后一块数据full_contentassistant_msg{role:assistant,content:}forlineinresponse.iter_lines():ifline:try:# 将每一行字节流解码为 JSON 对象json_linejson.loads(line.decode(utf-8))# 拼接文本内容content 字段在每个数据块里都有ifmessageinjson_lineandcontentinjson_line[message]:full_contentjson_line[message][content]# 检查是否是最后一个数据块 (doneTrue)ifjson_line.get(done):assistant_msg[content]full_content# 保存最终拼接的完整文本# 关键逻辑只在最后一个数据块中检查是否有工具调用请求# tool_calls 字段只会出现在流式响应的最后一个 JSON 对象中ifmessageinjson_lineandtool_callsinjson_line[message]:assistant_msg[tool_calls]json_line[message][tool_calls]break# 收到最后一块数据跳出循环exceptjson.JSONDecodeError:continue# 跳过无法解析的行# 将模型的完整回复可能包含 tool_calls追加到对话历史中messages.append(assistant_msg)exceptrequests.exceptions.ConnectionError:print(❌ 连接失败请检查网络或Ollama服务)breakexceptrequests.exceptions.Timeout:print(❌ 请求超时)breakexceptExceptionase:print(f❌ 发生未知错误:{e})break# --- Agent 决策逻辑 ---# 判定1如果回复中没有 tool_calls说明 AI 认为任务已完成可以直接回答用户iftool_callsnotinassistant_msg:print(✅ 模型完成思考输出最终回复。)returnassistant_msg[content]# 判定2如果回复中有 tool_calls说明 AI 需要调用工具来获取信息fortool_callinassistant_msg[tool_calls]:func_nametool_call[function][name]# 获取要调用的函数名func_argstool_call[function][arguments]# 获取函数参数print(f 执行工具:{func_name}参数:{func_args})# 在工具注册表中找到对应的真实函数并执行resultTOOL_FUNCS[func_name](**func_args)print(f 工具返回结果:{result})# 将工具的执行结果以 tool 角色追加到对话历史中# 这样在下一次循环时AI 就能看到工具返回的数据了messages.append({role:tool,content:result})# # 4. 启动 Agent# if__name____main__:# 启动 Agent 并传入用户的问题final_answerrun_ollama_agent(南京和成都的天气怎么样)print(\n最终答案,final_answer)特别说明forlineinresponse.iter_lines():ifline:try:# 将每一行字节流解码为 JSON 对象json_linejson.loads(line.decode(utf-8))# 拼接文本内容content 字段在每个数据块里都有ifmessageinjson_lineandcontentinjson_line[message]:full_contentjson_line[message][content]# 检查是否是最后一个数据块 (doneTrue)ifjson_line.get(done):assistant_msg[content]full_content# 保存最终拼接的完整文本# 关键逻辑只在最后一个数据块中检查是否有工具调用请求# tool_calls 字段只会出现在流式响应的最后一个 JSON 对象中ifmessageinjson_lineandtool_callsinjson_line[message]:assistant_msg[tool_calls]json_line[message][tool_calls]break# 收到最后一块数据跳出循环exceptjson.JSONDecodeError:continue# 跳过无法解析的行中详细解释下这个代码 # 拼接文本内容content 字段在每个数据块里都有ifmessageinjson_lineandcontentinjson_line[message]:full_contentjson_line[message][content]这段代码的核心作用是在流式Streaming响应中像拼图一样把 AI 吐出来的一小块一小块的文本拼凑成一句完整的话。虽然在请求头里设置了stream: False但 Ollama 的底层 API 实际上返回的仍然是一个换行符分隔的 JSON 流JSON Lines。这意味着 AI 不会一次性把一大段话发给你而是像打字机一样一个字或一个词地往外蹦每蹦出一个片段就包装成一个 JSON 发给你。我们来逐行拆解这段代码1. 安全地“拆快递”防御性编程ifmessageinjson_lineandcontentinjson_line[message]:因为 AI 返回的每一个 JSON 数据块结构可能不一样。有些数据块里只有进度信息没有message字段有些有message但可能只有thinking思考过程而没有content最终回复。2. 核心动作拼接文本full_contentjson_line[message][content]假设 AI 要回复“你好”它可能会分三次发给你* 第 1 个数据块{message: {content: 你}}* 第 2 个数据块{message: {content: 好}}* 第 3 个数据块{message: {content: }}作用 这行代码就是把这三个片段依次累加到full_content变量里。经过三次循环full_content就会变成完整的你好。这段代码就是一个**“流式文本拼接器”**。它一边接收 AI 吐出的碎片一边把它们拼成完整的句子同时还能保证在遇到残缺数据时不崩溃。等最后拼完了再把完整的full_content交给后面的逻辑去处理。