news 2026/9/26 7:25:21

Qwen-Agent本地部署实战:从零跑通智能体三层架构

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qwen-Agent本地部署实战:从零跑通智能体三层架构

1. 这不是“又一个大模型部署教程”,而是你真正能跑起来的 Qwen-Agent 实战路径

Qwen-Agent 不是玩具,它是一套面向真实业务场景的智能体开发框架——不是单纯调 API 的胶水代码,而是把规划(Planning)、记忆(Memory)、工具调用(Tool Calling)、多步推理(Multi-step Reasoning)全部封装进可复用、可调试、可监控的运行时。我去年在给一家本地政务服务平台做智能导办系统时,就踩着 Qwen-Agent 的源码一路摸到内核:它不依赖云端服务,所有决策链路都在本地可控;它不强制绑定某家模型,只要符合 OpenAI 兼容协议的模型都能接入;它甚至把「用户说一句‘帮我查下上个月的社保缴费记录’,Agent 自动拆解为:1. 调登录接口鉴权 → 2. 查个人账户信息 → 3. 拉取缴费明细 → 4. 汇总生成自然语言摘要」这个完整链条,变成 YAML 配置+Python 函数就能定义的模块。很多人卡在“本地部署”四个字上,以为只是下载个模型、起个服务、改个 config 就完事——错。真正的卡点在于:Agent 的执行引擎如何与本地模型通信?工具函数如何安全注入而不污染上下文?状态如何持久化避免对话中断就丢记忆?这些细节,官方文档一笔带过,社区教程要么照抄命令、要么只跑 demo。这篇就是从零开始,用一台 32GB 内存的 MacBook Pro M2 Max(无独显),实打实跑通全流程:从 Ollama 拉取 Qwen2.5-7B-Instruct,到启动 Qwen-Agent 的 Runtime Server,再到用 Flask 搭一个带历史记录、支持文件上传、能调用本地计算器和天气查询的对话页面。所有配置项我都做了横向对照——比如 Ollama 的--num_ctx参数、Qwen-Agent 的max_tokens、Flask 前端的stream开关,三者数值怎么配才不卡死、不截断、不丢 token。这不是理论推演,是我连续三天重启 47 次、重装 6 次依赖、抓包分析 12 个 HTTP 请求后,整理出的最小可行路径。

2. 为什么必须放弃“一键部署”幻觉:Qwen-Agent 的三层架构真相

Qwen-Agent 看似是一个 GitHub 仓库,但实际运行时是三个独立进程协同工作的结果。很多教程失败的根本原因,是把它们当成一个整体去启动,而忽略了每一层的职责边界和通信契约。我画过三张内存快照图,对比过 8 种启动顺序组合,最终确认:只有严格按「模型服务层 → Agent 运行时层 → 应用接入层」的顺序启动,并且每层之间用明确的协议对齐,才能稳定运行。这三层不是可选模块,而是硬性依赖链。

2.1 模型服务层:不是“随便找个模型就行”,而是协议兼容性生死线

Qwen-Agent 默认通过 OpenAI 兼容 API(即/v1/chat/completions)调用大模型。这意味着,你本地跑的模型服务,必须完全模拟 OpenAI 的请求/响应结构,连字段名、嵌套层级、错误码都不能差。我试过直接用 Transformers + FastAPI 手写接口,结果在 tool calling 场景下反复报invalid function call format——查了 6 小时才发现,OpenAI 的function_call字段要求是"function_call": {"name": "xxx", "arguments": "{...}"},而我的实现漏了arguments必须是 JSON 字符串(不是 dict),且name不能为空字符串。Ollama 之所以成为首选,不是因为它“简单”,而是它内置的 OpenAI 兼容层经过上百个模型验证,字段映射精准。但 Ollama 也有坑:它的--num_ctx参数控制上下文长度,而 Qwen-Agent 的max_tokens控制单次生成长度,两者必须满足max_tokens ≤ num_ctx - prompt_tokens,否则模型直接返回context_length_exceeded错误。我用 Qwen2.5-7B-Instruct 测试发现,当num_ctx=4096时,实际可用 prompt tokens 约 3800(预留 296 给 system prompt 和 tool schema),所以max_tokens最高只能设 2048。这个数字不是拍脑袋定的,是我在curl -X POST http://localhost:11434/v1/chat/completions里手动构造不同长度 prompt,观察 response 中usage.prompt_tokens变化后算出来的。

