v1: executing-plans 模式生成,54 文件 1320 行 Python

This commit is contained in:
hangshuo652
2026-05-24 10:02:52 +08:00
commit 06b295f780
55 changed files with 1749 additions and 0 deletions
View File
+50
View File
@@ -0,0 +1,50 @@
import json
from data.field_tree import FieldTree, Field
from agents.llm import LLMClient
PROMPT_AGENT1 = """You are a COBOL COPYBOOK parser. Given COPYBOOK text, output a JSON object:
{"fields": [{"name": "...", "level": N, "pic": "...", "usage": "DISPLAY|COMP-3|COMP|COMP-5", "offset": N, "length": N, "decimal": N, "signed": bool, "occurs": N|null, "redefines": "..."|null, "conditions": [{"name": "...", "value": "..."}], "children": [...]}]}
Return valid JSON only. No explanation."""
class Agent1Parser:
def __init__(self, llm: LLMClient):
self.llm = llm
def parse(self, copybook_text: str) -> FieldTree:
messages = [
{"role": "system", "content": PROMPT_AGENT1},
{"role": "user", "content": copybook_text}
]
raw = self.llm.call(messages)
return self._parse_response(raw)
def _parse_response(self, raw: str) -> FieldTree:
try:
data = json.loads(raw)
fields = self._to_fields(data.get("fields", []), offset=0)
return FieldTree(fields=fields, copybook_name="")
except (json.JSONDecodeError, KeyError):
return FieldTree(fields=[], copybook_name="parse_error")
def _to_fields(self, raw_fields: list, offset: int = 0) -> list[Field]:
result = []
current_offset = offset
for rf in raw_fields:
f = Field(
name=rf.get("name", ""),
level=rf.get("level", 0),
pic=rf.get("pic", ""),
usage=rf.get("usage", "DISPLAY"),
offset=current_offset,
length=rf.get("length", 0),
decimal=rf.get("decimal", 0),
signed=rf.get("signed", False),
occurs=rf.get("occurs"),
redefines=rf.get("redefines"),
conditions=rf.get("conditions", []))
children = rf.get("children", [])
f.children = self._to_fields(children, current_offset)
current_offset += f.length
result.append(f)
return result
+39
View File
@@ -0,0 +1,39 @@
import json
from data.field_tree import FieldTree
from data.test_case import TestCase, TestSuite, SparkConfig
from agents.llm import LLMClient
PROMPT_AGENT2 = """You are a COBOL test data designer. Given a FieldTree JSON, generate test data covering boundary values.
Output: {"test_cases": [{"id": "TC-001", "fields": {"FIELD_NAME": value, ...}, "coverage_targets": ["DP-001-TRUE"]}]}
For each field, generate 1-3 test cases covering: zero, boundary (MAX), typical value. Return valid JSON only."""
class Agent2Data:
def __init__(self, llm: LLMClient):
self.llm = llm
def design(self, tree: FieldTree, coverage_target: str = "boundary",
spark_mode: bool = False) -> TestSuite:
tree_json = {"fields": [{
"name": f.name, "level": f.level, "pic": f.pic,
"usage": f.usage, "length": f.length, "decimal": f.decimal,
"signed": f.signed, "redefines": f.redefines, "occurs": f.occurs
} for f in tree.flatten().values()]}
messages = [
{"role": "system", "content": PROMPT_AGENT2},
{"role": "user", "content": json.dumps(tree_json)}
]
raw = self.llm.call(messages)
test_cases = self._parse(raw)
suite = TestSuite(test_cases=test_cases)
if spark_mode:
suite.spark_config = SparkConfig(num_records=1000)
return suite
def _parse(self, raw: str) -> list[TestCase]:
try:
data = json.loads(raw)
return [TestCase(**tc) for tc in data.get("test_cases", [])]
except (json.JSONDecodeError, KeyError):
return [TestCase(id="TC-FALLBACK", fields={"BR-AMT": 0})]
+23
View File
@@ -0,0 +1,23 @@
from agents.llm import LLMClient
from data.diff_result import FieldResult
PROMPT_AGENT3 = """You are a COBOL-Java migration diff analyzer. Given a field mismatch, explain WHY the values differ and suggest a fix.
Output: {"issue_type": "...", "confidence": 0.0-1.0, "reason": "...", "suggestion": "..."}
You NEVER decide PASS/FAIL. Your role is diagnostic only. Return valid JSON only."""
class Agent3Diagnostic:
def __init__(self, llm: LLMClient):
self.llm = llm
def analyze(self, fr: FieldResult) -> str:
prompt = f"""Field: {fr.field_name}
COBOL value: {fr.cobol_value}
Java value: {fr.java_value}
Status: {fr.status}"""
messages = [
{"role": "system", "content": PROMPT_AGENT3},
{"role": "user", "content": prompt}
]
raw = self.llm.call(messages)
return raw
+47
View File
@@ -0,0 +1,47 @@
import json, hashlib, os
from pathlib import Path
from typing import Optional
import httpx
class LLMClient:
def __init__(self, model: str = "gpt-4o-mini", timeout: int = 15,
cache_dir: str = ".cache/llm"):
self.model = model
self.timeout = timeout
self.cache_dir = Path(cache_dir)
self.cache_dir.mkdir(parents=True, exist_ok=True)
def _cache_key(self, messages: list) -> str:
return hashlib.sha256(json.dumps(messages, sort_keys=True).encode()).hexdigest()
def _cache_get(self, key: str) -> Optional[str]:
path = self.cache_dir / f"{key}.json"
if path.exists():
return json.loads(path.read_text()).get("response")
return None
def _cache_set(self, key: str, response: str):
(self.cache_dir / f"{key}.json").write_text(json.dumps({"response": response}))
def call(self, messages: list, retries: int = 1) -> str:
key = self._cache_key(messages)
cached = self._cache_get(key)
if cached:
return cached
api_key = os.environ.get("OPENAI_API_KEY", "")
for attempt in range(retries + 1):
try:
resp = httpx.post(
"https://api.openai.com/v1/chat/completions",
json={"model": self.model, "messages": messages},
headers={"Authorization": f"Bearer {api_key}"},
timeout=self.timeout)
resp.raise_for_status()
result = resp.json()["choices"][0]["message"]["content"]
self._cache_set(key, result)
return result
except Exception:
if attempt == retries:
raise
return ""