FEAT(app): add prompting model

- Add prompting model configuration
- Enable custom prompt templates
This commit is contained in:
2025-12-24 20:00:21 +09:00
parent 536db91511
commit 4475931952
10 changed files with 571 additions and 140 deletions

View File

@@ -14,5 +14,5 @@ commonLabels:
# 이미지 태그 설정 (ArgoCD Image Updater가 자동으로 업데이트)
images:
- name: gitea0213.kro.kr/bluemayne/mas
newTag: main-sha-5b5b04c48822deaeeb63dba29dfbf57d701ec159
newTag: main-sha-ea3fe6fe34ba345cad8c778ddd0066547e99f0f0

View File

@@ -6,6 +6,7 @@ from .state import AgentState
from .orchestrator import orchestrator_node
from .planning_agent import planning_node
from .research_agent import research_node
from .decision_agent import decision_node
from .prompt_generator_agent import prompt_generator_node
__all__ = [
@@ -13,5 +14,6 @@ __all__ = [
'orchestrator_node',
'planning_node',
'research_node',
'decision_node',
'prompt_generator_node',
]

View File

@@ -0,0 +1,156 @@
"""
Decision Agent (Claude 4.5)
Planning과 Research 결과를 분석하여 최종 의사결정 (추천/비추천)
"""
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import SystemMessage, HumanMessage
from .state import AgentState
import os
import json
# Claude 4.5 모델 초기화
claude_decision = ChatAnthropic(
model="claude-sonnet-4-20250514",
api_key=os.getenv("ANTHROPIC_API_KEY"),
temperature=0.5
)
DECISION_SYSTEM = """You are the Decision Agent.
## Role
Analyze planning and research data to make final deployment decision (추천/비추천).
## Input
- Planning data: deployment requirements, resources needed
- Research data: current cluster state, existing tools
## Output Format (Korean Markdown)
Make a clear decision with reasoning:
```markdown
# [도구명] 도입 분석 결과
## 📊 현재 클러스터 상태
- **Kubernetes 버전**: [version]
- **노드 구성**: [nodes info]
- **기존 도구**: [existing tools]
- **리소스 상태**: [available resources]
## 💡 권장사항: [✅ 도입 추천 / ❌ 도입 비추천]
### 결정 이유
1. [이유 1]
2. [이유 2]
3. [이유 3]
### 🔄 대안 (비추천인 경우)
- [대안 1]: [설명]
- [대안 2]: [설명]
### 📌 고려사항 (추천인 경우)
- **필요 리소스**: [CPU, Memory]
- **예상 작업 시간**: [estimate]
- **복잡도**: [level]
## 🎯 결론
[1-2문장으로 최종 권장사항 요약]
```
## Guidelines
1. **한국어로 작성**
2. **명확한 결론** (✅ 추천 or ❌ 비추천)
3. **구체적인 이유** 제공
4. **사용자 친화적** (기술 용어 최소화)
5. 이모지 사용으로 가독성 향상
## Decision Output
Also output a JSON with decision:
{"recommendation": "approve" or "reject", "tool_name": "..."}
"""
def decision_node(state: AgentState) -> AgentState:
"""
Decision 노드: 최종 의사결정 (추천/비추천)
"""
messages = state["messages"]
task_plan = state.get("task_plan", {})
research_data = state.get("research_data", {})
# 입력 데이터 준비
plan_summary = json.dumps(task_plan, indent=2, ensure_ascii=False) if task_plan else "No plan available"
research_summary = json.dumps(research_data, indent=2, ensure_ascii=False) if research_data else "No research data"
# 사용자 원래 요청
user_request = messages[0]["content"] if messages else "Deploy infrastructure"
print(f"\n{'='*80}")
print(f"Decision Agent - Making final decision")
print(f"{'='*80}")
# Claude 호출
response = claude_decision.invoke([
SystemMessage(content=DECISION_SYSTEM),
HumanMessage(content=f"""분석 결과를 바탕으로 최종 의사결정을 내려주세요:
**사용자 요청:** {user_request}
**계획 데이터:**
```json
{plan_summary}
```
**클러스터 분석 결과:**
```json
{research_summary}
```
위 정보를 바탕으로:
1. 현재 클러스터 상태 요약
2. **도입 추천/비추천 명확히 결정**
3. 구체적인 이유 제시
4. 대안 또는 고려사항 제공
5. 최종 결론
**중요**: 한국어로 작성하고, 사용자 친화적으로 작성해주세요.
마지막에 JSON 형식으로 결정도 포함: {{"recommendation": "approve" or "reject", "tool_name": "..."}}
""")
])
content = response.content
# 추천/비추천 판단 (JSON 파싱 시도)
recommendation = "reject" # 기본값
try:
if '{"recommendation"' in content or "```json" in content:
import re
json_match = re.search(r'\{[^{}]*"recommendation"[^{}]*\}', content)
if json_match:
decision_json = json.loads(json_match.group(0))
recommendation = decision_json.get("recommendation", "reject")
except:
# 텍스트 기반 판단
if "✅ 도입 추천" in content or "추천" in content:
recommendation = "approve"
print(f"✅ Decision made: {recommendation}")
# 상태 업데이트
state["decision_report"] = {
"content": content,
"recommendation": recommendation
}
state["messages"].append({
"role": "decision",
"content": content
})
# 추천이면 prompt_generator로, 비추천이면 end
if recommendation == "approve":
state["current_agent"] = "orchestrator" # Orchestrator가 prompt_generator로 보냄
else:
state["current_agent"] = "end" # 비추천이면 바로 종료
return state

View File

@@ -20,32 +20,60 @@ claude_orchestrator = ChatAnthropic(
ORCHESTRATOR_PROMPT = """You are the Orchestrator of a K8s Analysis & Decision System.
## Role
Coordinate agents to analyze cluster and provide deployment recommendations.
Determine request type and route to appropriate agents.
## Request Types
### Type 1: Information Query (정보 조회)
Keywords: "알려줘", "조회", "확인", "보여줘", "찾아줘", "검색", "상태", "비밀번호", "목록", "리스트"
Examples:
- "PostgreSQL 비밀번호 알려줘"
- "현재 Pod 상태 확인해줘"
- "Secret 목록 보여줘"
Workflow: research → end
### Type 2: Deployment Decision (도입 결정)
Keywords: "도입", "설치", "배포", "필요", "결정", "추천", "분석", "사용"
Examples:
- "Tekton 도입할까?"
- "Harbor가 필요한지 분석해줘"
Workflow: planning → research → prompt_generator → end
## Available Agents
- planning: Understand what user wants to deploy and what info is needed
- research: Analyze K8s cluster state (kubectl commands)
- prompt_generator: Generate Korean recommendation report (추천/비추천 결정)
- planning: Plan deployment requirements (deployment_decision only)
- research: Analyze cluster state or retrieve information
- decision: Make final decision (추천/비추천) (deployment_decision only)
- prompt_generator: Generate implementation guide for other AI (deployment_decision, only if approved)
- end: Complete the task
## Workflow
1. User asks: "X를 도입하고 싶어" or "X 사용 여부를 결정해줘"
2. Planning → what would be needed for X
3. Research → analyze current cluster state
4. Prompt Generator → Korean recommendation (도입 추천/비추천)
5. End → show final decision to user
## Decision Logic
- No plan → NEXT_AGENT: planning
- Plan exists, no research → NEXT_AGENT: research
- Research done, no recommendation → NEXT_AGENT: prompt_generator
- Recommendation ready → NEXT_AGENT: end
**First, determine request_type (첫 호출 시만):**
- If user wants information → request_type = "information_query"
- If user wants deployment decision → request_type = "deployment_decision"
**Then route based on request_type:**
### For information_query:
- Current state: start → NEXT_AGENT: research
- Current state: research done → NEXT_AGENT: end
### For deployment_decision:
- Current state: start → NEXT_AGENT: planning
- Current state: planning done → NEXT_AGENT: research
- Current state: research done → NEXT_AGENT: decision
- Current state: decision done (추천) → NEXT_AGENT: prompt_generator
- Current state: decision done (비추천) → NEXT_AGENT: end
- Current state: prompt_generator done → NEXT_AGENT: end
Check state.get("task_plan"), state.get("research_data"), state.get("decision_report"), state.get("implementation_prompt") to determine current progress.
## Output Format
REQUEST_TYPE: <information_query|deployment_decision>
NEXT_AGENT: <agent_name>
REASON: <brief reason>
Keep workflow simple: planning → research → prompt_generator → end.
Analyze user intent carefully to choose the correct request type.
"""
@@ -114,6 +142,15 @@ def orchestrator_node(state: AgentState) -> AgentState:
if tool_outputs:
content = "\n".join(tool_outputs) + "\n\n" + content
# 요청 타입 파싱
request_type = state.get("request_type") # 기존 값 유지
if "REQUEST_TYPE:" in content and not request_type:
for line in content.split("\n"):
if line.startswith("REQUEST_TYPE:"):
request_type = line.split(":")[1].strip()
state["request_type"] = request_type
break
# 다음 에이전트 파싱
next_agent = "planning" # 기본값
if "NEXT_AGENT:" in content:
@@ -122,6 +159,29 @@ def orchestrator_node(state: AgentState) -> AgentState:
next_agent = line.split(":")[1].strip()
break
# request_type에 따른 라우팅 보정
if request_type == "information_query":
# 정보 조회: Planning 건너뛰기
if next_agent == "planning":
next_agent = "research"
elif request_type == "deployment_decision":
# 의사결정: 순서 보장 (planning → research → decision → prompt_generator(추천시만) → end)
task_plan = state.get("task_plan")
research_data = state.get("research_data")
decision_report = state.get("decision_report")
implementation_prompt = state.get("implementation_prompt")
if not task_plan:
next_agent = "planning"
elif not research_data:
next_agent = "research"
elif not decision_report:
next_agent = "decision"
elif decision_report and decision_report.get("recommendation") == "approve" and not implementation_prompt:
next_agent = "prompt_generator"
else:
next_agent = "end"
# 메시지 추가
state["messages"].append({
"role": "orchestrator",

View File

@@ -88,21 +88,65 @@ def planning_node(state: AgentState) -> AgentState:
json_str = content
task_plan = json.loads(json_str)
# 사용자 친화적인 한국어 요약 생성
summary_parts = []
target_tool = task_plan.get("target_tool", "알 수 없음")
summary_parts.append(f"📋 **{target_tool}** 요구사항 분석 완료\n")
# 필요 조건
requirements = task_plan.get("requirements", {})
if requirements:
summary_parts.append("**필요 조건**")
if requirements.get("min_k8s_version"):
summary_parts.append(f"- Kubernetes 버전: 최소 {requirements['min_k8s_version']} 이상")
resources = requirements.get("estimated_resources", {})
if resources:
cpu = resources.get("cpu", "")
memory = resources.get("memory", "")
storage = resources.get("storage", "")
resource_str = []
if cpu:
resource_str.append(f"CPU {cpu}코어")
if memory:
resource_str.append(f"메모리 {memory}")
if storage:
resource_str.append(f"스토리지 {storage}")
if resource_str:
summary_parts.append(f"- 예상 리소스: {', '.join(resource_str)}")
dependencies = requirements.get("dependencies", [])
if dependencies:
deps_str = ", ".join(dependencies)
summary_parts.append(f"- 의존성: {deps_str}")
# 확인이 필요한 사항
research_needed = task_plan.get("research_needed", [])
if research_needed:
summary_parts.append("\n**확인이 필요한 사항**")
for item in research_needed[:5]: # 최대 5개
# 영어를 한국어로 간단히 변환
item_ko = item.replace("Check", "확인:").replace("Verify", "검증:").replace("Analyze", "분석:")
summary_parts.append(f"- {item_ko}")
user_friendly_content = "\n".join(summary_parts)
except Exception as e:
task_plan = {
"task_type": "mixed",
"summary": "계획 파싱 실패, Research Agent로 이동",
"steps": [{"step": 1, "description": "정보 수집", "agent": "research"}],
"research_needed": ["사용자 요청 관련 정보"],
"success_criteria": ["작업 완료"],
"summary": "계획 파싱 실패",
"research_needed": ["클러스터 상태 확인"],
"error": str(e)
}
user_friendly_content = "📋 요구사항 분석 중...\n\n기본 정보를 확인하겠습니다."
# 상태 업데이트
state["task_plan"] = task_plan
state["messages"].append({
"role": "planning",
"content": content
"content": user_friendly_content
})
state["current_agent"] = "orchestrator" # 다시 orchestrator로 반환

View File

@@ -1,6 +1,6 @@
"""
Prompt Generator Agent (Claude 4.5)
다른 AI에게 전달할 구현 프롬프트를 Markdown으로 생성
Decision Agent의 추천 결과를 바탕으로 다른 AI에게 전달할 구현 프롬프트 생성
"""
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import SystemMessage, HumanMessage
@@ -13,86 +13,156 @@ import json
claude_prompt_gen = ChatAnthropic(
model="claude-sonnet-4-20250514",
api_key=os.getenv("ANTHROPIC_API_KEY"),
temperature=0.5
temperature=0.3
)
PROMPT_GENERATOR_SYSTEM = """You are the Decision & Recommendation Agent.
PROMPT_GEN_SYSTEM = """You are the Implementation Prompt Generator.
## Role
Analyze cluster state and provide user-friendly recommendations in Korean.
Generate detailed implementation prompts for other AI assistants (Claude Code, ChatGPT, etc.).
## Input
- Planning data: what would be needed if deploying
- Research data: current cluster state, existing resources
- Planning data: folder structure, required resources
- Research data: cluster state, existing tools
- Decision: deployment approved (추천)
## Output Format (Korean Markdown)
Create a user-friendly analysis report:
## Output Format (Markdown)
Create a comprehensive implementation guide that another AI can use:
```markdown
# [도구명] 도입 분석 결과
# [도구명] Kubernetes 배포 구현 가이드
## 📊 현재 클러스터 상태
- **Kubernetes 버전**: [version]
- **노드 구성**: [nodes info]
- **기존 도구**: [existing tools like ArgoCD, Gitea, etc.]
- **운영 중인 애플리케이션**: [number and types]
- **리소스 사용률**: [if available]
## 📋 프로젝트 개요
- **목표**: [도구] Kubernetes 클러스터에 배포
- **환경**: Kubernetes v[version], [nodes] 노드
- **사전 요구사항**: [prerequisites]
## 💡 권장사항: [도입 추천 / 도입 비추천]
### ✅ 도입을 추천하는 이유 (또는 ❌ 도입을 비추천하는 이유)
1. [이유 1]
2. [이유 2]
3. [이유 3]
### 🔄 대안 (도입 비추천인 경우)
- [대안 1]: [설명]
- [대안 2]: [설명]
### 📌 도입 시 고려사항 (도입 추천인 경우)
- **필요 리소스**: [CPU, Memory]
- **예상 작업 시간**: [time estimate]
- **복잡도**: [난이도]
- **유지보수 부담**: [maintenance effort]
## 🎯 결론
[1-2문장으로 최종 권장사항 요약]
---
## 📁 구현 가이드 (도입하기로 결정한 경우)
### 폴더 구조
## 📁 폴더 구조
다음과 같은 디렉토리 구조를 생성하세요:
\`\`\`
deploy/[tool]/
├── base/
│ ├── namespace.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── rbac.yaml
│ └── kustomization.yaml
└── overlays/prod/
└── kustomization.yaml
└── overlays/
└── prod/
├── resource-limits.yaml
└── kustomization.yaml
\`\`\`
### 주요 단계
1. [Step 1 설명]
2. [Step 2 설명]
3. [Step 3 설명]
## 🔧 구현 단계
### 검증 방법
\`\`\`bash
### Step 1: Namespace 및 RBAC 생성
**파일**: `deploy/[tool]/base/namespace.yaml`
```yaml
apiVersion: v1
kind: Namespace
metadata:
name: [namespace]
labels:
app: [tool]
```
**파일**: `deploy/[tool]/base/rbac.yaml`
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: [tool]-sa
namespace: [namespace]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: [tool]-role
rules:
- apiGroups: [""]
resources: ["pods", "services"]
verbs: ["get", "list", "watch"]
```
### Step 2: Deployment 생성
**파일**: `deploy/[tool]/base/deployment.yaml`
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: [tool]
namespace: [namespace]
spec:
replicas: [replicas]
selector:
matchLabels:
app: [tool]
template:
metadata:
labels:
app: [tool]
spec:
serviceAccountName: [tool]-sa
containers:
- name: [tool]
image: [image]:[tag]
ports:
- containerPort: [port]
resources:
requests:
memory: "[memory]"
cpu: "[cpu]"
limits:
memory: "[memory_limit]"
cpu: "[cpu_limit]"
```
### Step 3: Service 및 Ingress 생성
[... 계속 작성]
### Step 4: Kustomize 설정
[... 계속 작성]
## ✅ 검증 및 배포
### 배포 명령어
```bash
# Dry-run으로 검증
kubectl apply -k deploy/[tool]/overlays/prod --dry-run=client
# 실제 배포
kubectl apply -k deploy/[tool]/overlays/prod
# 상태 확인
kubectl get pods -n [namespace]
kubectl get svc -n [namespace]
\`\`\`
kubectl logs -f deployment/[tool] -n [namespace]
```
### 검증 체크리스트
- [ ] Pod가 Running 상태인지 확인
- [ ] Service가 올바른 포트로 노출되는지 확인
- [ ] Ingress가 정상 작동하는지 확인
- [ ] 로그에 에러가 없는지 확인
## 📌 주요 고려사항
- **리소스 제한**: [cluster 상황에 맞춘 권장사항]
- **보안**: [RBAC, NetworkPolicy 등]
- **모니터링**: [ServiceMonitor, 로그 수집 등]
- **백업**: [설정 백업 방법]
## 🔗 참고 자료
- [도구] 공식 문서: [URL]
- Kubernetes 배포 가이드: [URL]
```
## Guidelines
1. **한국어로 작성** (모든 내용)
2. **명확한 결론** 제시 (추천/비추천)
3. **구체적인 이유** 제공
4. **YAML 코드 제외** (폴더 구조만 간단히)
5. **사용자 친화적** (기술 용어 최소화)
6. 이모지 사용으로 가독성 향상
1. **실행 가능한 YAML 예시** 포함
2. **단계별 구현 가이드** 제공
3. **검증 명령어** 포함
4. **클러스터 상황에 맞춘 권장사항** (Research 데이터 활용)
5. 다른 AI가 바로 실행할 수 있도록 구체적으로 작성
"""
@@ -103,48 +173,52 @@ def prompt_generator_node(state: AgentState) -> AgentState:
messages = state["messages"]
task_plan = state.get("task_plan", {})
research_data = state.get("research_data", {})
decision_report = state.get("decision_report", {})
# 입력 데이터 준비
plan_summary = json.dumps(task_plan, indent=2, ensure_ascii=False) if task_plan else "No plan available"
research_summary = json.dumps(research_data, indent=2, ensure_ascii=False) if research_data else "No research data"
plan_summary = json.dumps(task_plan, indent=2, ensure_ascii=False) if task_plan else "No plan"
research_summary = json.dumps(research_data, indent=2, ensure_ascii=False) if research_data else "No research"
# 사용자 원래 요청
user_request = messages[0]["content"] if messages else "Deploy infrastructure"
tool_name = task_plan.get("target_tool", "Unknown") if task_plan else "Unknown"
print(f"\n{'='*80}")
print(f"Prompt Generator Agent - Generating implementation prompt")
print(f"Prompt Generator - Creating implementation guide")
print(f"{'='*80}")
# Claude 호출
response = claude_prompt_gen.invoke([
SystemMessage(content=PROMPT_GENERATOR_SYSTEM),
HumanMessage(content=f"""사용자 요청에 대한 분석 결과를 한국어로 작성해주세요:
SystemMessage(content=PROMPT_GEN_SYSTEM),
HumanMessage(content=f"""다른 AI에게 전달할 구현 가이드를 생성해주세요:
**사용자 요청:** {user_request}
**배포 대상:** {tool_name}
**계획 데이터:**
```json
{plan_summary}
```
**클러스터 분석 결과:**
**클러스터 상태:**
```json
{research_summary}
```
위 정보를 바탕으로:
1. 현재 클러스터 상태 요약
2. 도입 추천/비추천 결정 (명확한 이유와 함께)
3. 대안 제시 (비추천인 경우) 또는 구현 가이드 (추천인 경우)
4. 최종 결론
1. 실행 가능한 YAML 파일 예시 작성
2. 단계별 구현 가이드 제공
3. 클러스터 상황에 맞춘 리소스 설정 권장
4. 배포 및 검증 명령어 포함
5. 다른 AI가 바로 실행할 수 있도록 구체적으로 작성
**중요**: 한국어로 작성하고, YAML 코드는 제외하고, 사용자 친화적으로 작성해주세요.
**중요**: Markdown 형식으로 작성하고, 실제로 동작하는 YAML 코드를 포함해주세요.
""")
])
content = response.content
print(f"Prompt generated ({len(content)} characters)")
print(f"Implementation guide generated ({len(content)} characters)")
# 상태 업데이트
state["implementation_prompt"] = content

View File

@@ -20,37 +20,51 @@ groq_research = ChatOpenAI(
)
RESEARCH_PROMPT = """Research Agent: Analyze Kubernetes cluster state.
RESEARCH_PROMPT = """Research Agent: Analyze cluster or retrieve information.
Request commands in JSON:
## Two Modes
### Mode 1: Information Query (정보 조회)
User wants specific information (password, status, list, etc.)
- Execute the requested kubectl command
- Return the result directly
- No analysis needed
### Mode 2: Deployment Analysis (배포 분석)
User wants deployment decision
- Analyze cluster state comprehensively
- Collect version, tools, resources
- Provide structured findings
## Request commands in JSON:
{"commands": [{"tool": "execute_host", "command": "kubectl get nodes", "use_sudo": true}]}
Rules:
- Request 1-2 commands at a time
- Use execute_host for kubectl commands (with use_sudo: true)
- Focus on: version, existing tools, resources, nodes
- Output ONLY JSON when requesting commands
Final report format (Korean):
## Final report format
### For Information Query:
{
"summary": "정보 조회 완료",
"result": "actual command result",
"findings": [{"category": "조회 결과", "data": "..."}]
}
### For Deployment Analysis:
{
"summary": "클러스터 상태 요약",
"cluster_info": {
"k8s_version": "v1.x.x",
"nodes": "3 nodes (1 control-plane, 2 workers)",
"existing_tools": ["ArgoCD", "Gitea", "Prometheus"]
"nodes": "3 nodes",
"existing_tools": ["ArgoCD", "Gitea"]
},
"findings": [
{"category": "기존 CI/CD", "data": "ArgoCD 운영 중"},
{"category": "리소스", "data": "충분한 여유 있음"}
],
"recommendation": {
"deploy": true/false,
"reasons": ["이유1", "이유2"],
"alternatives": ["대안1", "대안2"]
}
"findings": [{"category": "...", "data": "..."}]
}
Keep findings concise and actionable. Focus on decision-making data.
Choose the appropriate format based on the user's request.
"""
@@ -59,19 +73,26 @@ def research_node(state: AgentState) -> AgentState:
Research 노드: 정보 수집 (JSON 기반 명령어 방식)
"""
messages = state["messages"]
request_type = state.get("request_type", "deployment_decision")
task_plan = state.get("task_plan") or {}
research_needed = task_plan.get("research_needed", []) if isinstance(task_plan, dict) else []
# 사용자 원래 요청 찾기
user_message = None
for msg in reversed(messages):
if msg.get("role") == "user":
user_message = msg.get("content", "")
break
# 연구 요청 구성
if research_needed:
if request_type == "information_query":
# 정보 조회 모드: 사용자 요청을 그대로 전달
research_request = f"사용자가 다음 정보를 요청했습니다:\n\n{user_message}\n\n해당 정보를 kubectl 명령어로 조회하여 결과를 반환해주세요."
elif research_needed:
# 배포 결정 모드: Planning의 지시 따름
research_request = f"다음 정보를 수집해주세요:\n" + "\n".join(f"- {item}" for item in research_needed)
else:
# 사용자의 원래 요청을 찾
user_message = None
for msg in reversed(messages):
if msg.get("role") == "user":
user_message = msg.get("content", "")
break
# 기본 모드
if user_message:
research_request = f"사용자 요청: {user_message}\n\n위 요청에 필요한 정보를 수집하고 분석해주세요."
else:
@@ -159,15 +180,66 @@ def research_node(state: AgentState) -> AgentState:
# 최종 리포트인 경우
elif "summary" in commands_data and "findings" in commands_data:
print("\n✅ 최종 리포트 수신")
# 최종 리포트를 content에 포함
final_content = "\n".join(tool_outputs) + "\n\n## 최종 분석 결과\n\n" + json.dumps(commands_data, indent=2, ensure_ascii=False)
# 요청 유형에 따라 다른 포맷
if request_type == "information_query":
# 정보 조회: 결과만 간단히 표시
result = commands_data.get("result", "")
findings = commands_data.get("findings", [])
summary_parts = ["✅ 조회 완료\n"]
# 조회 결과
if result:
summary_parts.append(f"**결과:**\n```\n{result}\n```")
elif findings:
for finding in findings[:3]:
data = finding.get("data", "")
if data:
summary_parts.append(f"{data}")
final_content = "\n".join(summary_parts)
# 정보 조회는 바로 종료
state["current_agent"] = "end"
else:
# 배포 분석: 상세 정보 표시
cluster_info = commands_data.get("cluster_info", {})
findings = commands_data.get("findings", [])
summary_parts = ["✅ 분석 완료\n"]
# 클러스터 정보
if cluster_info:
summary_parts.append("**클러스터 정보**")
if cluster_info.get("k8s_version"):
summary_parts.append(f"- Kubernetes: {cluster_info['k8s_version']}")
if cluster_info.get("nodes"):
summary_parts.append(f"- 노드: {cluster_info['nodes']}")
if cluster_info.get("existing_tools"):
tools = ", ".join(cluster_info['existing_tools'])
summary_parts.append(f"- 기존 도구: {tools}")
# 주요 발견사항
if findings:
summary_parts.append("\n**주요 발견사항**")
for finding in findings[:5]: # 최대 5개만
category = finding.get("category", "")
data = finding.get("data", "")
if category and data:
summary_parts.append(f"- {category}: {data}")
final_content = "\n".join(summary_parts)
# 배포 분석은 orchestrator로 돌아감
state["current_agent"] = "orchestrator"
state["research_data"] = commands_data
state["messages"].append({
"role": "research",
"content": final_content
})
state["current_agent"] = "orchestrator"
return state
except json.JSONDecodeError as e:
@@ -176,12 +248,13 @@ def research_node(state: AgentState) -> AgentState:
# 명령어도 없고 최종 리포트도 아니면 종료
if not commands_executed:
print("\n✅ 명령어 요청 없음, 종료")
# 텍스트 응답을 그대로 사용
content = "\n".join(tool_outputs) + "\n\n" + response_text
# 간단한 요약만 표시
content = "✅ 분석 완료\n\n기본 정보가 수집되었습니다."
state["research_data"] = {
"summary": "정보 수집 완료",
"findings": [{"category": "raw", "data": response_text}],
"findings": [{"category": "기본", "data": "클러스터 정보 수집 완료"}],
"recommendations": []
}
state["messages"].append({
@@ -193,11 +266,12 @@ def research_node(state: AgentState) -> AgentState:
# 최대 반복 도달
print(f"\n⚠️ 최대 반복 횟수 도달 ({max_iterations})")
content = "\n".join(tool_outputs) + "\n\n정보 수집을 완료했습니다."
content = "✅ 분석 완료\n\n기본 클러스터 정보가 수집되었습니다."
state["research_data"] = {
"summary": "정보 수집 완료 (최대 반복 도달)",
"findings": [{"category": "raw", "data": content}],
"summary": "정보 수집 완료",
"findings": [{"category": "클러스터", "data": "기본 정보 수집 완료"}],
"recommendations": []
}
state["messages"].append({
@@ -205,5 +279,5 @@ def research_node(state: AgentState) -> AgentState:
"content": content
})
state["current_agent"] = "orchestrator"
return state

View File

@@ -9,8 +9,10 @@ class AgentState(TypedDict):
"""에이전트 간 공유되는 상태"""
messages: list # 대화 메시지 이력
current_agent: str # 현재 활성 에이전트
request_type: Optional[str] # 요청 유형: "information_query" or "deployment_decision"
task_plan: Optional[dict] # Planning Agent 출력 (폴더 구조, YAML 설계)
research_data: Optional[dict] # Research Agent 출력 (K8s 클러스터 상태)
implementation_prompt: Optional[str] # Prompt Generator 출력 (Markdown 프롬프트)
decision_report: Optional[dict] # Decision Agent 출력 (추천/비추천 결정)
implementation_prompt: Optional[str] # Prompt Generator 출력 (구현 가이드)
iteration_count: int # 반복 횟수 (최대 2회)
error: Optional[str] # 에러 메시지

View File

@@ -56,8 +56,10 @@ async def main(message: cl.Message):
initial_state: AgentState = {
"messages": [{"role": "user", "content": message.content}],
"current_agent": "orchestrator",
"request_type": None, # Orchestrator가 결정
"task_plan": None,
"research_data": None,
"decision_report": None,
"implementation_prompt": None,
"iteration_count": 0,
"error": None
@@ -80,20 +82,22 @@ async def main(message: cl.Message):
agent_content = last_message["content"]
# 사용자에게 보여줄 에이전트만 필터링
user_facing_agents = ["planning", "research", "prompt_generator"]
user_facing_agents = ["planning", "research", "decision", "prompt_generator"]
if agent_name in user_facing_agents:
# 에이전트별 아이콘
agent_icons = {
"planning": "📋",
"research": "🔍",
"decision": "💡",
"prompt_generator": "📝"
}
agent_display_names = {
"planning": "도구 요구사항 분석",
"research": "클러스터 상태 분석",
"prompt_generator": "의사결정 보고서 생성"
"decision": "의사결정 분석",
"prompt_generator": "구현 가이드 생성"
}
icon = agent_icons.get(agent_name, "🤖")
@@ -118,7 +122,8 @@ async def main(message: cl.Message):
status_icons = {
"planning": "📋 도구 요구사항 분석 중...",
"research": "🔍 클러스터 상태 분석 중...",
"prompt_generator": "💡 의사결정 보고서 생성 중...",
"decision": "💡 의사결정 분석 중...",
"prompt_generator": "📝 구현 가이드 생성 중...",
"end": "✨ 분석 완료!"
}
status_text = status_icons.get(current_agent, "⏳ 작업 중...")
@@ -153,7 +158,8 @@ def rename(orig_author: str):
"orchestrator": "조율자 (Claude 4.5)",
"planning": "요구사항 분석 (Claude 4.5)",
"research": "클러스터 분석 (Groq)",
"prompt_generator": "의사결정 (Claude 4.5)"
"decision": "의사결정 (Claude 4.5)",
"prompt_generator": "구현 가이드 (Claude 4.5)"
}
return rename_dict.get(orig_author, orig_author)

View File

@@ -1,6 +1,6 @@
"""
LangGraph K8s Infrastructure Planning Workflow
워크플로우: Planning → Research → Prompt Generation → End
워크플로우: Planning → Research → Decision → Prompt Generation → End
"""
from typing import Literal
from langgraph.graph import StateGraph, END
@@ -9,6 +9,7 @@ from agents import (
orchestrator_node,
planning_node,
research_node,
decision_node,
prompt_generator_node
)
@@ -16,41 +17,50 @@ from agents import (
def router(state: AgentState) -> Literal[
"planning",
"research",
"decision",
"prompt_generator",
"end"
]:
"""
다음 에이전트 라우팅 로직
K8s 인프라 계획: planning → research → prompt_generator → end
정보 조회: research → end
도입 결정: planning → research → decision → prompt_generator (추천시) → end
"""
current = state.get("current_agent", "orchestrator")
# 명시적으로 지정된 다음 에이전트로 이동
if current in ["planning", "research", "prompt_generator"]:
if current in ["planning", "research", "decision", "prompt_generator"]:
return current
# end 상태
if current == "end":
return "end"
# 기본값: planning부터 시작
# 기본값: orchestrator가 결정
return "planning"
def create_mas_workflow():
"""
K8s Infrastructure Planning Workflow 생성
K8s Infrastructure Analysis & Decision Workflow 생성
워크플로우:
User Request (e.g., "Deploy Tekton")
워크플로우 1 (정보 조회):
User Request (e.g., "PostgreSQL 비밀번호 알려줘")
Orchestrator → Research → Orchestrator → End
워크플로우 2 (도입 결정):
User Request (e.g., "Tekton 도입할까?")
Orchestrator → Planning → Orchestrator
Research (K8s cluster analysis) → Orchestrator
Prompt Generator (Markdown implementation guide) → Orchestrator
Decision (추천/비추천) → Orchestrator
End (User copies prompt to another AI)
Prompt Generator (추천시만, 구현 가이드) → Orchestrator
End
"""
workflow = StateGraph(AgentState)
@@ -58,6 +68,7 @@ def create_mas_workflow():
workflow.add_node("orchestrator", orchestrator_node)
workflow.add_node("planning", planning_node)
workflow.add_node("research", research_node)
workflow.add_node("decision", decision_node)
workflow.add_node("prompt_generator", prompt_generator_node)
# 시작점: Orchestrator
@@ -70,6 +81,7 @@ def create_mas_workflow():
{
"planning": "planning",
"research": "research",
"decision": "decision",
"prompt_generator": "prompt_generator",
"end": END
}
@@ -78,6 +90,7 @@ def create_mas_workflow():
# 각 에이전트는 작업 후 Orchestrator로 복귀
workflow.add_edge("planning", "orchestrator")
workflow.add_edge("research", "orchestrator")
workflow.add_edge("decision", "orchestrator")
workflow.add_edge("prompt_generator", "orchestrator")
return workflow.compile()