把散落的提示词聚集到一块!
2026-06-08 晚 天气晴朗
起源
当前正在维护一个有多个不同提示词片段拼接需求然后发给LLM服务器的项目,然而提示词的拼接是一个很头痛的问题,特别是在业务增加的时候,我们的 promt_builder.py 提示词拼接模块已经来到了700行的规模。每次更改该模块代码的时候都是心浮气躁,整体拼接逻辑零散,不利于扩展以及维护。同时我们大部分基础提示词还是硬编码的,导致整个提示词系统臃肿、职责不单一,而且Ai改这些屎山也很容易出问题,出现不符合我们要求的问题。
目标
重构该子系统,使得代码可读性增加,减少不必要的拼接代码,要求模块单一职责化。同时保持对外API无变动,单元测试通过
实施过程
架构设计
我们将该子系统的数据流分为如下几个步骤
- 提示词从本地解析
- 提示词拼接
- 将完整提示词处理为LLM可用格式
- 与服务商交互
其中 4. 与服务商交互 已经由 llm_client.py 使用 litellm 库完成了,当前状态合理,不再重构范围内
数据结构确定
因为提示词拼接的本质就是文本拼接,我们的拼接准确的可以描述为 "这段提示词应该在何时何地出现" "何时",就是那次调用使用这个提示词 "何地",就是提示词在这次调用的时候的顺序,所以我们根据这个要求拟了一个粗糙版本的一个数据结构
{
"id": "A",
"section_type": "B",
"order": 10,
"content": "Hello world"
}
在这里,我们通过 section_type 值来确定何时,比如角色 A,当角色A的时候,这个提示词就会被拼接,同时 order 值用来确定何地,我们根据 order 的大小顺序来排序,告诉系统这个应该排第几位
在实际的论文排版系统中,这里的‘角色’就是不同的论文章节——封面、摘要、正文、参考文献、致谢。每个章节需要的提示词不同,但它们又有共同的底线要求。 当前,这个提示词就会被拼接,同时
order值用来确定何地,我们根据order的大小顺序来排序,告诉系统这个应该排第几位
但是在实际生产过程中,当前数据结构可能不够健壮,就比如可能遇到以下问题
- 如果两个
order值一样怎么处理 (默认提示词,以及自定义提示词) - 同一段提示词被多次引用怎么办? (如开头"你是一位xxx")
- 不可溯源,并不清楚到底是谁引入的这段提示词
所以迭代过后我们将数据结构确定为以下样式
body_instruction = PromptFragment(
section_type=["body"],
template=["*"],
source="builtin",
order=10,
dispatch=0,
content="Hello world",
)
| 新增key | 参数 | 说明 |
|---|---|---|
| template | list[str] | 我们项目需要双坐标才能定位到一个提示词 |
| source | str | 用于溯源 |
| dispatch | int | 第二键值,用于确认当 order 值一致的时候的顺序(如默认的 dispatch = 0,而用户自定义的片段 dispatch > 0) |
新改动:
- 将
section_type更改为列表,现在一个通用的提示词可以被多个 section 引用,不用反复创建 (template同理) - 新增
WILD_CARD (*)用于参数templatesection_type值,当应用该值的时候,所有section获取对应提示词的时候都会带有该段提示词
数据存储: 为了解决提示词硬编码进代码中,同时当前数据结构也非常适合作为json或者放到数据库中存储,所以也解决了提示词硬编码的问题
最终实现
最核心的还是这个提示词拼接,其余两个步骤都不复杂,我们就主要说这个
核心方法: get_full_prompt(section_type, template_id="*")
返回值: str
通过 section_type template_id 双值定位该次 LLM 调用所需的完整提示词
特殊设计
多重仲裁逻辑
在 template section_type 出现两个PromptFragment相同的时候进行多重仲裁的时候
dispatch不同 → 高者胜出dispatch相同 →builtin优先- 都是
builtin+dispatch相同 → 按确定性决胜键
总结
构完成后,prompt_builder.py 从 700 行代码降至 50 行。该子系统整体职责清晰:
prompt_store.py完成代码拼接以及本地存储数据加载prompt_builder.py完成提示词格式转化(仅 50 行)llm_client.py完成服务商交互
Ai 确实好用,跑起来的时候快的很,但是真正需要扩展的时候,Ai 不会说这段代码好丑,我想把它重构,他只会默默忍受。(当然有一些SKILL确实可以自动的重构)有些时候还是要确切的去落实,去真正的查看一些是否一些地方可以做抽象层,但是注意也不要过度了,那就是浪费时间,聚焦于将来扩展可能要求高的地方。
还有说其实设计一个抽象层是一个很令人愉悦的地方,也间接的展现了你对整个仓库的掌握程度,这种核心的模块作为开发者去了解还是很重要的,Ai固然好用,但是有些时候一些自己亲自动手才有的快感是Ai不能给你的。就假设我这个地方让Ai去做了一个抽象层,我今天就写不出来这篇文章了是吧
项目地址 PromptStore