1. 问题背景与痛点🎯

在合同信息采集场景中,AI 对话的目标并不是自由聊天,而是通过多轮交互逐步拿到合同关键字段,最终输出结构化信息,再交给后续流程处理。这个过程看起来简单,真正落地时却会遇到两个比较典型的问题。

⚠️ 第一个问题:长对话易丢失任务聚焦对话一旦拉长,模型就容易丢失当前任务的聚焦点。合同字段采集通常不是一轮就结束,很多时候要经过多次追问、补充、确认,尤其当用户回复不完整、信息不规范、字段之间存在关联时,对话轮次会不断增加。轮次一长,模型就很容易出现偏题、重复提问、字段顺序错乱等情况。表面上还是在 “聊天”,实际上已经开始偏离采集目标。

⚠️ 第二个问题:多合同类型导致提示词膨胀合同类型一多,提示词会越来越长。如果把所有合同的字段规则、问法、示例一次性塞进同一份提示词里,token 消耗会明显增加,而且后续每新增一种合同,提示词还要继续膨胀。更关键的是,提示词太长以后,模型并不一定能更准确,反而更容易被无关内容干扰,导致当前合同的字段采集不稳定。对于这种强流程、强结构化的场景来说,提示词并不是越大越好,反而是越聚焦越稳。

针对以上问题,更适合采用一种更轻量的方式:把合同采集拆成两个阶段,先确认合同类型,再进入对应合同的字段采集。也就是说,不让模型一开始就面对所有合同,而是让它先 “选对场景”,再 “进入模板”。

2. 方案核心思路:动态提示词按阶段切换💡

动态提示词的核心思路,其实就是把整个对话流程拆成两层。

第一层:合同选择阶段这个阶段的目标不是采字段,而是引导用户在多个合同类型中做出明确选择,比如材料购销合同、建筑服务机械合同、运输合同、租赁合同、劳务分包合同等。模型在这一阶段只需要判断用户有没有明确选定合同类型,如果还没有,就继续引导;如果已经选定,就在回复里输出一个可识别的标记,交给程序处理。

第二层:字段采集阶段等系统识别到用户已经确定合同类型后,Python 程序就直接切换 System prompt,把提示词替换成对应合同的字段采集模板。这时模型面对的就不再是 “全部合同合集”,而是当前这一个合同的专属提示词。这样做的效果很直接:任务边界更清晰,提示词更短,对话更集中,后续扩展也更方便。

这个方案的本质,其实不是让模型 “记住更多”,而是让模型 “每次只关注当前阶段最重要的内容”。对于合同采集这类流程型任务来说,这种分阶段方式会比一套超长提示词更稳定,也更符合实际业务。

3. 系统流程与实现方式🔧

程序执行后,默认加载的是 “合同选择” 阶段的提示词。这个提示词的作用,就是引导用户从多个合同中选一个,不去过早进入字段细节。模型在这个阶段会持续输出类似[用户选择:无]这样的标记,表示当前还没有确定合同;当用户明确说出某一种合同类型后,模型就输出[用户选择:材料购销合同]这类标记,程序据此完成切换。

关键设计:统一格式识别标记模型的回复里要带一个统一格式的识别标记。这样程序不需要做复杂的语义判断,只要看标记就知道当前状态。比如未选择时输出[用户选择:无],选定后输出[用户选择:X],其中 X 必须和配置中的合同名称保持一致。这个设计非常重要,因为一旦格式统一,后面的提示词切换逻辑就会非常稳定,程序也更容易维护。

在代码实现上,核心逻辑并不复杂。系统会维护一个完整的对话历史 history,其中第一个元素是 system prompt。用户每输入一句话,都会追加到历史中,然后调用大模型接口获取回复。拿到回复后,先判断是否包含合同选择标记;如果包含,就解析出用户当前选择的合同类型,再调用 switch_prompt () 函数切换提示词。切换的时候不需要重启会话,只需要直接更新history[0]["content"],下一轮对话自然就会进入新的合同模板。

这种方式的好处在于:上下文不会丢,但任务已经切换。也就是说,对话历史还在,前面的信息还在,模型的 “记忆” 也还在,只是系统提示词已经从 “引导选合同” 变成了 “采集某个合同的字段”。这正是动态提示词的核心价值所在:不是重开对话,而是在同一段对话里完成阶段切换。

下面这段代码就是这个逻辑的核心,重点看是 **“标记解析”“系统提示词替换”** 这两步。

from zai import ZhipuAiClient
import prompt_switch

client = ZhipuAiClient(api_key="xxxxxx")

def llm_core(messages: list):
    """
    接收完整的消息列表并返回 AI 的回复对象
    """
    try:
        response = client.chat.completions.create(
            model="glm-4.6",
            messages=messages,
            thinking={
                "type": "disabled",
            },
            max_tokens=6553,
            temperature=0.8,
        )
        return response
    except Exception as e:
        print(f"请求出错: {e}")
        return None

