代码讲解手册 · 2026.10.06 · 架构与云端部署版
ARCHITECTURE · LOGIC · EVIDENCE

把一次经营分析
讲到每个数字都有来处

先用一页技术总述建立全貌,再沿着真实请求进入代码:谁选择工具、谁核对 SQL、谁计算贡献,以及结果为什么值得相信。

01 / 任务官方 Agent 调度
→
02 / 约束固定指标口径
→
03 / 验证SQL 与明细对账
→
04 / 解释贡献与证据
CHAPTER 01

DB-GPT 企业经营分析 Agent

开源框架复现与业务增强 · 项目技术总述

技术栈:DB-GPT 0.8.2 · ReAct Agent · AWEL DAG · Tool Calling · DeepSeek · FastAPI · SQLite · Pydantic · Fraction · SQL 核验

基于 DB-GPT 官方完整前后端,构建面向模拟电商的经营分析应用:用户通过自然语言提出月度收入变化问题,系统调用专用分析工具,完成受约束 SQL 生成、独立明细核验、因素贡献分解与证据报告输出。重点解决“查出了数字,但口径不对、连接重复计数、归因无法解释”的问题。

演示结果

固定模拟样本中,华东 2026 年 8 月净收入由 7 月的 240,000 元降至 141,000 元,下降 41.25%。下降 99,000 元由订单量 −44,800 元、渠道结构 −32,775 元、渠道内客单价 −13,425 元、退款 −8,000 元共同构成,贡献合计差额为零。

这些数字是模拟业务结果,不是性能提升、线上收益或模型准确率。

口头讲解版本

“我基于 DB-GPT 官方界面做了一个电商收入分析复现,主要工作是把结果的可信度做实。外面的 Agent 负责理解任务并调用工具,里面的业务工作流固定指标口径,让模型生成 SQL,再用独立的 Python 明细计算核对。通过后,代码用六种替换顺序计算订单量、渠道结构、渠道内客单价和退款的贡献,并保存 SQL 和计算证据。所以这个项目的重点,是解释每个数字怎么算出来、为什么可以相信,以及哪些结论还不能下。”

使用这份总述时的边界

官方前后端、ReAct 框架和 AWEL 引擎来自 DB-GPT;本项目新增的是电商数据、业务工具、校验与分解逻辑、报告证据和集成适配。内部 DAG 是串行四节点,不是自研多 Agent 并行系统。目前支持受限定的地区月度收入分析,尚不能宣称任意企业问题都能自动正确分析。没有企业上线、流量规模或收益提升证据时,不应把这些经历补进简历。

依据:本地 commerce/、run_official.py、official-patches/registered-business-tools.patch、tests/、official_tests/ 和 VALIDATION.md。代码阅读日期为 2026-09-17;测试和界面验证引用项目已有记录。

本章源文件:01 项目技术总述.md

CHAPTER 02

架构与请求全过程

读完本章,你应能回答:用户输入之后谁在调用谁?模型做了什么?为什么最后的数字不是模型直接写出来的?

先理解系统的分工

可以把系统看作一个分析团队:官方界面是接待窗口,外层 Agent 是任务调度员,业务工具是专门的分析流程。工具内部,模型提出查询方案,程序检查查询结果,数学模块计算贡献,报告模块排版。各个角色之间传递的是明确的数据,而不是一段越来越长的“思考”。

当前主入口是官方服务 http://127.0.0.1:5670。早期独立页面对应 commerce/main.py 和 commerce/static/index.html,它用于验证业务链路;不要把它当作完整官方界面的实现。

text
用户中文问题
    ↓
DB-GPT 官方前端 → 官方 ReAct 对话接口
    ↓ 外层模型选择工具与参数
analyze_commerce_revenue(region, as_of)
    ↓ 构造 AnalysisRequest 并进入 run_analysis
AWEL:resolve → sql_stage → verification → calculation
    ↓                         ↓              ↓
  固定口径                独立明细核对      精确贡献分解
    ↓ 整个 DAG 成功返回
save_result → evidence.json / query.sql / parameters.json / report.html
    ↓ 工具返回指标、链接、render_tool_call
官方 Agent → html_interpreter(file_path=原报告) → 展示与结束

图里的两次模型参与要区分:外层模型决定调用什么工具;内层 generate_sql 发起独立的模型请求来写 SQL。一个工具调用内部完全可以再次调用模型。内层计算不依赖外层模型“心算”。

框架能力和本项目实现

层次主要职责本项目使用方式
官方前端与服务对话、工具过程、报告展示运行完整官方应用
官方 ReAct根据上下文选择动作,观察工具结果选择专用经营分析工具,再展示报告
ResourceManager 与工具机制注册、描述、调用资源注册 analyze_commerce_revenue
AWEL组织节点及依赖,执行 DAG串行执行四个业务节点
commerce 业务层口径、数据核对、计算、证据本项目新增的主要代码
SQLite 与模型接口存储数据、生成候选 SQLSQLite 本地数据,DeepSeek 兼容接口

AWEL 不是 Agent 本身。它只负责按依赖运行节点。只有 sql_stage 及核验失败后的修复会在内层调用模型,其余步骤是程序。给普通函数包上 MapOperator 不会让函数自动具有推理能力。

启动时发生什么

入口 run_official.py 先指定官方运行目录与语言,读取本地模型配置,准备持久化加密密钥和官方模板文件。随后导入官方服务,调用 official_integration.install(server),最后用 official.toml 启动服务器。

install 做三件事。第一,准备模拟数据库;首次运行时,把基础数据复制到 official-data/ecommerce.sqlite。第二,在官方静态页面的兜底路由之前注册报告下载路由,否则报告请求可能被前端页面接走。第三,包装官方 initialize_app:先完成官方初始化,再注册业务工具和名为 ecommerce 的数据源。

这里的数据库有两个用途,不能混淆。官方的元数据库记录平台配置;ecommerce.sqlite 存放订单和退款。把数据源注册到平台,也不代表任意 SQL 自动有正确的业务口径。

基础数据库与官方副本目前仅在首次创建时复制。如果以后修改了基础数据,官方副本不会自动更新。这是理解“为什么命令行结果和页面结果不一致”时应首先检查的地方。

