- 新增 orm.py:按 db_type 构建引擎(postgresql+psycopg2 / mssql+pyodbc), 声明式 Attachment 模型,init_schema 幂等建表 - 重写 db.py 为 SQLAlchemy Core 实现(动态 Table + quote 跨库正确引用、 id/sn 双键回查、跨库分页、先删后插幂等),对外签名不变 - 配置:config.yaml 默认 PostgreSQL,新增 config.mssql.yaml 保留 SQL Server, config_loader 支持可选 driver 与 db_type - write_attachments.py 新增 --init-db - 依赖 requirements.txt 增 sqlalchemy / psycopg2-binary(保留 pyodbc) - README 对齐:修正目标表字段描述并新增数据库抽象层小节
362 lines
21 KiB
Markdown
362 lines
21 KiB
Markdown
# 布莱迪压力表 - 订单附件识别工具
|
||
|
||
根据"总排号"从数据库查询"新参数"字段,调用大语言模型判断该订单是否携带
|
||
附件,并以 JSON 输出结果。支持粗分类(资料/配件/耗材)和精分类("大类:细分
|
||
类目",具体到针型阀、说明书等)两种粒度。
|
||
|
||
> 数据访问层基于 **SQLAlchemy** 抽象,支持 **SQL Server(mssql+pyodbc)** 与
|
||
> **PostgreSQL(postgresql+psycopg2)** 两种数据库,通过 `config.database.db_type`
|
||
> 切换。源表/目标表的标识符引用由 SQLAlchemy 按方言自动处理(PG 用 `"名"`,
|
||
> MSSQL 用 `[名]`),无需改代码。
|
||
|
||
## 安装
|
||
|
||
```bash
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
依赖包含 `sqlalchemy`、`psycopg2-binary`(PostgreSQL 驱动,已自带 libpq)与
|
||
`pyodbc`(SQL Server 驱动)。使用 **PostgreSQL** 无需额外系统组件;使用
|
||
**SQL Server** 需要系统已安装对应的 ODBC 驱动,本项目 `config.yaml` 中使用的版本为
|
||
"ODBC Driver 18 for SQL Server",请按服务器实际安装的驱动版本填写 `database.driver`。
|
||
若服务器上已有 SQL Server 管理工具/客户端环境,通常已包含该驱动;否则需自行
|
||
安装 Microsoft 官方 ODBC Driver。
|
||
|
||
## 配置
|
||
|
||
编辑 `config.yaml`,填入以下三部分(首次使用需替换为真实值,**请勿将含真实
|
||
数据库密码 / API Key 的配置文件提交到版本库**):
|
||
|
||
- `database`:数据库连接信息与源表/字段名。
|
||
- `db_type`:数据库类型,`postgresql` 或 `mssql`(缺省按 `driver` 是否含
|
||
"SQL Server" 推断;PostgreSQL 无需 `driver`)。
|
||
- `server` / `port` / `database` / `username` / `password`:连接信息(两种库通用)。
|
||
- `schema` / `table` / `id_column` / `id_field` / `param_column`:源表的
|
||
schema、表名与列名(`id_column` 为**总排号**列,对应 `--sn`;`id_field` 为
|
||
数据库**真实 ID** 列,对应 `--id`;`param_column` 为**新参数**列)。这些名称
|
||
按源表实际填写,SQL Server / PostgreSQL 两端保持一致即可。
|
||
- 仅 `mssql` 需要:`driver`、`trust_server_certificate`。
|
||
- 切换到 SQL Server 时可直接用 `--config config.mssql.yaml`(已内置原 SQL Server
|
||
连接信息)。
|
||
- `llm`:OpenAI 兼容接口的 `base_url`、`api_key`、`model`
|
||
- `business`:并发数、日志级别、默认分类模式(`default_mode`)、日志目录(`log_dir`)、
|
||
是否启用"其他"兜底类目(`enable_other_category`,默认关闭)
|
||
|
||
## 使用
|
||
|
||
```bash
|
||
# 指定方式有两种键类型,可混用:
|
||
# --sn 按总排号列查询(如 26B742),即原先 --id 的语义
|
||
# --id 按数据库真实 ID 列查询(如 802)
|
||
# 无论用哪种,输出 JSON 的 zong_pai_hao 一律为回查到的总排号
|
||
|
||
# 单个总排号,默认粗分类
|
||
python main.py --sn 26B742
|
||
|
||
# 批量,逗号分隔
|
||
python main.py --sn 26B742,26B743,26B744
|
||
|
||
# 按数据库真实 ID 指定(单个 / 多个)
|
||
python main.py --id 802
|
||
python main.py --id 802,803,804
|
||
|
||
# 批量,文件输入(每行一个总排号)
|
||
python main.py --ids-file ids.txt
|
||
|
||
# 精分类:输出"大类:细分类目"组合
|
||
python main.py --sn 26B742 --mode fine
|
||
|
||
# 指定其他配置文件
|
||
python main.py --id 802 --config other_config.yaml
|
||
|
||
# 临时覆盖日志目录
|
||
python main.py --sn 26B742 --log-dir /tmp/debug_logs
|
||
|
||
# 格式化输出(默认是紧凑的 JSON Lines,每行一条)
|
||
python main.py --sn 26B742,26B743 --pretty
|
||
|
||
# 允许模型使用"其他"兜底类目
|
||
python main.py --sn 26B742 --mode fine --enable-other
|
||
|
||
# 额外在 stderr 打印本批运行汇总(耗时/token/缓存命中率);stdout 仍只输出干净 JSON Lines
|
||
python main.py --sn 26B742,26B743 --summary
|
||
```
|
||
|
||
## 分类粒度:coarse / fine
|
||
|
||
由 `--mode` 参数或配置文件 `business.default_mode` 指定,`--mode` 优先级更高。
|
||
|
||
- **coarse(默认)**:只判断三个大类——资料 / 配件 / 耗材
|
||
- **fine**:在同一个 `types` 字段里直接给出"大类:细分类目",例如
|
||
"配件:针型阀""资料:说明书"
|
||
|
||
两种模式对应的提示词都定义在 `prompts.py` 中,修改分类边界或细分类目
|
||
枚举,只需要改这一个文件。
|
||
|
||
### 分类体系(已与业务方确认边界)
|
||
|
||
**资料类**(12 个细分):出厂检测检验报告、材质证明类、说明书、
|
||
检验记录过程性、图纸类、标定校验证书、质量证明书、原产地证明、检验合格证书
|
||
综合、其他交工文件、营业执照、型式检验报告
|
||
|
||
**配件类**(25 个细分):针型阀、球阀、截止阀、旋塞阀、角阀、阀组、冷凝圈、
|
||
冷凝管、冷凝弯、虹吸管、表弯管、缓冲管缓冲弯、法兰隔膜、隔离器、过压保护器、
|
||
散热器散热片、铅封、转换接头、焊接接头短节短管、卡箍抱箍、紧固件、活接头、
|
||
接线盒、电缆插头、变送器
|
||
|
||
**耗材类**(1 个细分):垫片(四氟/紫铜/缠绕/密封圈等各种材质,单列为
|
||
第三大类,不算在配件里,避免大量小垫片稀释"配件"标签的信息量)
|
||
|
||
**其他类(可选,默认关闭)**:由 `--enable-other` 或配置文件
|
||
`business.enable_other_category` 开启。开启后新增第四大类"其他",只有
|
||
唯一细分值"其他",作为兜底——遇到确实是随货附件、但不属于以上任何一个
|
||
具体细分类目的情况才使用。关闭时模型和格式校验都不知道这个选项的存在,
|
||
行为与不支持"其他"之前完全一致。
|
||
|
||
**明确不算配件**:位号牌/铭牌/标牌(标识件);缓冲钉/阻尼钉/阻尼帽(均为
|
||
工艺处理,不算随货实物配件)
|
||
|
||
**明确不算资料**:装箱单/送货单/贴箱等物流包装指令;合格证(无论参数文本
|
||
是否提到,一律忽略——既不影响"是否携带附件"的判断,也不会出现在细分
|
||
类目里)
|
||
|
||
## 输出格式
|
||
|
||
默认逐行输出 JSON(JSON Lines),便于管道处理和逐条消费:
|
||
|
||
```json
|
||
{"zong_pai_hao": "26B742", "status": "ok", "has_attachment": true, "types": ["资料"], "meta": {"elapsed_ms": 1476.6, "attempts": 1, "llm": {"model": "deepseek-v4-flash", "elapsed_ms": 1472.8, "usage": {"prompt_tokens": 1297, "completion_tokens": 15, "total_tokens": 1312, "prompt_cache_hit_tokens": 1280, "prompt_cache_miss_tokens": 17, "cache_hit_rate": 0.9869}}}}
|
||
{"zong_pai_hao": "26B744", "status": "ok", "has_attachment": true, "types": ["配件:针型阀", "配件:表弯管"]}
|
||
{"zong_pai_hao": "26B999", "status": "not_found", "has_attachment": null, "types": [], "meta": {"elapsed_ms": null, "attempts": 0, "llm": null}}
|
||
```
|
||
|
||
每条结果都额外带一个 `meta` 运行统计对象(结构见下方"meta 字段说明")。加上 `--summary`
|
||
后还会在 **stderr** 额外打印整批汇总(JSON),而 stdout 仍只输出上述干净的 JSON Lines,便于管道/重定向。
|
||
|
||
`types` 是唯一的类型字段,不再有单独的 `fine_types`:
|
||
- `coarse` 模式下,`types` 是大类列表,如 `["资料", "配件"]`。
|
||
- `fine` 模式下,`types` 里每一项都是"大类:细分类目",如
|
||
`["资料:说明书", "配件:针型阀"]`,同一个订单可能同时出现多个大类、多个细分。
|
||
- 若启用了"其他"兜底类目,对应项会体现为 `"其他:其他"`,格式与其余项保持一致。
|
||
|
||
### status 字段说明
|
||
|
||
| status | 含义 |
|
||
|---|---|
|
||
| `ok` | 识别成功 |
|
||
| `not_found` | 数据库中查不到该总排号 |
|
||
| `empty_param` | 总排号存在,但"新参数"字段为空,未调用 LLM |
|
||
| `llm_call_error` | LLM 调用失败(网络/接口错误),重试耗尽 |
|
||
| `llm_format_error` | LLM 输出内容不符合约定格式,重试耗尽 |
|
||
| `db_error` | 数据库连接/查询失败,影响整批请求 |
|
||
|
||
`status` 非 `ok` 时,`has_attachment` 为 `null`,`types` 为空数组,
|
||
不会有猜测性的默认值混入结果。
|
||
|
||
### meta 字段说明(运行统计)
|
||
|
||
每条结果的 `meta` 提供调用层面的性能与用量统计,便于核算耗时与 token 成本:
|
||
|
||
- `elapsed_ms`:本条从进入分类到出结果的总耗时(毫秒,含格式校验重试与解析)。未发起 LLM 的情况(`not_found` / `empty_param` / `db_error`)为 `null`。
|
||
- `attempts`:实际尝试次数(含格式校验重试);调用彻底失败时为重试上限。
|
||
- `llm`:本次成功调用 LLM 的统计块;未调用或调用失败(如 `not_found`、`llm_call_error`)时为 `null`。
|
||
- `model`:实际服务的模型名(来自接口返回)。
|
||
- `elapsed_ms`:单次 LLM 调用的耗时(毫秒)。
|
||
- `usage`:token 用量与缓存统计:
|
||
- `prompt_tokens`:输入 token 总量(= 缓存命中 + 未命中)。
|
||
- `completion_tokens` / `total_tokens`:输出 / 总 token。
|
||
- `prompt_cache_hit_tokens` / `prompt_cache_miss_tokens`:输入中命中 / 未命中硬盘缓存的 token 数(DeepSeek 上下文缓存特性,相同前缀的后续请求会命中,见下)。
|
||
- `cache_hit_rate`:缓存命中率 = `prompt_cache_hit_tokens / prompt_tokens`,取值 0~1;首次冷调用为 0.0,缓存预热后显著升高。
|
||
|
||
成功调用示例:
|
||
```json
|
||
"meta": {"elapsed_ms": 1476.6, "attempts": 1, "llm": {"model": "deepseek-v4-flash", "elapsed_ms": 1472.8, "usage": {"prompt_tokens": 1297, "completion_tokens": 15, "total_tokens": 1312, "prompt_cache_hit_tokens": 1280, "prompt_cache_miss_tokens": 17, "cache_hit_rate": 0.9869}}}
|
||
```
|
||
|
||
`--summary` 打印的整批汇总字段与 `meta.llm.usage` 对应:`count` / `total_elapsed_ms` / `llm_calls` /
|
||
各类 `total_*_tokens` / `total_cache_hit_tokens` / `total_cache_miss_tokens` / `avg_cache_hit_rate`
|
||
(按 token 加权的整体命中率)。
|
||
|
||
## 对话日志
|
||
|
||
每个实际发起 LLM 调用的总排号,都会在日志目录(默认 `logs/`,可用
|
||
`--log-dir` 或配置文件 `business.log_dir` 指定)下生成一个独立的日志文件:
|
||
|
||
```
|
||
logs/26B742_20260723_153012_123456.log
|
||
```
|
||
|
||
日志内容为纯文本,完整记录:
|
||
- 发给模型的完整对话(system prompt + few-shot 示例 + 实际用户消息)
|
||
- 模型的每一次原始回复——**包括被格式校验判定无效、触发重试的那些**
|
||
- 每次尝试的格式校验结果(通过/失败及原因)
|
||
- 每次尝试的 `[调用统计]`:实际模型名、单次调用耗时、token 用量(prompt/completion/total)与缓存命中情况(hit/miss/命中率),调用失败时相应字段显示"无"
|
||
- 最终的解析结果和 status
|
||
|
||
同一总排号被重复处理不会覆盖旧日志(文件名带精确到微秒的时间戳),方便对比
|
||
"调整提示词前后,同一条记录的判断有没有变化"。
|
||
|
||
`not_found`(查不到)和 `empty_param`(参数为空)的总排号不会生成日志文件,
|
||
因为它们本就没有与 LLM 的对话内容可记。
|
||
|
||
## 故障排查:模型回复为空(推理型模型特有)
|
||
|
||
如果日志里出现"[调用结果] 已收到模型回复"但"[模型原始回复]"下面是空的(或
|
||
`status` 为 `llm_call_error`,错误信息提到"推理耗尽了max_tokens预算"),这是
|
||
带思维链(thinking/reasoning)能力的模型的已知行为——像 DeepSeek-V4 系列,
|
||
默认会先打一段思考草稿再给正文,而 `max_tokens` 限制的是"思考+正文"的总量。
|
||
如果思考阶段把预算用完,正文就会被截断成空字符串。
|
||
|
||
本工具通过两处配置应对:
|
||
- `llm.max_tokens`:控制单次回复长度上限。本项目已开启 `llm.disable_thinking: true`
|
||
(关闭推理链),因此 `max_tokens` 设为 `200` 即可覆盖"大类+细分"的短输出;若把
|
||
`disable_thinking` 改回 `false` 启用思考,则需调大到能覆盖"思考+正文"的总量(如
|
||
1024),否则思考阶段可能耗尽预算、正文被截断成空。
|
||
- `llm.disable_thinking`:设为 `true` 时会通过 `extra_body={"thinking":
|
||
{"type": "disabled"}}` 关闭推理链——分类任务规则清晰、不需要模型思考,关闭
|
||
后响应更快、更省 token,也从根源上避免"思考耗尽预算"的问题。仅 DeepSeek-V4
|
||
系列等支持该参数的模型有效;换用其他不支持该参数的模型时改回 `false`
|
||
(多数接口对不认识的字段会直接忽略,但不保证所有厂商都是如此)。
|
||
|
||
## 设计说明
|
||
|
||
**为什么 LLM 不直接输出 JSON?**
|
||
JSON 的括号、引号、转义更容易被模型写错。约定 LLM 只输出"标签: 值"格式的
|
||
纯文本(coarse 模式 2 行,fine 模式 3 行),程序侧用严格的正则做格式校验——
|
||
校验通过才提取字段、组装成本工具自己定义的 JSON 结构;校验失败会触发重试
|
||
(次数由配置文件 `business.format_retry` 控制,与 LLM 网络层重试 `llm.max_retry`
|
||
分开计数),重试仍失败则标记 `llm_format_error`,绝不把不确定的内容硬凑进
|
||
最终结果。
|
||
|
||
**fine 模式的交叉自洽校验:**
|
||
第三行(细分类目)和第二行(大类)必须自洽——比如模型选了"针型阀",第二行
|
||
就必须包含"配件",否则视为模型输出自相矛盾,同样触发格式校验失败重试。
|
||
校验通过后,`parser.py` 会把每个细分类目和它所属的大类拼接成"大类:细分类目"
|
||
(如"配件:针型阀"),作为最终对外的 `types`;程序内部不再单独保留"大类列表"
|
||
和"细分类目列表"两份数据。
|
||
|
||
**"其他"兜底类目是怎么接入这套校验体系的:**
|
||
"其他"被当成第四个大类,且细分类目固定只有"其他"自身一个值(`其他:其他`),
|
||
不允许模型编造具体名称——这样既给了"枚举之外的附件"一个去处,又不破坏
|
||
"细分类目必须严格匹配枚举"这条不信任 LLM 输出的核心原则。是否启用由
|
||
`enable_other` 参数控制(对应 `--enable-other` 或配置文件
|
||
`business.enable_other_category`),并且同一次调用里,提示词(`prompts.py`)
|
||
和格式校验(`parser.py`)用的 `enable_other` 必须一致,否则会出现"提示词
|
||
允许但校验拒绝"的不一致——这层一致性由 `classifier.py` 统一负责传递。
|
||
|
||
## 写入落库(落库到 Common.Attachment)
|
||
|
||
分类结果除在控制台以 JSON Lines 输出外,还可通过写入入口落库到生产库的
|
||
`Common.Attachment` 表,供下游系统按总排号查询该订单的附件类目。
|
||
|
||
```bash
|
||
# 全量写入(按真实 ID 升序遍历源表全部记录)
|
||
python write_attachments.py
|
||
|
||
# 仅前 N 个(按真实 ID 升序,测试用,避免一次性消耗大量 LLM 额度)
|
||
python write_attachments.py --limit 10
|
||
|
||
# 降序取前 N 个
|
||
python write_attachments.py --limit 10 --order desc
|
||
|
||
# 按真实 ID 闭区间过滤:仅处理 ID 在 [800,805] 的记录
|
||
python write_attachments.py --range 800,805
|
||
|
||
# 组合:区间内降序、再取前 3 个
|
||
python write_attachments.py --range 800,805 --order desc --limit 3
|
||
|
||
# 指定总排号文件(每行一个,键类型 sn)
|
||
python write_attachments.py --ids-file ids.txt
|
||
|
||
# 按总排号指定(键类型 sn,即原先 --id 的语义):单个或逗号分隔多个
|
||
python write_attachments.py --sn 26B742
|
||
python write_attachments.py --sn 26B742,26B743,26B744
|
||
|
||
# 按数据库真实 ID 指定(键类型 id):单个或逗号分隔多个
|
||
python write_attachments.py --id 802
|
||
python write_attachments.py --id 802,803
|
||
|
||
# --id / --sn / --ids-file 可混用;提供任一后不再全表扫描(--limit/--order/--range 此时无效)
|
||
|
||
# 粗分类写入(小类统一填占位值);不指定 --mode 则用配置文件 business.default_mode
|
||
python write_attachments.py --mode coarse
|
||
|
||
# 先预览将写入/跳过的行,不真正落库
|
||
python write_attachments.py --dry-run
|
||
|
||
# 首次在目标库建表(幂等:仅创建 Common schema 与 Attachment 表,已存在则跳过;
|
||
# 切换数据库或新环境首次部署前先执行一次)
|
||
python write_attachments.py --init-db
|
||
|
||
# 允许"其他"兜底类目
|
||
python write_attachments.py --mode fine --enable-other
|
||
```
|
||
|
||
运行结束后,除上面的 `ok/empty_param/not_found/error` 与"将写入行数/跳过数"统计外,还会打印一行
|
||
`[汇总]`:本批总耗时、LLM 调用次数、token 总量(prompt/completion/total)与加权缓存命中率,便于核算成本。
|
||
|
||
落库约定(与 `db.py` 常量、`write_attachments.py` 的转换逻辑保持一致):
|
||
|
||
- **有附件**(`has_attachment=true`,`status=ok`):每个 `type` 写一行。
|
||
- `fine` 模式:`type` 形如 `大类:细分`,按首个冒号拆分为 `(SN, 大类, 细分)`。
|
||
- `coarse` 模式:`type` 是大类,小类统一填占位值 `无` → `(SN, 大类, '无')`。
|
||
- **无附件**(`has_attachment=false`,含 `empty_param` 与模型判无附件):写哨兵行
|
||
`(SN, '无附件', '无')`,下游用 `WHERE MajorCategory <> '无附件'` 取真实附件。
|
||
- **无法确定/失败**(`has_attachment=null`,含 `not_found` / `llm_*_error` /
|
||
`db_error`):一律不写,既不当作无附件,也不留脏数据。
|
||
- **幂等**:写入时对同一总排号先删除旧行再插入本次结果(依赖 `(SN, MajorCategory,
|
||
MinorCategory)` 复合主键保证唯一),重跑安全。
|
||
|
||
目标表 `Common.Attachment` 字段(三列**复合主键**、均 `NOT NULL`;列类型由 ORM 模型
|
||
`String(30)` / `String(40)` 按方言统一生成:PostgreSQL 端为 `varchar`,SQL Server 端为
|
||
`nvarchar`):
|
||
- `SN`:总排号/关联键
|
||
- `MajorCategory`:附件大类
|
||
- `MinorCategory`:附件小类
|
||
|
||
## 数据库抽象层(多库支持)
|
||
|
||
数据访问层已重构为 SQLAlchemy,由两层组成,业务代码(`classifier.py` /
|
||
`write_attachments.py`)只调用 `db.py` 的 4 个函数,无需感知底层方言:
|
||
|
||
- `orm.py`:方言无关的底层。
|
||
- `build_engine` / `get_engine`:按 `config.database.db_type` 生成 SQLAlchemy Engine
|
||
(`postgresql+psycopg2` 或 `mssql+pyodbc`),含连接池复用(`pool_pre_ping`)与
|
||
登录/语句超时;`get_engine` 按连接信息缓存 Engine,避免重复建池。
|
||
- `Attachment`:目标表 `Common.Attachment` 的声明式 ORM 模型(三列复合主键,
|
||
`quote=True` 保留大小写),跨库统一的建表/读写入口。
|
||
- `init_schema`:方言感知地 `CREATE SCHEMA IF NOT EXISTS "Common"` + `create_all`,
|
||
供 `--init-db` 幂等建表。
|
||
- `db.py`:基于 SQLAlchemy Core 的查询/落库实现。
|
||
- 源表(表名/列名含中文、由配置驱动)用动态 `Table(..., quote=True,
|
||
quote_schema=True)` 构造,标识符引用由 SQLAlchemy 按方言生成(PG 用 `"名"`、
|
||
MSSQL 用 `[名]`),彻底摆脱手写引号拼接。
|
||
- 对外 4 个函数签名与旧版完全一致:`fetch_params_by_ids`(支持 `--id` 整型真实 ID
|
||
与 `--sn` 总排号双键,并回查总排号)、`fetch_param_by_id`、`fetch_all_ids`
|
||
(`distinct()` + `order_by()` + `limit()/offset(0)`,分页语法跨库自动适配)、
|
||
`upsert_attachments`(先删后插,依赖复合主键幂等)。
|
||
|
||
**切换数据库**:默认 `config.yaml` 指向 PostgreSQL;切回 SQL Server 只需
|
||
`python write_attachments.py --config config.mssql.yaml`(或 `main.py --config ...`)。
|
||
两库源表 schema/表名/列名一致,仅需改连接信息与 `db_type`,无需改代码。
|
||
|
||
## 项目结构
|
||
|
||
```
|
||
├── config.yaml # 配置文件(默认 PostgreSQL;含 db_type 切换)
|
||
├── config.mssql.yaml # SQL Server 版配置(数据库不可达时切换用,--config 指定)
|
||
├── main.py # 命令行入口:分类并输出 JSON Lines(支持 --summary 在 stderr 打印批汇总;直接 import 同目录各模块)
|
||
├── write_attachments.py # 写入入口:分类结果落库到 Common.Attachment(全表扫描支持 --limit/--order/--range 按真实 ID 排序与范围过滤;--id/--sn/--ids-file/--mode/--dry-run/--enable-other/--init-db/--config;运行后打印 token/缓存汇总)
|
||
├── requirements.txt
|
||
├── config_loader.py # YAML 配置读取与校验
|
||
├── orm.py # 数据库抽象层:SQLAlchemy 引擎构建(mssql/postgresql)、Attachment ORM 模型、init_schema 建表
|
||
├── db.py # 基于 SQLAlchemy 的查询与落库(fetch_params_by_ids 支持按 id/sn 双键查询、fetch_all_ids / upsert_attachments,跨库无关)
|
||
├── prompts.py # 提示词与细分类目枚举(唯一需要改分类边界时编辑的文件)
|
||
├── llm_client.py # LLM 调用 (OpenAI 兼容接口)
|
||
├── parser.py # LLM 输出格式校验与清洗 → 结构化数据
|
||
├── order_logger.py # 每个总排号一份的完整对话日志
|
||
├── classifier.py # 编排:查库 -> 调LLM -> 校验解析 -> 记日志 -> 组装结果
|
||
├── attachment_classifier.py # 历史版本(旧 Excel 版,已弃用,保留仅供参考)
|
||
└── docs/ # 补充文档
|
||
```
|