提示词驱动软件:用自然语言改变程序行为的设计与实现
在现代软件系统中,配置和参数化是常见需求。传统的做法是使用配置文件、命令行参数或图形界面,但这些方式对普通用户并不友好。用户不会去修改 YAML 或 JSON 文件来表示“把输出变成大写”或“忽略所有以 test 开头的行”。更直接的想法是:软件应该能通过一句自然语言提示词改变行为。这个思路就是“提示词驱动软件”的基本含义,也是本文要讨论和实现的核心主题。
本文会从一个可运行的最小项目入手,设计一个文本处理工具,用户输入类似“转大写”“统计单词数”的提示词,程序就能改变处理逻辑。更重要的是,这个设计不依赖复杂的 AI 模型,而是用一套可扩展的提示词注册机制实现。你可能在工作中遇到过类似的诉求:市场人员想用一句话导出报表,运营人员想用一句话筛选日志,测试人员想用一句话切换数据格式。如果软件允许通过提示词改变行为,这些需求就能以更轻量的方式实现。
全文会先解释提示词驱动软件的概念和原理,然后搭建一个基于 Python 的示例项目,从环境准备、目录结构、核心代码到运行验证一步步走通。最后给出常见的排查思路和最佳实践。阅读完这篇文章,你将具备在现有软件中预留提示词入口、注册自定义行为、处理未匹配提示词、以及在生产环境中避免注入风险的基本能力。
1. 提示词驱动的软件是什么,为什么值得做
1.1 从配置文件到提示词输入
传统软件的行为变化主要靠外部参数。你可以通过config.ini设置“是否显示详情”,通过--verbose参数控制日志级别,或者在设置界面勾选选项。这些方式的问题在于,用户需要知道参数的名字、位置和取值范围,才能产生预期效果。配置项越多,学习成本越高。
提示词驱动的思路是反过来:用户只描述结果,由软件负责解析并找到对应的处理逻辑。比如用户说“去掉空行”,软件通过规则匹配到remove_empty_lines函数并执行。用户不需要知道函数名,也不需要关心配置路径,只需要说一句符合约定的话。
从技术实现上看,配置文件和提示词并不冲突。配置文件适合静态、稳定的参数,提示词适合动态、灵活的交互式场景。两者的关系更像是“配置描述状态,提示词描述动作”。一个成熟的软件通常会同时使用这两种机制。
1.2 提示词驱动的典型场景
提示词驱动并不是 AI 应用的专属概念。在很多常见工具中,这种模式已经存在:
- 命令行工具支持自然语言别名,比如
git的 alias 命令可以把git checkout缩写为git co,也可以通过自定义别名把一整段复杂命令变成一个单词。 - 代码生成工具根据用户输入的描述生成代码片段,这类工具的核心就是提示词到代码模板的映射。
- 运维机器人接受“重启所有 web 服务”这样的指令,把它解析为具体的运维命令并执行。
- 数据处理平台允许用户输入过滤条件表达式,比如
age > 18 and city == "北京",这类表达式其实是一种半结构化的提示词。
这些场景的共性是:用户不必理解内部实现,只要表达目标,软件负责翻译和执行。当你把一个系统从“配置驱动”升级为“提示词驱动”,交付物就从一份说明书变成了一个会听话的工具。
1.3 提示词驱动与 AI 模型的关系
现在一说“提示词”,很多人会联想到大型语言模型。大模型确实可以理解任意自然语言,但它不是提示词驱动软件的唯一实现方式。以本文的文本处理工具为例,如果用户说“转大写”,完全可以用简单的字符串匹配对应到upper()函数。如果用户说“把所有字母转成大写,并且去掉首尾空格”,可能需要规则引擎或正则表达式。只有当用户需求极其灵活,比如“写一封周报邮件”的时候,才需要借助大模型。
所以在实际工程中,建议采用分级策略:
- 结构化、可枚举的提示词用规则匹配。
- 半结构化的提示词用模板解析。
- 完全开放的自然语言需求才接入 AI 模型。
这样做的好处是成本低、响应快、可测试、可回滚。大模型不是万能的,也不应该成为所有场景的第一选择。
2. 设计一个提示词驱动的软件框架
2.1 整体架构:提示词解析、行为注册、执行与安全
一个提示词驱动系统至少需要四层结构:
- 输入层:接收用户输入的原始提示词。
- 解析层:把提示词拆解为可执行的命令,比如把“转大写”解析成动作
upper。 - 行为层:根据动作名称调用对应的处理函数。
- 安全层:校验提示词是否合法、是否有权执行,避免恶意字符串导致程序崩溃或权限泄漏。
在这个架构里,行为注册表是核心。它维护着一张从“动作名称”到“函数对象”的映射。用户输入提示词后,解析层负责从提示词中提取动作名称,行为层再根据动作名称找到函数并执行。
一个常见的设计错误是把解析逻辑和业务逻辑写在一起。比如在同一个函数里先判断字符串再执行处理,这会导致新行为难以扩展。正确的做法是把“如何识别提示词”和“如何处理数据”完全解耦。新增加一个行为时,只需要注册一个新的提示词和对应的处理函数,不用改动解析器的核心代码。
2.2 行为注册表的设计
行为注册表本质上是一个字典,键是动作名称,值是一个处理函数。为了避免函数签名不统一,通常会定义一个统一的接口。在 Python 中,可以用一个协议类或简单的函数签名约定来实现。
一个行为处理函数应该接收文本和可选参数,返回处理后的结果。比如:
def upper_action(text: str, args: dict) -> str: return text.upper()注册表结构如下:
ACTIONS = { "upper": upper_action, "lower": lower_action, "count_words": count_words_action, "add_line_numbers": add_line_numbers_action, }更复杂的系统还会为每个动作增加元信息,比如动作说明、参数定义、是否敏感等。这些信息可以用于提示词解析时的模糊匹配,也可以用于在前端生成帮助列表。
2.3 提示词解析策略
解析策略直接决定了软件的易用性。最简单的策略是精确匹配,用户必须输入“upper”或“转大写”才能正确触发。更好的策略是支持同义词和模糊匹配。
推荐的做法是维护一个“解析规则”列表,每个规则包含:
- 一组可匹配的提示词模板。
- 一个目标动作名称。
- 一个权重,表示匹配的优先级。
当用户输入提示词时,系统会按顺序遍历规则,找到第一个匹配的规则并返回动作名称。匹配可以用以下几种方式:
- 精确匹配:
prompt == "转大写"。 - 包含匹配:
"大写" in prompt。 - 正则匹配:
re.search(r"转\s*大写", prompt)。 - 相似度匹配:使用编辑距离或词向量相似度。
实际项目中,精确和包含匹配已经能覆盖大部分需求。相似度匹配适合用户表达不固定的场景,但对计算资源和测试要求更高。
3. 环境准备与项目结构
3.1 运行环境和依赖
本文示例使用 Python 3.9 以上版本,不需要安装任何第三方依赖。整个项目只有一个包和两个数据文件。操作系统中只要具备基础的 Python 环境即可。
环境检查命令:
python --version如果输出为Python 3.9.x或更高版本,就可以继续。如果你在 Windows 系统上,请确保python命令出现在环境变量中。在 Linux 或 macOS 上,通常默认可用。
为了便于演示,项目的运行入口是命令行。所有功能都能通过python main.py "提示词" "原始文本"这样的方式调用。这种形式也方便接入后续的 Web 接口或消息机器人。
3.2 项目目录和文件说明
创建以下目录结构:
prompt-driven-text/ ├── main.py # 入口文件 ├── actions.py # 内置行为和注册表 ├── parser.py # 提示词解析逻辑 ├── prompt_rules.json # 用户可配置的提示词规则 └── custom_actions.py # 用户自定义行为示例main.py负责命令行参数读取和结果输出。actions.py定义处理函数和默认行为注册表。parser.py实现把提示词转换为动作名称。prompt_rules.json是用户可以直接修改的规则文件,用于实现“通过提示词改变软件行为”的核心效果。custom_actions.py提供示例代码,说明如何把自定义函数注册到系统中。
4. 实现一个可运行的文本处理工具
4.1 定义行为注册接口
不同行为处理函数的输入输出可能不一致。为了统一,统一规定处理函数接收两个参数:一是待处理的文本,二是从提示词中解析出的参数字典。返回值必须是字符串,或能安全转换为字符串的对象。
定义一个基础函数签名示例:
def upper_action(text: str, args: dict) -> str: return text.upper() def lower_action(text: str, args: dict) -> str: return text.lower() def count_words_action(text: str, args: dict) -> str: return str(len(text.split())) def add_line_numbers_action(text: str, args: dict) -> str: lines = text.splitlines() return "\n".join(f"{i+1}: {line}" for i, line in enumerate(lines)) def reverse_text_action(text: str, args: dict) -> str: return text[::-1]这里每一个函数都接收args参数。即使当前实现没用上,也保留这个参数,方便后续扩展参数解析。
4.2 内置提示词与处理函数
有了处理函数后,需要建立提示词到动作的映射。这里不直接在代码里写死,而是通过规则文件来配置。规则文件格式如下:
[ {"keywords": ["转大写", "uppercase", "upper"], "action": "upper"}, {"keywords": ["转小写", "lowercase", "lower"], "action": "lower"}, {"keywords": ["统计单词", "count words", "word count"], "action": "count_words"}, {"keywords": ["添加行号", "add line numbers", "line number"], "action": "add_line_numbers"}, {"keywords": ["反转", "reverse"], "action": "reverse_text"} ]这个文件的含义是:当提示词中出现任意一个关键词时,就把该提示词映射到对应的动作。你可以直接修改这个文件,为同一个动作增加更多关键词,或者把关键词指向不同的动作。
4.3 命令行入口和提示词解析
main.py的职责是读取用户输入、加载规则、调用解析器、执行行为并输出结果。代码结构如下:
import json import sys from actions import ACTION_MAP from parser import parse_prompt def load_rules(path: str) -> list: with open(path, "r", encoding="utf-8") as f: return json.load(f) def main(): if len(sys.argv) < 3: print("使用方法: python main.py <提示词> <文本>") return prompt = sys.argv[1] text = sys.argv[2] rules = load_rules("prompt_rules.json") action_name = parse_prompt(prompt, rules) if action_name is None: print(f"无法识别提示词: {prompt}") print("有效的动作包括: " + ", ".join(ACTION_MAP.keys())) return handler = ACTION_MAP.get(action_name) if handler is None: print(f"动作 {action_name} 没有对应的处理函数") return result = handler(text, {}) print(result) if __name__ == "__main__": main()parser.py实现了解析逻辑。为了支持用户自定义规则,它需要读取规则文件并匹配关键词。具体实现如下:
import re def parse_prompt(prompt: str, rules: list) -> str: normalized_prompt = prompt.strip().lower() for rule in rules: for keyword in rule["keywords"]: if keyword.lower() in normalized_prompt: return rule["action"] return None这里使用了最直观的子串匹配。考虑到中文提示词不区分大小写,因此先统一转为小写。如果需要更严格的匹配,可以改为正则表达式或精确匹配。
4.4 让你的软件通过提示词改变:自定义规则扩展
为了让软件真正具备“通过提示词改变”的能力,需要支持用户添加自定义行为。假设你想让软件支持“按分隔符拆分文本”的功能,可以按以下步骤完成:
- 在
custom_actions.py中实现处理函数:
def split_by_comma(text: str, args: dict) -> str: return "\n".join(text.split(","))- 在
actions.py的ACTION_MAP中注册该函数:
from custom_actions import split_by_comma ACTION_MAP = { "upper": upper_action, "lower": lower_action, "count_words": count_words_action, "add_line_numbers": add_line_numbers_action, "reverse_text": reverse_text_action, "split_by_comma": split_by_comma, }- 在
prompt_rules.json中添加规则:
{"keywords": ["按逗号拆分", "split by comma", "split with comma"], "action": "split_by_comma"}完成这步后,运行:
python main.py "按逗号拆分" "apple,banana,orange"输出结果为三行内容。这说明软件的行为已经通过新提示词发生了改变,无需修改入口逻辑。
5. 关键代码与参数详解
5.1 注册表数据结构和参数说明
ACTION_MAP是整个系统的行为中心。键是动作名称,值是函数对象。这个设计的关键点在于:
- 新增行为不需要改动
main.py和parser.py,只需要增加函数并注册。 - 动作名称在规则文件和代码之间形成契约,保持一致才能正确解析。
- 函数对象可以直接作为值传递,调用时使用统一的参数结构。
下面是一个示例注册表:
ACTION_MAP = { "upper": upper_action, "lower": lower_action, "count_words": count_words_action, "add_line_numbers": add_line_numbers_action, "reverse_text": reverse_text_action, }实际项目中可能还要加上说明字段、权限字段和错误处理字段。可以把注册表从字典扩展为包含元信息的列表,设计数据类来管理。
5.2 解析器的实现逻辑
parse_prompt函数的核心是遍历规则和关键词。这段代码简单但有效,不过有几个设计选择值得说明:
- 使用
in匹配子串,优点是规则编写成本低,缺点是可能出现误匹配。比如“统计单词”会匹配“统计单词数量”,这通常没有问题,但如果关键词过短,比如“转”,就会匹配到很多无关提示词。 - 规则顺序会影响匹配结果。如果两个规则包含相同关键词,第一个匹配的规则生效。因此规则文件应该把更具体的规则放在前面。
normalized_prompt统一转为小写,是为了让英文关键词匹配更宽容,但也会影响中文大小写无关的处理。中文本身没有大小写问题,所以结果是安全的。
如果你需要精确控制匹配,可以引入正则表达式。下面是包含正则支持的解析器变体:
def parse_prompt(prompt: str, rules: list) -> str: for rule in rules: pattern = rule.get("pattern") if pattern and re.search(pattern, prompt, re.IGNORECASE): return rule["action"] for keyword in rule["keywords"]: if keyword.lower() in prompt.lower(): return rule["action"] return None这样既保留了关键词的易用性,又允许用户在规则文件中配置更复杂的正则以应对特殊场景。
5.3 为什么不建议使用裸正则匹配
正则表达式很强大,但在提示词解析中直接使用裸正则,容易出现两类问题:
- 正则表达式自身的错误会导致解析器崩溃。如果规则文件由非技术人员维护,正则表达式很难维护和调试。
- 正则表达式过于宽泛时会产生灾难性回溯,导致响应卡顿。
因此建议把正则视为一种高级选项,通过单独的pattern字段配置,并做好异常捕获。默认使用关键词包含匹配,因为理解成本最低、测试最方便。
6. 运行验证与结果分析
6.1 启动和基本用法
首先进入项目目录,执行以下命令验证基本功能:
python main.py "转大写" "hello world"预期输出:
HELLO WORLD然后测试行号功能:
python main.py "添加行号" "第一行 第二行 第三行"预期输出:
1: 第一行 2: 第二行 3: 第三行这些结果证明动作注册和解析流程都正常工作。
6.2 自定义提示词的验证
修改prompt_rules.json,加入一组新关键词前,先确认相应的处理函数已经注册。例如,将内置的“反转”动作再添加一个中文关键词“倒序”。修改规则后重新运行:
python main.py "倒序" "abc"预期输出:
cba这验证了规则文件修改后立即生效,不需要重新启动程序。这正是“软件通过提示词改变”最直观的体现:行为的变化来自规则和数据,而不是代码变更。
6.3 边界情况和错误处理
输入文本为空时,执行“统计单词”会报错吗?目前实现中text.split()会返回空列表,长度为 0,输出0,行为正常。如果使用text.splitlines()且文本末尾有换行符,可能产生空字符串条目,需要根据业务决定是否过滤。
当提示词无法匹配任何规则时,main.py会输出提示并列出所有可用动作。这是避免用户迷茫的必要反馈。
如果规则文件被修改为错误格式,比如缺失action字段,parse_prompt会抛出KeyError。生产环境需要捕获这些异常并给出可读提示。
7. 常见问题排查
7.1 提示词未匹配到任何行为
| 问题现象 | 常见原因 | 检查方式 | 处理建议 |
|---|---|---|---|
| 输入提示词后提示“无法识别” | 关键词写得不一致 | 打印规则文件内容,看关键词是否在提示词中 | 将提示词和规则关键词都转为小写后再比较 |
| 提示词匹配了错误的动作 | 规则顺序不当 | 查看规则文件顺序,排查是否被前一条规则抢占 | 把更具体的规则放在前面,或使用正则精确匹配 |
| 动作名称对不上 | 规则文件里的动作名和注册表不一致 | 打印ACTION_MAP.keys(),与规则里的action对比 | 统一命名规范,建议增加启动时的完整性校验 |
7.2 处理函数抛出异常
处理函数内部可能有业务异常。比如按逗号拆分时,如果文本中根本没有逗号,函数会返回原文本,这属于正常行为。但如果传入不可转换的数据类型,比如None,就可能抛出异常。建议在main.py中包裹try-except,记录错误日志并输出友好提示。
try: result = handler(text, {}) except Exception as e: print(f"执行动作 {action_name} 时出错: {e}")7.3 自定义规则文件加载失败
规则文件是 JSON 格式,最容易出现的问题是多写了一个逗号或注释。JSON 不支持注释。建议使用json.load时捕获JSONDecodeError,并提示具体行号和列号。
import json try: with open("prompt_rules.json", "r", encoding="utf-8") as f: rules = json.load(f) except json.JSONDecodeError as e: print(f"规则文件格式错误: {e}")7.4 生产环境中的安全风险与防护
这个示例中,提示词只是映射到内部函数,没有直接执行用户提供的代码,因此注入风险较低。但一旦你把提示词解析扩展到支持动态代码执行、调用外部命令或读取敏感文件,就必须考虑安全设计。
常见风险包括:
- 提示词尝试读取任意文件,比如
cat /etc/passwd。 - 提示词尝试调用删除操作,比如
rm -rf。 - 提示词内容携带恶意正则导致拒绝服务。
防护建议:
- 使用白名单机制,只允许注册过的动作。
- 对注册表增加权限标记,比如
admin_only动作只允许特定用户调用。 - 限制规则文件来源,生产环境不应允许未经验证的用户直接修改规则文件。
- 对正则表达式进行复杂度限制,或提示词长度限制。
8. 最佳实践与扩展方向
8.1 学习环境与生产环境的差异
学习环境中,规则文件可以直接修改并即时生效,这带来很高的灵活性。但生产环境需要额外考虑:
- 规则文件存放在独立目录,并做好版本控制。
- 规则变更应通过发布流程生效,而不是直接修改线上文件。
- 记录每次提示词的调用日志,便于问题回溯。
- 对提示词解析结果做指标监控,比如未匹配率、动作调用频率。
- 建立回滚机制,当新规则导致行为异常时,可以快速恢复到上一版本。
8.2 维护一个提示词规则清单
提示词规则会随着时间不断增加。建议在项目中维护一个规则文档,至少包含以下信息:
| 动作名称 | 触发关键词 | 处理函数 | 参数示例 | 依赖权限 | 负责人 |
|---|---|---|---|---|---|
| upper | 转大写, uppercase, upper | upper_action | 无 | 无 | 开发组 |
| count_words | 统计单词, word count | count_words_action | 无 | 无 | 开发组 |
| split_by_comma | 按逗号拆分, split by comma | split_by_comma | 无 | 无 | 开发组 |
规则清单不仅是开发文档,也是测试用例的来源。每个规则都应该有对应的自动化测试,确保关键词在后续修改中不会失效。
8.3 扩展方向:结合大模型和动态更新
当规则匹配无法覆盖用户开放的表达时,可以考虑在解析层加入大模型分类或实体抽取。典型做法是:
- 用户输入任意文本。
- 先将输入送到意图识别模型,得到意图标签。
- 意图标签再映射到具体的动作名称。
- 模型无法识别时,落到默认的兜底规则。
这种方式把“提示词驱动”从固定关键词升级为语义理解,但也会带来延迟、成本和结果不确定性问题。建议先在规则层解决 80% 的确定性需求,再把模糊情况交给模型处理。
另一条扩展方向是动态更新行为注册表。在一个运行中的服务里,通过热加载机制扫描新增的处理函数,并把新的动作名称注册到注册表中,无需重启进程。Python 中可以使用importlib.reload或插件框架实现,但要注意线程安全、异常隔离和版本兼容。
综合来看,“软件应能通过提示词改变”是一种注重交互和扩展性的软件设计思路。它并不要求所有行为都交由 AI 模型理解,而是希望在产品中预留一个灵活的入口,让用户用更接近自然语言的方式控制工具。本文的文本处理工具是一个最小示例,你可以把它扩展到日志分析、报表生成、测试数据构造、运维命令执行等场景。关键是掌握“解析规则、行为注册、安全校验、快速回滚”这几个核心环节,这样无论接入多少种提示词,软件都能保持稳定和可控。
