
在实际项目里把一个大模型接进业务系统并不难难的是让它从一个“你问它答”的问答接口变成一个能查数据、调接口、做决策的自主智能体。阿里云 Qwen千问系列模型在当前的模型版本中已经支持工具调用Function Calling配合阿里云百炼平台的托管能力和开源部署方案已经可以搭出一条从问答到自主智能体的完整路径。这篇文章会沿着这条路径展开先把 Qwen 的最小问答链路跑通再解释 Function Calling 为什么是智能体的核心机制然后补上记忆层Embedding Milvus 检索最后给出生产环境的落地建议和排错清单。学完以后你可以在自己的项目里实现一个能查询业务数据、调用内部接口并且带检索记忆的智能体服务。1. 先理解“问答”和“自主智能体”的边界1.1 问答模型解决的是“生成”不是“行动”一个尚未集成任何工具的 Qwen 问答接口本质上完成的是“文本生成”任务。你给它一段用户问题它根据训练时学习到的知识、当前上下文和生成参数返回一段最可能的文本。这个过程的输入输出都是文本模型没有权限去查数据库、没有能力去调用 HTTP 接口也不会主动更新自己的知识。这也是很多项目把模型接上线以后发现它只能“聊”不能“干”的原因。在工程上纯问答模型适合的场景包括客服话术生成、代码注释、文档摘要、翻译、规则问答。这些场景的共同点是答案要么藏在模型的参数里要么已经出现在用户提供的上下文里。一旦问题需要“实时数据”例如“查一下这个订单现在到哪了”纯问答模型就失效了因为它并不知道订单系统的当前状态。1.2 自主智能体多了四个能力模块从问答模型升级到自主智能体不是换一个更大的模型而是要在模型外面补一套决策和执行的机制。业内通常把智能体拆成四个部分大脑负责理解用户意图、拆解任务、决定下一步动作。这里仍然是 Qwen 模型但每次请求的输入不再只是用户问题而是“系统提示词 历史对话 工具定义 当前任务”。工具模型本身不能直接执行动作必须通过一段代码或一个 API 向模型暴露能力。常见的工具包括查询订单接口、天气接口、数据库查询、文件读写、网页检索。记忆分为短期记忆和长期记忆。短期记忆是对话历史长期记忆通常用向量数据库保存业务知识通过检索把最相关的内容注入上下文。循环智能体不是一次请求就结束。模型先输出“我准备调用某个工具参数是什么”程序去执行工具把工具结果回填给模型模型再决定继续调用下一个工具还是给出最终答案。这个“计划 - 调用 - 观察 - 再计划”的循环就是智能体与普通问答的本质区别。1.3 当前工程落地的形态人机协同为主有限自主执行从当前实际落地的项目看完整意义上的“全自主智能体”在大多数业务场景里还很少见。常见形态是“人机协同为主、有限自主执行”系统允许智能体在限定范围内自主调用只读查询类工具但涉及写操作、支付、删除、发送消息等高风险动作时必须回到人工确认。这个设计不是技术做不到而是为了可控。你在实现智能体时建议把工具按风险分级风险级别工具示例执行策略低风险只读查询订单状态、查天气、检索知识库智能体自主执行中风险写操作修改草稿、发送测试消息记录日志后执行可回滚高风险动作支付、删除数据、对外发布生成待确认动作人工确认后执行2. 环境准备Qwen 接入的三种方式2.1 方式一通过阿里云百炼 API 接入阿里云百炼Model Studio是 Qwen 系列模型的托管平台。它的优势在于不需要自己准备 GPU 服务器开通服务后拿到 API Key 就可以调用。目前百炼提供兼容 OpenAI Chat Completions 格式的接口因此主流的 Python、Java、Node.js 等语言都能快速接入。接入前需要准备一个阿里云账号并开通百炼服务。在控制台创建 API Key。注意 Key 只显示一次要立即保存。确定调用模型名。常见的有 qwen-plus、qwen-turbo以及工具调用能力更完整的 qwen-max 等。不同模型名对应的上下文长度和费用不同落地前要去百炼控制台确认最新的模型列表。注意api_key 不要硬编码在代码里。学习阶段可以放环境变量生产阶段必须放到密钥管理服务或者配置中心。2.2 方式二把 Qwen 部署到自己的服务器如果业务有数据隔离要求或者调用量很大、长期使用可以考虑在自有服务器上部署 Qwen 开源模型。Qwen 提供了多个参数规模的开源版本。参数越小部署越容易但能力越弱参数越大能力越强对 GPU 显存要求越高。以常见情况为例模型规模显存需求示例适用场景小参数模型消费级显卡或小显存实例学习、原型验证、简单问答中等规模单张企业级 GPU专业问答、代码生成大规模模型多卡部署或分布式推理复杂推理、工具调用、生产环境本地部署需要额外处理模型下载、推理框架、GPU 驱动、并发排队等问题。建议先在开发机跑通再迁移到云端 GPU 实例。OpenAI 兼容层的部署配置以官方部署文档为准不要凭记忆猜测版本参数。2.3 方式三Java 项目里配置阿里云 Maven 仓库和 Spring Initializr如果项目是 Java 技术栈第一个遇到的问题常常不是代码而是依赖下载。在 Maven 的 settings.xml 中配置阿里云镜像仓库可以显著提升依赖拉取速度mirror idaliyunmaven/id mirrorOfcentral/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror这段配置的作用是把原本从中央仓库拉取的依赖改从国内镜像拉取。需要注意的是镜像仓库只是下载源不改变依赖的坐标和版本。不同团队还可能使用私服这时要结合私服策略调整mirrorOf避免所有仓库都被镜像接管。新建 Spring Boot 项目时也可以使用阿里云的 Spring Initializr 服务地址https://start.aliyun.com 。它提供的是经过阿里云适配的初始化模板适合需要快速生成标准工程的情况。如果团队内部有统一脚手架优先使用内部版本。2.4 三种接入方式的对比接入方式成本数据控制部署难度推荐场景百炼 API按调用量计费数据经云服务处理低快速原型、中小规模、开发测试自建部署硬件加运维成本数据留在自己环境高数据隔离、大规模长期调用Java 生态集成与接入方式叠加与接入方式相关中已有 Spring Boot 体系接入方式之间不是互斥的。常见做法是开发环境用百炼 API 快速跑通生产环境根据数据合规和成本评估是否迁移到自建部署。迁移时代码层尽量使用兼容接口让切换成本降到最低。3. 跑通最小问答链路3.1 最小 Python 示例先跑通一个最小的问答链路再谈智能体。这里使用 Python 的 openai SDK通过兼容模式访问百炼import os from openai import OpenAI client OpenAI( api_keyos.getenv(DASHSCOPE_API_KEY), base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1 ) response client.chat.completions.create( modelqwen-plus, messages[ {role: system, content: 你是一名经验丰富的 Java 架构师。}, {role: user, content: 用三句话解释什么是 Function Calling。} ], temperature0.7, max_tokens512 ) print(response.choices[0].message.content)运行前确认两点第一环境变量 DASHSCOPE_API_KEY 已设置第二网络环境能访问 base_url。如果网络策略禁用了外部域名请求需要先确认百炼 endpoint 是否在访问白名单里。3.2 关键参数说明参数含义常见取值调大影响调小影响temperature采样随机性0.0 到 1.0回答更多样可能不稳定回答更确定偏向保守top_p核采样比例0.1 到 1.0候选词更多候选词更集中max_tokens单次最大生成 token 数128 到 4096按模型输出更长成本更高输出可能被截断messages对话消息列表按角色排列上下文更完整但可能超限上下文不足可能导致遗忘在函数调用场景里temperature 建议设置得低一些例如 0.2 到 0.4。工具调用要求模型输出稳定的 JSON 参数随机性太大会导致参数格式错误或参数值漂移。3.3 运行和验证运行上面的脚本正常情况下会输出一段关于 Function Calling 的解释。这里要验证的不只是“有输出”而是输出是否满足要求。可以依次验证修改 system 提示词观察回答风格是否变化。修改 temperature比较同一问题的稳定性和多样性。故意给一个超出 max_tokens 的复杂问题观察输出是否被截断并理解如何通过流式输出解决。这一步能帮你确认 API Key、网络、模型名、参数四条链路都正常。如果其中任何一环有问题后面的智能体代码都会失败所以不要跳过。3.4 这一步最常见的坑第一个坑是模型名写错。不同区域、不同账号可能支持的模型名不同报错信息里会显示类似 model not found 的提示。处理方式是去百炼控制台查看支持列表不要凭记忆猜测。第二个坑是 API Key 设置错误。常见现象是 401 或 InvalidApiKey。检查环境变量是否真的传入了进程可以在脚本里打印 Key 的前几位和后几位用于确认但不要完整打印。第三个坑是把 max_tokens 设太小。问答内容稍长就会被截断看起来像“回答不完整”。实际上模型输出被强制停止了需要调大 max_tokens 或改用流式输出。4. Function Calling问答升级为智能体的核心机制4.1 为什么模型本身不能直接调用工具模型是一个文本生成器它没有权限访问你的系统也不会连接外部服务。所谓 Function Calling本质是“模型决定要调用哪个工具并生成调用参数”真正执行工具的是你的代码。模型输出的不是最终答案而是一个结构化的“调用请求”。因此实现工具调用必须有两部分一部分是向模型声明“你有这些工具可用每个工具的参数长什么样”另一部分是程序端执行工具后把结果以新的消息形式回传给模型让模型生成最终回答。缺了任意一部分工具调用都无法成立。4.2 工具定义的 JSON Schema向模型声明工具时通常使用 JSON Schema 描述每个工具的名称、描述、参数类型和必填项。描述写得越清楚模型越不容易选错工具。下面是一个查询订单状态的工具定义{ type: function, function: { name: query_order_status, description: 根据订单号查询订单当前状态。只有用户明确提供了订单号时才调用。, parameters: { type: object, properties: { order_id: { type: string, description: 用户提供的订单号例如 OD202501010001 } }, required: [order_id] } } }注意几个细节description 里要说明“何时调用”和“何时不调用”required 里明确必填参数参数类型尽量精确避免让模型自行猜测。工具定义本身是 prompt 的一部分也会占用上下文 token因此不要无限制地加工具只保留当前任务真正需要的。4.3 一个带工具循环的智能体实现下面用 Python 写一个最简智能体循环。流程是用户提问 - 模型判断是否调用工具 - 程序执行工具 - 回传结果 - 模型生成最终回答。import json import os from openai import OpenAI client OpenAI( api_keyos.getenv(DASHSCOPE_API_KEY), base_urlhttps://dashscope.aliyuncs.com/compatible-mode/v1 ) def query_order_status(order_id: str) - str: # 实际项目中这里会调用内部订单服务 return f订单 {order_id} 的状态是已发货预计明天送达。 tools [ { type: function, function: { name: query_order_status, description: 根据订单号查询订单当前状态, parameters: { type: object, properties: { order_id: { type: string, description: 用户提供的订单号 } }, required: [order_id] } } } ] def run_agent(user_message: str, max_steps: int 3) - str: messages [ {role: system, content: 你是订单助手。用户询问订单状态时先调用工具查询再用自然语言回复。}, {role: user, content: user_message} ] for step in range(max_steps): response client.chat.completions.create( modelqwen-plus, messagesmessages, toolstools, tool_choiceauto ) message response.choices[0].message if message.tool_calls: messages.append({ role: assistant, tool_calls: [ { id: tc.id, type: function, function: { name: tc.function.name, arguments: tc.function.arguments } } for tc in message.tool_calls ] }) for tc in message.tool_calls: args json.loads(tc.function.arguments) result query_order_status(args[order_id]) messages.append({ role: tool, tool_call_id: tc.id, content: result }) else: return message.content return 已达到最大执行步数无法完成用户的请求。 print(run_agent(帮我查一下订单 OD202501010001 到哪了))这段代码有四个关键点每轮请求都要把之前的消息完整传给模型尤其是 tool 调用结果。模型需要看到工具返回内容才能继续推理。程序执行工具后必须使用 tool 角色并带上 tool_call_id与模型输出的工具调用请求一一对应。max_steps 是循环上限防止智能体在工具间反复横跳消耗大量调用成本。每个工具内部要做好异常处理。工具抛异常时要把错误信息回传给模型让模型决定是换参数重试还是给用户一个合理答复而不是直接让整个程序崩溃。注意不要把工具返回结果原样无限制地回填给模型。工具返回的可能是大段 JSON 或长文本回填前要按需裁剪字段控制上下文 token 消耗。4.4 工具调用流程里最容易出错的地方工具参数解析很容易踩坑。模型输出的 arguments 是 JSON 字符串但有时包含多余空格、换行甚至缺失字段。建议使用宽松解析方式解析失败时回退到正则提取并把整段原文记录下来方便排查。另外一个常见问题是工具描述不明确导致模型在不需要工具时也调用工具。比如用户只是闲聊“你好”模型不该去查订单。解决方式是在工具描述里增加触发条件并在 system 提示词里明确“只有用户提供订单号时才调用查询工具”。还需要注意 tool_choice 参数。默认 auto 让模型自己决定是否调用工具如果业务想强制模型必须调用某个工具可以设为指定工具名。但强制调用会牺牲模型的判断能力一般只在不需要判断的场景使用。5. 给智能体加上记忆Qwen Embedding Milvus 检索5.1 什么时候需要外部记忆智能体在对话中会产生两类记忆需求。第一类是会话内的短期记忆通过把历史 messages 传入模型来实现。第二类是跨会话的长期记忆比如企业知识库、历史工单、产品文档。这些内容不可能全部塞进模型上下文因为 token 有限且成本高所以需要先做检索只把最相关的片段注入提示词。当出现以下信号时就该接入外部记忆用户问题涉及私有文档、内部知识模型训练时没有见过。用户问题需要综合多份文档才能回答。智能体每次回答前都需要相同背景材料重复写入提示词太浪费。希望通过业务数据生成个性化回答而不是每次从头问起。5.2 RAG 工作流程检索增强生成Retrieval-Augmented Generation的流程分两步。第一步是离线准备把业务文档切分成片段对每个片段生成向量写入向量数据库。第二步是在线检索用户提问时先生成问题的向量再在向量数据库中查找最相似的片段把片段作为上下文拼到消息里最后让模型生成回答。切分是决定效果的关键。切得太大会引入无关内容切得太小会让语义不完整。常见做法是按标题和段落语义切分每个片段控制在几百 token 左右同时保留来源信息方便回答时溯源。5.3 Java LangChain4j Milvus 接入示例在 Java 技术栈里可以使用 LangChain4j 简化向量检索的接入。LangChain4j 提供统一 API屏蔽了向量数据库的差异。下面是一个示例 Maven 依赖片段dependency groupIddev.langchain4j/groupId artifactIdlangchain4j/artifactId version${langchain4j.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-milvus/artifactId version${langchain4j.version}/version /dependency dependency groupIddev.langchain4j/groupId artifactIdlangchain4j-open-ai/artifactId version${langchain4j.version}/version /dependency版本号${langchain4j.version}需要替换为你实际使用的稳定版本并且要与 Spring Boot 版本兼容。依赖下载前确认 Maven 镜像配置已生效。写入和检索的示例结构如下EmbeddingModel embeddingModel OpenAiEmbeddingModel.builder() .baseUrl(https://dashscope.aliyuncs.com/compatible-mode/v1) .apiKey(System.getenv(DASHSCOPE_API_KEY)) .modelName(text-embedding-v3) .build(); MilvusEmbeddingStore embeddingStore MilvusEmbeddingStore.builder() .host(127.0.0.1) .port(19530) .collectionName(qwen_agent_kb) .dimension(1024) .build();先把文档片段转成向量并写入再在回答时按相似度检索。这里要注意 dimension向量维度必须与 Embedding 模型输出的维度一致不一致时写入会报维度错误。embedding 模型名和维度参数在不同阶段可能调整落地前以当前 API 文档为准。5.4 集合设计和检索参数字段作用推荐设置collectionName知识库集合名按业务域命名如 order_kb、policy_kbdimension向量维度与 embedding 模型输出一致metricType相似度算法常用 COSINE 或 IP按数据特点选择id主键使用业务侧生成的唯一 IDcontent原始文本保存原文方便回填上下文metadata元数据来源、时间、权限域便于过滤检索时还需要设置 topK 和相似度阈值。topK 控制返回片段数量一般取 3 到 10。阈值过严会漏掉相关内容过松会引入噪音。不要只看 topK建议同时输出得分人工抽样评估一次找到当前文档集的合理阈值。6. 运行验证和常见问题排查6.1 一套可复用的验证顺序智能体项目上线前建议按这个顺序验证单次问答是否正常不携带工具时基本问答能正确返回。工具声明是否生效输入一个明确需要工具的提问确认模型输出了 tool_calls。工具执行是否正确查看程序实际调用了哪个函数传参是否合理。工具结果回填是否成功确认 tool 角色的消息带上了正确的 tool_call_id。最终回答是否基于工具结果检查回答内容是否引用了工具返回的数据而不是模型自己编造。异常分支是否可控工具抛错、模型连续调用工具、上下文超限时程序是否能优雅退出。6.2 问题现象、原因和处理表问题现象常见原因检查方式处理建议模型回答完全不调用工具工具未传入、描述不清、模型不支持打印请求中的 tools 字段检查 model 名确认传入 tools增强 description换支持工具调用的模型工具调用了但程序没执行只把工具发给模型没有写执行分支检查代码是否解析了 message.tool_calls补上 for 循环执行工具的逻辑工具结果回传后模型仍错答tool_call_id 不匹配或消息顺序错误打印 messages 列表检查角色确保 tool 消息紧跟对应的 assistant tool_calls 消息上下文超限历史消息或工具结果越来越大查看报错信息中的 token 数接入摘要机制裁剪历史限制工具返回长度参数解析失败模型输出非法 JSON打印原始 arguments增加容错解析记录原文定位模型输出问题检索结果与问题无关切分粒度差、embedding 不匹配、阈值不对单独跑检索用例检查召回优化切分、重选 embedding 模型、调整阈值6.3 排查链路遇到问题按链路从下往上排网络和 Key先确认 base_url 可达API Key 有效。请求参数打印完整请求体逐字段确认 tools、messages、model 是否符合预期。模型返回查看原始响应区分是没生成 tool_calls还是生成了