Files
cobol-java-v3/docs/detailed-design/05-agents-llm.md
T

465 lines
25 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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]`),超长设计书可能丢失信息