1. 从“会聊天”到“能干活”:LLM 接入开发工作流的整体设计思路
很多人第一次接触大语言模型,停留在网页对话框里问几句、答几句,觉得新鲜,但关掉页面之后,它跟自己的日常工作没有任何关系。这个项目要解决的核心问题,就是把这层“新鲜感”变成“生产力”——让模型真正嵌入到写代码、查资料、处理文本、生成结构化数据的日常流程里。我把它定位成一本“初学者手册”的第三部分,前两部分讲的是基础概念和调用方式,这一部分重点落在“融入”两个字上:不是单独写一个脚本调一次接口,而是让模型成为你开发工作流里的一个常规环节,像用 Git 提交代码、像用格式化工具整理文件一样自然。
为什么强调“工作流”而不是“应用”?因为初学者最容易犯的错,是上来就想做一个完整的聊天产品,结果卡在界面、部署、并发这些跟模型本身无关的事情上,学了两周还在调按钮颜色。而工作流是更底层、更通用的能力:你写一个函数,输入一段文本,输出一段结构化结果,这个函数可以被脚本调用、被编辑器插件调用、被定时任务调用。掌握了这个,你再去包界面、做产品,就是水到渠成的事。这也是我选择从“工作流”切入的核心理由——先让模型在你的终端里跑起来,再谈其他。
这个内容适合谁?如果你已经能跑通一次最简单的模型调用,知道 API Key 怎么配、请求怎么发,但不知道下一步该往哪走,那这份手册就是为你写的。如果你是完全零基础,建议先补一下前两部分,至少手动发过一次请求、看过一次返回的 JSON 结构,再来读这篇会更顺。整篇内容我会围绕四个核心环节展开:整体设计思路、核心细节与实操要点、完整实操过程、常见问题排查。每个环节都会给出可以直接抄作业的代码和配置,也会解释每一步为什么这么做,避免你只会复制粘贴、换个场景就懵。
在开始之前,先明确一个原则:模型是工具,不是魔法。它的输出不稳定、会犯错、有成本,所以工作流设计的第一要务是“可控”。可控体现在三个层面:输入可控(提示词模板化)、输出可控(结构化解析)、异常可控(失败重试与降级)。后面所有的设计,都是围绕这三个“可控”来展开的。理解了这一点,你就不会盲目追求“一次调用解决所有问题”,而是会像搭积木一样,把复杂任务拆成多个小步骤,每一步都让模型做它最擅长的事。
2. 核心细节解析与实操要点:把模型调用变成可复用的函数
2.1 提示词模板化:别在代码里手写大段文字
初学者最常见的写法,是在代码里直接拼字符串:prompt = "请帮我总结这段话:" + text。这种写法在 demo 阶段没问题,一旦你要处理不同任务、不同格式,代码就会变成一团乱麻。我的做法是把提示词抽成独立的模板文件,用占位符标记变量,代码只负责填充。这样做的好处有三个:一是修改提示词不用动代码逻辑,二是同一个模板可以复用到不同项目,三是方便做版本管理,哪天效果变差了可以回滚。
具体实现上,我习惯用简单的字符串模板,比如{input_text}、{language}这样的占位符,然后用编程语言自带的格式化方法填充。不推荐一上来就用复杂的模板引擎,对初学者来说增加理解成本,收益不明显。模板文件建议按任务命名,比如summarize.txt、extract_keywords.txt、translate.txt,放在一个prompts目录下。每个模板里除了任务指令,还要写清楚输出格式要求,比如“只输出 JSON,不要有任何额外解释”,这一点非常关键,后面讲输出解析时会详细说。
注意:模板里的指令要具体,避免“帮我处理一下”这种模糊表述。模型不会读心,你说得越清楚,它输出越稳定。比如“总结”要说明总结成几句话、面向什么读者;“提取关键词”要说明提取几个、是否允许重复。
2.2 输出结构化:让模型返回能直接用的数据
模型默认返回的是一段自然语言,人看着舒服,但程序处理起来很痛苦。工作流里真正有价值的是结构化数据,比如 JSON、列表、键值对。让模型输出 JSON 有两种常见做法:一种是在提示词里明确要求“以 JSON 格式输出”,另一种是使用模型平台提供的结构化输出功能(如果有的话)。初学者建议先用第一种,通用性强,不依赖特定平台。
这里有个坑:模型有时候会在 JSON 外面包一层解释文字,比如“好的,以下是结果:{...}”。直接解析会报错。我的处理方式是两步走:先用正则表达式把 JSON 部分抠出来,再做解析。正则不用写得太复杂,匹配第一个{到最后一个}就行。解析失败时不要直接崩溃,而是记录原始输出,方便排查是提示词问题还是模型抽风。另外,JSON 里的字段名建议用英文,避免中文编码带来的各种奇怪问题,值可以是中文,不影响。
2.3 异常处理与重试:模型调用失败是常态
网络请求会超时、接口会限流、模型会返回空结果,这些都是常态,不是异常。初学者写的脚本往往一次失败就整个崩掉,这在手动运行时还能接受,一旦放进定时任务或批量处理,就是灾难。我的做法是给每次调用包一层重试逻辑:失败后等待一小段时间再试,最多试三次。等待时间建议用指数退避,比如第一次等 1 秒,第二次等 2 秒,第三次等 4 秒,避免短时间内反复冲击接口。
重试之外还要有降级方案。比如批量处理 100 条数据,其中 3 条反复失败,不应该让整个任务卡住。我的做法是记录失败项,跳过继续处理,最后统一输出失败列表,人工决定是重跑还是手动处理。这样即使部分失败,整体任务也能完成,效率高很多。还有一点:重试时要区分错误类型,网络超时值得重试,参数错误重试多少次都没用,反而浪费时间和额度。判断方法很简单,看错误信息里有没有“超时”“连接”这类词,有就重试,没有就直接记录失败。
2.4 成本与速度的平衡:不是所有任务都需要最强模型
初学者容易陷入一个误区:所有任务都用最大最强的模型。实际上,不同任务对模型能力的要求差别很大。简单的文本分类、格式转换,用小模型就够了,速度快、成本低;复杂的推理、长文总结,才需要上大模型。我的经验是,先用小模型跑一遍,看效果能不能接受,不能接受再换大模型。这样能在保证效果的前提下,把成本压到最低。
速度方面,批量处理时可以考虑并发调用,但要注意接口的速率限制。盲目开几十个并发,很容易触发限流,反而更慢。建议从 3 到 5 个并发开始试,观察有没有报错,再逐步调整。另外,把多个小任务合并成一次调用,也能减少请求次数。比如要提取一篇文章的关键词和摘要,可以写一个提示词让模型同时输出两者,而不是调两次接口。这样既省时间又省钱,缺点是提示词会变长,需要权衡。
3. 实操过程与核心环节实现:从零搭一个文本处理工作流
3.1 环境准备与依赖安装
先说一下环境。我用的是 Python,版本 3.9 以上,太老的版本有些库不支持。依赖方面,核心就两个:一个是 HTTP 请求库,用来调接口;一个是 JSON 处理,Python 自带,不用额外装。如果你要用异步并发,再加一个异步 HTTP 库。安装命令很简单,一行搞定:
pip install requests如果你打算用异步,就换成:
pip install httpx不建议一上来就装一堆框架,什么 LangChain、LlamaIndex,对初学者来说是黑盒,出了问题不知道从哪查。先用最原始的方式把流程跑通,理解每一步在干什么,之后再考虑用框架提效。这是我踩过坑之后的建议:框架能帮你省事,但也会掩盖细节,基础不牢的时候用框架,等于把命运交给别人。
3.2 配置文件管理:API Key 不要写死在代码里
API Key 是敏感信息,绝对不能硬编码在代码里,更不能提交到代码仓库。我的做法是放在一个独立的配置文件里,比如config.ini或.env文件,代码运行时读取。.env文件要加到.gitignore里,避免误提交。配置内容至少包括:接口地址、API Key、默认模型名称、超时时间。示例配置如下:
[api] base_url = https://api.example.com/v1 api_key = your_key_here model = small-model timeout = 30读取配置用 Python 自带的configparser就行,不用装额外库。这样做的好处是,换环境、换 Key 只需要改配置文件,代码一行不用动。团队协作时,每个人用自己的 Key,互不干扰。还有一点:配置文件里不要写注释说明 Key 的用途,避免泄露更多信息。
3.3 核心调用函数的实现
接下来是核心部分:写一个通用的调用函数。这个函数接收提示词和输入文本,返回模型输出。内部负责组装请求、发送、解析响应、处理异常。我把它拆成几个小函数,每个只做一件事,方便调试和复用。第一个函数负责读取提示词模板并填充变量:
def build_prompt(template_name, **kwargs): with open(f"prompts/{template_name}.txt", "r", encoding="utf-8") as f: template = f.read() return template.format(**kwargs)第二个函数负责发送请求:
import requests def call_model(prompt, config): headers = { "Authorization": f"Bearer {config['api_key']}", "Content-Type": "application/json" } payload = { "model": config["model"], "messages": [{"role": "user", "content": prompt}], "temperature": 0.3 } response = requests.post( f"{config['base_url']}/chat/completions", headers=headers, json=payload, timeout=config["timeout"] ) response.raise_for_status() return response.json()["choices"][0]["message"]["content"]这里有几个参数值得说明。temperature控制输出的随机性,范围一般是 0 到 1,值越低输出越稳定、越保守,适合做数据提取、格式转换这类任务;值越高输出越多样,适合创意写作。我默认设 0.3,兼顾稳定和灵活。timeout设 30 秒,大部分请求够用,长文本可以适当调大。raise_for_status()会在 HTTP 状态码不是 200 时抛异常,配合前面的重试逻辑使用。
3.4 输出解析与结果落盘
模型返回的是字符串,需要解析成结构化数据才能用。我写一个解析函数,先尝试直接解析 JSON,失败则用正则提取再解析:
import json import re def parse_json_output(text): try: return json.loads(text) except json.JSONDecodeError: match = re.search(r"\{.*\}", text, re.DOTALL) if match: return json.loads(match.group()) raise ValueError(f"无法解析输出: {text[:200]}")解析成功后,结果要落盘保存。我习惯用 JSON Lines 格式,每行一条记录,方便追加和后续处理。文件名带上时间戳,避免覆盖。保存函数很简单:
import json from datetime import datetime def save_result(result, output_file): with open(output_file, "a", encoding="utf-8") as f: f.write(json.dumps(result, ensure_ascii=False) + "\n")ensure_ascii=False是为了让中文正常显示,不然会变成一堆转义字符,人没法看。输出文件建议放在output目录下,和输入数据分开,保持目录结构清晰。
3.5 批量处理的完整流程
把上面的零件组装起来,就是一个完整的批量处理脚本。流程是:读取输入文件、逐条构建提示词、调用模型、解析输出、保存结果、记录失败项。伪代码如下:
def process_batch(input_file, template_name, output_file): config = load_config() failed = [] with open(input_file, "r", encoding="utf-8") as f: lines = f.readlines() for i, line in enumerate(lines): text = line.strip() if not text: continue prompt = build_prompt(template_name, input_text=text) try: raw = call_model_with_retry(prompt, config) result = parse_json_output(raw) result["_source"] = text save_result(result, output_file) except Exception as e: failed.append({"index": i, "text": text, "error": str(e)}) if failed: save_result(failed, output_file.replace(".jsonl", "_failed.jsonl"))这个流程看起来简单,但每一步都有讲究。读取输入时跳过空行,避免浪费调用;保存时带上原始文本,方便回溯;失败项单独存一个文件,方便重跑。我实测下来,处理几百条数据,成功率一般在 95% 以上,剩下的失败项多半是网络波动或模型偶发抽风,重跑一次基本都能过。
4. 常见问题与排查技巧实录
4.1 模型返回格式不对怎么办
这是最高频的问题。你要求返回 JSON,它给你返回一段带解释的文字。排查思路分三步:第一,检查提示词里有没有明确说“只输出 JSON,不要任何额外文字”,措辞要强硬,不要用“请尽量”这种软绵绵的表达;第二,检查temperature是不是太高,调低到 0.1 试试;第三,如果还是不行,就在解析函数里做容错,用正则提取 JSON 部分。如果正则也提取不到,说明模型根本没按格式来,这时候要考虑换模型或者把任务拆得更细。
还有一个技巧:在提示词里给一个输出示例,模型模仿能力很强,看到示例就知道你要什么格式。示例要简短,一个字段就够,不用写完整数据。比如:
输出格式示例:{"keywords": ["关键词1", "关键词2"]}4.2 调用超时或连接失败
超时和连接失败通常跟网络环境有关,排查顺序是:先确认接口地址能不能访问,用浏览器或命令行工具试一下;再确认 API Key 有没有过期或额度用完;最后看是不是并发太高被限流了。如果是限流,降低并发数,或者在请求之间加一点延迟。我一般会在批量处理时,每处理 10 条就暂停 1 秒,给接口喘口气,实测能明显降低失败率。
重试逻辑这里要再强调一次:不要所有错误都重试。参数错误、认证失败这类问题,重试一百次也没用,只会浪费时间。判断方法前面说过,看错误信息关键词。我的重试函数里会先判断错误类型,只有网络类错误才重试,其他直接抛出,让上层记录失败。
4.3 结果不稳定,同样输入两次输出不一样
这是模型的固有特性,不是 bug。temperature大于 0 时,输出就有随机性。如果你的任务要求完全可复现,把temperature设为 0,但即使这样,不同批次的模型版本也可能有细微差异。我的建议是:对稳定性要求高的任务,不要追求完全一致,而是追求“结果在可接受范围内”。比如提取关键词,两次结果可能措辞不同,但核心词都在,这就够了。如果确实需要完全一致,那就要在提示词里把要求写到极致,并且接受偶尔的偏差。
4.4 成本失控怎么控制
成本失控通常是因为两个原因:用了过大的模型,或者调用了太多次。控制方法:第一,按任务分级,简单任务用小模型,复杂任务才用大模型;第二,合并请求,能一次调用完成的不拆成两次;第三,加缓存,同样的输入不要重复调用,把结果存下来,下次直接读缓存。缓存可以用输入文本的哈希值做键,简单有效。我做过统计,加缓存之后,重复数据的处理成本能降一半以上。
4.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 | 解决方式 |
|---|---|---|---|
| 返回格式不对 | 提示词不明确或温度过高 | 检查提示词措辞和 temperature | 强化格式要求,降低温度,加正则容错 |
| 调用超时 | 网络波动或并发过高 | 测试接口连通性,查看并发数 | 降低并发,加重试,延长超时时间 |
| 结果不稳定 | 模型随机性 | 对比多次输出 | 降低温度,接受合理波动 |
| 成本过高 | 模型过大或重复调用 | 统计调用次数和模型 | 分级用模型,加缓存,合并请求 |
| 部分失败 | 网络或限流 | 查看失败项错误信息 | 记录失败项,单独重跑 |
4.6 几个我踩过的坑
第一个坑:把 API Key 写在了代码里,提交到了公开仓库,虽然发现后立刻删了,但已经暴露了一段时间。从那以后,我所有项目都用.env文件,并且第一件事就是配.gitignore。第二个坑:批量处理时没加失败记录,一条失败整个脚本崩掉,前面处理的全白费。后来改成逐条捕获异常,失败项单独存,任务再也没中断过。第三个坑:提示词写得太随意,模型输出五花八门,解析代码写了一堆兼容逻辑。后来把提示词规范化,输出格式固定,解析代码简化了一大半。这些坑说到底都是“图省事”造成的,前期多花十分钟规范流程,后期能省几个小时排查。
5. 把工作流用起来:几个真实场景的落地思路
5.1 场景一:批量整理会议纪要
手头有一堆会议记录,格式混乱,需要提取出“决议事项”“负责人”“截止时间”三个字段。做法是写一个提示词模板,要求模型以 JSON 输出这三个字段,然后批量跑一遍。跑完之后,结果直接导入表格工具,人工核对一遍就能用。这个场景的关键是字段要固定,提示词里把字段名和含义写清楚,模型输出就很稳定。我实测过几十份纪要,准确率在 90% 以上,剩下的主要是负责人名字识别错误,人工改一下就行。
5.2 场景二:代码注释批量生成
有一批老代码没有注释,读起来费劲。做法是写一个脚本,遍历代码文件,把每个函数体提取出来,让模型生成注释,再写回文件。这个场景要注意的是,模型生成的注释可能不准确,尤其是业务逻辑复杂的函数。我的做法是先生成到一个单独的文件里,人工审核后再合并,不要直接写回原文件。另外,提示词里要说明注释风格,比如“用中文,简洁,说明函数用途和参数含义”,不然模型可能生成一大段废话。
5.3 场景三:文本分类打标签
有一批用户反馈,需要按主题分类。做法是定义好类别列表,让模型从列表里选,输出类别名称。这个场景的关键是类别要互斥、覆盖全面,不然模型会纠结。提示词里把类别定义写清楚,每个类别给一两个例子,效果会好很多。分类任务用小模型就够,速度快、成本低。如果类别多,可以分批分类,每次给模型几个类别,让它选,比一次性给几十个类别准确率高。
5.4 场景四:定时任务自动处理
把上面的脚本挂到定时任务里,比如每天早上跑一次,处理前一天新增的数据。这个场景要注意的是日志记录,每次运行的时间、处理条数、成功失败数都要记下来,出问题好排查。另外,定时任务里的脚本要加锁,避免上一次还没跑完,下一次又启动了。简单做法是运行时创建一个锁文件,跑完删除,启动前检查锁文件是否存在。
6. 进阶方向:从单次调用到多步协作
6.1 任务拆解:让模型分步思考
复杂任务一次调用往往做不好,拆成多步效果更好。比如“分析一篇文章并生成摘要和关键词”,可以拆成两步:第一步提取关键词,第二步基于关键词生成摘要。每一步的提示词都更聚焦,输出质量更高。拆解的粒度没有固定标准,我的经验是:如果一次调用的输出里包含多个不相关的部分,就考虑拆开。拆开之后,每一步的输出都可以单独校验,出问题容易定位。
6.2 结果校验:用规则兜底
模型输出不能全信,关键结果要用规则校验。比如提取的日期格式对不对、数字范围合不合理、必填字段有没有缺失。校验不通过就重试或者标记人工处理。规则校验的代码很简单,但能挡住大部分低级错误。我一般会在解析之后加一层校验函数,检查字段类型、长度、取值范围,不通过就抛异常,走失败处理流程。
6.3 人工审核环节的设计
完全自动化的流程听起来很美,但实际落地时,关键结果还是需要人工审核。设计工作流时,要把人工审核作为一个正式环节,而不是事后补救。具体做法是:模型输出先存到一个“待审核”文件,人工审核后移到“已确认”文件,后续流程只读“已确认”的数据。这样既保证了效率,又控制了风险。审核界面不用太复杂,一个表格工具打开 JSON Lines 文件就能看,改完保存即可。
6.4 持续优化:收集 bad case 反哺提示词
工作流跑起来之后,要持续收集效果不好的案例,分析原因,反过来优化提示词。我习惯建一个bad_cases目录,把模型输出不对的输入输出对存进去,每周看一次,找规律。常见的问题有:提示词有歧义、缺少示例、格式要求不明确。针对性地改一改,效果往往立竿见影。这个过程是长期的,没有一劳永逸的提示词,只有不断迭代的提示词。
7. 一些个人体会和实用建议
这套工作流我用了大半年,最大的感受是:模型的能力上限很高,但你的流程设计决定了它的下限。同样的模型,有人用起来得心应手,有人用起来处处碰壁,差别就在流程上。提示词模板化、输出结构化、异常可控化,这三件事做到位,模型就能稳定地为你干活。反之,如果每次都随手写提示词、随手解析输出、出了问题就重跑,那效率还不如自己手动做。
另一个体会是:不要追求一步到位。我见过太多人想做一个“全能助手”,结果什么都做不好。正确的做法是从一个小场景切入,把流程跑通,再逐步扩展。比如先做文本分类,跑顺了再加摘要,再加关键词,一步步来。每加一个功能,都要保证不影响已有的流程。这样积累下来,工作流会越来越强大,而且每一步都是可控的。
最后分享一个小技巧:给每个任务写一个简单的测试用例,包含输入和期望输出,改提示词之后跑一遍测试,看有没有退化。测试用例不用多,三五个就够,覆盖典型场景和边界情况。这个习惯能帮你避免“改了一个地方,坏了另一个地方”的尴尬。测试用例用 JSON 文件存,脚本读进来跑,对比输出,简单有效。