资讯动态

本地大模型落地:从工作流编排到API调用的完整实战

发布时间:2026/10/19 14:04:38 来源:尧图企业网站定制
本地大模型跑起来之后最容易出现的状态就是玩了两天对话然后不知道它能干嘛了。裸的模型接口只能做单轮问答没有状态管理没有知识来源提示词全得写死在代码里。我卡在这儿很久直到把工作流编排层补上、把API口子打开这台本地模型才真正开始干活。这篇是“本地大模型落地”系列的第1篇主线很明确把工作流创建、节点编排、API调用整条链路走通适合已经跑通本地模型、正准备让它参与实际业务的开发者。我默认你已经把模型跑起来了——比如用Ollama拉了几个开源模型命令行里能正常对话。这一步不重复重点写“搭好之后”的事。读完这篇你应该能独立创建一个带知识库检索的多节点工作流把它的API地址暴露出来并用代码正常调用。1. 模型能聊天不等于能落地工作流层到底在补什么很多人误以为本地模型跑通就算部署完了其实那只是拿到了一个“会说话的引擎”。真正放进业务里你会发现三个很现实的问题它不记得上次聊了什么它不知道你的私有文档它只能被动接一句话回一句话做不了“先查资料再回答”这种组合动作。1.1 裸模型接口的真实使用体验我当时试用的时候直接往模型接口里怼了一句话“帮我分析这份技术方案里的风险点。”结果它回了一堆非常正确的废话因为它压根没看过那份方案。我又尝试把整篇文档塞进系统提示词里再问结果上下文窗口直接撑爆。这就是裸模型的天花板。你可以靠写代码给每个场景单独拼提示词、接检索库、管理历史会话但每加一个场景就多做一遍重复劳动而且逻辑一复杂就很难维护。我后来选择用工作流平台把这层编排能力接出来不是因为它花哨是为了把“流程”和“代码”解耦。1.2 工作流层在整条链路里的位置你可以把工作流理解成一条流水线收到用户请求、检索知识库、拼提示词、调用模型、整理输出每一步都是流水线上的一个工位。工作流平台负责把工位按顺序串起来并且自动处理参数传递。举一个最典型的场景企业私域知识问答。用户问“公司的报销流程是什么”系统先做两件事——检索公司制度文档里有“报销”相关内容的段落然后把检索到的段落拼到提示词里再让模型基于这些段落组织回答。如果模型没找到相关内容就让它如实说“没找到”而不是瞎编。这套流程如果用代码写至少要处理检索逻辑、拼接逻辑、兜底逻辑用工作流编排就是拖几个节点点几下配置的事。所以我建议你的第一步不是先学API怎么调而是先理解工作流的定位它是把模型从“聊天工具”变成“业务组件”的那一层桥梁。下面所有内容都围绕这座桥来展开。2. 把本地模型接进平台Base URL、API Key与模型ID的匹配规则本地模型要能在工作流平台里被调用第一关是“模型供应商”配置。这一步看着简单实际卡人最多。你的模型跑在本地推理服务上平台本身不知道它在哪你得把“怎么找到模型、用哪个模型”告诉平台。2.1 选供应商入口时的判断逻辑大部分本地推理服务都会提供OpenAI兼容的API接口这是行业里默认的事实标准。也就是说你在平台里配置模型时不需要选某个特定的本地框架直接走“OpenAI-API兼容”这一类就行。关键参数有三个Base URL指向本地推理服务的地址通常要写到/v1这一层。比如 Ollama 默认端口是 11434那 Base URL 一般就是http://宿主机IP:11434/v1。API Key本地服务通常不校验身份但平台的表单一般必填随便填一串字符就行比如ollama。Model ID必须和推理服务里的模型标识完全一致大小写、冒号、版本号都不能错。你可以在本地推理服务里执行模型列表命令查比如 Ollama 就是ollama list。2.2 最容易踩的地址坑容器环境里的“宿主机IP”如果你是把工作流平台也用Docker容器跑起来的这里有一个99%的人都会踩的坑填localhost或127.0.0.1是调不通的因为这两个地址指向的是容器自己不是宿主机。我当时第一次配置完测试对话一直报连接拒绝排查了半天才发现是地址写错了。解决办法有两个填宿主机在局域网里的IP比如http://192.168.1.20:11434/v1前提是两台机器之间网络能通。在平台的容器编排文件里加上宿主映射配置让容器内部能用host.docker.internal这个域名访问宿主机然后把 Base URL 填成http://host.docker.internal:11434/v1。填完参数后先到平台里新建一个最简单的聊天应用选上刚接好的模型发一句“你好”测试。能正常回复说明模型连接没问题接下来才进入工作流编排环节。配置项推荐值说明供应商类型OpenAI-API兼容适用大多数本地推理服务Base URLhttp://宿主机IP:11434/v1必须带/v1容器环境不要写localhostAPI Key任意字符串本地服务一般不校验Model ID模型列表里的完整标识大小写和冒号都不能错3. 从零编排一个知识问答工作流节点顺序与变量传递模型接通之后我建议你做的第一个工作流就是“知识库问答”。它最能体现工作流的价值也能覆盖最常用的业务场景。整个工作流只需要五个节点开始、知识检索、LLM、条件分支、结束。3.1 前置准备知识库的分段和索引在画布上拖节点之前得先把知识库准备好。在平台的“知识库”菜单里新建一个库上传文档平台会按你设置的分段规则把文档切成若干段然后做向量化索引。这里有个小决策分段长度不宜过长。我自己的经验是分段长度设300到500字比较合适。太长的话检索回来的段落会占掉大量上下文窗口模型能读到的有效信息反而变少太短的话语义容易被切断检索效果变差。向量检索这步我不过多展开工作流里只需要知道它能提供“与问题最相关的几个文本片段”。3.2 节点的连接顺序和关键配置打开工作流编排画布按这个顺序拖节点开始节点定义输入变量。我定了一个名为query的变量对应API调用时传入的用户问题。知识检索节点选择刚建好的知识库把检索的查询变量指向{{#start.query#}}。TopK设为3到5意思是最多返回几条相关片段。分数阈值设在0.4到0.6之间低于阈值的片段直接丢弃避免把不相关的内容喂给模型。条件分支节点判断知识检索节点有没有返回结果。有结果走“带上下文生成答案”的路径没结果走“兜底回答”的路径让模型直接说没找到相关内容。LLM节点这是整个链路的核心。模型选你刚才接好的那个系统提示词里引用检索结果用户消息里引用开始节点传入的原始问题。结束节点定义输出变量名比如answer把LLM节点的输出接过去。连接关系大概是开始节点 → 知识检索节点 → 条件分支节点 → LLM节点 → 结束节点。条件分支的另一条路径可以不经过LLM节点直接生成一句固定的兜底文案再进结束节点。这样至少保证接口永远有返回值不会出现空响应。3.3 模板变量传递的写法工作流里最反人类的地方就是变量引用。你需要在节点配置里手写变量名格式通常是{{#节点ID.输出字段#}}。节点ID是画布上每个节点自动生成的名称不是你自己起的节点标题。我的系统提示词模板大概长这样你是企业内部知识助手。请只依据下面的文档片段回答问题。 如果文档片段中没有相关内容请直接说明“没有找到相关依据”不要自行编造。 文档片段 {{#knowledge_retrieval.result#}} 用户问题 {{#start.query#}}第一次编辑的时候建议把模板粘进去后先跑一次测试看最终输出里变量是不是被正确替换成了真实内容。出现undefined或者{{...}}原文就说明变量引用的ID写错了回去对一下节点ID。这个编排思路不局限于知识问答。你后续想做的问题分类、工单自动回复、标签提取都是同一套骨架接收输入 → 查询/处理 → 模型生成 → 输出结果。4. 把工作流发布成API密钥创建与一次完整的HTTP调用工作流编排好只是内部跑通要让它被外部系统调用必须走API。平台在这方面做得比较省事每个应用在发布时都会自动附带一组API接口你只需要创建一个密钥然后按约定格式发HTTP请求就行。4.1 创建API密钥在应用的“访问API”菜单里找到密钥管理入口新建一个密钥。平台会生成一串app-开头的字符串这串就是你的身份凭证。注意两点请求时把它放在请求头里格式是Authorization: Bearer app-开头那串不要放到URL参数里。密钥不要提交到代码仓库也不要直接烧进前端。后端服务之间调用没问题一旦放进浏览器环境等于把仓库钥匙挂在门口。工作流类型的应用核心接口一般是POST /v1/workflows/run。它和聊天对话型接口不同这里的入参结构里用的是inputs不叫messages。开始节点定义了哪些变量inputs里就必须提供哪些字段。4.2 用curl快速验证接口先把环境的联通性验证一遍我用的是一个最朴素的请求curl --location --request POST http://你的平台地址/v1/workflows/run \ --header Authorization: Bearer app-xxxxxx \ --header Content-Type: application/json \ --data-raw { inputs: { query: 公司针对差旅报销有什么规定 }, response_mode: blocking, user: tester-001 }response_mode有两个选择blocking表示等整个工作流跑完再一次性返回结果streaming表示按事件流持续推送过程数据。后面的章节专门讲两者的取舍这里先用blocking验证基本链路。如果一切配置正确你会收到一个JSON响应里面有工作流的运行状态和输出结果。如果返回报错优先检查请求头里的密钥、inputs里的字段名、以及开始节点的变量名是否一致。4.3 Python调用的最小可用代码确认curl能通之后我通常会写一个简单的Python函数封装方便后面集成到业务侧import requests url http://你的平台地址/v1/workflows/run headers { Authorization: Bearer app-xxxxxx, Content-Type: application/json, } def run_workflow(query_text): payload { inputs: {query: query_text}, response_mode: blocking, user: tester-001, } resp requests.post(url, headersheaders, jsonpayload, timeout300) resp.raise_for_status() data resp.json() return data[data][outputs][answer] if __name__ __main__: print(run_workflow(公司针对差旅报销有什么规定))这段代码里我给了两个细节timeout设了300秒因为本地模型推理速度不如云端接口快尤其第一次调用可能还要加载模型权重很容易触发默认超时读取结果时走的是标准字段路径data.outputs.answer这个路径对应结束节点里定义的输出变量名。5. 响应模式怎么选阻塞返回与流式输出的对接差异我在第一次接API时没太在意response_mode后来发现这个参数直接影响对接方案尤其本地模型场景下差别很大。这里单独拆开讲值得你花几分钟理解。5.1 两种模式的取舍逻辑blocking模式逻辑最简单后端把所有工作流节点跑完最终一次性返回一个完整的JSON。对调用方来说代码写起来很省事一个请求拿完整结果。但它的代价是等待时间长。本地模型每秒钟只能生成几十个token一段长回答可能要几十秒。请求方如果默认超时时间是10秒就会收到超时错误但这不代表工作流没跑完只是你的客户端等不住了。streaming模式把结果拆成一连串事件通过服务器主动推送的方式一条一条发给调用方。第一个事件可能在两三秒内就到用户看到的效果是“开始有响应了然后字一个个蹦出来”。体感上比干等几十秒好很多。对比维度blockingstreaming编码复杂度低中首响应速度慢要等全跑完快推送首事件客户端超时风险高低典型场景后台批量处理交互式问答、聊天5.2 流式推送的事件流里到底有什么streaming模式下推送的数据格式是SSE也就是一行一行的事件流。每一行以data:开头后面跟着一个JSON对象里面用event字段标记当前事件类型。完整的事件序列大概是这样的data: {event: workflow_started, data: {workflow_run_id: xxx}} data: {event: node_started, data: {node_id: knowledge_retrieval, node_type: knowledge-retrieval}} data: {event: node_finished, data: {node_id: knowledge_retrieval, outputs: {result: ...}}} data: {event: node_started, data: {node_id: llm, node_type: llm}} data: {event: text_chunk, data: {text: 根据公司规定}} data: {event: text_chunk, data: {text: 差旅报销需要}} data: {event: node_finished, data: {node_id: llm, outputs: {answer: 根据公司规定差旅报销需要...}}} data: {event: workflow_finished, data: {outputs: {answer: 根据公司规定差旅报销需要...}}}对调用方来说最重要的是text_chunk事件它代表模型生成的增量文本其次是workflow_finished代表整个流程结束可以拿到最终结构化输出。node_started和node_finished可以用来做过程展示比如在前端界面显示“正在检索知识库”“正在生成答案”。Python里读取流式响应代码也不复杂import requests url http://你的平台地址/v1/workflows/run headers { Authorization: Bearer app-xxxxxx, Content-Type: application/json, } payload { inputs: {query: 公司针对差旅报销有什么规定}, response_mode: streaming, user: tester-001, } resp requests.post(url, headersheaders, jsonpayload, streamTrue, timeout(10, 300)) for line in resp.iter_lines(decode_unicodeTrue): if not line or not line.startswith(data:): continue # 解析SSE事件 json_str line[5:].strip() print(json_str)注意这里的timeout传的是元组(10, 300)10秒是连接阶段超时300秒是读取阶段超时。连接只要通得够快就行但读取阶段要容忍模型慢慢生成。5.3 前端对接流式响应的两个提醒如果你要把流式接口接到浏览器里有两点绕不开一是Web前端用常规的fetch读取流式响应要注意处理响应流而不是等整个响应结束二是SSE本身跨域限制比较严格不配置好跨域策略前端会直接读不到数据。我的建议是不要硬磕前端直连在后端写一层薄薄的转发代理把API密钥藏在服务端前端只跟自己的后端通信既解决跨域又解决密钥暴露问题。6. 部署环境里的四个高频坑排查链路与最终解法把整条链路搭好只是开始真实部署之后问题才会集中冒出来。我在本地化环境中踩过不少坑挑四个典型的展开讲每个都给排查链路不直接跳答案。6.1 模型响应慢导致API返回超时症状API偶尔返回504或客户端报超时但你自己在命令行里测模型接口又觉得挺快。我当时的排查链路是这样先用Python直接量一次模型原生接口的耗时从发请求到收到完整回复记下秒数——发现要四十多秒。然后看工作流平台的前端日志确认请求已经发给模型了最后看调用方的超时设置——默认10秒远小于实际耗时。原因不复杂本地模型在低算力环境下生成速度有限长回答耗时长而调用方的超时设置按云端接口的标准写。解决办法也直接客户端超时放到300秒能改流式就改流式让用户先看到响应正在产生如果模型加载慢可以考虑在服务器空闲时保活或者换一个量化版本的小模型。6.2 并发请求上去就排队卡死症状单个请求没问题三五个请求同时进来后到的请求半天没响应。排查的时候我先看模型服务那边的日志发现并没有报错只是请求在排队。原因很直接本地推理服务是单实例跑的同一时间只能处理有限的并发而且它们默认没有做负载均衡。这种场景的解法分两步。第一步在推理服务上设置并发限制或者队列策略让超出的请求快速失败而不是无限排队第二步在工作流平台侧给应用设置并发上限两侧的配置要匹配。这里要特别提醒如果业务方希望强并发支撑正确的路线是把推理服务部署多个副本对外提供统一入口而不是指望单个实例扛所有流量。6.3 长文档检索后上下文超窗症状知识库检索没返回报错但LLM节点生成内容时提示模型输入太长或者回答答非所问、直接截断。排查时我先看知识检索节点实际返回了多少字发现TopK设了5每个段落又偏长拼起来小一万字了超过了模型的上下文窗口。这个问题本质是知识检索参数和模型能力不匹配。处理办法很直接把TopK从5降到3或者缩小知识库的分段长度调高分数阈值让低相关度片段进不来再不行给知识检索节点后面加一个“截断处理”的代码节点拼接时直接限制总长度。任何时候模型能有效处理的最大长度都要比它声称的上下文长度留出至少20%的余量否则一旦系统提示词和检索内容加起来稍微超一点就容易出问题。6.4 API请求的inputs与开始节点变量对不上症状API调用返回参数格式错误或者工作流里拿不到传入的内容输出一片空白。这种问题大多不是代码问题而是配置不对齐。排查时先回工作流画布点开开始节点确认里面定义的变量名。我的变量叫query那所有API请求的inputs字段就必须是{query: ...}。如果你在开始节点上定义了query和session_id两个变量API请求里两个都必须传缺一个就可能报错。平台返回的错误信息一般会明确指出缺少哪个字段别再盯着密钥和URL空转直接按提示补字段最快。四个坑走下来你会发现多数问题的根子不在工作流平台本身而在上下游配置模型服务能力、调用方超时策略、知识库分段方式。把这些配置当成整个系统的一部分去管理出问题时才不至于盲人摸象。整个过程跑通之后我的体会是本地模型的落地难度不在模型本身而在它周围的编排、对接和参数调优。工作流把这层复杂度接住了API把这层能力释放了出去。下一篇可以接着聊知识库的清洗和分段细节那部分直接决定检索质量是另一个坑很多的环节。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