2.2 Agent 运行时层:核心是“状态机”而非“脚本”,必须理解其生命周期

Qwen-Agent 的Runtime不是传统意义上的 Web Server,而是一个事件驱动的状态机。它接收用户输入 → 触发 Planning 模块生成思维链 → 根据 tool schema 匹配可用工具 → 调用工具函数 → 收集结果 → 再次规划 → 直到生成最终回复。这个过程里,memory是关键变量。很多人以为 memory 就是聊天记录,其实不然:Qwen-Agent 的 memory 分三层——short-term(当前对话轮次的中间状态,存在内存里)、long-term(跨会话的结构化知识,需存数据库)、tool-result cache(工具调用结果缓存,避免重复计算)。默认配置里short-term用InMemoryChatHistory,看似省事,但一旦 Runtime 进程重启,整个对话历史就清空。我在政务项目里改成用 SQLite 存long-term memory,表结构就两个字段:session_id TEXT, message JSON,每次add_message()前先INSERT OR REPLACE,get_messages()时按session_id查询并json.loads()。这样即使 Flask 重启,用户换个浏览器再打开,只要传相同的session_id,Agent 还能接着上次的逻辑往下走。这个改动只加了 12 行代码,但让整个系统从“演示 Demo”变成了“可上线产品”。

2.3 应用接入层:前端不是“展示层”,而是协议翻译器

绝大多数教程教你怎么用 curl 测试 Agent,却没人告诉你:真实用户不会敲命令行。而把 Agent 接入网页,难点不在 UI,而在流式响应(streaming)的协议转换。Qwen-Agent 的 Runtime 默认返回 SSE(Server-Sent Events),但 Flask 的stream_with_context返回的是 chunked transfer encoding,两者 event 格式不兼容。我试过直接return Response(stream_with_context(...), content_type='text/event-stream'),结果前端EventSource收到的全是乱码。后来发现,必须手动拼接 SSE 格式:每个 chunk 要以data:开头,结尾加两个换行\n\n,错误时发event: error\ndata: {...}\n\n。更麻烦的是,Qwen-Agent 的 streaming 响应里,tool call 的中间步骤(如{"type":"tool_call","name":"get_weather","args":"{...}"})和最终回复({"type":"final_answer","content":"..."})混在一个 stream 里,前端必须自己解析type字段来决定渲染逻辑——是显示“正在查询天气”,还是插入一张天气卡片,还是直接追加文字。这个解析逻辑,我封装成一个parseQwenStream工具函数,37 行 TypeScript,现在成了我们所有 Agent 项目的标配。

3. 从零开始的实操:四步落地,每一步都附真实终端日志

别被“从零开始”吓住。我拆解成四个原子操作,每个操作都有明确的验证标准。只要终端输出匹配我写的预期日志,就说明这一步成功了。全程不用 sudo,不碰 Docker,纯 Python + Ollama,Mac/Windows/Linux 通用。

3.1 第一步:安装并验证 Ollama 模型服务(耗时约 3 分钟)

先确认 Ollama 已安装(官网下载 dmg 或 exe,双击安装即可)。打开终端,执行:

ollama list

如果返回空列表,说明服务正常但没模型。接着拉取 Qwen2.5-7B-Instruct(这是目前本地部署效果最好、显存占用最友好的 Qwen 系列):

ollama pull qwen2.5:7b-instruct-q4_k_m

注意参数q4_k_m:这是量化等级,q4_k_m表示 4-bit 量化,M 级精度,在 16GB 内存机器上也能跑,比q8版本快 2.3 倍,质量损失不到 1.2%(我用 MMLU 测过)。拉取完成后,用官方测试命令验证:

ollama run qwen2.5:7b-instruct-q4_k_m "你好,请用中文介绍你自己"

预期输出(截取关键部分):

我是通义千问,由通义实验室研发的超大规模语言模型。我能够回答问题、创作文字,比如写故事、写公文、写邮件、写剧本、逻辑推理、编程等等,还能表达观点,玩游戏等。 >>>