def main():
    prompt = prompt_switch.switch_prompt("无")
    history = [{"role": "system", "content": prompt}]

    print("--- 已进入 RPA 助手模式 (输入 'exit' 或 'quit' 退出) ---")

    while True:
        user_input = input("\n用户: ").strip()

        if user_input.lower() in ["exit", "quit"]:
            print("对话结束。")
            break
        if not user_input:
            continue

        history.append({"role": "user", "content": user_input})
        result = llm_core(history)

        if result:
            ai_reply = result.choices[0].message.content

            user_choice = "无"
            if ai_reply.startswith("[用户选择:"):
                end_index = ai_reply.find("]")
                if end_index != -1:
                    user_choice = ai_reply[6:end_index]
                    ai_reply = ai_reply[end_index + 1:].strip()

            if user_choice and user_choice != "无":
                new_prompt = prompt_switch.switch_prompt(user_choice)
                history[0]["content"] = new_prompt

            print(f"助手: {ai_reply}")
            history.append({"role": "assistant", "content": ai_reply})
        else:
            print("助手: 抱歉,我现在遇到了一些问题,请稍后再试。")

if __name__ == "__main__":
    main()

📂 提示词配置建议:单独维护提示词配置这部分也建议单独维护,不要直接写死在主流程里。因为未来合同类型大概率还会继续增加,提示词模板也会持续调整。如果把这些内容拆开管理,主流程会比较干净,后续维护也会更轻松。比如 “无” 状态对应的是合同选择提示词,而 “劳务分包合同”“材料购销合同”“运输合同” 等,则分别对应各自的字段采集模板。这样一来,新增合同的成本只是新增一份模板,而不是改一整套逻辑。

def switch_prompt(prompt_type: str) -> str:
    """
    根据传入的参数切换不同的提示词
    """
    prompts = {
        "无": "# 角色定位 ... 合同选择阶段提示词 ...",
        "劳务分包合同": "# 角色定位 ... 劳务分包合同字段采集提示词 ...",
        "材料购销合同": "# 角色 ... 材料购销合同字段采集提示词 ...",
        "建筑服务机械合同": "# 角色定位 ... 建筑服务机械合同字段采集提示词 ...",
        "运输合同": "# 角色 ... 运输合同字段采集提示词 ...",
        "租赁合同": "# 角色定位 ... 租赁合同字段采集提示词 ...",
    }

    return prompts.get(prompt_type, prompts["无"])

4. 方案优势与适用场景✅

这套方式最大的优势,是把原本杂糅在一起的任务拆得非常清楚。模型在每个阶段只处理一个目标,合同选择阶段只做合同识别,字段采集阶段只做字段确认,不会把两个任务混在一起。这样一来,提示词长度会明显下降,对话稳定性也会更好,尤其是在合同种类继续扩展的情况下,维护成本会比 “一个大提示词解决所有问题” 的方式低很多。

另外,这种方案还有一个很实际的优点,就是更适合业务增长。合同类型新增之后,只需要补一份提示词模板,不需要动主对话流程。对于实际项目来说,这一点很重要,因为业务变化往往比代码改动更快。动态提示词这种方式,本质上给系统加了一层 “阶段切换” 的能力,能让同一套对话框架适配更多合同类型,而且不会因为内容越来越多而变得越来越重。

🎯 适合场景

  • 多合同类型的对话采集
  • 通过聊天收集结构化字段
  • 需要按业务类型动态切换 prompt 的任务
  • 合同、表单、审批、报价单这类分阶段信息收集的流程型业务

只要场景满足 “先确认类型,再逐项收集” 的特点,这种方式通常都比较合适。

5. 总结与后续优化方向📌

合同字段采集场景里,真正影响效果的,往往不是模型能力本身,而是任务边界是否清楚、提示词是否聚焦、流程是否拆分合理。长对话会让模型丢失注意力,长提示词会让模型负担变重,这两件事叠在一起,就很容易让采集过程不稳定。动态提示词的做法,就是把这个问题拆开处理:先选合同,再采字段;先切阶段,再进模板。

从工程实现上看,这种方案并不复杂,核心就是三件事:让模型输出统一标记、根据标记切换提示词、在同一会话里保持上下文连续。它没有额外的复杂训练,也不依赖大规模改造,却能明显改善多合同场景下的对话稳定性。对于合同采集这种强流程场景来说,这种轻量但有效的方式,往往比堆一份超长提示词更实用。

🚀 后续优化方向

  • 把提示词配置彻底拆到独立文件里,方便统一管理
  • 给字段采集加上完整性校验,减少遗漏
  • 在合同选择阶段进一步加强意图识别,提升切换准确率
  • 结合历史对话状态,尽量减少重复询问

这样做下去,动态提示词就不只是一个 “切换模板” 的技巧,而会逐步变成一套比较完整的对话采集框架。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