还得是“靠人”!打通大模型与业务的“最后一公里”

152 阅读6分钟

最近,我跟团队落地了一个基于 Python 的 AI Web 应用项目,终于让我体会到,大模型与实际业务融合远比简单调用 API 更复杂,也更值得深究。

很多人觉得“大模型接入业务”不就是简单调用接口、传输数据、接收结果吗?

可当我们实际做了才发现,这种看似顺滑的流程,其实非常脆弱。一旦业务稍微复杂,光靠模型 API 堆功能,马上暴露各种问题:上下文丢失、逻辑割裂、输出格式随意。这就像买了一辆超级跑车,却只在狭窄的车库里来回倒腾,根本发挥不了它的真实价值。

我们要的是,让模型真正融入流程。

我们的 Python AI 应用主要实现了四个功能:文档校对、文档生成、智能邮件,以及未来的系统对接展望。

image.png

文档校对

第一大功能是文档校对。很多平台已经支持文件上传,但只是粗放地处理。我们设计了专用的 Python 解析脚本,先自动识别文档结构(比如代码的规范格式、财务报表特定指标等),然后结合明确的业务规则调用大模型。这种细致的前置处理,大大提高了模型的输出质量,降低了人工后续的干预。

功能展示:

image.png

image.png

代码示例:


      // 开始核对按钮点击事件
      document.getElementById('startCheck').addEventListener('click', async function() {
        console.log('开始核对按钮被点击');
        const preCheckFile = document.getElementById('preCheckFile').files[0];
        const billingFiles = document.getElementById('billingReportFiles').files;
        const steps = document.querySelectorAll('.step');

        // 步骤1:解析预核对表
        startStep(steps[0], '正在解析预核对表...');
        updateProgressBar(1);
        const preCheckFormData = new FormData();
        preCheckFormData.append('file', preCheckFile);
        try {
          console.log('开始发送预核对表解析请求');
          const preCheckResponse = await fetch('/parse_excel', {
            method: 'POST',
            body: preCheckFormData
          });
          console.log('预核对表解析请求响应状态:', preCheckResponse.status);
          if (!preCheckResponse.ok) throw new Error('预核对表解析失败');
          const preCheckResult = await preCheckResponse.json();
          parseResults.preCheckData = preCheckResult.data;
          completeStep(steps[0], `预核对表解析完成,共 ${preCheckResult.data.length} 条记录`);
          // 修改调用方式,直接传入数据
          showPartialData([preCheckResult.data]);
        } catch (error) {
          console.error('预核对表解析出错:', error);
          failStep(steps[0], error.message);
          return;
        }

        // 步骤2:解析计费报表
        startStep(steps[1], '正在解析计费报表...');
        updateProgressBar(2);
        try {
          console.log('开始发送计费报表解析请求');
          for (let i = 0; i < billingFiles.length; i++) {
            const billingFormData = new FormData();
            billingFormData.append('file', billingFiles[i]);
            const billingResponse = await fetch('/parse_excel', {
              method: 'POST',
              body: billingFormData
            });
            console.log(`第 ${i + 1} 个计费报表解析请求响应状态:`, billingResponse.status);
            if (!billingResponse.ok) throw new Error('计费报表解析失败');
            const billingResult = await billingResponse.json();
            parseResults.billingData.push(billingResult.data);
          }
          completeStep(steps[1], `计费报表解析完成,共 ${billingFiles.length} 个文件`);
          // 修改显示部分解析数据的调用,传入合并后的数据
          showPartialData([parseResults.preCheckData, ...parseResults.billingData]);
        } catch (error) {
          console.error('计费报表解析出错:', error);
          failStep(steps[1], error.message);
          return;
        }

        // 步骤3:AI 分析差异
        startStep(steps[2], 'AI正在分析差异...');
        updateProgressBar(3);
        // 先检查是否已经存在加载元素,避免重复创建
        let loadingElement = document.getElementById('aiLoading');
        if (!loadingElement) {
          loadingElement = document.createElement('div');
          loadingElement.id = 'aiLoading';

          // 创建加载圈元素
          const spinner = document.createElement('div');
          spinner.className = 'loading-spinner';

          // 创建提示文本元素
          const text = document.createElement('span');
          text.textContent = 'AI 分析中,请稍候...';

          // 将加载圈和提示文本添加到加载提示元素中
          loadingElement.appendChild(spinner);
          loadingElement.appendChild(text);

          document.body.appendChild(loadingElement);
        }

        try {
          console.log('开始发送 AI 分析差异请求');
          const compareResp = await fetch('/compare_and_ask', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              pre_check: parseResults.preCheckData,
              billing: parseResults.billingData
            })
          });
          console.log('AI 分析差异请求响应状态:', compareResp.status);
          if (!compareResp.ok) throw new Error('AI分析差异失败');
          const compareResult = await compareResp.json();
          parseResults.differences = compareResult.data;

          // 显示 AI 分析结果
          const parsedResultsElement = document.getElementById('parsedResults');
          let resultContent = '';
          if (compareResult.data) {
            if (typeof compareResult.data === 'string') {
              resultContent = compareResult.data;
              console.log("resultContent", resultContent);
            } else if (Array.isArray(compareResult.data) || typeof compareResult.data === 'object') {
              resultContent = `<pre style="white-space: pre-wrap; word-wrap: break-word;">${JSON.stringify(compareResult.data, null, 2)}</pre>`;
            }
          } else {
            resultContent = '未获取到有效的 AI 分析结果';
          }
          if (parsedResultsElement) {
            parsedResultsElement.innerHTML = resultContent;
          } else {
            console.error('未找到 parsedResults 元素');
          }

          completeStep(steps[2], 'AI分析差异完成');
          // 移除加载提示
          if (loadingElement) {
            document.body.removeChild(loadingElement);
          }
          // 步骤4:生成核对报告
          startStep(steps[3], '正在生成核对报告...');
          updateProgressBar(4);
          setTimeout(() => {
            completeStep(steps[3], '核对报告生成完成');
            // 移除切换到核对结果 tab 的逻辑
          }, 1000);
        } catch (error) {
          console.error('AI 分析差异出错:', error);
          failStep(steps[2], error.message);
          // 移除加载提示
          if (loadingElement) {
            document.body.removeChild(loadingElement);
          }
        }
      });