看到>>>提示符,说明模型加载成功,可以交互。此时 Ollama 服务已在http://localhost:11434运行,这是后续所有通信的基础地址。

3.2 第二步:安装 Qwen-Agent 并启动 Runtime(耗时约 2 分钟)

新建项目目录,创建虚拟环境:

mkdir qwen-agent-demo && cd qwen-agent-demo python3 -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate

安装 Qwen-Agent(注意:必须用--no-deps,否则会装一堆冲突的旧版依赖):

pip install --no-deps qwen-agent pip install -r https://raw.githubusercontent.com/QwenLM/Qwen-Agent/main/requirements.txt

创建配置文件config.yaml:

llm: model: 'qwen2.5:7b-instruct-q4_k_m' model_type: 'qwen' api_base: 'http://localhost:11434/v1' api_key: 'ollama' # Ollama 不需要 key,但 Qwen-Agent 强制要求非空 max_tokens: 2048 temperature: 0.7 top_p: 0.9 tools: - name: 'calculator' description: 'Perform basic arithmetic operations like addition, subtraction, multiplication, division.' parameters: expression: type: string description: 'The arithmetic expression to evaluate, e.g., "2 + 2 * 3".' - name: 'get_weather' description: 'Get current weather information for a city.' parameters: city: type: string description: 'The name of the city, e.g., "Beijing".'

启动 Runtime:

qwen_agent_runtime --config config.yaml --host 0.0.0.0 --port 3000

成功标志是终端最后几行:

INFO: Uvicorn running on http://0.0.0.0:3000 (Press CTRL+C to quit) INFO: Started reloader process [12345] INFO: Started server process [12346] INFO: Waiting for application startup. INFO: Application startup complete.

此时访问http://localhost:3000/docs应能看到 Swagger UI,证明 Runtime 已就绪。注意:--host 0.0.0.0是为了让 Flask 前端能跨域访问,不是安全风险——因为这只是本地开发环境,生产环境必须加反向代理和认证。

3.3 第三步:编写工具函数并注册(耗时约 5 分钟)

工具函数不是写完就完事,必须符合 Qwen-Agent 的签名规范:函数名即name,参数必须用**kwargs接收,返回值必须是dict且含result字段。创建tools.py:

import requests import subprocess def calculator(**kwargs): """Calculate arithmetic expression""" try: # 使用 Python eval,仅限本地可信环境 result = eval(kwargs.get('expression', '0')) return {'result': str(result)} except Exception as e: return {'error': f'Calculation failed: {str(e)}'} def get_weather(**kwargs): """Get weather for city (mock implementation)""" city = kwargs.get('city', 'Beijing') # 实际项目中这里调用真实天气 API,此处用 mock 数据 mock_data = { 'Beijing': {'temp': 22, 'condition': 'Sunny', 'humidity': 45}, 'Shanghai': {'temp': 28, 'condition': 'Cloudy', 'humidity': 72}, 'Guangzhou': {'temp': 31, 'condition': 'Rainy', 'humidity': 88} } data = mock_data.get(city, {'temp': 15, 'condition': 'Unknown', 'humidity': 50}) return {'result': f"Weather in {city}: {data['condition']}, {data['temp']}°C, humidity {data['humidity']}%"} # 注册工具(关键!) from qwen_agent.llm import get_chat_model from qwen_agent.tools import register_tool register_tool(calculator, async_mode=False) register_tool(get_weather, async_mode=False)

然后修改config.yaml,在tools下添加:

tools: - name: 'calculator' ... - name: 'get_weather' ... - name: 'file_reader' # 新增一个读文件工具 description: 'Read and summarize content from uploaded text files.' parameters: file_path: type: string description: 'Local path to the text file.'

对应在tools.py里加:

def file_reader(**kwargs): """Read local text file""" try: with open(kwargs.get('file_path', ''), 'r', encoding='utf-8') as f: content = f.read()[:2000] # 限制长度防爆内存 return {'result': f"File content (first 2000 chars): {content}"} except Exception as e: return {'error': f'Failed to read file: {str(e)}'} register_tool(file_reader, async_mode=False)

重启 Runtime,Swagger UI 的/v1/chat/completions接口文档里,tools列表就会多出file_reader。这就是注册生效的证据。

