# 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]`),超长设计书可能丢失信息