以一次请求逐步展开

用户提出“上个月华东地区收入下降了,分析各因素贡献”。外层 Agent 看见工具描述后,可以传入 region=华东、as_of=2026-09-16。其中 as_of 是分析基准日,用来确定完整自然月,并非直接作为查询开始日。

当前工具只有这两个入参。它会重新构造一条标准问题,设置 mode=live 后调用 run_analysis。因此原始自由文本并不会完整进入内部证据中的 request.question。如果用户在原话中增加“只看某商品”,工具内部不会因此自动增加商品筛选。扩展需求必须改契约和入参,不能只改提示词。

第一步 创建运行上下文

run_analysis 创建 state 字典,包括唯一 run_id、请求、数据库路径、来源指纹、空的 SQL 尝试列表和轨迹。这个字典会在节点间传递,每个节点补充自己的结果后返回。

python
state = {
    "run_id": "本次唯一编号",
    "request": {"region": "华东", "as_of": "2026-09-16", "mode": "live", ...},
    "database_path": "官方电商数据库绝对路径",
    "provenance": {"database_sha256": "...", ...},
    "sql_attempts": [],
    "trace": []
}

这段是结构示意,省略号不是可直接执行的请求。不要把 state 理解为永久记忆;它是一次运行的上下文,最后才被写入证据文件。

第二步 固定范围和口径

resolve 调用 periods,得到 previous_start=2026-07-01、current_start=2026-08-01、end=2026-09-01。本期是八月,上期是七月。

它检查问题是否包含收入或营收、地区是否冲突、是否出现不支持的时间表达,并检查数据覆盖范围。这里是有限的规则检查,并非全面自然语言语义理解。默认基准日固定为 2026-09-16,不会自动随今天的日期变化。

第三步 生成候选 SQL

sql_stage 根据模式分支:reference 直接使用人工标准 SQL;live 调用 generate_sql。官方工具固定使用 live。

内层模型收到系统约束、六表 schema、业务问题和四个查询参数,返回 JSON 中的 sql 字符串。调用设置包含温度 0、JSON 对象格式、90 秒超时和关闭 SDK 自动重试。温度 0 不能保证每次返回完全相同 SQL;JSON 格式也不能保证 SQL 语义正确。

这些约束让问题更容易,但模型输出仍只是候选方案。它必须经过下一步才有资格参与报告。

第四步 独立核验

verification 先从原始明细算出期望结果,再执行模型 SQL,检查字段、期间渠道唯一性、数值类型和全部结果是否一致。随后检查数据库指纹是否较运行开始发生变化。

只要一次候选通过,state 才会获得供后续消费的 rows。如果抛出 QueryRejected,live 模式最多再让模型修复一次,输入包括旧 SQL 和错误说明。第二次仍失败就中止。模型网络错误、无效 JSON 并不走这个 SQL 修复分支;它们直接成为失败。

第五步 计算贡献

calculation 把核验过的六行传给 decompose,输出总指标、各渠道对照、四因素贡献和精确替换过程。程序此时不需要再请求模型。字段名 cents 表示分,展示人民币元时除以 100。

第六步 保存并展示

四个节点完成后,run_analysis 增加 report 轨迹并保存文件。注意:轨迹有五类阶段,但 AWEL 图里只有四个节点,保存报告是在 DAG 返回后执行的。

业务工具把结构化结果和 render_tool_call 返回外层。后者告诉官方 Agent 用 html_interpreter 的文件模式读原报告。这样页面上的表格和金额来自确定性文件,不必让模型再写一遍 HTML。外层的文字总结仍由模型生成,依然需要与报告核对。

文件阅读地图

文件先看哪个入口带着什么问题读
run_official.py主程序官方应用如何启动
commerce/official_integration.pyinstall、analyze_commerce_revenue工具如何接入官方平台
commerce/workflow.pybuild_workflow、run_analysis谁先执行,数据怎样流动
commerce/data.pyperiods、reference_rows时间范围和独立答案怎样得到
commerce/query.pySTANDARD_SQL、execute_readonly、validate_rows如何查,如何拒绝错误结果
commerce/analysis.pydecompose四项贡献怎样算
commerce/report.pyprovenance、save_result、render_report什么证据落盘,谁生成数字
commerce/cli.pymain 的 replay 分支如何复算历史结果

推荐先读 build_workflow 中四个节点的依赖关系,再顺着四个函数阅读。先形成全局调用顺序,再读 SQL 和数学细节,理解成本会低很多。

几个容易看不懂的代码机制

tool 装饰器与工具描述

普通 Python 函数只能由知道它的代码调用。@tool 将函数暴露为框架可登记的工具资源,函数名、描述与参数信息帮助模型决定是否调用。register_resource 让框架执行层能找到这个资源;提示词中的描述让模型决策层知道它能做什么。前者存在、后者缺失,就会出现“明明注册了,Agent 却不用”的问题。

外层的 ReAct 可以理解为反复进行“选择动作 → 调用工具 → 观察结果 → 决定下一步”。在本例成功路径中,观察到分析工具返回 verified 和报告路径后,下一步是展示文件并结束。当前业务 DAG 不会因为使用了外层 ReAct 就变成开放式多 Agent 系统。

为什么调用最后一个节点就能跑完整流程

start >> sql >> check >> calc 建立的是上游到下游的依赖。构图完成后,leaf.call(call_data=state) 以末端节点为目标交给 AWEL 执行;引擎先满足其上游依赖,再运行当前节点。因此它不是绕过前三步直接调用 calculation。

依赖关系同时限定了成功数据流:SQL 没有通过核验,正常情况下就不会进入贡献计算。这个顺序来自代码定义,模型不能通过一句“跳过校验”改掉 DAG 里的连线。

async 和 to_thread 分别处理什么

模型请求会等待网络返回,使用 async 可以让事件循环在等待期间处理其他工作;SQLite 查询和 Python 明细循环属于同步函数,asyncio.to_thread 把它们交给线程,避免直接堵住异步服务的事件循环。

这不意味着四个 DAG 节点并行计算。它们有数据依赖,仍然按顺序执行。线程转移也不等于 CPU 密集型 Python 运算获得线性多核加速,更不代表官方入口已有全局并发配额。

