FEAT(app): add prompting model
- Add prompting model configuration - Enable custom prompt templates
This commit is contained in:
@@ -14,5 +14,5 @@ commonLabels:
|
||||
# 이미지 태그 설정 (ArgoCD Image Updater가 자동으로 업데이트)
|
||||
images:
|
||||
- name: gitea0213.kro.kr/bluemayne/mas
|
||||
newTag: main-sha-5b5b04c48822deaeeb63dba29dfbf57d701ec159
|
||||
newTag: main-sha-ea3fe6fe34ba345cad8c778ddd0066547e99f0f0
|
||||
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
|
||||
156
services/backend/agents/decision_agent.py
Normal file
156
services/backend/agents/decision_agent.py
Normal 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
|
||||
@@ -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",
|
||||
|
||||
@@ -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로 반환
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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] # 에러 메시지
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user