320 lines
11 KiB
Markdown
320 lines
11 KiB
Markdown
# 05 - LLM 代理模块详细设计
|
||
|
||
## 1. 模块概述
|
||
|
||
LLM 代理模块(`agents/`)是 COBOL 迁移验证平台 V3 的智能分析层,负责通过大语言模型(LLM)完成三项核心任务:
|
||
|
||
1. **COPYBOOK 解析**(Agent1):将 COBOL COPYBOOK 源码解析为结构化字段树(`FieldTree`)
|
||
2. **测试数据设计**(Agent2):基于字段树生成边界测试用例(`TestSuite`)
|
||
3. **差异诊断**(Agent3):对 COBOL/Java 字段比对不一致项进行根因分析与建议
|
||
|
||
此外,模块还包含一个**式样书驱动测试数据生成器**(`DesignDataGenerator`),通过解析日文详细设计书,结合 LLM 生成有业务意义的机能测试数据。
|
||
|
||
模块底层封装了统一的 `LLMClient`,提供缓存、重试、多模型兼容能力。
|
||
|
||
## 2. 文件清单
|
||
|
||
| 文件 | 职责 | 依赖 |
|
||
|------|------|------|
|
||
| `__init__.py` | 包入口,公开 API 导出 | 所有子模块 |
|
||
| `llm.py` | LLM API 客户端(缓存 + 重试) | `httpx` |
|
||
| `agent1_parser.py` | COPYBOOK → FieldTree 解析代理 | `llm.py`, `data.field_tree` |
|
||
| `agent2_data.py` | FieldTree → TestSuite 测试数据设计代理 | `llm.py`, `data.field_tree`, `data.test_case` |
|
||
| `agent3_diagnostic.py` | FieldResult → 诊断建议文本代理 | `llm.py`, `data.diff_result` |
|
||
| `design_data.py` | 式样书驱动测试数据生成器 | `llm.py`, `design_data_input_parser` |
|
||
| `design_data_input_parser.py` | 式样书 .md 解析器 | 标准库 `re`, `dataclasses` |
|
||
|
||
## 3. Agent1 解析代理(Agent1Parser)
|
||
|
||
### 3.1 职责
|
||
|
||
将 COBOL COPYBOOK 源码文本发送给 LLM,由 LLM 输出结构化 JSON,再转换为 `FieldTree` 对象。这是整个流水线的第一步——只有正确解析字段结构,后续测试生成和比对才有基础。
|
||
|
||
### 3.2 数据流
|
||
|
||
```
|
||
COPYBOOK 源码文本
|
||
↓ (LLM call)
|
||
JSON: {"fields": [{name, level, pic, usage, offset, length, decimal, signed, occurs, redefines, conditions, children}]}
|
||
↓ (_load / _fields 递归)
|
||
FieldTree
|
||
```
|
||
|
||
### 3.3 核心类
|
||
|
||
```python
|
||
class Agent1Parser:
|
||
def __init__(self, llm: LLMClient)
|
||
def parse(self, text: str) -> FieldTree
|
||
def _load(self, d: dict) -> FieldTree
|
||
def _fields(self, raw: list[dict], off: int) -> list[Field]
|
||
```
|
||
|
||
### 3.4 LLM 提示词
|
||
|
||
系统提示词(P1)要求 LLM 扮演 COBOL COPYBOOK 解析器角色,输出严格 JSON 格式,包含字段的名称、层级、PIC 子句、USAGE 类型、偏移量、长度、小数位、符号、OCCURS、REDEFINES、88-level 条件及子字段嵌套。
|
||
|
||
### 3.5 字段偏移量计算
|
||
|
||
`_fields` 方法递归遍历 JSON 字段列表,逐字段计算字节偏移量(`cur += f.length`),确保每个字段在记录中的物理位置准确。子字段通过递归调用 `_fields` 处理嵌套层级。
|
||
|
||
### 3.6 错误处理
|
||
|
||
- LLM 返回非法 JSON 时,`parse` 捕获异常,返回一个 `FieldTree(copybook_name="parse_error")` 空树
|
||
- 使用 bare `except` 捕获所有解析异常,保证流水线不中断
|
||
|
||
## 4. Agent2 数据生成代理(Agent2Data)
|
||
|
||
### 4.1 职责
|
||
|
||
基于 `FieldTree` 结构,通过 LLM 生成边界测试用例集合(`TestSuite`)。每个测试用例指定字段值和覆盖目标决策点。
|
||
|
||
### 4.2 数据流
|
||
|
||
```
|
||
FieldTree
|
||
↓ (flatten → JSON)
|
||
{"fields": [{name, pic, usage, length, decimal, signed}]}
|
||
↓ (LLM call)
|
||
{"test_cases": [{id, fields: {FIELD: value}, coverage_targets: [DP-001]}]}
|
||
↓ (构造 TestCase)
|
||
TestSuite
|
||
```
|
||
|
||
### 4.3 核心类
|
||
|
||
```python
|
||
class Agent2Data:
|
||
def __init__(self, llm: LLMClient)
|
||
def design(self, tree: FieldTree, target="boundary", spark_mode=False) -> TestSuite
|
||
```
|
||
|
||
### 4.4 LLM 提示词
|
||
|
||
系统提示词(P2)要求 LLM 扮演 COBOL 测试数据设计师角色,根据字段树生成边界测试用例,输出严格 JSON 格式。
|
||
|
||
### 4.5 Spark 模式
|
||
|
||
当 `spark_mode=True` 时,生成的 `TestSuite` 附带 `SparkConfig(num_records=1000)`,用于大数据量测试场景。
|
||
|
||
### 4.6 错误处理
|
||
|
||
- LLM 调用失败或返回非法 JSON 时,生成一条兜底用例 `TestCase(id="TC-FALLBACK", fields={"BR-AMT": 0})`
|
||
- 使用 bare `except` 捕获反序列化异常
|
||
|
||
## 5. Agent3 诊断代理(Agent3Diagnostic)
|
||
|
||
### 5.1 职责
|
||
|
||
对单个字段比对结果(`FieldResult`)进行根因分析,输出包含问题类型、置信度、原因和建议的 JSON 诊断文本。
|
||
|
||
### 5.2 数据流
|
||
|
||
```
|
||
FieldResult (field_name, cobol_value, java_value, status)
|
||
↓ (格式化 prompt)
|
||
LLM call
|
||
↓
|
||
JSON: {"issue_type": "...", "confidence": 0.5, "reason": "...", "suggestion": "..."}
|
||
```
|
||
|
||
### 5.3 核心类
|
||
|
||
```python
|
||
class Agent3Diagnostic:
|
||
def __init__(self, llm: LLMClient)
|
||
def analyze(self, fr: FieldResult) -> str
|
||
```
|
||
|
||
### 5.4 LLM 提示词
|
||
|
||
系统提示词(P3)明确要求 LLM **不做 PASS/FAIL 判定**,仅提供诊断分析。这是设计上的重要约束——Agent3 只是辅助分析工具,最终判定由比对引擎完成。
|
||
|
||
### 5.5 错误处理
|
||
|
||
- 依赖 `LLMClient.call()` 的重试机制
|
||
- 返回原始 LLM 响应字符串,调用方负责解析
|
||
|
||
## 6. 式样书驱动测试数据生成器(DesignDataGenerator)
|
||
|
||
### 6.1 职责
|
||
|
||
从日文详细设计书(.md)中提取程序元信息(`ProgramMeta`),结合 COBOL 源码、COPYBOOK 结构和 DB 定义,通过 LLM 生成有业务意义的机能测试数据。
|
||
|
||
### 6.2 数据流
|
||
|
||
```
|
||
设计书 .md + COBOL 源码
|
||
↓ (DesignDataInputParser.parse)
|
||
ProgramMeta (program_id, pgm_pattern, files, keys, process_detail, ...)
|
||
↓ (加载规则 + 构建 prompt)
|
||
LLM call
|
||
↓
|
||
JSON: {"records": [{field_name: value}]}
|
||
↓ (_resolve_field_names 字段名映射)
|
||
list[dict]
|
||
```
|
||
|
||
### 6.3 核心类
|
||
|
||
```python
|
||
class DesignDataGenerator:
|
||
def __init__(self, llm_client: LLMClient, cpy_dirs: list, rules_dir: str = "rules")
|
||
def generate(self, design_md_text, source_text, file_db_md_text=None,
|
||
db_md_text=None, replacing_rules=None, v3_field_names=None) -> list[dict]
|
||
```
|
||
|
||
### 6.4 式样书解析器(DesignDataInputParser)
|
||
|
||
解析日文详细设计书 Markdown 文档,提取以下结构化信息:
|
||
|
||
| 数据类 | 内容 |
|
||
|--------|------|
|
||
| `ProgramMeta` | 程序ID、程序名、系统名、PGM类型、PGM模式、功能概要 |
|
||
| `FileInfo` | 文件编号、文件/DB名、标识符、DD名、I/O类型、COPY群、记录格式、记录长度、媒体 |
|
||
| `KeyInfo` | 键文件名、排序条件、键条件 |
|
||
| `ModuleInfo` | 模块编号、功能、程序ID、COPY名 |
|
||
| `TableInfo` | 表名、DB ID、列定义、主键列 |
|
||
|
||
解析通过 Markdown 标题匹配(`## 基本情報`、`## 使用ファイル一覧` 等)和表格解析实现,不依赖外部 Markdown 解析库。
|
||
|
||
### 6.5 输入类型自动判定
|
||
|
||
`_determine_input_type` 根据输入文件的媒体类型自动判定:
|
||
|
||
| 媒体类型 | input_type |
|
||
|----------|------------|
|
||
| 仅 PS(顺序文件) | `file` |
|
||
| 仅 DB(数据库) | `db` |
|
||
| 混合或无输入 | `mixed` / `file` |
|
||
|
||
### 6.6 字段名映射(_resolve_field_names)
|
||
|
||
外部 Agent 生成的字段名可能与 V3 内部字段名不一致,映射规则:
|
||
|
||
1. **REPLACING 展开**:`(A)` → `R01` 等
|
||
2. **前缀连字符处理**:`R01-EMP-ID` → `R01EMP-ID`
|
||
3. **直接匹配**:精确匹配 V3 字段名
|
||
4. **去连字符匹配**:`R01EMP-ID` → `R01EMPID`
|
||
5. **去下划线匹配**:`R01_EMP_ID` → `R01EMPID`
|
||
6. **无法映射的字段丢弃**
|
||
|
||
### 6.7 规则加载
|
||
|
||
`_load_rules` 从 `rules/pgm_pattern/` 和 `rules/special_feature/` 目录加载所有 `.md` 规则文件,作为 LLM 上下文的一部分。
|
||
|
||
### 6.8 记录去重(_dedup)
|
||
|
||
支持按指定键字段去重,`additional_records` 优先保留。
|
||
|
||
## 7. LLM 接口封装(LLMClient)
|
||
|
||
### 7.1 职责
|
||
|
||
封装 LLM API 调用,提供文件缓存、自动重试、多模型兼容能力。所有 Agent 通过此客户端与 LLM 交互。
|
||
|
||
### 7.2 核心类
|
||
|
||
```python
|
||
class LLMClient:
|
||
def __init__(self, model="gpt-4o-mini", timeout=15, cache_dir=".cache/llm")
|
||
def call(self, messages: list[dict], retries=1) -> str
|
||
def _key(self, msgs: list[dict]) -> str # SHA256 哈希键
|
||
def _get(self, k: str) -> str | None # 缓存读取
|
||
def _set(self, k: str, v: str) -> None # 缓存写入
|
||
```
|
||
|
||
### 7.3 缓存机制
|
||
|
||
- **键生成**:对消息列表进行 JSON 序列化后取 SHA256 哈希
|
||
- **存储**:以 `{hash}.json` 文件存储在 `.cache/llm/` 目录
|
||
- **格式**:`{"response": "..."}`
|
||
- **效果**:相同输入直接返回缓存结果,避免重复调用 LLM
|
||
|
||
### 7.4 重试机制
|
||
|
||
- 默认重试 1 次(共 2 次尝试)
|
||
- 使用 `httpx.post` 发送 HTTP 请求
|
||
- 重试时捕获所有异常,仅在最后一次失败时抛出
|
||
|
||
### 7.5 环境变量配置
|
||
|
||
| 环境变量 | 说明 | 默认值 |
|
||
|----------|------|--------|
|
||
| `LLM_API_KEY` | API 密钥 | 空字符串 |
|
||
| `OPENAI_API_KEY` | 备用 API 密钥 | 空字符串 |
|
||
| `LLM_API_BASE` | API 基础 URL | `https://api.openai.com/v1` |
|
||
|
||
### 7.6 API 调用格式
|
||
|
||
```json
|
||
POST {base}/chat/completions
|
||
{
|
||
"model": "{model}",
|
||
"messages": [...]
|
||
}
|
||
Header: Authorization: Bearer {key}
|
||
```
|
||
|
||
响应解析路径:`response["choices"][0]["message"]["content"]`
|
||
|
||
## 8. 接口定义
|
||
|
||
### 8.1 模块公开 API
|
||
|
||
```python
|
||
# agents/__init__.py
|
||
LLMClient # LLM API 客户端(含缓存 + 重试)
|
||
Agent1Parser # COPYBOOK → FieldTree
|
||
DesignDataGenerator # 式样书 → 机能测试数据
|
||
Agent2Data # FieldTree → TestSuite(测试数据设计)
|
||
Agent3Diagnostic # FieldResult → 诊断建议文本
|
||
```
|
||
|
||
### 8.2 典型调用链
|
||
|
||
```
|
||
Agent1Parser.parse(copybook_text) → FieldTree
|
||
Agent2Data.design(field_tree) → TestSuite
|
||
DesignDataGenerator.generate(design_md, source_text) → list[dict]
|
||
Agent3Diagnostic.analyze(field_result) → str (JSON)
|
||
```
|
||
|
||
### 8.3 数据模型依赖
|
||
|
||
| 模型 | 定义位置 | 用途 |
|
||
|------|----------|------|
|
||
| `FieldTree` | `data.field_tree` | 字段树结构 |
|
||
| `Field` | `data.field_tree` | 单个字段定义 |
|
||
| `TestCase` | `data.test_case` | 单条测试用例 |
|
||
| `TestSuite` | `data.test_case` | 测试用例集合 |
|
||
| `SparkConfig` | `data.test_case` | Spark 生成配置 |
|
||
| `FieldResult` | `data.diff_result` | 字段比对结果 |
|
||
|
||
## 9. 错误处理
|
||
|
||
### 9.1 错误策略
|
||
|
||
模块采用**宽容降级**策略:
|
||
|
||
| 异常场景 | 处理方式 | 影响 |
|
||
|----------|----------|------|
|
||
| LLM 返回非法 JSON | 返回空/兜底结果 | 流水线继续 |
|
||
| LLM API 调用失败 | 重试后抛出异常 | 上层捕获 |
|
||
| 式样书解析失败 | 返回空列表 | 跳过数据生成 |
|
||
| 字段名映射失败 | 丢弃无法映射的字段 | 部分数据丢失 |
|
||
| 缓存文件损坏 | 跳过缓存,重新调用 | 无功能影响 |
|
||
|
||
### 9.2 日志策略
|
||
|
||
- 使用 Python `logging` 模块
|
||
- 关键步骤记录 INFO 级别日志(解析开始、LLM 响应、记录数)
|
||
- 异常记录 WARNING 级别日志
|
||
- 字段映射丢弃记录 DEBUG 级别日志
|
||
|
||
### 9.3 已知局限
|
||
|
||
1. Agent1/Agent2/Agent3 的 bare `except` 可能掩盖非预期异常
|
||
2. 缓存键基于消息内容哈希,模型变更不会自动失效旧缓存
|
||
3. `DesignDataGenerator` 的 LLM prompt 包含截断(`process_detail[:2000]`),超长设计书可能丢失信息
|