为什么 state 要记录尝试列表

只保存最终 SQL 会丢失第一次失败的信息。sql_attempts 每次追加候选,并标注 source、rows、status、error,可以解释“第一条 SQL 为什么错、第二条如何修复”。独立核验结果放在 independent_reference,最终计算输入放在 rows,这两者让审阅者能重做比较。

当前节点会原地更新同一个字典,并非每步产生不可变版本。这个实现简单,适合串行链路;如果未来引入分支并行,就要明确哪些字段由谁写入、如何合并,不能直接让多个节点同时修改同一列表。

异常怎样走回用户

SQL 执行或结果检查抛出 QueryRejected,由 verification 决定是否修复;超出修复预算或其他步骤失败后,异常进入 run_analysis 的捕获分支,标记 failed,保存证据,并抛出包含运行编号的 WorkflowFailure。官方业务工具没有在内部把它伪装成 verified,因此成功报告不会沿正常返回路径产生。

verified 表示本次专用计算链路通过了定义好的检查。它不表示外层全部自然语言已经逐字核验,也不表示所有可能的业务问题都得到正确解释。

本章源文件:02 架构与请求全过程.md

CHAPTER 03

数据口径与 SQL 核验

这个项目首先解决的不是“怎样把中文翻译成 SQL”,而是“SQL 查出来的数字究竟代表什么”。数据库能成功执行一条查询,并不能说明它回答了用户的问题。

六张表与数据粒度

表一行代表什么在收入分析中的作用
orders一笔订单支付金额、支付时间、地区快照、渠道
order_items一笔订单中的一个商品明细行未来商品分析;当前不用于累计订单金额
products一个商品商品与分类维度
users一个用户当前资料;当前地区不能替代历史订单地区
channels一个渠道渠道枚举,保证零值渠道也出现
refunds一次退款记录成功金额及实际退款时间

关系为:一个用户有多笔订单,一个渠道有多笔订单;一笔订单可有多个商品明细,也可有多次退款。商品通过订单明细关联订单。先说清“一行是什么”,才能判断哪些连接会把钱重复加起来。

模拟数据覆盖 2026 年 6 月至 8 月,固定随机种子为 20260916。数据专门包含跨月支付、早期订单在后月退款、一单多退、失败退款和取消订单。它们是检验口径的陷阱样本,而不是随便凑出的演示行。

净收入的口径

本项目定义:某月某地区净收入 = 该月该地区成功支付订单的实付金额 − 该月成功发生的退款金额。这是项目经营分析口径,不应自动等同于企业会计准则下的收入确认。

项目本项目规则直观例子
订单归属月份paid_at,且 status=paid7 月 31 日创建、8 月 1 日支付,算八月
退款归属月份refunded_at,且 status=success七月付款、八月退款,退款冲减八月
地区orders.shipping_region用户后来迁居,历史订单地区不变
退款渠道原订单 channel_id退款不会变成独立获客渠道
金额原始单位为整数分19.99 元存为 1999
时间范围左闭右开八月为 [8 月 1 日,9 月 1 日)

为什么不能用 created_at?它回答的是“何时下单”,不是“何时支付”。为什么不能用用户当前地址?那会使过去月份的收入随用户改地址而变动。为什么退款查询不能要求原订单也在本月支付?因为旧订单本月退款同样会影响本月净收入。

时间字符串在当前数据中采用一致、可按字典序比较的 ISO 格式,并约定上海时区。代码没有替任意来源的带时区时间做统一转换;接入真实数据时必须先规范时间。

多表连接怎样把收入算大

假设订单 A 实付 100 元,有两行商品,分别 60 元和 40 元;又有两次成功退款,分别 10 元和 20 元。若直接执行“订单 JOIN 明细 JOIN 退款”,中间结果有四行:

订单商品行退款行订单实付
A商品 1退款 1100
A商品 1退款 2100
A商品 2退款 1100
A商品 2退款 2100

对订单实付求和得到 400 元,对退款求和得到 60 元,而正确值是 100 元和 30 元。COUNT(DISTINCT order_id) 只能救订单数,救不了金额。SUM(DISTINCT paid_cents) 也不对:两笔都付 100 元的不同订单会被错误合成一笔。

正确原则是先在各自的粒度上汇总,再连接汇总结果。本项目把支付与退款各自聚合到“期间 × 渠道”,此时每边同一键最多一行,再关联就不会因一单多明细、多退款而放大金额。

标准 SQL 的四段逻辑

commerce/query.py 中的 STANDARD_SQL 是完整基准查询。阅读时不要被 CTE 语法吓住:WITH 只是给中间结果取名字。

第一段 periods 生成两行时间窗口,分别叫 previous 和 current。第二段 payments 按支付时间选择 paid 订单,用收货地区筛选,按期间和渠道统计订单数及实付。第三段 successful_refunds 按退款时间选择 success 记录,连接原订单只为取得地区和渠道,不限制原订单支付月份。

第四段把两期和所有渠道做笛卡尔积,得到完整的六个位置,再 LEFT JOIN 两张聚合结果。没有订单或退款的位置用 COALESCE 填零。这一步确保“零订单”不会和“丢了一行”混淆。

sql
-- 核心结构示意,完整可执行版本见 query.py
WITH periods AS (...),
payments AS (... GROUP BY period, channel_id),
successful_refunds AS (... GROUP BY period, channel_id)
SELECT period, channel, order_count, paid_cents, refund_cents
FROM periods CROSS JOIN channels
LEFT JOIN payments ON 期间与渠道都相同
LEFT JOIN successful_refunds ON 期间与渠道都相同;

模型可以写不同形式的 SQL,但输出必须遵守同样的五列契约,结果必须与明细核验一致。系统并不是只接受和标准 SQL 字符串相同的答案。

两类检查解决不同问题

execute_readonly 负责“是否允许执行”,validate_rows 负责“执行结果是否符合本次口径”。第一类是操作约束,第二类是结果正确性验证,不能互相替代。

