v1: executing-plans 模式生成,54 文件 1320 行 Python
This commit is contained in:
@@ -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
|
||||
@@ -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})]
|
||||
@@ -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
|
||||
@@ -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 ""
|
||||
Reference in New Issue
Block a user