3.4 第四步:搭建 Flask 对话页面(耗时约 8 分钟)

创建app.py:

from flask import Flask, render_template, request, jsonify, Response import requests import json import uuid app = Flask(__name__) SESSIONS = {} # 简单内存 session,生产环境换 Redis @app.route('/') def index(): return render_template('index.html') @app.route('/chat', methods=['POST']) def chat(): data = request.json user_input = data.get('message', '') session_id = data.get('session_id', str(uuid.uuid4())) if session_id not in SESSIONS: SESSIONS[session_id] = [] # 构造 Qwen-Agent 请求 payload = { "messages": [ {"role": "user", "content": user_input} ], "stream": True, "session_id": session_id } def generate(): try: with requests.post( 'http://localhost:3000/v1/chat/completions', json=payload, stream=True, timeout=(10, 60) ) as r: for line in r.iter_lines(): if line: line = line.decode('utf-8').strip() if line.startswith('data: '): try: chunk = json.loads(line[6:]) yield f"data: {json.dumps(chunk)}\n\n" except json.JSONDecodeError: continue except requests.exceptions.RequestException as e: yield f"event: error\ndata: {{\"error\": \"{str(e)}\"}}\n\n" return Response(generate(), mimetype='text/event-stream') if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)

创建templates/index.html:

<!DOCTYPE html> <html> <head><title>Qwen-Agent Demo</title></head> <body> <div id="chat-container"> <div id="messages"></div> <input type="text" id="user-input" placeholder="Type your message..." /> <button onclick="sendMessage()">Send</button> </div> <script> let sessionId = localStorage.getItem('sessionId') || ''; if (!sessionId) { sessionId = Date.now().toString(36) + Math.random().toString(36).substr(2, 9); localStorage.setItem('sessionId', sessionId); } function appendMessage(role, content) { const messages = document.getElementById('messages'); const div = document.createElement('div'); div.innerHTML = `<strong>${role}:</strong> ${content}`; messages.appendChild(div); messages.scrollTop = messages.scrollHeight; } async function sendMessage() { const input = document.getElementById('user-input'); const message = input.value.trim(); if (!message) return; appendMessage('You', message); input.value = ''; const eventSource = new EventSource(`/chat?session_id=${sessionId}`); eventSource.onmessage = function(event) { const data = JSON.parse(event.data); if (data.type === 'final_answer') { appendMessage('Agent', data.content); eventSource.close(); } else if (data.type === 'tool_call') { appendMessage('Agent', `Calling ${data.name} with ${JSON.stringify(data.args)}`); } }; eventSource.onerror = function(err) { appendMessage('Error', 'Connection failed. Check if backend is running.'); eventSource.close(); }; } </script> </body> </html>

启动 Flask:

python app.py

访问http://localhost:5000,输入计算 123*456,应该看到 Agent 先回复“正在调用计算器”,然后给出正确结果56088。这就是端到端跑通的铁证。

4. 配置对照表:那些让你崩溃的参数,到底该怎么配

参数不是随便填的数字,而是各层之间的契约。下面这张表,是我用 17 个不同模型、在 3 种硬件配置(16GB/32GB/64GB)上实测 216 次后总结的黄金配比。左边是参数名,中间是推荐值,右边是“为什么这么配”的底层逻辑。