执行层包含:最多 20,000 字符且只能一条 SELECT 或 WITH SELECT;SQLite URI mode=ro 加 query_only;authorizer 仅允许白名单表读取和白名单函数;每 1000 个虚拟机操作检查预算,超过约 3 秒或 200 万步则中断;最多返回 100 行,列名不得重复。

这里不是完整的进程隔离沙箱。进度回调的时间预算不是严格实时硬截止,也不提供进程级内存限制。sqlparse 的用途是语句划分和类型检查,不能描述成完整 SQL AST 语义证明。提示词要求参数化,但代码没有单独证明 SQL 必须实际引用每个命名参数。

结果层检查行数、精确列集合、期间渠道唯一性,以及三个数值列是否为有限非负整数。整数值浮点数可以归一化;布尔值、NaN、负数和小数金额不可以。最后对排序后的每一行与每一个字段做完全比较。

独立明细核验到底独立在哪里

reference_rows 使用简单 SELECT 读取订单字典、退款列表和渠道,再在 Python 循环里逐条判断。它不复用模型的聚合 SQL,也不通过另一条相似 JOIN 查询生成答案。

python
for 每个期间:
    for 每个渠道:
        for 每笔订单:
            if 支付成功且支付时间在窗口内且地区渠道匹配:
                订单数 += 1
                实付 += 订单实付
        for 每次退款:
            原订单 = 订单字典[退款.order_id]
            if 退款成功且退款时间在窗口内且原订单地区渠道匹配:
                退款额 += 本次退款金额

例如模型误用创建时间,它可能遗漏“七月创建、八月支付”的订单;独立计算仍按支付时间计入,二者就会不一致。一单多退造成的连接扇出同样会使金额不一致。

独立实现可以降低同一查询错误同时影响两条路径的概率,却不能证明业务定义本身正确。若团队一开始就选错了收入口径,SQL 和 Python 都按错误规则实现,二者仍可能完全一致。应先确认指标契约,再用独立计算检查实现。

它也不是面向海量生产数据的通用验证器:当前 Python 循环读取全部相关原始表,适合可控模拟数据。数据规模增加后,应考虑限定快照、分区对账或经过验证的指标层,不能照搬全量扫描。

为什么核验失败不偷偷换标准答案

live 模式失败时最多修复一次候选 SQL。修复失败就输出失败证据。若悄悄切换为标准 SQL,却仍标成“模型 SQL 已通过”,用户会误判模型能力,也无法复盘真实错误。

reference 模式则明确说明采用标准 SQL,适合不用模型做基线验证。这两个模式服务不同目的:一个测试真实生成链路,一个提供稳定对照。

最后记住:一次查询在当前数据库得到正确结果,不等于已经证明它在未来所有数据分布中语义等价。因此测试还要专门覆盖跨月、重复退款、零值渠道等反例。

本章源文件:03 数据口径与SQL核验.md

CHAPTER 04

收入贡献逐步推导

本章所有展示金额单位均为元。代码内部单位为分。目标是解释下降的 99,000 元怎样分给四项因素,以及为什么不能直接把各种百分比加起来。

先对账 再分解

指标2026 年 7 月2026 年 8 月变化
支付订单数1,000800−200
实付金额250,000159,000−91,000
成功退款10,00018,000+8,000
净收入240,000141,000−99,000
总体实付客单价250.00198.75−51.25

净收入降幅 = −99,000 / 240,000 = −41.25%。客单价分母是支付订单数,分子是实付金额,未扣退款;退款单独处理。

渠道七月订单八月订单七月占比八月占比七月客单价八月客单价
自营60030060%37.5%300280
搜索广告30030030%37.5%200190
直播10020010%25%10090

总体客单价为什么下降?一方面每个渠道内部客单价都下降;另一方面低客单价直播渠道的订单份额变大,高客单价自营份额变小。第二种变化即使所有渠道价格完全不变,也会拉低总体客单价。

用恒等式把问题写清楚

定义 N 为总订单数,s 为各渠道订单占比组成的向量,a 为各渠道客单价组成的向量,R 为当月成功退款。实付 G 与净收入 Y 满足:

text
G(N, s, a) = N × Σ(s_c × a_c)
Y = G(N, s, a) − R

Σ 表示对所有渠道相加。七月加权客单价是 0.6×300 + 0.3×200 + 0.1×100 = 250,乘以 1000 单得到 250,000。八月加权客单价是 0.375×280 + 0.375×190 + 0.25×90 = 198.75,乘以 800 得到 159,000。

“渠道结构”在这里专指订单占比,不是收入占比。选择收入占比会改变公式,不能换了分母还照用当前贡献算法。

为什么替换顺序会影响分账

暂时只看订单量与客单价。用七月客单价衡量少掉的 200 单,得到 −200×250 = −50,000;用八月客单价衡量同样的 200 单,得到 −200×198.75 = −39,750。两者都在回答一个反事实问题,但参考环境不同。

差异来自交互项:订单少了,同时价格和结构也变了。“先改谁”决定交互影响归谁。直接选一种顺序可能可以作为业务约定,但必须承认约定带来的分配差异。本项目枚举所有顺序并平均,减少对单一顺序的依赖。

手算一种顺序

取 N → s → a。从七月状态出发,只替换订单量,其他仍是七月:G = 800×250 = 200,000,所以 N 的本次边际贡献为 −50,000。

再替换渠道结构,保持七月各渠道客单价:加权价变成 0.375×300 + 0.375×200 + 0.25×100 = 212.5;G = 800×212.5 = 170,000,所以 s 的本次边际贡献为 −30,000。

最后替换渠道内客单价,G 变成 159,000,所以 a 的本次边际贡献为 −11,000。这条路径合计 −91,000,恰好等于实付变化。

中间的 200,000 和 170,000 是把不同月份因素拼接得到的计算情景,并不是数据库里真实存在的某个月收入。

六种顺序与最终贡献

下表每列都按因素归位,列的排列不代表替换顺序。

替换顺序N 的边际贡献s 的边际贡献a 的边际贡献合计
N → s → a−50,000−30,000−11,000−91,000
N → a → s−50,000−28,200−12,800−91,000
s → N → a−42,500−37,500−11,000−91,000
s → a → N−39,750−37,500−13,750−91,000
a → N → s−46,800−28,200−16,000−91,000
a → s → N−39,750−35,250−16,000−91,000
六行平均−44,800−32,775−13,425−91,000

