REFACTOR(repo): simplify project structure
Some checks failed
Build Docker Image / build-and-push (push) Has been cancelled

- Move services/backend to langgraph/
- Move deploy/docker/Dockerfile to Dockerfile
- Remove deploy/, services/ folders
- Update GitHub Actions workflow paths
- Remove kustomization update logic (managed by K3S-HOME/applications)
This commit is contained in:
2026-01-05 16:45:33 +09:00
parent 49083910c6
commit 013140f02c
30 changed files with 4 additions and 1094 deletions

165
langgraph/chainlit_app.py Normal file
View File

@@ -0,0 +1,165 @@
"""
Chainlit UI for MAS Platform
"""
import chainlit as cl
from workflow import mas_graph
from agents import AgentState
import os
from dotenv import load_dotenv
import contextvars
load_dotenv()
# Chainlit의 local_steps ContextVar 초기화
try:
from chainlit.step import local_steps
local_steps.set([])
except:
pass
@cl.on_chat_start
async def start():
"""채팅 시작 시"""
await cl.Message(
content="☸️ **K8s 인프라 분석 & 의사결정 시스템**에 오신 것을 환영합니다!\n\n"
"클러스터를 분석하고, 도구 도입 여부를 결정해드립니다.\n\n"
"**어떻게 작동하나요?**\n"
"1. 📋 도구 분석 → 2. 🔍 클러스터 상태 확인 → 3. 💡 추천/비추천 결정\n\n"
"**사용 예시**\n"
"```\n"
"Tekton 도입 여부를 결정해줘\n"
"Harbor가 필요한지 분석해줘\n"
"Prometheus를 설치해야 할까?\n"
"```\n\n"
"**결과물**\n"
"✅ 도입 추천 또는 ❌ 도입 비추천 (이유 포함)\n"
"🔄 대안 제시\n"
"📌 구현 가이드 (도입하는 경우)\n\n"
"궁금한 도구를 알려주세요!"
).send()
@cl.on_message
async def main(message: cl.Message):
"""메시지 수신 시"""
# local_steps ContextVar 초기화
try:
from chainlit.step import local_steps
local_steps.set([])
except:
pass
try:
# 초기 상태
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
}
# 응답 메시지 생성
response_msg = cl.Message(content="")
await response_msg.send()
# 상태 표시용 메시지
status_msg = cl.Message(content="⏳ 작업 중...")
await status_msg.send()
# MAS 그래프 실행
async for event in mas_graph.astream(initial_state):
for node_name, state in event.items():
if node_name != "__end__":
last_message = state["messages"][-1]
agent_name = last_message["role"]
agent_content = last_message["content"]
# 사용자에게 보여줄 에이전트만 필터링
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": "클러스터 상태 분석",
"decision": "의사결정 분석",
"prompt_generator": "구현 가이드 생성"
}
icon = agent_icons.get(agent_name, "🤖")
display_name = agent_display_names.get(agent_name, agent_name)
# 내부 라우팅 정보 제거 (NEXT_AGENT, REASON 등)
cleaned_content = agent_content
for keyword in ["NEXT_AGENT:", "REASON:", "MESSAGE:"]:
if keyword in cleaned_content:
# 라우팅 정보가 포함된 경우 해당 부분 제거
lines = cleaned_content.split("\n")
cleaned_lines = [line for line in lines if not line.strip().startswith(keyword.replace(":", ""))]
cleaned_content = "\n".join(cleaned_lines)
# 스트리밍 업데이트
response_msg.content += f"\n\n{icon} **{display_name}**:\n{cleaned_content.strip()}"
await response_msg.update()
elif agent_name == "orchestrator":
# Orchestrator는 간단한 상태 메시지만 표시
current_agent = state.get("current_agent", "")
status_icons = {
"planning": "📋 도구 요구사항 분석 중...",
"research": "🔍 클러스터 상태 분석 중...",
"decision": "💡 의사결정 분석 중...",
"prompt_generator": "📝 구현 가이드 생성 중...",
"end": "✨ 분석 완료!"
}
status_text = status_icons.get(current_agent, "⏳ 작업 중...")
status_msg.content = status_text
await status_msg.update()
# 상태 메시지 제거
await status_msg.remove()
# 최종 업데이트
await response_msg.update()
except Exception as e:
error_msg = f"❌ 오류가 발생했습니다: {str(e)}"
await cl.Message(content=error_msg).send()
print(f"Error in main: {e}")
import traceback
traceback.print_exc()
@cl.on_settings_update
async def setup_agent(settings):
"""설정 업데이트"""
print(f"Settings updated: {settings}")
# 사이드바 설정
@cl.author_rename
def rename(orig_author: str):
"""에이전트 이름 매핑"""
rename_dict = {
"orchestrator": "조율자 (Claude 4.5)",
"planning": "요구사항 분석 (Claude 4.5)",
"research": "클러스터 분석 (Groq)",
"decision": "의사결정 (Claude 4.5)",
"prompt_generator": "구현 가이드 (Claude 4.5)"
}
return rename_dict.get(orig_author, orig_author)