1. 为什么非要从"调API"升级到"内容平台":动机与选型实录
大概在去年年底,我遇到了一个很实际的困扰:手里同时维护着两个技术博客,再加上给公司技术公众号供稿,每周至少需要产出三到四篇长文。靠人工硬写,灵感这东西又不稳定,碰上项目忙的时候,断更就成了常态。
当时市面上不是没有AI写作工具,但我把主流的那几家都试了一圈之后,彻底打消了付费订阅的念头。原因很简单:通用写作工具给的是通用模板,它不理解我的博客定位,也不知道我的读者想看什么。 每次生成的稿子都需要大改,改完一算时间成本,比自己从零写还高。
这时候我意识到,真正需要的不是"一个会写文章的网站",而是"一整套围绕自己需求定制的内容生产流水线"——从输入灵感关键词开始,到生成标题、生成正文大纲、逐段扩写,再到格式化输出成Markdown文件,最后推送发布。这正好契合了"从API到内容平台"这个定位:底层是大模型API,上层是完整的全栈应用。
技术选型上,我一开始在几个大模型API服务商之间犹豫过。后来选了硅基流动,原因有三个:
- 模型选择灵活。同一个API Key下面可以调用DeepSeek系列、Qwen系列、GLM系列等多个开源模型,不用为了换模型再注册别的平台。
- 有免费额度。对于个人开发者验证想法来说,免费的额度足够完成整个Demo的开发和测试。
- 接口兼容OpenAI格式。这意味着我之前积累的OpenAI SDK调用经验可以几乎零成本迁移过来。
这套方案做下来,我从输入一个主题到拿到一篇结构完整、可以直接发出去的初稿,时间从原来的几个小时压缩到了十分钟左右。整个过程我后面会拆开讲,包括后端怎么设计、API怎么调、提示词怎么写、踩了哪些坑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 硅基流动API接入:注册、Key管理与第一次真正跑通
2.1 注册环节最容易出问题的几个小地方
硅基流动的注册流程本身不复杂,邮箱验证之后就能进控制台。但有两个细节值得提一下:
密码规则比较严。 注册的时候密码必须同时包含大小写字母和数字,长度也有要求。这个卡了我一下,因为平时习惯用统一密码格式,结果试了三次才通过。后来我干脆用密码管理器重新生成了一个,省得以后再折腾。
API Key的创建入口在控制台的"API密钥"页面。 创建之后,密钥只显示一次,刷新页面之后就再也看不到了。所以创建完第一件事就是复制保存到自己的密钥管理工具里。我习惯用环境变量的方式管理,而不是硬编码在代码里,后面会细说。
2.2 模型选型:不能只看参数大小
硅基流动平台上的模型列表很长,但实际用来做博客文章生成,我最终锁定的是DeepSeek系的中大杯模型。
有人可能会问,为什么不直接用参数最大的那个模型?这里涉及一个实操层面的权衡:参数量大的模型确实生成质量高,但响应速度慢、单次调用成本也高。 博客文章生成是一个需要多次调用的流程(标题、大纲、逐段扩写),单次推理时间的累加会直接影响用户体验。
我最终的模型选择逻辑是这样的:
| 使用场景 | 推荐模型 | 原因 |
|---|---|---|
| 标题生成 | DeepSeek系列轻量模型 | 任务简单,响应速度快,成本低 |
| 文章大纲生成 | DeepSeek系列标准模型 | 需要一定的结构化能力 |
| 长文逐段扩写 | DeepSeek系列增强模型 | 上下文窗口大,生成质量高,能保持风格一致 |
这里有一个我实际测试的对比数据:用轻量模型生成标题,平均耗时3秒左右,质量完全够用;但用轻量模型生成长文,写到第三段就开始出现内容重复、逻辑断裂的问题。所以**"什么任务配什么模型"比"什么任务都用最强模型"更划算。**
2.3 API调用的核心参数:改哪些、怎么改
硅基流动的API兼容OpenAI格式,所以调用方式很标准,一个POST请求到/v1/chat/completions即可。我这里用的Python SDK,核心代码如下:
python复制from openai import OpenAI
client = OpenAI(
api_key=os.getenv("SILICONFLOW_API_KEY"),
base_url="https://api.siliconflow.cn/v1"
)
response = client.chat.completions.create(
model="deepseek-ai/DeepSeek-V3",
messages=[
{"role": "system", "content": "你是一位资深技术博客编辑..."},
{"role": "user", "content": "为主题「xxx」生成10个吸引人的标题"}
],
temperature=0.8,
max_tokens=2048,
top_p=0.7,
stream=False
)
参数这块我踩过几个坑,逐个说一下:
temperature(温度)。这个参数控制生成内容的随机性,范围一般是0到2。值越低,输出越确定;值越高,越发散。我用下来发现,标题生成可以用0.8-0.9,让模型"脑洞"大一点;正文生成建议0.5-0.7,保证逻辑连贯的同时不至于太死板。 有一次我为了图稳,把temperature设成0.2,结果生成的标题全是"如何使用XX"这种模板化的东西,完全没有点击欲望。
max_tokens(最大输出长度)。这里有一个很多人忽略的问题:max_tokens限制的是输出token数,不是输入。博客文章生成场景下,逐段扩写时max_tokens要设得足够大,否则生成到一半会被截断,得到一个没写完的段落。我实测下来,生成2000字左右的段落,max_tokens至少要给4096。而且要注意,上下文长度是输入和输出共享的——热词里提到过"maximum context length is 1048576 tokens",这是平台上下文的上限,但单次输出的上限是由模型决定的,两个概念别混淆。
top_p(核采样)。这个参数和temperature有点类似,都影响输出多样性。工程师的说法是"从累计概率超过top_p的token里采样"。实际操作中,我通常固定top_p为0.7,只调temperature来改变风格。两者同时大改容易让输出变得不可控。
stream(流式输出)。如果生成时间超过10秒,建议开启流式。不只是为了好看,而是因为很多HTTP客户端有默认的超时时间,比如10秒或30秒,如果服务端一直没有返回,连接会被掐断。开启流式之后,数据块持续返回,连接保持活跃,就能避开这个问题。
2.4 错误处理:那些API返回的报错码该怎么看
在实际调用中,API不可能永远稳定。我在开发和运行期间遇到过几类典型错误,这里直接给结论:
- 503 Server Overloaded。服务端过载,属于临时性问题。处理方式是重试,但要有退避策略——第一次失败等2秒重试,第二次4秒,第三次8秒,最多重试5次。不要无限重试,也不要一失败就立刻打回去。
- 400 Context Length Exceeded。输入加上输出超过模型的上下文限制。处理方式是拆分请求,把长文章分段落生成,每段单独发一次请求,然后把结果拼起来。
- 529 Overloaded。和503类似,也是过载,处理方式同样是重试。
- Authentication Failed。检查API Key是否正确、是否过期。注意硅基流动的Key分为普通Key和细粒度Key,权限不同,调用某些模型需要开通对应权限。
3. 全栈工程实现:后端、数据库与异步任务设计
3.1 后端框架选择:FastAPI为什么比Flask和Node.js都合适
我最终选了FastAPI作为后端框架。这不是随大流,而是有明确的理由:
第一,FastAPI原生支持异步。博客文章生成涉及多次外部API调用,每次调用耗时几秒到几十秒不等。如果是同步阻塞的框架,一个生成请求占住一个工作线程,并发一高,服务直接卡死。异步可以让我在等待API返回的同时处理其他请求。
第二,FastAPI自动生成API文档。/docs页面自动列出了所有接口的定义和参数,前端同学联调的时候不需要再翻文档,省了很多沟通成本。
第三,Pydantic的请求校验。前端传过来的参数(比如标题风格、文章长度、目标读者)直接在模型层做校验,非法参数直接返回400,不会一路传到大模型那边浪费API额度。
3.2 API Key安全:打死也不要把Key放进前端
这是全栈开发里我最想强调的一条。很多人做Demo图省事,把API Key直接写在JavaScript里,请求直接从前端发到模型API。这在个人项目里勉强能跑,但一旦项目要部署上线、多人使用,就是灾难级的隐患——Key可以直接从浏览器开发者工具里翻出来,别人拿去调用API,费用全算你头上。
我用的是标准方案:前端 -> 后端代理 -> 大模型API。前端只跟自己的后端对话,后端从环境变量读取API Key,再转发请求到硅基流动。后端代码大概是这样的:
python复制from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import os
import httpx
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # 生产环境换成真实域名
allow_methods=["*"],
allow_headers=["*"],
)
class GenerateRequest(BaseModel):
topic: str
style: str = "tech"
length: int = 800
@app.post("/api/generate/outline")
async def generate_outline(req: GenerateRequest):
api_key = os.getenv("SILICONFLOW_API_KEY")
if not api_key:
raise HTTPException(status_code=500, detail="API Key not configured")
# 构造提示词
# 调用模型API
# 返回结构化大纲
return {"outline": outline_data}
环境变量在.env文件里管理,.env文件永远不要提交到Git仓库。生产环境用云平台的密钥管理服务来存。
3.3 数据库设计:文章、任务、生成记录怎么建模
内容平台的核心数据模型我拆成了四张表:
topics(灵感表)。记录用户输入的灵感关键词、选题类型、来源渠道。这张表的目的是积累选题库,也是之后"自动推荐选题"功能的数据基础。
articles(文章表)。记录最终的成文。字段包括标题、slug(URL别名)、封面图URL、正文内容(Markdown格式)、所属分类、标签、状态(草稿/已发布/已下架)、发布时间。
generate_tasks(生成任务表)。记录每一次生成任务的执行状态。为什么不把任务信息直接放articles表?因为一次文章生成包含多个子任务(生成标题 -> 生成大纲 -> 分段落扩写),每个子任务都有自己的状态和结果。拆成独立的任务表,便于追踪和管理。
generation_logs(调用日志表)。记录每一次模型API调用的入参、出参、耗时、token消耗、费用估算。这张表的作用是复盘——哪个模型性价比高、哪种提示词效果最好、单篇文章的实际成本是多少,都能从这里统计出来。
3.4 异步任务机制:怎么解决"生成一篇要等三分钟"的体验问题
文章生成是个慢操作。用户点击"生成"按钮之后,如果页面一直转圈三分钟,体验非常糟糕。我的做法是任务化 + 主动轮询:
前端点击生成 -> 后端收到请求,创建一个generate_task记录,状态为"pending" -> 后端立刻返回task_id -> 后台异步执行生成流程(调API、写结果、更新状态) -> 前端拿到task_id后,每隔几秒轮询一次任务状态 -> 状态变为"completed"时,前端拉取文章内容和任务详情。
这个方案不复杂,几十行代码就能实现,但体验上完全是质变。用户点击生成之后可以继续浏览其他页面,等生成完成后在任务列表里看到结果。
4. 前端交互与内容管理:从一个输入框到一套编辑器
4.1 前端技术栈:React + TailwindCSS,为什么不用Next.js
前端我用的是React + TailwindCSS,没有上Next.js这类全栈框架。原因是我后端已经用FastAPI搭好了,前端只需要一个单页应用负责交互,不需要服务端渲染。Next.js的SSR在这种场景下属于"用不上的功能",还白白增加部署复杂度。
页面结构我做了四个:
- 灵感输入页:一个大的输入框,用户可以输入一句模糊的想法(比如"Kubernetes的存储卷管理"),下面有几个下拉选项(文章类型、风格、目标字数),一个"开始生成"按钮。
- 生成过程页:展示任务流程的状态,标题生成完成 -> 大纲生成完成 -> 正在扩写第二节,每一分钟更新一次进度。用进度条和当前执行步骤来反馈进度。
- 内容编辑页:生成完成之后进入编辑器,左侧是Markdown源码,右侧是实时预览,顶部是标题。用户可以对生成的内容进行修改,改完点"保存"。
- 文章管理页:以列表形式展示所有生成过的文章,支持筛选、搜索、删除。
4.2 Markdown编辑:为什么不用大而全的富文本编辑器
博客写作圈子的主流格式是Markdown,GitHub、各大技术社区都原生支持。所以我前端没有用一般的富文本编辑器,而是选了文本域 + Markdown实时渲染的方案。
这个选择的好处是:
- 大模型生成的正文本身就是Markdown格式,直接放进文本域,无需任何转换。
- Markdown渲染结果可控,代码块、引用块、表格都能正确展示。
- 后续如果要对接GitHub等平台自动发布,Markdown文件可以直接上传,兼容性最好。
我在前端用的渲染库是react-markdown,配合remark-gfm插件支持表格、任务列表等GitHub风格语法。代码高亮用的是react-syntax-highlighter,按需引入语言包,避免整个包体积过大。
4.3 人工审核:自动化生成不等于无人值守
这是我很想强调的一点:自动化的目的是提效,不是取代人的判断。 AI生成的文章可能存在事实性错误、逻辑偏差、或者某些选题本身就不合适。所以我的流程里强制保留一个人工审核环节:
- 生成完成后,文章状态是"待审核",不会自动发布。
- 用户进入编辑页,通读全文,修改有问题的地方。
- 确认无误后,手动点击"发布",文章才会被标记为已发布状态,并推送到发布队列。
在提示词里,我也会要求模型尽量避免编造精确的数据、统计数字、或者未经证实的"案例"。如果确实需要数据支撑,提示词会引导模型使用"根据公开资料显示"这类客观表述,或者用占位符标记"待核实"。
5. 提示词工程:让模型写出"能直接发"的博客,而不是"AI味"的文章
5.1 结构化提示词模板:System、Context、Task三层分离
很多人在写提示词的时候,习惯把所有要求都堆在一个message里。这在简单任务上行得通,但在博客生成这种复杂任务上,会导致模型注意力分散,顾此失彼。
我采用三层结构的提示词设计模式:
System层:定义模型的角色和整体行为约束。
code复制你是一位资深的技术博主和编辑,拥有10年以上的技术写作经验。
你熟悉技术博客的叙事结构、标题技巧和读者心理。
你的文章风格清晰、务实、有深度,避免空洞的套话和AI腔。
Context层:提供本次生成任务的背景信息和参考材料。
code复制本次任务的主题是:{topic}
目标读者:{audience}
文章风格偏好:{style}
参考文章示例:(可选,粘贴几篇你喜欢的文章开头,让模型模仿)
Task层:给出明确的、可拆解的执行指令。
code复制请完成以下步骤:
1. 为这个主题生成10个候选标题,其中3个偏实用性,3个偏故事性,2个偏热点结合,2个偏争议性。
2. 从中选择最合适的1个标题,一并输出。
3. 基于这个标题生成文章的大纲,包含引言、3-5个正文小节(每个小节有明确的分论点)、结语。
4. 每个小节标注预计字数。
这种"角色-背景-任务"三层的拆分方式,实测下来比单段式提示词的生成质量稳定得多。
5.2 标题生成与正文生成为什么要解耦
最早我尝试过"给一个主题直接生成全文"的一步到位方案。效果不理想——标题往往过于平淡,没有吸引力,而正文也会因为标题没有定好而缺乏方向感。
后来我把流程改成了"先标题后大纲再正文"的三段式管线:
第一步:生成标题。
只调用一次模型,输出10个候选标题。这步单独跑,是因为标题选择的决策点比较集中,用户可以快速做判断,不需要等正文慢慢生成。
第二步:生成大纲。
选定标题之后,让模型基于标题生成结构化的大纲。大纲包含引言、各章节的小标题和核心论点摘要。大纲相当于建筑的设计图纸,必须用户确认才能进入下一步。
第三步:分段扩写。
大纲确认后,逐个小节进行扩写。这里分次调用API,每次只扩写一个章节。好处是:
- 每次请求的上下文较短,模型注意力更集中;
- 如果某一节生成质量不佳,只需要重新生成这一节,不用整篇重新生成;
- 可以控制总字数,每节字数相加就是文章总长度。
5.3 常见的"AI味"问题与修复手段
自动化生成的文章最大的问题就是"一看就是AI写的"。我总结下来主要是三类问题:
问题一:过度使用"首先、其次、最后"等连接词。
修复方式是在提示词里明确要求"避免使用排比式的连接词,用更自然、口语化的过渡衔接段落"。
问题二:内容过于正确、没有个人观点。
AI倾向于输出"安全"的中立内容,但这恰恰让文章变得乏味。修复方式是给模型一个明确的角色设定,比如"你是一位踩过很多坑的资深工程师",并且要求"在适当的地方加入个人经验和主观判断的表达"。
问题三:缺乏具体细节。
AI生成的内容往往停留在概念层面,缺少具体的操作步骤、参数配置、实测数据。修复方式是在提示词中要求"每个小节必须包含至少一个具体的操作示例或实际案例",如果模型编造细节,人工审核时再作修正。
这里分享一个实测有效的技巧:给模型"喂"几段你以前写过的文章开头。 不需要很多,两三段就够。模型能从这几个样本里学到你的语气和节奏,生成的文章会更"像你"。具体做法是在Context层里加上一段低优先级的"参考风格"文本。
6. 从开发到生产:部署方案、真实踩坑与成本优化
6.1 部署选型:一台云服务器能搞定的事,就不上K8s
整个项目的部署复杂度其实不高,没必要一上来就整Kubernetes那套。我的方案是:
- 一台云服务器(2核4G起步),上面跑Docker容器。
- 后端FastAPI用
uvicorn多进程跑,配合Nginx做反向代理和静态文件服务。 - 前端React项目构建后打包成静态文件,直接让Nginx托管。
- PostgreSQL数据库,单独一个Docker容器,数据目录挂载到宿主机磁盘。
- 用
docker-compose.yml编排服务,一条命令就能完成部署。
有人可能会问,调用模型API的场景,服务部署在国内服务器还是国外服务器有没有区别?实际体验下来,硅基流动的API在国内直连的延迟就很低,不需要走任何中转。我自己用的是国内云服务器,整个生成流程的耗时瓶颈主要在大模型推理时间上,API的网络开销可以忽略不计。
6.2 踩坑实录:限流、超时与token消耗
在生产环境跑了一周之后,我开始陆续遇到一些测试阶段没暴露的问题。
踩坑一:并发请求触发限流。
博客生成流程里,扩写阶段是串行调用API的——扩写完第一节再扩写第二节。用户如果同时发起两篇文章的生成请求,瞬间的API并发量会翻倍。硅基流动对普通用户的API有速率限制(RPM,每分钟请求数),超过之后会返回429或503。我的应对方案是在后端加一个简单的信号量,限制同时进行中的模型调用数量。假设限流是60 RPM,就设置并发数为5,每篇文章平均调用15-20次API,5个并发刚好在限流边缘。
踩坑二:长文章生成超时。
第一次测试生成5000字长文的时候,前端轮询到90秒就超时了。排查之后发现是Nginx的proxy_read_timeout默认值是60秒,后端还没处理完,代理层先把连接掐了。修复方式是在Nginx配置里调大超时时间:
nginx复制location /api/ {
proxy_pass http://backend:8000;
proxy_read_timeout 300s;
proxy_connect_timeout 30s;
}
另外FastAPI这一侧也要注意,同步的第三方HTTP客户端(比如requests)会阻塞事件循环,一定要用异步客户端(httpx.AsyncClient)来调模型API,否则并发能力会大打折扣。
踩坑三:token消耗比预期快。
硅基流动的计费方式是按token计费,输入和输出都算。我一开始没有做任何优化,生成一篇3000字的文章,有大纲和分段扩写,总token消耗通常在2万到3万之间。如果一天生成10篇,成本就相当可观。
我的优化方案是:
- 精简System提示词。不需要每次都把长篇的角色设定完整拼一遍,可以缩短成几句话。
- 上下文裁剪。扩写每个小节的时候,只需要把"标题 + 大纲 + 当前小节的分论点"传给模型,不需要把已经生成的整篇文章也塞进去,这样可以大幅减少输入token。
- 使用更小的模型做简单任务。标题生成用轻量模型,只有扩写正文才用最强模型。
这三招优化下来,单篇文章的API成本降低了差不多40%。
6.3 成本追踪与告警:别等月底账单出来才肉疼
我强烈建议在生产环境接一个简单的成本追踪方案。我的做法是在调用日志表里记录每次请求的prompt_tokens和completion_tokens,然后按模型的单价(在平台的价格页面可以查到)换算成费用,每天跑一个定时任务统计当天的总消耗。
如果单日消耗超过设定阈值(比如50元),给我推一个告警通知。这个机制帮我及早发现了"某个用户反复点击生成按钮导致token消耗异常"的问题,避免了月底账单爆炸。
7. 回顾与扩展:这套实践还能进一步做成什么
整个项目从零到上线,我前后花了两周业余时间。核心代码量其实不大,加起来大概两千行左右,大头在提示词调优和各种边界情况的处理上。
现在这套平台已经稳定运行了半年,累计生成了上百篇文章,其中有一部分我人工修改后发布到了自己的博客,效果还不错。更大的价值反而在于选题库——半年积累的选题记录让我的内容规划变得非常清楚。
如果你也想做类似的实践,我最后再给三条建议:
第一,不要一上来就追求大而全。
先跑通"输入主题 -> 生成标题 -> 生成大纲 -> 分段扩写 -> 人工编辑 -> 输出Markdown"这条主链路,其他功能(用户体系、权限管理、自动发布)等需要的时候再加。先让内容能稳定地生产出来,比什么都重要。
第二,提示词是花时间最多、但回报最高的事。
同样的模型,提示词写得好不好,生成质量的差距是肉眼可见的。多试几次,把每一次生成的输出和当时的提示词对应起来看,慢慢就能摸到规律。所谓"AI写作能力",很大程度上就是"AI提示词能力"。
第三,永远保留人工审核环节。
自动化是放大我们能力的手段,但最终的判断责任在于人。特别是要发布到公开平台的内容,一个事实性错误或者不合适的表述,可能比不更新更伤读者信任。
技术的价值在于让人把时间花在真正需要人的地方——选题的判断、内容的打磨、与读者的互动。这部分,AI暂时还替代不了。
