11 KiB
05 - LLM 代理模块详细设计
1. 模块概述
LLM 代理模块(agents/)是 COBOL 迁移验证平台 V3 的智能分析层,负责通过大语言模型(LLM)完成三项核心任务:
- COPYBOOK 解析(Agent1):将 COBOL COPYBOOK 源码解析为结构化字段树(
FieldTree) - 测试数据设计(Agent2):基于字段树生成边界测试用例(
TestSuite) - 差异诊断(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 核心类
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 核心类
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 核心类
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 核心类
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 内部字段名不一致,映射规则:
- REPLACING 展开:
(A)→R01等 - 前缀连字符处理:
R01-EMP-ID→R01EMP-ID - 直接匹配:精确匹配 V3 字段名
- 去连字符匹配:
R01EMP-ID→R01EMPID - 去下划线匹配:
R01_EMP_ID→R01EMPID - 无法映射的字段丢弃
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 核心类
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 调用格式
POST {base}/chat/completions
{
"model": "{model}",
"messages": [...]
}
Header: Authorization: Bearer {key}
响应解析路径:response["choices"][0]["message"]["content"]
8. 接口定义
8.1 模块公开 API
# 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 已知局限
- Agent1/Agent2/Agent3 的 bare
except可能掩盖非预期异常 - 缓存键基于消息内容哈希,模型变更不会自动失效旧缓存
DesignDataGenerator的 LLM prompt 包含截断(process_detail[:2000]),超长设计书可能丢失信息