1. 项目概述:当AI代理成为“付款员”

最近在几个自动化流程优化的项目里,我遇到了一个既典型又棘手的问题:我们成功地让AI代理(AI Agent)接管了诸如采购审批、供应商付款、员工报销审核等一系列涉及资金流动的环节。系统跑起来很顺畅,决策逻辑清晰,审批链条自动闭合,资金也能准确无误地划转出去。但每到月底财务对账,或者审计需要调取凭证时,麻烦就来了——AI代理能“动钱”,却生不成一张像样的“票据”或“凭证”。这里的“票据”是个广义概念,它可能是一张标准格式的电子发票、一份带有完整签批意见的PDF报告、一个可供追溯的审计日志包,或者仅仅是业务方能看懂的一纸“付款说明”。

这不仅仅是技术上的小瑕疵,而是一个可能让整个自动化流程价值归零的致命缺陷。没有合规、可读、可归档的“凭证”,资金流动就变成了黑箱操作,财务无法入账,审计无法通过,业务方也无法对上下游交代。这个项目标题“AI Agents Can Move Money But Can't Produce Receipts”精准地戳中了当前AI Agent在落地金融、财务、供应链等严肃场景时的核心痛点: 执行力与合规性、可解释性之间的巨大鸿沟

简单来说,我们造出了一个高效的“付款机器人”,但它付完款后,只会沉默地站在原地,无法向人类世界出具任何“已付款”的证明。本文将深入拆解这一现象背后的技术根源、业务逻辑冲突,并分享一套我们在实践中摸索的,让AI Agent既能“付款”也能“开票”的完整架构方案与实操细节。

2. 核心矛盾拆解:为什么“动钱”易,“开票”难?

要解决问题,首先得理解问题为何存在。AI Agent在流程自动化中“动钱”和“开票”这两个动作,本质上是两种截然不同的能力,其难度和所需的技术栈完全不同。

2.1 “动钱”的本质:结构化API调用

让AI Agent移动资金,在技术实现上相对直接。无论是通过网银接口、第三方支付平台(如支付宝、微信支付的企业接口)还是企业内部财务系统(如用友、金蝶的API),其核心都是一个 高度结构化的API调用

  1. 输入明确 :付款金额、收款方账户、付款用途、附言等信息,都可以从上游流程(如审批通过的采购订单、报销单)中提取出结构化的数据。
  2. 过程确定 :调用哪个支付接口、传递哪些参数、处理哪些标准化的返回码(成功、失败、处理中),这一套逻辑是确定的、可编程的。
  3. 结果二元 :支付操作通常只有成功或失败几种明确状态,结果清晰。

AI Agent在这里扮演的角色,更像是一个“智能路由器”或“决策执行器”。它根据预设规则或LLM(大语言模型)对上下文的理解,决定“是否支付”以及“支付给谁”,然后触发一个封装好的、健壮的支付函数。这个函数是传统软件工程的范畴,与AI的“智能”部分相对解耦。难点在于风控和异常处理,而非“执行”动作本身。

2.2 “开票”的挑战:非结构化内容生成与格式合规

相比之下,生成一张“票据”或“凭证”,则复杂得多。它要求AI Agent具备以下能力:

  1. 信息聚合与推理 :凭证需要整合分散的信息。例如,一张付款凭证可能需要包含:原始申请事由(从审批流中提取)、支付金额与日期(从支付接口回调获取)、收款方信息(从供应商主数据获取)、关联的业务合同编号(从合同管理系统获取)、以及本次支付对应的会计科目(根据业务类型和公司财务规则推断)。AI Agent需要像侦探一样,把散落在各系统的信息碎片拼凑完整。
  2. 非结构化内容生成 :凭证中的“备注”、“说明”等字段,往往需要一段通顺、准确、符合业务语境的自然语言描述。例如,“支付2024年Q2云端数据存储服务费,对应合同号CT20240315,服务周期为4月1日至6月30日。”这要求LLM具备强大的文本生成和上下文理解能力。
  3. 严格的格式与合规性 :“票据”必须符合既定规范。这可能是国家法定的发票样式、公司内部的凭证模板、或是行业通用的对账单格式。任何字段的位置、字体、编码规则(如发票号)都不能出错。这涉及到 模板引擎 样式渲染 乃至 数字签名 合规校验 等一系列技术。
  4. 可审计性与不可篡改性 :生成的凭证必须能被追溯和审计。这意味着需要记录生成凭证的“原料”(所有输入数据)、生成逻辑(AI Agent的决策依据和提示词)、以及生成结果本身,并将其关联存储,形成完整的审计线索。