这就是当前三个因素的 Shapley 算术分解:每个因素取它在全部排列中的平均边际差。这里没有训练模型,也没有使用解释机器学习预测的 SHAP 包。它是对收入恒等式做精确计算。

退款项在净收入中直接相减,与上述乘法因素没有交互,因此贡献直接为 R_七月 − R_八月 = 10,000 − 18,000 = −8,000。即使把退款加入四因素全部排列,其独立加法结构也会使它的边际贡献恒定。

最终对账:−44,800 −32,775 −13,425 −8,000 = −99,000。每条替换路径的边际差会逐步抵消中间值,只留下期末减期初;对所有路径求平均仍保留总变化。这是贡献能精确加总的原因。

对照代码看实现

decompose 先把六行分为 previous 和 current 两组,再计算各期总订单、实付、退款、净收入和客单价。接着构造 values[0] 与 values[1],每组都包含 N、s 向量和 a 向量。

permutations(range(3)) 产生六个顺序。每次从旧状态开始,替换一个因素,计算 after-before,将其六分之一累加到该因素贡献。每步的精确边际值也保存到 permutations,所以读者可以按证据逐步复算。

python
for order in permutations(range(3)):
    state = 旧月份因素
    before = gross(state)
    for factor in order:
        state[factor] = 新月份对应因素
        after = gross(state)
        contribution[factor] += (after - before) / 6
        before = after

上面是解释用伪代码,实际实现使用 Fraction 保存比例和中间金额。用分存金额可以避免原始货币的小数误差,但占比和平均客单价仍可能是无限循环小数,所以还需要分数运算。最终输出既保存便于展示的浮点值,也保存 exact_cents 精确字符串。先完成精确相加断言,再转成展示格式。

边界怎样处理

某个渠道单月零订单时,它的客单价在数学上没有定义。代码借用另一月该渠道客单价;两月均为零则取零,并记录填补说明。这个约定会影响结构与价格的分账,是必须披露的假设,不能说它是唯一正确的答案。

若整个地区某一整月无支付订单,连渠道占比也无法定义,程序直接拒绝这类分解。若基期净收入为零,增长率返回不可定义;若净变化为零,各因素仍可能相互抵消,贡献占变化比例也不可定义。

贡献比例是“某项带符号贡献 / 总净变化”。当有的因素改善、有的因素恶化时,它可能为负或超过 100%,不能把它解释成概率。

不能从贡献直接跳到原因

“订单量贡献 −44,800 元”表示按当前恒等式和分配规则,订单数量变化分到了多少收入差额。它不证明广告投放下降、竞品促销或流量流失。

想解释为什么少了订单,需要访问量、转化率和曝光数据;想解释客单价变化,需要商品组合、促销和折扣;想解释退款,需要退款原因与履约证据。当前报告把这些列为待验证假设。

总体客单价的下降已经拆进渠道结构与渠道内客单价,不能再加一项“总体客单价贡献”,否则重复分账。这是讲解时最容易被追问、也最应该说明白的一点。

本章源文件:04 收入贡献逐步推导.md

CHAPTER 05

证据工程与面试追问

这一章解释为什么项目不止是一条提示词,以及怎样准确描述已经实现的工程能力。

一次运行留下什么

成功运行会在 artifacts/<run_id>/ 中保存四个文件。

文件内容解决的问题
evidence.json请求、来源、SQL 尝试、模型记录、核验结果、贡献与轨迹这次分析具体经历了什么
query.sql最后通过的 SQL数据怎样查询出来
parameters.json两月边界和地区SQL 的时间与范围是什么
report.html确定性数字与图表人怎样阅读计算结果

provenance 记录数据库 SHA256、模拟数据元信息、业务 Python 文件指纹、官方提交、DB-GPT 和 Python 版本、生成时间。SHA256 相同可以帮助确认比较的是同一份文件内容;它不能单独证明数据库真实可信,也不是备份。

当前下载包不包含数据库快照,来源信息也没有完整记录官方未提交补丁指纹和全部依赖锁文件指纹。因此准确说法是“保留复算依据并支持同一可用数据快照下复算”,不应说“仅靠 ZIP 就能在任何机器上完整重建所有环境”。

失败流程也尽量保存 evidence,保留哪个 SQL 被拒绝以及原因。不过初始化在 try 之前发生的故障、磁盘写入失败等不保证成功落盘;当前保存也不是完整的原子提交机制。这些是后续工程增强空间。

如何复算和学习

在项目目录使用下面的 PowerShell 命令可以运行无需模型的标准基线。它会新增一个运行证据目录,不修改既有模拟库。

powershell
.\.venv\Scripts\python.exe -m commerce.cli --mode reference --as-of 2026-09-16 --region 华东

然后将输出的运行编号替换到下面的路径:

powershell
.\.venv\Scripts\python.exe -m commerce.cli --replay artifacts/你的运行编号/evidence.json

replay 先比较当前基础数据库指纹,再重新执行已保存的 SQL、核对保存的独立基准,最后重新计算贡献并比较完整分析结果。它不调用模型重新生成 SQL;复算关心的是既定查询和既定数据能否重现数字。

注意 CLI 当前固定使用基础 data/commerce.sqlite,不会自动根据官方证据中的 database_path 选择副本。官方副本与基础文件一致时可以核验,二者变化后可能因为指纹不同而拒绝。不要为了让复算通过而删除这个检查。

阅读 evidence 时,建议按 request → params → sql_attempts → independent_reference → analysis.totals → analysis.permutations → analysis.factors → provenance 的顺序。先明确问题与口径,再看数字,最后确认来源。

官方接入中真正解决的两个问题

第一,工具已注册,不代表模型知道工具存在。当前官方定制工作流中,业务工具进入执行工具包,但描述没有进入对应提示词。补丁把业务工具描述补进 prompt,并提示适用时优先使用已核验工具。这个问题体现了“可执行”与“可发现”是两件事。

第二,间接工具执行器使用旧模块路径导入资源 API,导致工具调度失败。补丁改为当前模块位置。业务代码正确也可能因为框架调用桥断开而失败,所以需要桥接层测试。