文档生成

第二大功能是文档生成。听起来简单,就是数据填报到报表中,但我们测试发现,GPT 之类的大模型并不擅长处理高度结构化和规则复杂的数据填充。这一步只能依靠 Python 原生脚本实现。我们使用 pandas 等工具,将处理好的结构化数据自动填入模板,实现批量文档输出。

image.png

image.png

可以实现:一键打印全部,包含8个合同、7个表单样式、97页文档,每月至少节省2人/天工作量。

自动邮件

第三大功能是智能邮件生成和发送。看似简单的邮件自动化,如果仅依靠模型生成内容,常常会缺乏具体业务语境。我们选择了一个巧妙的融合方式:通过前面生成的精准报表结果,让大模型编写出语义清晰且业务贴切的邮件内容,再通过 Python 自动发送邮件,实现无感式的业务流程闭环。

prompt 示例:

你是会议纪要生成专家,请根据如下规则,为每个月不同厂商生成标准格式的项目考核纪要。

【目录结构】
- 每个目录如 `json/3e9f7a``json/2a4b6c` 表示不同月份(如 3e9f7a 表示特定年月);
- 每个目录中包含:
  - `a1b2c3.json`:包含厂商 A、厂商 B、厂商 C 各厂商的解析服务数据、评分、金额等结算信息;
  - `d4e5f6.json`:包含当月各厂商的考核扣分项目和扣分事由;
  - `7g8h9i/`:输出纪要存放路径,其中需生成:
    - `x1.json` → 厂商A 纪要
    - `y2.json` → 厂商B 纪要
    - `z3.json` → 厂商C 纪要

【任务目标】
- 根据 `a1b2c3.json``d4e5f6.json` 两个文件,为每个厂商生成一个标准会议纪要(JSON格式);
- 每份纪要内容包括:会议信息、合同名称、考核评分、扣分详情、整改措施、结算信息;
- 请按以下映射关系提取数据生成:
  - `"厂商A"` 对应 `x1.json`
  - `"厂商B"` 对应 `y2.json`
  - `"厂商C"` 对应 `z3.json`

【输出格式】
每个厂商输出一个 JSON 对象,结构统一如下:
{
  "title": "...",
  "meeting": {
    "name": "特定年月考核评审会议纪要",
    "date": "下一月份",
    "participants": {
      "公司X": ["人员A", "人员B", "人员C", "人员D", "人员E", "人员F", "人员G"],
      "<厂商全称>": ["参会人列表"]
    }
  },
  "contract": {
    "name": "...",
    "contract_number": "如有则填写,否则省略"
  },
  "assessment": {
    "period": "特定年月",
    "score_criteria": {
      "指标1": 40,
      "指标2": 20,
      "指标3": 30,
      "指标4": 10
    },
    "result": {
      "评分": "...",
      "扣分情况": "...",
      "说明": "..."
    },
    "整改措施": { ...如有扣分则列出... }
  },
  "settlement": {
    ...每个模块的数据...
  },
  "note": "特此纪要"
}

【细节要求】
- 若某厂商无扣分,写明“扣分情况:无”,说明中突出“支撑稳定、无客户投诉”;
- 若有扣分,需写明模块、扣分原因、影响评分、整改方案;
- 所有金额含税,税率默认 6%,如有单价不同请按预处理文件给出;
- 会议名称与日期需自动根据目录名生成;
- 输出格式为 JSON,不能包含其他文字或注释;

请依次生成每个厂商的纪要,输出为 `7g8h9i/x1.json``y2.json``z3.json` 文件内容。

外部对接

第四个功能目前还在规划阶段:接入外部系统。我们希望未来实现系统与系统、模型与模型之间的直接交互。

image.png

小结

然而,实现前三个功能的过程中,我们也遇到了不少挑战。

1、首先是环境兼容性问题,比如内外网隔离导致的库依赖冲突。为了避免踩坑,我们严格控制依赖版本,争取做到一次构建,到处稳定运行。

2、其次是架构上的博弈。一开始我们希望打造一个通用的文档解析器,但实际运行后发现,业务场景差异巨大,通用型并不能真正适配所有情况。最终我们转向了“通用解析器 + 定制化脚本”的组合模式,既保持了通用性的框架,又能灵活响应不同业务的特殊需求。

3、最棘手的难点在于,大模型本质上并不会真正“思考”。它擅长的是即时响应,却无法贯穿历史上下文,缺乏整体一致性和逻辑重构的能力。为了解决这个问题,我们采用了代码规则前置、输出后验证、必要时人工闭环的三步组合策略,确保模型输出始终稳定可靠。

经过这一系列尝试,我想分享一个关键洞察:真正落地的大模型应用,不能只靠模型 API 堆叠功能,必须依托脚本能力、系统集成能力,以及业务场景的精准判断。