feat: add AI manifest generation to pod scaffold
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 1m6s
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 1m6s
This commit is contained in:
@@ -78,12 +78,34 @@ public class ScaffoldingController {
|
||||
}
|
||||
System.setProperty("AXHUB_SOURCE_DIR", workspacePath);
|
||||
|
||||
return PodScaffolder.scaffoldPod(moduleName, port, shortName, author, date);
|
||||
return PodScaffolder.scaffoldPod(moduleName, port, shortName, author, date, req.get("toolServiceManifest"));
|
||||
} catch (Exception e) {
|
||||
return "오류 발생: " + e.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/pod-draft")
|
||||
public ResponseEntity<?> generatePodManifestDraft(@RequestBody Map<String, String> req) {
|
||||
String description = req.getOrDefault("description", "").trim();
|
||||
if (description.isBlank()) return ResponseEntity.badRequest().body(Map.of("error", "Pod 업무 설명을 입력해주세요."));
|
||||
try {
|
||||
String prompt = """
|
||||
Generate only YAML for an MCP tool service manifest.
|
||||
The root must be mcp.manifest.routing-functions with one routing function.
|
||||
Include name, description, server-id, category-key, product-boundary, business-domain,
|
||||
business-outcome, primary-entities, capabilities, select-if, reject-if,
|
||||
confidence-server-ids, and decision-policy.
|
||||
Use valid YAML only, without Markdown fences or explanations.
|
||||
Pod module: %s
|
||||
Business description: %s
|
||||
""".formatted(req.getOrDefault("moduleName", "dat-was-cus"), description);
|
||||
String content = stripCodeFence(generateAiContent(prompt, req.get("model")));
|
||||
return ResponseEntity.ok(Map.of("toolServiceManifest", content));
|
||||
} catch (Exception e) {
|
||||
return ResponseEntity.internalServerError().body(Map.of("error", "AI Manifest 초안 생성 실패: " + safeMessage(e)));
|
||||
}
|
||||
}
|
||||
|
||||
@GetMapping("/browse-folder")
|
||||
public String browseFolder() {
|
||||
try {
|
||||
|
||||
@@ -839,6 +839,24 @@
|
||||
<input type="number" class="form-control" name="port" placeholder="e.g. 8086" required>
|
||||
<div class="input-hint">Unique port for Gateway routing and local development.</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Pod 업무 설명</label>
|
||||
<textarea class="form-control" name="podDescription" id="podDescription" rows="3" placeholder="예: 보험 계약과 보험금 지급을 처리하는 처리계 서버"></textarea>
|
||||
<div class="d-flex justify-content-between align-items-center mt-2">
|
||||
<div class="input-hint">설명을 입력하면 AI가 tool-service-manifest.yml 초안을 작성합니다.</div>
|
||||
<div class="d-flex gap-2 align-items-center">
|
||||
<select id="podAiModelSelect" class="form-select" aria-label="Pod Manifest AI 모델 선택" style="width:220px;">
|
||||
<optgroup label="[신한라이프 내부망]"><option value="Qwen3-Coder" selected>Qwen3-Coder</option><option value="Gemma-4-31B">Gemma-4-31B</option></optgroup>
|
||||
<optgroup label="[외부 OpenRouter 무료]"><option value="cohere/north-mini-code:free">Cohere North Mini Code</option><option value="inclusionai/ling-3.0-flash:free">InclusionAI Ling 3 Flash</option><option value="openai/gpt-oss-20b:free">OpenAI GPT-OSS 20B</option><option value="google/gemma-4-31b-it:free">Google Gemma 4 31B</option><option value="nvidia/nemotron-3-nano-30b-a3b:free">NVIDIA Nemotron 3 Nano</option></optgroup>
|
||||
</select>
|
||||
<button type="button" class="btn-secondary-action" onclick="createPodManifestDraft(this)">AI로 Manifest 채우기</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label">tool-service-manifest.yml</label>
|
||||
<textarea class="form-control" name="toolServiceManifest" id="toolServiceManifest" rows="12" placeholder="AI 초안 또는 직접 작성한 YAML"></textarea>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Author</label>
|
||||
@@ -926,8 +944,8 @@
|
||||
<div class="col-md-6 mt-3 mt-md-0">
|
||||
<label class="form-label">Protocol</label>
|
||||
<select class="form-select" name="routingType">
|
||||
<option value="HTTP">HTTP (REST)</option>
|
||||
<option value="MCI">MCI (Legacy)</option>
|
||||
<option value="HTTP">HTTP (REST)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1626,6 +1644,22 @@
|
||||
|
||||
handleFormSubmit('podForm', '/api/v1/scaffold/pod');
|
||||
|
||||
async function createPodManifestDraft(button) {
|
||||
const description = document.getElementById('podDescription').value.trim();
|
||||
if (!description) { alert('Pod 업무 설명을 입력해주세요.'); return; }
|
||||
if (button) { button.disabled = true; button.textContent = 'AI 생성 중...'; }
|
||||
try {
|
||||
const response = await fetch('/api/v1/scaffold/pod-draft', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({description, moduleName: document.querySelector('#podForm [name="moduleName"]').value, model: document.getElementById('podAiModelSelect').value})
|
||||
});
|
||||
const result = await response.json();
|
||||
if (!response.ok) throw new Error(result.error || 'AI Manifest 생성 실패');
|
||||
document.getElementById('toolServiceManifest').value = result.toolServiceManifest || '';
|
||||
} catch (error) { alert(error.message); }
|
||||
finally { if (button) { button.disabled = false; button.textContent = 'AI로 Manifest 채우기'; } }
|
||||
}
|
||||
|
||||
const groupedTools = [];
|
||||
|
||||
function toMethodName(baseName) {
|
||||
|
||||
@@ -50,11 +50,24 @@ public class PodScaffolder {
|
||||
envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||
}
|
||||
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||
return scaffoldPod(rootDir, moduleName, portStr, shortName, author, createDate);
|
||||
return scaffoldPod(rootDir, moduleName, portStr, shortName, author, createDate, null);
|
||||
}
|
||||
|
||||
public static String scaffoldPod(String moduleName, String portStr, String shortName, String author,
|
||||
String createDate, String toolServiceManifest) throws IOException {
|
||||
String envSourceDir = System.getProperty("AXHUB_SOURCE_DIR");
|
||||
if (envSourceDir == null || envSourceDir.isBlank()) envSourceDir = System.getenv("AXHUB_SOURCE_DIR");
|
||||
Path rootDir = envSourceDir != null ? Paths.get(envSourceDir) : Paths.get(".");
|
||||
return scaffoldPod(rootDir, moduleName, portStr, shortName, author, createDate, toolServiceManifest);
|
||||
}
|
||||
|
||||
static String scaffoldPod(Path rootDir, String moduleName, String portStr, String shortName,
|
||||
String author, String createDate) throws IOException {
|
||||
return scaffoldPod(rootDir, moduleName, portStr, shortName, author, createDate, null);
|
||||
}
|
||||
|
||||
static String scaffoldPod(Path rootDir, String moduleName, String portStr, String shortName,
|
||||
String author, String createDate, String toolServiceManifest) throws IOException {
|
||||
Path modulePath = rootDir.resolve(Paths.get(moduleName));
|
||||
if (Files.exists(modulePath)) {
|
||||
return "[오류] 이미 존재하는 모듈입니다: " + moduleName;
|
||||
@@ -135,6 +148,8 @@ public class PodScaffolder {
|
||||
spring:
|
||||
application:
|
||||
name: %s
|
||||
config:
|
||||
import: optional:classpath:tool-service-manifest.yml
|
||||
profiles:
|
||||
active: local
|
||||
logging:
|
||||
@@ -151,6 +166,9 @@ public class PodScaffolder {
|
||||
TESTER-DEV: ALL
|
||||
""".formatted(portStr, moduleName, moduleName.replace("dap-", ""));
|
||||
writeUtf8(resPath.resolve("application.yml"), applicationYml);
|
||||
String manifest = toolServiceManifest == null || toolServiceManifest.isBlank()
|
||||
? defaultToolServiceManifest(moduleName) : toolServiceManifest.trim() + System.lineSeparator();
|
||||
writeUtf8(resPath.resolve("tool-service-manifest.yml"), manifest);
|
||||
|
||||
String applicationLocalYml = """
|
||||
# Local 환경 전용 설정 (H2 메모리 DB 등)
|
||||
@@ -368,6 +386,28 @@ public class PodScaffolder {
|
||||
Files.writeString(path, content, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static String defaultToolServiceManifest(String moduleName) {
|
||||
String key = moduleName.replace("dat-was-", "");
|
||||
return """
|
||||
mcp:
|
||||
manifest:
|
||||
routing-functions:
|
||||
- name: route_to_%s
|
||||
description: %s 업무 서버로 요청을 라우팅합니다.
|
||||
server-id: %s
|
||||
category-key: %s
|
||||
product-boundary: insurance
|
||||
business-domain: %s 업무
|
||||
business-outcome: %s 관련 업무를 처리합니다.
|
||||
primary-entities: []
|
||||
capabilities: []
|
||||
select-if: %s 관련 요청인 경우
|
||||
reject-if: 다른 업무 영역이 주된 요청인 경우
|
||||
confidence-server-ids: [%s]
|
||||
decision-policy: 요청의 주요 업무 영역을 기준으로 서버를 선택합니다.
|
||||
""".formatted(moduleName.replace("-", "_"), key, moduleName, key, key, key, key, moduleName);
|
||||
}
|
||||
|
||||
private static String capitalize(String str) {
|
||||
if (str == null || str.isEmpty()) return str;
|
||||
return str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||
|
||||
Reference in New Issue
Block a user