矛盾的核心在于 :驱动AI Agent“动钱”的,主要是其 决策与执行能力 ;而要求其“开票”,则考验的是其 内容生成、格式编排与系统集成能力 。许多AI Agent框架在设计之初,重点优化了前者(通过工具调用/Tool Calling),而严重忽视了后者,将其笼统地归为“发个通知”或“写条记录”,没有上升到“生成关键业务凭证”的高度来设计。

3. 解决方案架构:构建“凭证生成层”

我们的解决思路是,在AI Agent的核心决策循环与最终的业务动作之间,插入一个专门的 “凭证生成层” 。这个层不负责决定“是否做”,而是负责在“做完之后”,如何生成“做过了”的证明。

3.1 整体架构设计

一个完整的、具备凭证生成能力的AI Agent系统,其逻辑架构应包含以下关键模块:

[感知与决策层] -> [执行层] -> [凭证生成层] -> [存储与分发层]
        |               |              |               |
      (LLM核心)    (工具调用)   (凭证引擎)    (归档/通知)
  1. 感知与决策层 :基于LLM的Agent核心,分析用户请求或流程上下文,决定需要执行的动作(例如:“批准该笔付款并支付”)。
  2. 执行层 :调用具体的工具函数(Tool)完成动作,如调用支付接口完成转账,并 捕获执行的关键结果元数据 (如:支付平台返回的交易流水号、支付成功时间、实际扣款金额)。
  3. 凭证生成层(核心新增模块)
    • 凭证模板库 :预定义各种业务场景下的凭证模板(如“供应商付款凭证”、“员工报销凭证”、“服务采购确认单”),模板是包含占位符(如 {{vendor_name}} , {{payment_date}} , {{amount}} )的文件(JSON Schema、HTML、Word或PDF模板)。
    • 数据采集器 :根据当前业务场景和模板定义,自动从多个源头采集数据:执行层返回的结果、决策过程中的上下文信息、从外部系统(CRM、ERP)实时查询的关联数据。
    • 内容生成引擎 :对于需要自然语言描述的字段,由一个小型但精准的LLM调用(或使用主Agent的LLM)来生成。例如,根据采购物品清单和合同条款,生成“付款事由”描述。这里需要设计精确的提示词(Prompt),约束生成内容的风格和要素。
    • 格式渲染器 :将填充好的数据,注入模板,生成最终格式的文件。这可能用到Jinja2(用于HTML/文本)、Apache POI(用于Office文档)、或专业的PDF生成库(如wkhtmltopdf、iText)。
    • 合规校验器 :对生成的凭证进行规则检查,例如金额大小写是否匹配、必填字段是否齐全、编码是否符合规则等。
  4. 存储与分发层 :将最终生成的凭证文件(如PDF)存储到文件服务器或对象存储(如AWS S3、阿里云OSS),并将存储路径和元数据记录到业务数据库。同时,触发分发动作,如发送邮件给相关人员、上传至财务系统、或推送消息到工作群。

3.2 关键组件技术选型与实操

凭证模板的定义与管理 我们放弃了在代码中硬编码格式的方式,转而采用声明式的模板。例如,使用一个JSON Schema来定义凭证的数据结构和简单的展示要求,或者直接使用HTML/CSS来设计模板,因为它能提供最灵活的排版能力,便于生成PDF。