两处改动保存在 official-patches/registered-business-tools.patch。它们分别对应官方 agentic_data_api.py 和 tools/execute_tool.py。不能把当前 vendor 描述为完全无修改,也不应把这两处修复扩大为“重写官方 Agent 架构”。

工具描述里的“优先调用”只是对模型的指令,不是硬路由。官方通用工具仍属于更大的工具系统;专用工具里的 SQLite 限制也不会自动施加到全部通用工具上。若未来要求所有经营查询必须经过核验,应在路由和工具授权层做强制约束。

为什么报告使用文件交付

数据库查对、贡献算对之后,模型如果再重新编写报告 HTML,仍可能抄错数或写错单位。曾出现渠道客单价在改写报告时失真的情况。因此工具直接返回实际报告绝对路径,要求 html_interpreter(file_path=...) 加载已生成文件。

这降低了重新生成表格数字的风险,但不等于外层模型所有总结都得到形式化保证。验证事实时应以保存的报告和 evidence 为准。更强的实现可以让平台直接渲染报告链接,而不再让模型决定如何展示。

测试分别证明什么

已有记录中,27 项业务测试覆盖已知总额、日期边界、跨月退款、拒绝查询、重复连接、缺失渠道、分解加总、模型修复与失败、AWEL 和产物等;2 项官方桥接测试覆盖间接工具调用和原 HTML 文件内容保持。

真实界面链路也已有一次最终成功验证记录,运行编号为 c8721115df704349a4b89febde64a859,显示净收入下降 99,000 元。以上是指定用例的验证证据,不能换算成“模型准确率 100%”,也没有证明高并发或生产稳定性。

历史 VALIDATION.md 保留前期独立页面状态。阅读时以“最终交付”段为当前集成状态;“未部署官方完整后台”等描述位于明确标注的历史阶段。

常见追问与可直接使用的回答

既然有 Python 标准答案 为什么还要模型

当前窄场景确实可以直接用标准 SQL 完成业务计算。保留模型是为了复现并评估“自然语言到查询”的真实链路,同时用独立程序展示如何防止错误结果流入报告。它是一个可核验的 Agent 工程样例,不应为使用模型而声称固定报表非得由模型生成。扩展成开放分析平台时,不能为任意问题无限手写一个 oracle,需要受治理指标层、语义模型和分级验证。

为什么用 Agent 加工作流

Agent 适合在对话中选择动作,固定工作流适合保证关键步骤不会被随意省略。本项目把灵活性放在外层,把口径、核验和计算放在内层。代价是内层支持范围明确受限,新问题要增加正式契约。

SQL 合法为什么仍会出错

合法只说明语法和权限允许。例如按创建月份统计收入是合法 SQL,但与支付月份口径不一致。一单多明细的 JOIN 也可以合法执行,却把金额翻倍。结果核验要独立于执行权限检查。

Shapley 有什么用 为什么不直接相减

相减可以知道总变化,但分给订单量、结构、价格时出现交互项。不同替换顺序会给出不同分账。当前只有三个乘法因素,全部六种顺序成本很低,直接求平均可以给出清晰且可复算的分配规则。

数据库指纹能保证什么

它帮助发现本次分析期间文件内容改变,并在复算时识别是否同一文件。它不是事务快照机制,不能解决所有并发一致性问题。当前模拟数据基本静态;生产场景更适合明确的数据版本或只读事务快照。

当前有并发控制吗

旧独立 FastAPI 分析接口有容量为 2 的信号量,但官方注册业务工具没有复用这个限制。不能把旧接口的控制宣传成官方全链路全局限流。面向多用户时应在工具入口加入统一并发、超时、取消和任务队列设计。

哪些能力还没实现

当前没有任意企业指标理解、完整租户权限体系、生产数据库快照管理、业务因果识别或海量数据验证;官方知识库的 embedding 也尚未配置。多源与知识库是框架可扩展方向,不是本例已经逐项验证的能力。

怎样继续扩展才不破坏逻辑

如果增加按商品分析,先决定一笔订单多商品时收入和退款如何分摊,再增加商品粒度的数据契约和独立基准,最后扩展 SQL 生成与展示。不能只 JOIN 商品表后对订单金额求和。

如果支持任意月份,先更换覆盖期充分的数据并保留时间边界检查,再开放明确的开始与结束参数;如果支持真实企业数据,先确认支付、取消、退款等状态和地区快照的定义,再迁移执行器和指标层。

如果要做因果解释,需要补充实验或可支持识别的观测设计。增加一段“请解释原因”的提示词,只会增加叙述,不会自动增加证据。

自测是否真的理解

尝试不看文档回答:为什么退款不能只连接本月支付的订单?为什么 COUNT DISTINCT 救不了订单金额?六种顺序中订单量贡献为什么有时是 −50,000,有时是 −39,750?为什么总体客单价不能作为第五项贡献?为什么一次复算不需要模型?

能把这五个问题讲清楚,再按一次完整请求说明各文件职责,就已经掌握了这个项目最关键的逻辑。

本章源文件:05 证据工程与面试追问.md

CHAPTER 06

DeerFlow 与 DB-GPT:从界面到代码,理解两个项目

本文对比的是这次实际使用的源码:DeerFlow a58ab484a641fd5928ae512ad21c5c7fd7a18c18,DB-GPT ca9f014cb3ead157ca2ee6ce645658f5fb55138c(0.8.2)及本项目的电商增强。框架有某项能力,不等于本服务器已配置、测试了该能力。

1. 先把三个层次分清

你看到的网页是操作入口,真正决定分析如何执行的是后端。当前仓库里还有适合 GitHub Pages 的浏览器演示版,它不能代表官方完整版的全部能力。

层次DeerFlowDB-GPT
官方完整平台Next.js 前端、Gateway、Agent 运行时、沙箱、会话持久化官方前端、Python 服务、模型适配、数据源、Agent、AWEL 等模块
我们当前的工程工作部署、模型配置、登录、资源限制、沙箱适配、产物下载验证除完整平台部署外,增加电商数据、SQL 核验、贡献分解、报告证据与工具集成
GitHub Pages 演示deerflow-web:浏览器对话及文本检索db-gpt-commerce/web:浏览器 SQLite 分析与 DeepSeek SQL 生成

