把散落的提示词聚集到一块!

12 阅读5分钟

把散落的提示词聚集到一块!

2026-06-08 晚 天气晴朗

起源

当前正在维护一个有多个不同提示词片段拼接需求然后发给LLM服务器的项目,然而提示词的拼接是一个很头痛的问题,特别是在业务增加的时候,我们的 promt_builder.py 提示词拼接模块已经来到了700行的规模。每次更改该模块代码的时候都是心浮气躁,整体拼接逻辑零散,不利于扩展以及维护。同时我们大部分基础提示词还是硬编码的,导致整个提示词系统臃肿、职责不单一,而且Ai改这些屎山也很容易出问题,出现不符合我们要求的问题。

目标

重构该子系统,使得代码可读性增加,减少不必要的拼接代码,要求模块单一职责化。同时保持对外API无变动,单元测试通过

实施过程

架构设计

我们将该子系统的数据流分为如下几个步骤

  1. 提示词从本地解析
  2. 提示词拼接
  3. 将完整提示词处理为LLM可用格式
  4. 与服务商交互

其中 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参数说明
templatelist[str]我们项目需要双坐标才能定位到一个提示词
sourcestr用于溯源
dispatchint第二键值,用于确认当 order 值一致的时候的顺序(如默认的 dispatch = 0,而用户自定义的片段 dispatch > 0)

新改动:

  • section_type 更改为列表,现在一个通用的提示词可以被多个 section 引用,不用反复创建 (template同理)
  • 新增 WILD_CARD (*) 用于参数 template section_type 值,当应用该值的时候,所有section获取对应提示词的时候都会带有该段提示词

数据存储: 为了解决提示词硬编码进代码中,同时当前数据结构也非常适合作为json或者放到数据库中存储,所以也解决了提示词硬编码的问题

最终实现

最核心的还是这个提示词拼接,其余两个步骤都不复杂,我们就主要说这个

核心方法: get_full_prompt(section_type, template_id="*")

返回值: str

通过 section_type template_id 双值定位该次 LLM 调用所需的完整提示词

特殊设计

多重仲裁逻辑

template section_type 出现两个PromptFragment相同的时候进行多重仲裁的时候

  1. dispatch 不同 → 高者胜出
  2. dispatch 相同 → builtin 优先
  3. 都是 builtin + dispatch 相同 → 按确定性决胜键

总结

构完成后,prompt_builder.py 从 700 行代码降至 50 行。该子系统整体职责清晰:

  • prompt_store.py 完成代码拼接以及本地存储数据加载
  • prompt_builder.py 完成提示词格式转化(仅 50 行)
  • llm_client.py 完成服务商交互

Ai 确实好用,跑起来的时候快的很,但是真正需要扩展的时候,Ai 不会说这段代码好丑,我想把它重构,他只会默默忍受。(当然有一些SKILL确实可以自动的重构)有些时候还是要确切的去落实,去真正的查看一些是否一些地方可以做抽象层,但是注意也不要过度了,那就是浪费时间,聚焦于将来扩展可能要求高的地方。

还有说其实设计一个抽象层是一个很令人愉悦的地方,也间接的展现了你对整个仓库的掌握程度,这种核心的模块作为开发者去了解还是很重要的,Ai固然好用,但是有些时候一些自己亲自动手才有的快感是Ai不能给你的。就假设我这个地方让Ai去做了一个抽象层,我今天就写不出来这篇文章了是吧

项目地址 PromptStore