参数位置参数名推荐值为什么必须这么配(含计算过程)
Ollama 启动命令--num_ctx4096(Qwen2.5-7B)
8192(Qwen2.5-14B)
上下文长度决定模型能“记住”多少。Qwen2.5-7B 的最大 context 是 32K,但 Ollama 默认只给 2048。实测发现,设为 4096 时,prompt_tokens占用稳定在 3800 左右(system prompt 256 + tool schema 320 + history 3200),留出 296 token 给max_tokens。设太高(如 8192)会导致显存暴涨,M2 Max 直接 OOM。
Qwen-Agent config.yamlmax_tokens2048(Qwen2.5-7B)
4096(Qwen2.5-14B)
必须 ≤num_ctx - prompt_tokens。前面算出prompt_tokens ≈ 3800,所以max_tokens最大只能4096 - 3800 = 296?错。因为prompt_tokens是动态的——history 越长,它越大。所以取保守值2048,确保即使 history 达到 10 轮(每轮平均 300 tokens),3800 + 3000 = 6800 > 4096,这时 Ollama 会自动 truncation,但max_tokens还能保证生成长度。
Qwen-Agent config.yamltemperature0.7太低(0.1)导致回复僵硬,像机器人念稿;太高(0.95)导致幻觉率飙升。我用 100 条政务咨询语料测试,temperature=0.7时准确率 89.3%,0.9时跌到 72.1%。0.7 是效果和稳定性平衡点。
Flask app.pytimeout=(10, 60)(10, 60)第一个数10是连接超时(connect timeout),必须够短,否则用户点击发送后卡 30 秒才报错;第二个数60是读取超时(read timeout),必须够长,因为 tool call 可能要调外部 API。设太短(如 10),天气查询这种网络请求必失败。
Ollama 模型名qwen2.5:7b-instruct-q4_k_m必须带-q4_k_m后缀不同量化后缀内存占用天差地别:q8占 14GB,q4_k_m占 5.2GB,q2_k占 3.8GB。但q2_k在复杂 reasoning 任务上错误率比q4_k_m高 17%(MMLU 测试)。所以q4_k_m是性价比最优解。
Qwen-Agent config.yamlapi_key'ollama'(任意非空字符串)Qwen-Agent 的 LLM 类强制校验api_key非空,但 Ollama 根本不用 key。填'ollama'是约定俗成的占位符,填' '会报错,填'123'也行,但'ollama'一看就知道是本地模式。

这张表不是凭空写的。比如timeout参数,我故意把read timeout设成10,然后发一条查询北京天气,结果 Flask 日志里全是ReadTimeout,前端卡死。改成60后,同样请求 100% 成功。再比如api_key,我试过填空字符串,Qwen-Agent 启动时报AttributeError: 'NoneType' object has no attribute 'strip',源码里llm/base.py第 87 行self.api_key.strip()没判空。这些坑,都得亲手踩过才敢写进表里。

5. 常见问题与排查技巧实录:那些让我凌晨三点还在看日志的瞬间

部署不是一帆风顺的。我把最常遇到的 7 个问题,按发生频率排序,每个都附上真实 terminal 日志、根本原因、三步解决法。这些问题,90% 的人会卡超过 2 小时,而你知道答案后,3 分钟就能解决。

5.1 问题 1:Runtime 启动报错ModuleNotFoundError: No module named 'openai'

现象:执行qwen_agent_runtime --config config.yaml后,终端立刻退出,报错:

Traceback (most recent call last): File "/path/to/venv/bin/qwen_agent_runtime", line 5, in <module> from qwen_agent.runtime import main File "/path/to/venv/lib/python3.11/site-packages/qwen_agent/runtime.py", line 12, in <module> from openai import OpenAI ModuleNotFoundError: No module named 'openai'

根本原因:Qwen-Agent 依赖openai包做类型提示和部分工具,但pip install qwen-agent时--no-deps跳过了它。这不是 bug,是设计选择——因为你要用 Ollama,就不该装官方 OpenAI SDK。

三步解决法:

  1. 执行pip install openai==1.35.13(必须指定版本,新版 1.40+ 有 breaking change)
  2. 检查venv/lib/python3.11/site-packages/openai/__init__.py是否存在,确认安装成功
  3. 重新运行qwen_agent_runtime,错误消失

提示:不要用pip install --upgrade openai,新版会破坏 Qwen-Agent 的BaseModel兼容性,导致tool schema解析失败。

5.2 问题 2:前端收到event: error,内容是Connection refused

现象:Flask 页面打开正常,但点击 Send 后,浏览器 console 显示:

EventSource failed to connect: error

同时 Flask 日志里有:

requests.exceptions.ConnectionError: HTTPConnectionPool(host='localhost', port=3000): Max retries exceeded...

根本原因:Qwen-Agent Runtime 没在运行,或者端口被占用。qwen_agent_runtime默认监听0.0.0.0:3000,如果之前异常退出,端口可能没释放。

三步解决法:

  1. 执行lsof -i :3000(Mac/Linux)或netstat -ano | findstr :3000(Windows),找到占用进程 PID
  2. 执行kill -9 PID(Mac/Linux)或taskkill /PID PID /F(Windows)强制结束
  3. 重新运行qwen_agent_runtime --config config.yaml --host 0.0.0.0 --port 3000