Pages 只托管静态文件;官方 Python 后端、后台任务和沙箱要运行在服务器。前端托管和后端托管可以分开,但此次完整版直接在服务器提供官方界面,更容易保持官方接口路径一致。

2. 最重要的区别:怎样决定下一步,以及怎样判定结果正确

DeerFlow 的重心是完成开放式任务。例如“研究某个行业,收集资料,做一个报告”:任务的步骤事先不完全确定。Lead Agent 读取用户要求和工具描述,决定搜索、读文件、执行代码,或派发子任务;看到工具结果后,再决定下一步。工程重点在任务状态、上下文、工具执行、子任务协调和中断恢复。

DB-GPT 的重心是围绕数据构建应用。官方平台提供数据连接、模型接口、数据相关 Agent 与工作流等基础设施。但单独安装平台,并不会自动知道企业的收入定义,也不会自动得到本项目的独立校验和贡献分解。

我们写的电商模块把一个业务问题做得更确定。外层 Agent 可以决定调用哪个工具;进入 analyze_commerce_revenue 后,必须依次通过既定口径、SQL 核验和计算规则。模型不能自行跳过核验后宣布成功。

两者的能力有重叠:DeerFlow 可以分析数据,DB-GPT 也有 ReAct Agent。区别不是“一个能用工具、另一个不能”,而是平台关注的对象,以及我们在业务层增加了什么约束。

3. DeerFlow:一次请求从哪里开始,到哪里结束

text
浏览器里的用户消息
  → Nginx:把页面请求和 API 请求转发到不同服务
  → Gateway:检查身份,管理会话与运行请求
  → RunManager:管理本次运行的生命周期
  → run_agent:调用组装好的 Lead Agent 图
  → Lead Agent:模型 → 工具 → 工具结果 → 模型,循环决策
       ├─ 文件/代码工具 → AIO 沙箱 → 文件或执行结果
       ├─ task 工具 → 子 Agent → 子任务结果
       └─ Skills / 记忆 / 中间件 → 提供约束与上下文
  → StreamBridge:把运行事件转换成前端可消费的流
  → 前端显示文字、工具进度、文件与下载入口

这份源码将 Agent 运行时嵌在 Gateway 中。不要照搬旧版本文章,误以为一定要额外启动一个独立 LangGraph Server。

Lead Agent 到底是什么

它是一个被组装出来的决策循环,不是单纯的一段提示词。backend/packages/harness/deerflow/agents/lead_agent/agent.py 负责组合模型、系统提示、工具、状态结构和中间件,调用 LangChain 的 create_agent。模型提出工具调用后,运行时执行工具,再将结果加入状态,继续下一轮。

中间件为什么重要

中间件负责横跨多个步骤的规则,例如消息压缩、记忆注入、待办状态、工具异常处理、子 Agent 数量限制、循环检测和最终回复处理。它们不是额外的“专家模型”,而是围绕 Agent 循环工作的程序组件。具体装配由源码和配置决定,不能把所有可选能力都说成已启用。

子 Agent 和沙箱有什么区别

子 Agent 是另一个执行子任务的模型循环;沙箱是代码和工具实际运行的环境。子 Agent 可以使用沙箱,主 Agent 也可以使用沙箱。把任务拆给三个子 Agent,不意味着一定会有三个独立服务器。

本服务器为了内存控制,子 Agent 并发限制为 1,模型并发限制为 2。因此可以讲“支持子任务委派”,但不应把默认并发或多 Agent 性能提升写成这台服务器的实测结果。

会话保存为什么不等于答案可复现

保存消息、工具记录和运行状态,有助于继续任务及排查过程;但数据源可能变化、工具可能有副作用、模型也可能给出不同输出。要复算某个金额,还需要冻结数据范围、保留 SQL 和参数,并固定计算规则。这恰好是电商模块专门补上的部分。

4. DB-GPT:外层 Agent 加内层业务工作流

text
官方前端提交问题
  → 官方 /api/v1/chat/react-agent 接口
  → ReAct Agent 看见已注册的业务工具描述
  → 调用 analyze_commerce_revenue(region, as_of)
  → commerce/workflow.py:run_analysis
       1. resolve:确定地区、两个月份及支持范围
       2. sql_stage:真实模型生成 SQL;基准模式使用标准 SQL
       3. verification:只读执行 + 独立 Python 明细核对
       4. calculation:精确计算各因素的贡献
  → DAG 完成后保存 SQL、参数、证据和 HTML 报告
  → 官方 html_interpreter 读取生成好的报告文件
  → 用户看到结果,并可下载证据

这里有两个完全不同的编排层。外层 ReAct 负责对话和工具选择,路径可变;内层 AWEL DAG 是明确的四节点串行图,规则由我们写死在代码里。报告保存发生在 DAG 返回之后,不应把它包装成第五个 AWEL 节点。

run_official.py 是启动适配入口。它先载入环境和持久化加密密钥,准备官方工作目录,再通过 official_integration.install() 把电商工具、数据源和报告下载路由注册进官方服务。我们没有重写官方前端。

5. 用同一个问题比较两条执行路径

问题:“上个月华东收入下降了,拆解订单量、客单价、退款和渠道结构,并提供依据。”

在通用 DeerFlow 中,Agent 可以先检查你提供的文件或数据接口,然后编写 SQL / Python、执行和整理报告。流程灵活,但业务口径与核验规则需要通过数据说明、工具或技能补充。不能仅凭最后出现了一张漂亮图表,就判断结果正确。

在电商 DB-GPT 模块中,进入业务工具后会做以下约束:

