from typing import List from agent.models import ProgramMeta class PromptBuilder: """将解析后的程序元数据和规则组装成 API prompt。""" def build(self, meta: ProgramMeta, rules_text: str, group_descriptions: List[str], group_count: int) -> str: """构建完整的 API prompt。""" parts = [] parts.append(self._build_basic_info(meta)) parts.append(self._build_process_detail(meta)) parts.append(self._build_input_structures(meta)) parts.append(self._build_output_records(meta)) parts.append(rules_text) parts.append(self._build_output_format(meta)) parts.append(self._build_generation_instruction( group_descriptions, group_count, meta.input_type )) return '\n\n'.join(parts) def _build_basic_info(self, meta: ProgramMeta) -> str: lines = [ f"## プログラム基本情報", f"- システム名: {meta.system_name}", f"- プログラムID: {meta.program_id}", f"- プログラム名: {meta.program_name}", f"- PGMパターン: {meta.pgm_pattern}", f"- 入力タイプ: {self._input_type_label(meta.input_type)}", ] if meta.summary_lines: lines.append(f"- 機能概要: {' '.join(meta.summary_lines)}") return '\n'.join(lines) def _build_process_detail(self, meta: ProgramMeta) -> str: return f"## 処理詳細\n\n```\n{meta.process_detail}\n```" def _build_input_structures(self, meta: ProgramMeta) -> str: parts = ["## 入力構造"] input_files = [f for f in meta.files if 'I' in f.io] for f in input_files: parts.append(f"### ファイル {f.identifier} (DD名: {f.dd_name}, COPY: {f.copy_group}, 媒体: {f.medium})") fields = meta.copy_fields.get(f.identifier, []) if fields: parts.append("| 項目名 | PIC | バイト数 |") parts.append("|--------|-----|----------|") for cf in fields: parts.append(f"| {cf.name} | {cf.pic_type} | {cf.pic_bytes} |") else: parts.append("(構造情報なし)") db_inputs = [f for f in meta.files if 'I' in f.io and f.medium == 'DB'] if db_inputs or meta.db_tables: parts.append("### DBテーブル構造") for table_name, table in meta.db_tables.items(): parts.append(f"**表名: {table_name}**") pk_str = ', '.join(table.pk_columns) parts.append(f"主キー: {pk_str}") parts.append("| 項目名 | 英字名 | タイプ | 最大長 | KEY |") parts.append("|--------|--------|--------|--------|-----|") for col in table.columns: key_mark = '✓' if col.is_pk else '' parts.append(f"| {col.name_jp} | {col.name_en} | {col.type} | {col.max_len} | {key_mark} |") return '\n\n'.join(parts) def _build_output_records(self, meta: ProgramMeta) -> str: return f"## 出力レコード定義\n\n```\n{meta.output_records}\n```" def _build_output_format(self, meta: ProgramMeta) -> str: lines = [ "## 出力形式", "", "### JSON形式(ファイル入力の場合)", "", "JSON構造:", "```json", "{", ' "program": "{プログラムID}",', ' "records": [', ' { "input": { "FD名": { "項目名": "値", ... } } },', ' ...', ' ]', "}", "```", "", "### 項目値のルール(PIC → JSON値)", "| PIC | JSON内表示 | 例 |", "|-----|-----------|-----|", "| PIC X(n) | 左詰め + スペース埋め | `\"A0000001\"` |", "| PIC 9(n) | 右詰め + 先行ゼロ | `\"00000101\"` |", "| PIC S9(n) | 符号 + 右詰め + 先行ゼロ | `\"+0000101\"` |", "| PIC S9(n)V9(m) | 符号 + 右詰め + 小数点含む | `\"+001234567\"` |", "| PIC S9(n) COMP | 通常の10進数文字列 | `\"300\"` |", "| PIC S9(n) COMP-3 | 通常の10進数文字列 | `\"1234\"` |", "| PIC 9(n) COMP-3 | 通常の10進数文字列 | `\"1234\"` |", "| FILLER(純粋予約) | グループIDとレコード番号を含むパターン | `\"D000000...001\"` |", "| FILLER(業務予約) | 全スペース(PIC X) または 全ゼロ(PIC 9) | |", "", "### SQL形式(DB入力の場合)", "各グループ1つのSQLファイル: `{program}_g{groupId}.sql`", "", "SQL例:", "```sql", "-- Group: 1", "INSERT INTO TABLE_NAME (COL1, COL2) VALUES", "('val1', 'val2');", "```", "", "### データ生成の注意", "- 項目名に意味がある場合(DATE→日付、NAME→氏名)、実際の形式に合った値を生成すること", "- 隣接するレコード間で、同じ項目に異なる値を設定すること", ] return '\n'.join(lines) def _build_generation_instruction(self, descriptions: List[str], count: int, input_type: str) -> str: lines = [ "## 生成指示", f"生成するグループ数: {count}", "各グループの内容:", ] for i, desc in enumerate(descriptions, 1): lines.append(f" - g{i}: {desc}") if input_type == 'file': lines.append("出力: JSONファイル(1グループ=1JSONファイル)") elif input_type == 'db': lines.append("出力: SQL INSERTファイル(1グループ=1SQLファイル)") else: lines.append("出力: JSONファイル + SQL INSERTファイル") lines.append("") lines.append("応答形式: JSONで返してください。最上位キー \"groups\" を使い、各グループを \"g1\", \"g2\" ... とします。") lines.append("例: {\"groups\": {\"g1\": {\"program\": \"XXX\", \"records\": [...]}, \"g2\": {...}}}") return '\n'.join(lines) @staticmethod def _input_type_label(t: str) -> str: labels = {'file': 'ファイル', 'db': 'DB', 'mixed': '混合(ファイル+DB)'} return labels.get(t, t)