注意:不要用--port 3001换端口,因为 Flask 代码里写死http://localhost:3000,改端口就得改代码,徒增风险。

5.3 问题 3:Agent 死循环调用同一个 tool,比如不停查天气

现象:输入北京天气怎么样,Agent 回复:

正在调用 get_weather 工具... 正在调用 get_weather 工具... 正在调用 get_weather 工具...

然后卡住,不再生成最终回复。

根本原因:tool 函数返回格式错误。Qwen-Agent 要求{'result': 'xxx'},如果你返回{'weather': 'Sunny'}或{'result': {'temp': 22}},Agent 无法识别,就认为 tool 调用失败,触发重试机制。

三步解决法:

  1. 在tools.py的get_weather函数末尾加一行print(f"Tool return: {ret}"),看实际返回什么
  2. 确保返回字典只有result键,且值是字符串(不是 dict、list)
  3. 重启 Runtime,问题消失

实操心得:我第一次写file_reader时,返回了{'content': 'xxx'},结果 Agent 死循环。改成{'result': 'xxx'}立刻正常。这个result键名是硬编码在 Qwen-Agent 源码里的,不能改。

5.4 问题 4:Ollama 拉取模型超时,报dial tcp: lookup registry.ollama.ai: no such host

现象:执行ollama pull qwen2.5:7b-instruct-q4_k_m卡住,几分钟后报 DNS 错误。

根本原因:国内网络访问registry.ollama.ai不稳定,不是墙的问题,是域名解析慢。

三步解决法:

  1. 打开终端,执行curl -v https://registry.ollama.ai,看是否能连通
  2. 如果超时,临时换 DNS:sudo networksetup -setdnsservers Wi-Fi 223.5.5.5 114.114.114.114(Mac)或修改网络适配器 DNS(Windows)
  3. 再执行ollama pull,速度立竿见影

注意:这只是临时方案。长期建议用ollama create命令从本地 GGUF 文件构建模型,彻底绕过网络。

5.5 问题 5:Flask 页面发送消息后,Agent 回复中文乱码,显示查询北京天水

现象:输入查询北京天气,Agent 返回查询北京天水,明显是 UTF-8 字节被当 Latin-1 解码。

根本原因:Flask 的Response默认 charset 是ISO-8859-1,而 Qwen-Agent 返回的是 UTF-8 编码的 JSON。

三步解决法:

  1. 修改app.py的generate()函数,在yield前加line = line.encode('utf-8').decode('utf-8')(看似多余,实则强制 utf-8)
  2. 更关键的是,在Response创建时指定 charset:Response(generate(), mimetype='text/event-stream; charset=utf-8')
  3. 重启 Flask,乱码消失

实操心得:这个 bug 在 Chrome 里不明显,但在 Safari 里必现。因为 Safari 对 charset 更严格。

5.6 问题 6:Runtime 启动后,Swagger UI 里/v1/chat/completions的Try it out按钮点不动

现象:打开http://localhost:3000/docs,找到接口,填好messages,点Execute,按钮变灰,无反应。

根本原因:Swagger UI 的 CORS 策略阻止了跨域请求。虽然你在qwen_agent_runtime启动时加了--host 0.0.0.0,但默认没开 CORS。

三步解决法:

  1. 停止 Runtime
  2. 重新启动时加--cors-allow-origin "*"参数:
    qwen_agent_runtime --config config.yaml --host 0.0.0.0 --port 3000 --cors-allow-origin "*"
  3. 刷新 Swagger 页面,按钮恢复正常

提示:生产环境绝不能用*,必须指定你的前端域名,如--cors-allow-origin "http://localhost:5000"。

5.7 问题 7:Agent 能调用 tool,但最终回复里没有final_answer,只有tool_result

现象:输入计算 2+2,Agent 先返回{"type":"tool_call","name":"calculator","args":"{\"expression\":\"2+2\"}"},然后返回{"type":"tool_result","result":"4"},但再也不发{"type":"final_answer","content":"2+2=4"}。

根本原因:Qwen-Agent 的planning模块没被触发。通常是因为messages里缺少system角色的 prompt,或者model配置指向了不支持 tool calling 的模型。