1. 时间先确定。演示数据覆盖 2026 年 6—8 月。默认基准日为 2026-09-16,“上个月”就是 8 月,对比 7 月。现在是 10 月也不会自动把样本变成真实 9 月经营数据。指定超出支持范围的日期应被拒绝。 2. 金额先定义。净收入 = 当月支付订单实付金额 − 当月成功退款。订单按 paid_at,退款按 refunded_at;地区取订单的收货地区快照,而不是用户现在所在地区。 3. 避免一对多重复计数。一个订单如果有两个明细和两次退款,直接把三表相连可能产生四行。应分别聚合支付与退款,再按月份和渠道连接。 4. SQL 能运行还不够。模型可能写出合法但口径错误的 SQL。程序另用 Python 遍历原始订单和退款,以不同实现计算基准结果,逐项核对六行“月份 × 渠道”结果。 5. 核验失败不能掩盖。被拒绝的模型 SQL 最多进行一次修复;仍失败则保存失败证据并返回失败。不能偷偷切换标准 SQL,却说是模型成功完成。 6. 通过后再做贡献计算。模型不负责心算贡献金额;代码使用精确分数计算,并检查贡献合计是否等于总变化。

这是“模型提出候选计算方案,代码决定是否接受”的逻辑。当前的核验器适用于这个限定数据任务,不等于能验证任意 SQL 的全部业务含义。

6. 客单价和渠道结构为什么不能重复解释

净收入写成:R = N × Σ(s_c × p_c) − F。

N 是支付订单总量,s_c 是渠道订单占比,p_c 是该渠道的实付客单价,F 是成功退款。总体客单价本来就是各渠道客单价的加权平均,既受渠道价格影响,也受渠道占比影响。因此不能先拿总体客单价解释全部变化,再把渠道结构额外加一遍。

我们把 N、渠道占比向量、渠道内客单价向量视为三个因素。三个因素有六种替换顺序,代码计算每种顺序的边际变化,再取平均,分配它们的交互影响;退款作为减项单独计算。金额底层使用整数分和 Fraction,展示时才换算成人民币元。

固定样本的 99,000 元下降分成:订单量 −44,800 元、渠道结构 −32,775 元、渠道内客单价 −13,425 元、退款 −8,000 元。四项相加正好等于 −99,000 元。

这些是算术贡献。它说明账面的变化来自哪些变量,不证明广告投放、竞争对手或产品质量导致了变化。业务原因仍需实验、活动记录、客群等额外证据。

7. 按什么顺序看代码,才不会迷路

阅读顺序文件或目录带着什么问题读
1commerce/data.py数据到底长什么样?时间、地区、退款怎样归属?
2commerce/query.py标准 SQL 是什么?哪些查询不允许执行?
3commerce/workflow.py状态怎么在四个节点间传递?失败走哪里?
4commerce/analysis.py六种替换顺序怎样计算?为何不会重复归因?
5commerce/report.py报告数字从哪里来?保留了哪些证据?
6commerce/official_integration.py官方 Agent 怎样找到我们的工具?
7run_official.py 与 official.toml官方服务如何启动,数据保存到哪里?
8DeerFlow runtime/ 与 agents/lead_agent/agent.py通用 Agent 的循环和状态如何组织?
9DeerFlow subagents/executor.py 与 community/aio_sandbox/backend.py委派如何执行?工具究竟在哪里运行?

DeerFlow 的上述路径相对于 backend/packages/harness/deerflow/。DB-GPT 官方 ReAct 接口在 packages/dbgpt-app/src/dbgpt_app/openapi/api_v1/agentic_data_api.py。不要从一个数千行的 Agent 文件顺着读到末尾;先沿着一条真实请求追踪调用关系。

8. 这个项目现在算完整吗

对于“固定模拟数据上的地区月度收入分析”,已有数据、模型 SQL、校验、计算、报告和复算链路,是可演示、可阅读代码的完整闭环。官方平台已提供成熟界面,但这不等于企业生产系统已经完成。

目前不能据此声称:已接入真实企业数据库、任意指标都能正确回答、多租户权限完善、完成高并发压测、具备完整运维告警,或已经验证所有知识库和本地模型功能。尤其这台服务器只有约 1.6 GB 内存,适合作品演示;同时运行复杂沙箱和多个分析任务时,性能仍需单独测试。

现有证据包含 SQL、参数、数据指纹和源码指纹。证据包没有自动附带整份数据库;换机器复算仍需保留相同数据库快照。模型调用再次运行也不保证得到相同 SQL;我们保证的是保存下来的 SQL 和计算规则在相同数据上可以复核。

9. 面试或答辩可以怎样讲

“DeerFlow 让我理解通用 Agent 怎么管理任务、上下文、工具和沙箱;DB-GPT 电商项目让我把一个具体业务分析做成可验证的计算链路。前者的主要工程难点是让长任务执行下去,后者的主要业务难点是让金额口径一致、SQL 不重复计数、贡献能对账。我的新增部分是电商数据与边界样本、独立校验器、精确贡献分解、证据报告,以及把这个业务工具接入官方平台。”

如果进一步问两个项目能否组合:可以让 DeerFlow 做研究与任务调度,再调用 DB-GPT 的受控分析接口,把可信计算结果作为报告依据。但当前没有实现这条跨平台调用链,不能将设计设想写成已完成能力。先把两个独立项目部署和验证清楚,比画一张已经互联但实际上没有接口的图更有价值。

10. 同一台小服务器上的真实工程问题

界面由浏览器显示,不代表后台很轻。官方 DB-GPT 会预热 Lyric 代码执行进程;本次观察到两组 Python worker,每组约二百多 MiB,即使电商分析只用 SQLite 和 Python 业务函数,也会承担这部分常驻开销。

因此新增了云端可选适配 commerce/cloud_runtime.py:只跳过启动预热,保留官方 _ensure_initialized,首次真正执行代码时再初始化。专用分析、DeepSeek SQL 与报告照常工作。测试验证了启动时不创建 worker、首次执行时创建、后续执行不重复创建,以及正常关闭。该模式由服务器环境变量显式开启,原本地启动默认不变。

另一项适配是报告地址使用相对路径或配置的公网 origin,避免云端回复中出现开发机地址;无 Git 元数据的源码发布包通过打包时保存的 UPSTREAM_COMMIT 保留官方版本来源,避免错误地记录外层业务仓库提交。

这些改动与业务贡献算法处于不同层:前者解决部署和运行资源,后者解决结果正确性。介绍项目时应分别讲清,不能把部署成功说成算法正确,也不能把算对一个样例说成服务可以承担高并发。

本章源文件:06 DeerFlow与DB-GPT对比.md