<!-- 一个简化的付款凭证HTML模板 -->
<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: SimSun; }
        .header { text-align: center; }
        .title { font-size: 20px; font-weight: bold; }
        .info-table { width: 100%; border-collapse: collapse; }
        .info-table td { border: 1px solid #000; padding: 8px; }
        .field-label { font-weight: bold; width: 20%; }
    </style>
</head>
<body>
    <div class="header">
        <div class="title">付款凭证</div>
        <div>凭证号:{{ receipt_number }}</div>
    </div>
    <br>
    <table class="info-table">
        <tr><td class="field-label">付款日期</td><td>{{ payment_date }}</td></tr>
        <tr><td class="field-label">收款方</td><td>{{ payee_name }} ({{ payee_account }})</td></tr>
        <tr><td class="field-label">付款金额</td><td>¥{{ amount }} (大写:{{ amount_cn }})</td></tr>
        <tr><td class="field-label">付款事由</td><td>{{ payment_reason }}</td></tr>
        <tr><td class="field-label">关联业务单号</td><td>{{ related_order }}</td></tr>
        <tr><td class="field-label">交易流水号</td><td>{{ transaction_id }}</td></tr>
        <tr><td class="field-label">备注</td><td>{{ remarks }}</td></tr>
    </table>
    <br>
    <div>生成时间:{{ generated_at }} | 操作Agent:{{ agent_id }}</div>
</body>
</html>

实操心得 :模板中的占位符命名要有明确的业务语义,并建立一份数据字典。例如, payee_account 明确代表收款账号,避免使用模糊的 account1 。这能极大降低后续数据映射的复杂度。

数据采集的异步化与容错 凭证生成往往需要查询多个外部系统。为了不阻塞主流程,我们采用异步消息队列(如RabbitMQ、Kafka)来驱动凭证生成任务。执行层在支付成功后,向队列发送一个“生成付款凭证”的消息,消息体内包含业务ID和支付流水号。凭证生成服务消费该消息,然后并行或串行地调用各个数据源的接口。

注意事项 :必须为每个凭证生成任务设置唯一的 task_id ,并实现幂等性。防止因消息重复消费导致生成多张重复凭证。同时,对每一个外部数据源接口的调用都要有超时和重试机制,对于非核心字段(如供应商的详细地址),要有降级方案(例如,留空或填写“信息暂缺”)。

LLM在内容生成中的精准控制 对于“付款事由”、“审批意见摘要”等文本字段,直接让LLM自由发挥风险很高。我们的做法是设计“结构化提示词”:

你是一名财务助理,需要根据以下信息生成一段专业、简洁的付款事由描述,用于填充付款凭证。
请严格遵循以下格式和内容要求:
- 开头固定为“支付”。
- 必须包含的关键要素:[业务类型]、[服务/商品名称]、[关联合同编号]、[服务周期]。
- 语言风格:客观、正式、无修饰词。
- 输出仅包含事由描述本身,不要有任何额外解释。

【信息】
业务类型:云服务采购
服务名称:对象存储服务(OSS)
合同编号:CT2024-OSS-001
服务周期:2024年7月1日 至 2024年7月31日
支付性质:月度服务费

请生成付款事由:

通过这样的提示词,我们将一个开放生成任务,转变为一个“填空+微调”的任务,极大提高了输出的稳定性和合规性。

格式渲染与PDF生成 将填充好的HTML渲染成PDF,我们选择使用 wkhtmltopdf 的无头浏览器方案。虽然性能不是最优,但它对CSS 3的支持较好,能还原复杂的排版。在Docker环境中部署非常方便。

# 在凭证生成服务中调用wkhtmltopdf
wkhtmltopdf --encoding UTF-8 --page-size A4 --no-background receipt_template.html output_receipt.pdf

踩坑记录 :中文字体渲染是个大坑。务必在服务器或Docker镜像中安装所需的中文字体(如 fonts-wqy-microhei ),并在HTML模板的CSS中显式指定字体族。否则生成的PDF会是乱码。

4. 核心流程实现:从支付成功到凭证归档

让我们以一个具体的“供应商付款”场景,串联起整个流程。

4.1 步骤详解

步骤1:Agent决策与支付执行 AI Agent审核采购订单后,决定付款。它调用 make_payment 工具函数,传入供应商ID、金额等信息。该函数调用银行API完成支付,并返回一个结构化的结果:

{
  "success": true,
  "transaction_id": "BANK20240718123456",
  "payment_time": "2024-07-18 15:30:22",
  "actual_amount": 12500.00
}

步骤2:触发凭证生成任务 支付工具函数在执行成功后,不会直接结束。它会将关键结果和业务ID(采购单号 PO-202407-001 )打包,发送一个消息到消息队列的 receipt.payment.vendor 主题。

步骤3:凭证生成服务消费与处理 凭证生成服务监听该主题。收到消息后:

  1. 解析与去重 :解析消息,检查 task_id (可由 业务ID+交易流水号 哈希生成)是否已处理过,实现幂等。
  2. 数据聚合
    • 基础数据 :从消息中获取 transaction_id , payment_time , actual_amount
    • 业务数据 :根据 PO-202407-001 ,调用采购系统API,获取供应商详情、采购物品清单。
    • 主数据 :根据供应商ID,调用CRM系统API,获取供应商银行账户信息。
    • 财务数据 :根据公司会计规则引擎(或配置表),自动判定此笔付款应记入的会计科目(如“管理费用-办公费”)。
  3. 内容生成 :将“采购物品清单”和“合同编号”作为输入,调用LLM API(如GPT-4的ChatCompletion),使用前述的精准提示词,生成“付款事由”文本。
  4. 数据组装 :将所有采集和生成的数据,组装成一个符合凭证模板Schema的JSON对象。
  5. 渲染与生成 :将JSON数据注入HTML模板,调用 wkhtmltopdf 生成PDF文件,保存至对象存储(如生成路径: oss://company-receipts/2024/07/18/vendor_payment_PO-202407-001.pdf )。
  6. 记录与关联 :将凭证的存储路径、元数据、生成日志,与原始业务ID、支付流水号关联,存入“凭证溯源数据库”。

步骤4:通知与归档 凭证生成服务调用通知服务,发送一封邮件给申请人和财务人员,邮件内容包含付款摘要和 凭证PDF附件 。同时,将凭证存储路径写回原采购订单和财务系统的对应字段,完成流程闭环。

4.2 关键参数与配置示例

  • 消息队列配置 :我们使用RabbitMQ,一个 receipt 交换机,绑定多个队列对应不同凭证类型。消息TTL(存活时间)设置为24小时,防止堆积。
  • LLM调用配置 :对于凭证内容生成,我们使用专门的、低延迟的GPT-4 Turbo模型,并设置较低的温度( temperature=0.2 )以保证输出稳定性。每次调用都有严格的超时(如5秒)和重试(1次)策略。
  • 凭证存储策略 :对象存储中按 业务类型/年/月/日 进行目录划分,便于管理和后期迁移。所有凭证文件设置不可变性(Immutable)策略,防止误删或篡改。
  • 数据库设计 receipts 表核心字段包括: id , business_id , business_type , transaction_id , receipt_path , receipt_data_json , generated_at , status

5. 常见问题与实战排查指南

在实际部署和运行中,我们遇到了形形色色的问题。以下是典型问题的排查清单。

问题现象 可能原因 排查步骤与解决方案
凭证生成任务堆积,延迟高 1. 消息队列消费者(凭证生成服务)宕机或性能瓶颈。
2. 外部数据源(如ERP系统)API响应慢或超时。
3. PDF渲染过程耗时过长。
1. 检查服务状态 :查看凭证生成服务的日志与监控,确认进程存活,CPU/内存无异常。
2. 分析队列 :查看RabbitMQ管理界面,确认队列消息堆积数。增加消费者实例数量。
3. 定位慢步骤 :在生成服务中为每个步骤(数据查询、LLM调用、渲染)添加详细耗时日志。针对慢步骤优化:如为ERP查询增加缓存、优化LLM提示词减少token数、考虑更换更快的PDF渲染引擎(如WeasyPrint)。
生成的PDF内容错乱或乱码 1. HTML模板中的CSS样式在PDF渲染时不兼容。
2. 服务器缺少中文字体。
3. 数据注入时,特殊字符(如 & , < , > )未转义,破坏了HTML结构。
1. 简化样式 :优先使用 wkhtmltopdf 兼容的CSS属性,避免使用太新的CSS Grid/Flexbox布局。使用表格进行简单排版更可靠。
2. 安装字体 :在Dockerfile中增加安装中文字体的步骤,并在CSS中使用 font-family 指定该字体。
3. 数据清洗 :在将数据注入模板前,对所有字符串字段进行HTML转义。
LLM生成的“事由”描述不符合要求 1. 提示词(Prompt)不够精确或存在歧义。
2. 提供给LLM的源信息质量差(如合同编号缺失)。
3. 模型温度(Temperature)参数过高。
1. 迭代提示词 :采用“角色定义+任务描述+格式要求+示例”的提示词结构。通过大量测试案例来优化提示词。
2. 数据质量检查 :在调用LLM前,增加一个数据校验步骤,确保关键输入字段不为空且格式正确。
3. 调整参数 :将 temperature 降至0.1-0.3,增加 top_p 或设置 seed 以保证输出一致性。
凭证数据不完整(某些字段为空) 1. 外部数据源API调用失败或返回异常。
2. 数据映射规则错误,未能从API响应中正确提取字段。
3. 该字段本身在源系统中就允许为空。
1. 检查API日志 :查看调用外部系统时的请求与响应日志,确认是否超时、鉴权失败或返回了错误码。
2. 验证数据映射 :核对凭证模板的占位符与数据采集器的输出字段名是否完全一致。编写单元测试覆盖各种数据场景。
3. 定义缺省值 :在模板或数据组装逻辑中,为允许为空的字段设置合理的缺省值(如“暂无”)。
同一笔业务生成了多张凭证 消息队列重复投递(at-least-once delivery特性),且凭证生成服务未实现幂等性。 实现幂等 :在生成服务入口,利用 task_id (业务唯一标识)在数据库中先进行查询。如果已存在 status 为“成功”的记录,则直接返回已有凭证路径,不再执行生成逻辑。可以在数据库层面为 task_id 设置唯一索引,防止重复插入。

独家避坑技巧

  • “双流”记录法 :除了生成最终凭证,务必同步记录一份结构化的“凭证生成日志”,以JSON格式存储本次生成所有的输入数据、调用的外部API及其响应、LLM的输入输出、以及中间任何错误。这份日志是排查问题和应对审计的“救命稻草”。
  • 灰度发布与回滚 :修改凭证模板或生成逻辑时,采用灰度策略。例如,先让新模板为10%的业务生成凭证,对比新旧版本无误后再全量。同时,务必保留旧版本的模板和代码,以便快速回滚。
  • 人工复核通道 :对于大额支付或异常复杂的业务,系统可以配置为“生成凭证后,暂不自动分发,而是推送至人工复核列表”。复核人员确认无误后点击“放行”,系统才执行邮件发送等动作。这为自动化流程增加了一个关键的风险控制点。

让AI Agent从“哑巴执行者”进化成“有据可查的合规操作员”,关键在于认识到“生成凭证”不是一个附属功能,而是一个与核心决策、执行同等重要的独立能力模块。通过构建专门的“凭证生成层”,并妥善处理数据、模板、内容生成和系统集成,我们完全可以让AI Agent在高效“动钱”的同时,稳稳地“开出票据”,真正打通企业自动化流程的“最后一公里”。这个过程充满细节挑战,但每解决一个,系统的可靠性与信任度就提升一分。

更多推荐