三步解决法:

  1. 检查config.yaml的llm.model是否确实是qwen2.5:7b-instruct-q4_k_m(不是qwen2.5:7b,后者是 base 模型,没 instruction tuning)
  2. 在 Flask 的payload里,强制加上 system message:
    "messages": [ {"role": "system", "content": "You are a helpful AI assistant. Use tools when needed."}, {"role": "user", "content": user_input} ],
  3. 重启 Runtime,问题解决

实操心得:Qwen2.5 系列里,只有instruct后缀的模型才微调过 tool calling 能力。base 模型即使有 schema,也只会 ignore。

6. 我的真实体会:本地部署的价值,从来不在“能不能跑”,而在“敢不敢改”

跑通 Qwen-Agent 本地部署,对我而言,最大的收获不是技术本身,而是心态转变。以前做智能客服,所有逻辑都写在 prompt 里,模型一升级,整个流程就崩;现在,我把 80% 的业务规则写进 tool 函数里,模型只负责“思考怎么调用”,具体“怎么查数据”“怎么算结果”全在 Python 里。上周客户提了个新需求:“用户问‘我上个月交了多少社保’,要自动从 Excel 表里拉数据”。如果是云端方案,得等厂商排期、改 API、测一周;而我现在,10 分钟写个read_exceltool,3 分钟注册,5 分钟测试,上线。更关键的是,所有数据不出内网,审计报告里“数据本地化”这一条,直接打钩。Qwen-Agent 的本地部署,不是为了炫技,而是为了把 AI 的控制权,从 API Key 手里,夺回到工程师手里。它让我相信:真正的 AI 应用,不该是黑盒调用,而应该是白盒组装——就像搭乐高,模型是基础积木,Agent 是连接件,tool 是功能模块,而你,才是那个决定怎么拼的人。

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

多版本状态机架构:从可观测到可认知的演进路径

这几年我一直被一个问题缠着&#xff1a;状态机、多版本、可观测、可认知&#xff0c;这四个词放在一起到底意味着什么。先讲个真实场景&#xff0c;我接手过一套支付核心系统&#xff0c;状态机逻辑已经迭代到第四个版本&#xff0c;但线上同时跑着 v2 和 v4 两套状态引擎。系…

作者头像 李华
网站建设 2026/9/26 7:23:23

LintCode刷题必备:Java与Python双实现算法代码包详解

简介&#xff1a;这份压缩包提供基于 Java 与 Python 双语实现的 lintcode 算法与数据结构题解&#xff0c;面向毕业设计准备者、算法初学者及求职备考的开发者&#xff0c;用于通过实际编码吃透经典题目与核心思想。资源按日期与题号分目录组织&#xff0c;每道题均配有思路说…

作者头像 李华
网站建设 2026/9/26 7:23:01

Python爬虫实战:构建CSDN技术趋势分析雷达

当一个技术方向突然从社区里冒出来、到处都在讨论的时候&#xff0c;你通常已经错过了最佳的学习窗口。我一直在想能不能有个东西&#xff0c;可以像雷达一样持续扫描技术社区里的讨论热度&#xff0c;提前嗅到趋势的变化。这个"Python爬虫实战&#xff1a;构建技术趋势分…

作者头像 李华
网站建设 2026/9/26 7:23:01

3个真正能跑起来的开源AI视频工程方案

1. 这不是“又一个AI视频工具合集”&#xff0c;而是能真正跑起来的工程级方案 最近在技术社区刷到不少标题党文章&#xff0c;动辄“10个爆火AI视频项目”“全网最全文生视频开源库”&#xff0c;点进去一看全是GitHub Star数截图三行简介失效链接。我花了整整两周时间&#…

作者头像 李华
网站建设 2026/9/26 7:22:06

渗透测试Fuzz实战:从底层逻辑到环境搭建与进阶玩法

1. 渗透测试与Fuzz的底层逻辑拆解1.1 从一次真实项目说起&#xff1a;为什么要用Fuzz去年接手一个物联网设备的固件审计项目&#xff0c;设备通过MQTT协议与云端通信&#xff0c;固件里跑着一个用C写的协议解析模块。手工构造了几十个畸形报文&#xff0c;测了两天&#xff0c;…

作者头像 李华