465 lines
25 KiB
Markdown
465 lines
25 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. Agent 架构模型(感知-规划-行动-记忆)
|
||
|
||
本节描述 LLM 代理模块的核心架构模型,采用经典的**感知-规划-行动-记忆**(Perception-Planning-Action-Memory)循环。
|
||
|
||
### 3.1 架构图
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||
│ Agent 架构模型 (感知-规划-行动-记忆) │
|
||
├─────────────────────────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ ┌──────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 感知层 (Perception) │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
||
│ │ │ COBOL源码 │ │ COPYBOOK │ │ 设计书.md │ │ 比对结果 │ │ │
|
||
│ │ │ 输入 │ │ 结构 │ │ 业务规则 │ │ 差异项 │ │ │
|
||
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ └────────────────┼────────────────┼────────────────┘ │ │
|
||
│ │ ▼ │ │
|
||
│ │ ┌───────────────────────┐ │ │
|
||
│ │ │ read.py / DesignData │ │ │
|
||
│ │ │ InputParser │ │ │
|
||
│ │ └───────────┬───────────┘ │ │
|
||
│ └──────────────────────────┼───────────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌──────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 规划层 (Planning) │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
||
│ │ │ 字段树构建 │ │ 路径枚举 │ │ 约束生成 │ │ 测试策略 │ │ │
|
||
│ │ │ FieldTree │ │ BranchTree │ │ Constraints │ │ TestSuite │ │ │
|
||
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ └────────────────┼────────────────┼────────────────┘ │ │
|
||
│ │ ▼ │ │
|
||
│ │ ┌───────────────────────┐ │ │
|
||
│ │ │ core.py / design.py │ │ │
|
||
│ │ │ enum_paths │ │ │
|
||
│ │ └───────────┬───────────┘ │ │
|
||
│ └──────────────────────────┼───────────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌──────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 行动层 (Action) │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
||
│ │ │ LLM调用 │ │ 规则引擎 │ │ 数据生成 │ │ JSON输出 │ │ │
|
||
│ │ │ DeepSeek │ │ Fallback │ │ Records │ │ output.py │ │ │
|
||
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ └────────────────┼────────────────┼────────────────┘ │ │
|
||
│ │ ▼ │ │
|
||
│ │ ┌───────────────────────┐ │ │
|
||
│ │ │ agents/llm.py │ │ │
|
||
│ │ │ LLMClient.call() │ │ │
|
||
│ │ └───────────┬───────────┘ │ │
|
||
│ └──────────────────────────┼───────────────────────────────────────────┘ │
|
||
│ │ │
|
||
│ ▼ │
|
||
│ ┌──────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 记忆层 (Memory) │ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
||
│ │ │ pi_map │ │ assignments │ │ 决策点缓存 │ │ LLM缓存 │ │ │
|
||
│ │ │ 字段映射 │ │ 赋值链 │ │ DecisionPts │ │ .cache/llm │ │ │
|
||
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
|
||
│ │ │ │ │ │ │ │
|
||
│ │ └────────────────┼────────────────┼────────────────┘ │ │
|
||
│ │ ▼ │ │
|
||
│ │ ┌───────────────────────┐ │ │
|
||
│ │ │ models.py / config │ │ │
|
||
│ │ │ 共享数据模型 │ │ │
|
||
│ │ └───────────────────────┘ │ │
|
||
│ └──────────────────────────────────────────────────────────────────────┘ │
|
||
│ │
|
||
│ ┌──────────────────────────────────────────────────────────────────────┐ │
|
||
│ │ 循环反馈 (Feedback Loop) │ │
|
||
│ │ │ │
|
||
│ │ 覆盖率分析 → 未覆盖分支 → 补充测试数据 → 重新执行 → 更新记忆 │ │
|
||
│ │ (coverage.py) (DecisionPoints) (design.py) (runners/) (pi_map) │ │
|
||
│ └──────────────────────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 3.2 四层职责
|
||
|
||
| 层级 | 职责 | 核心组件 | 数据模型 |
|
||
|------|------|----------|----------|
|
||
| **感知层** | 解析输入,提取结构信息 | `read.py`, `DesignDataInputParser` | COBOL源码, FieldTree |
|
||
| **规划层** | 构建分支树,枚举路径,生成约束 | `core.py`, `design.py`, `cond.py` | BranchTree, Constraints |
|
||
| **行动层** | 执行LLM调用,生成测试数据 | `agents/llm.py`, `output.py` | TestSuite, JSON |
|
||
| **记忆层** | 缓存中间结果,共享状态 | `models.py`, `config/` | pi_map, assignments |
|
||
|
||
### 3.3 循环反馈机制
|
||
|
||
Agent 架构采用**闭环反馈**设计:
|
||
|
||
1. **感知 → 规划**: 解析结果驱动分支树构建
|
||
2. **规划 → 行动**: 路径约束指导测试数据生成
|
||
3. **行动 → 记忆**: 生成结果更新缓存和状态
|
||
4. **记忆 → 感知**: 覆盖率分析触发新一轮感知
|
||
|
||
### 3.4 LLM 在架构中的位置
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────┐
|
||
│ Agent 架构 │
|
||
├─────────────────────────────────────────────────┤
|
||
│ │
|
||
│ 规划层 行动层 │
|
||
│ ┌─────────────┐ ┌─────────────┐ │
|
||
│ │ 路径枚举 │ │ LLM调用 │ │
|
||
│ │ (规则引擎) │──────│ (DeepSeek) │ │
|
||
│ └─────────────┘ └─────────────┘ │
|
||
│ │ │ │
|
||
│ │ ┌─────────────┐ │ │
|
||
│ └────│ 记忆层 │───┘ │
|
||
│ │ (缓存+状态) │ │
|
||
│ └─────────────┘ │
|
||
└─────────────────────────────────────────────────┘
|
||
```
|
||
|
||
LLM 作为**行动层**的核心组件,负责:
|
||
- **Agent1**: COPYBOOK → FieldTree 解析
|
||
- **Agent2**: FieldTree → TestSuite 测试数据设计
|
||
- **Agent3**: FieldResult → 诊断建议文本
|
||
- **DesignDataGenerator**: 式样书 → 机能测试数据
|
||
|
||
当 LLM 调用失败时,系统自动**降级到规则引擎**(fallback),确保流水线不中断。
|
||
|
||
### 3.5 记忆层详解
|
||
|
||
记忆层是 Agent 架构的**状态中心**,存储:
|
||
|
||
| 记忆类型 | 说明 | 位置 |
|
||
|----------|------|------|
|
||
| **短期记忆** | 单次执行的中间结果 | pi_map, assignments |
|
||
| **长期记忆** | 跨执行的缓存数据 | .cache/llm/ |
|
||
| **共享记忆** | 模块间传递的数据 | models.py |
|
||
|
||
---
|
||
|
||
## 4. 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` 捕获所有解析异常,保证流水线不中断
|
||
|
||
## 5. 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` 捕获反序列化异常
|
||
|
||
## 6. 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 响应字符串,调用方负责解析
|
||
|
||
## 7. 式样书驱动测试数据生成器(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` 优先保留。
|
||
|
||
## 8. 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"]`
|
||
|
||
## 9. 接口定义
|
||
|
||
### 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` | 字段比对结果 |
|
||
|
||
## 10. 错误处理
|
||
|
||
### 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]`),超长设计书可能丢失信息
|