ใ……ใ…Žใ…‡
Add CodeWeaver Gradio app
515f392
import asyncio
import logging
import os
import sys
import uuid
from pathlib import Path
import gradio as gr
from dotenv import load_dotenv
# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋กœ๋“œ (์—์ด์ „ํŠธ/ํŠธ๋ ˆ์ด์‹ฑ import ์ด์ „์— ์‹คํ–‰)
load_dotenv()
# ํ”„๋กœ์ ํŠธ ๋ฃจํŠธ๋ฅผ ๊ฒฝ๋กœ์— ์ถ”๊ฐ€
sys.path.insert(0, str(Path(__file__).parent.parent))
from src.agent.graph import agent
from src.agent.state import AgentState
# ๋กœ๊น… ์„ค์ • (WARNING ์ด์ƒ๋งŒ ์ถœ๋ ฅ)
logging.basicConfig(
level=logging.WARNING,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# ์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋กœ๊ทธ๋Š” WARNING๋งŒ
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
logging.getLogger("langsmith").setLevel(logging.WARNING)
# CodeWeaver ๋ชจ๋“ˆ ๋กœ๊ทธ๋„ WARNING๋งŒ (๋กœ๊ทธ ๋น„ํ™œ์„ฑํ™”)
logging.getLogger("src.agent").setLevel(logging.WARNING)
logging.getLogger("src.tools").setLevel(logging.WARNING)
logging.getLogger("src.vector_db").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
async def chat(
message: str,
history: list,
thread_id: str,
) -> str:
"""
์‚ฌ์šฉ์ž ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์—์ด์ „ํŠธ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
Args:
message: ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ฉ”์‹œ์ง€
history: ๋Œ€ํ™” ๋‚ด์—ญ (Gradio ์ž๋™ ๊ด€๋ฆฌ)
thread_id: ์„ธ์…˜๋ณ„ ๊ณ ์œ  ID (MemorySaver๊ฐ€ ๋Œ€ํ™” ๋งฅ๋ฝ ์ถ”์ ์— ์‚ฌ์šฉ)
Returns:
์—์ด์ „ํŠธ์˜ ์ตœ์ข… ๋‹ต๋ณ€
"""
if not message or not message.strip():
return "์งˆ๋ฌธ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
try:
# ์ดˆ๊ธฐ ์ƒํƒœ ์ƒ์„ฑ (Pydantic BaseModel ์‚ฌ์šฉ)
from langchain_core.messages import HumanMessage
initial_state = AgentState(
user_question=message.strip(),
messages=[HumanMessage(content=message.strip())],
conversation_history=history[-5:] if history else None, # ์ตœ๊ทผ 5ํ„ด๋งŒ ์ „๋‹ฌ
)
# ์„ธ์…˜๋ณ„ thread_id๋ฅผ config์— ์ „๋‹ฌ (MemorySaver๊ฐ€ ๋Œ€ํ™” ๋งฅ๋ฝ ์œ ์ง€)
config = {"configurable": {"thread_id": thread_id}}
# ์—์ด์ „ํŠธ ์‹คํ–‰
result = await agent.ainvoke(initial_state, config=config)
# ์ตœ์ข… ๋‹ต๋ณ€ ์ถ”์ถœ
final_answer = result.get("final_answer", "๋‹ต๋ณ€์„ ์ƒ์„ฑํ•˜์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.")
return final_answer
except Exception as e:
logger.error("์—๋Ÿฌ ๋ฐœ์ƒ: %s", e, exc_info=True)
return f"โš ๏ธ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: {str(e)}\n๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”."
def create_demo() -> gr.Blocks:
"""Gradio ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค."""
# CSS ์Šคํƒ€์ผ (๊น”๋”ํ•œ ๋””์ž์ธ)
# - Gradio ๊ธฐ๋ณธ CSS๊ฐ€ .contain/.gradio-container ํญ์„ ๋ฎ์–ด์“ฐ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด
# ๋‘˜ ๋‹ค !important๋กœ ๊ณ ์ •ํ•˜์—ฌ "์ฒ˜์Œ๋ถ€ํ„ฐ ๋„“์€ ํญ"์„ ํ™•์‹คํžˆ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค.
css = """
.gradio-container {
max-width: 1280px !important;
width: min(1280px, 100%) !important;
margin: 0 auto !important;
}
.contain {
max-width: 1280px !important;
width: min(1280px, 100%) !important;
margin: 0 auto !important;
padding-top: 1.5rem;
}
.message { font-size: 1.1rem; line-height: 1.6; }
"""
with gr.Blocks(
title="CodeWeaver - AI ๊ฐœ๋ฐœ ๋„์šฐ๋ฏธ",
theme=gr.themes.Soft(),
css=css
) as demo:
gr.Markdown("""
# ๐Ÿค– CodeWeaver
### AI ๊ธฐ๋ฐ˜ ๊ฐœ๋ฐœ ์งˆ๋ฌธ ๋‹ต๋ณ€ ์‹œ์Šคํ…œ
์ดˆ๋ณด ๊ฐœ๋ฐœ์ž๋ฅผ ์œ„ํ•œ ์นœ์ ˆํ•œ AI ๋„์šฐ๋ฏธ์ž…๋‹ˆ๋‹ค.
**์ฃผ์š” ๊ธฐ๋Šฅ:**
- โœ… ์—๋Ÿฌ ํ•ด๊ฒฐ (๋””๋ฒ„๊น…)
- โœ… ๊ฐœ๋… ํ•™์Šต
- โœ… ์ฝ”๋“œ ๋ฆฌ๋ทฐ ๋ฐ ๊ฐœ์„  ์ œ์•ˆ
- โœ… **๋‹ค์ค‘ ์งˆ๋ฌธ ์ฒ˜๋ฆฌ** (์ตœ๋Œ€ 2๊ฐœ๊นŒ์ง€ ๋™์‹œ ์ฒ˜๋ฆฌ)
- โœ… **๋Œ€ํ™” ๋งฅ๋ฝ ์ดํ•ด** (์ด์ „ ๋Œ€ํ™”๋ฅผ ์ฐธ๊ณ ํ•œ ํ›„์† ์งˆ๋ฌธ ๋‹ต๋ณ€)
- โœ… **์Šค๋งˆํŠธ ์บ์‹ฑ** (์œ ์‚ฌ ์งˆ๋ฌธ ์ฆ‰์‹œ ๋‹ต๋ณ€)
- โœ… **์ž๋™ ๊ฒ€์ƒ‰ ๊ฐœ์„ ** (๊ฒฐ๊ณผ ๋ถ€์กฑ ์‹œ ์ฟผ๋ฆฌ ์ž๋™ ์ตœ์ ํ™”)
๐Ÿ’ฌ ๊ฐœ๋ฐœ ๊ด€๋ จ ์งˆ๋ฌธ์„ ์ž์œ ๋กญ๊ฒŒ ํ•ด๋ณด์„ธ์š”!
- ๋‹จ์ผ ์งˆ๋ฌธ: "Spring Boot JPA N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€?"
- ๋‹ค์ค‘ ์งˆ๋ฌธ: "JWT๊ฐ€ ๋ญ์•ผ? CORS๋Š”?" (์ตœ๋Œ€ 2๊ฐœ)
- ํ›„์† ์งˆ๋ฌธ: "์ข€ ๋” ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด์ค˜" (์ด์ „ ๋‹ต๋ณ€ ์ฐธ๊ณ )
""")
# ์„ธ์…˜๋ณ„ ๊ณ ์œ  ID (๋ธŒ๋ผ์šฐ์ € ์„ธ์…˜๋งˆ๋‹ค ๋…๋ฆฝ์ ์œผ๋กœ ์ƒ์„ฑ)
session_id = gr.State(value=lambda: str(uuid.uuid4()))
# ์ฑ„ํŒ… ์ธํ„ฐํŽ˜์ด์Šค
chatbot_interface = gr.ChatInterface(
fn=chat,
examples=None, # examples๋Š” ์•„๋ž˜ Accordion์—์„œ ์ˆ˜๋™ ์ฒ˜๋ฆฌ
chatbot=gr.Chatbot(height=500),
textbox=gr.Textbox(
placeholder="์งˆ๋ฌธ์„ ์ž…๋ ฅํ•˜์„ธ์š”...",
container=False,
scale=7
),
retry_btn=None,
undo_btn=None,
clear_btn="๐Ÿ—‘๏ธ ๋Œ€ํ™” ์ดˆ๊ธฐํ™”",
additional_inputs=[session_id], # thread_id ์ „๋‹ฌ
)
# Clear ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ƒˆ ์„ธ์…˜ ID ์ƒ์„ฑ (์ƒˆ ๋Œ€ํ™” ์‹œ์ž‘)
def reset_session():
new_id = str(uuid.uuid4())
return new_id
# Clear ๋ฒ„ํŠผ์— ์„ธ์…˜ ๋ฆฌ์…‹ ํ•ธ๋“ค๋Ÿฌ ์ถ”๊ฐ€
if chatbot_interface.clear_btn:
chatbot_interface.clear_btn.click(
reset_session,
None,
session_id,
queue=False
)
# ๋น ๋ฅธ ์งˆ๋ฌธ ๋ฒ„ํŠผ๋“ค (Accordion ๋ฐ–์œผ๋กœ ๋ถ„๋ฆฌ)
gr.Markdown("### ๐Ÿ’ฌ ์˜ˆ์‹œ ์งˆ๋ฌธ")
example_questions = [
"Spring Boot JPA N+1 ๋ฌธ์ œ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์€?",
"ImportError: No module named 'requests' ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•",
"Docker Compose ์„ค์ • ์˜ˆ์ œ๋ฅผ ์•Œ๋ ค์ฃผ์„ธ์š”",
"์ด ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•ด์ฃผ์„ธ์š”: for i in range(len(arr)): print(arr[i])",
"JWT๊ฐ€ ๋ญ์•ผ? CORS๋Š”?", # ๋‹ค์ค‘ ์งˆ๋ฌธ ์˜ˆ์‹œ
]
with gr.Row():
for question in example_questions:
btn = gr.Button(
question,
variant="secondary",
size="sm",
scale=1,
)
# ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ž…๋ ฅ์ฐฝ์— ์ž๋™ ์ž…๋ ฅ
btn.click(
fn=lambda q=question: q,
outputs=[chatbot_interface.textbox],
)
# ์ •๋ณด ์„น์…˜
with gr.Accordion("๐Ÿ“Š ์‹œ์Šคํ…œ ์ •๋ณด", open=False):
gr.Markdown("""
### ์‚ฌ์šฉ๋œ ๊ธฐ์ˆ 
- **LLM**: Gemini 2.5 Flash Lite
- **์ž„๋ฒ ๋”ฉ**: BAAI/bge-m3 (๋กœ์ปฌ)
- **๋ฒกํ„ฐ DB**: Qdrant Cloud
- **๊ฒ€์ƒ‰ API**: Stack Overflow, GitHub, Tavily
- **ํ”„๋ ˆ์ž„์›Œํฌ**: LangGraph
### ์ฃผ์š” ๊ธฐ๋Šฅ
- ๐Ÿ” **๋ณ‘๋ ฌ ๊ฒ€์ƒ‰**: Stack Overflow, GitHub, ๊ณต์‹ ๋ฌธ์„œ ๋™์‹œ ๊ฒ€์ƒ‰
- ๐Ÿ’พ **์˜๋ฏธ์  ์บ์‹ฑ**: ์œ ์‚ฌ ์งˆ๋ฌธ(์ž„๊ณ„๊ฐ’ 0.85 ์ด์ƒ) ์ฆ‰์‹œ ๋‹ต๋ณ€
- ๐ŸŽฏ **์˜๋„ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…**: debugging/learning/code_review ์ž๋™ ๋ถ„๋ฅ˜
- ๐Ÿ”„ **์ž๋™ ์ฟผ๋ฆฌ ๊ฐœ์„ **: ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ๋ถ€์กฑ ์‹œ ์ตœ๋Œ€ 1ํšŒ ์ž๋™ ์ตœ์ ํ™”
- ๐Ÿ“ **์ดˆ๋ณด์ž ์นœํ™” ๋‹ต๋ณ€**: ์˜๋„๋ณ„ ๋งž์ถคํ˜• ๋‹ต๋ณ€ ๊ตฌ์กฐ
- ๐Ÿ”€ **๋‹ค์ค‘ ์งˆ๋ฌธ ์ฒ˜๋ฆฌ**: ๋…๋ฆฝ ์งˆ๋ฌธ 2๊ฐœ๊นŒ์ง€ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ
- ๐Ÿ’ฌ **๋Œ€ํ™” ๋งฅ๋ฝ ์ดํ•ด**: clarification ์งˆ๋ฌธ์€ ํžˆ์Šคํ† ๋ฆฌ ๊ธฐ๋ฐ˜ ๋‹ต๋ณ€
### LangGraph๋กœ ๊ตฌํ˜„ํ•œ ํ•ต์‹ฌ ๊ธฐ๋Šฅ
1. โœ… **Conditional Edges**: ์งˆ๋ฌธ ์œ ํ˜•/์บ์‹œ ์—ฌ๋ถ€/๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ์— ๋”ฐ๋ฅธ ๋™์  ๋ผ์šฐํŒ…
2. โœ… **Send API**: 3๊ฐœ ๊ฒ€์ƒ‰ ์†Œ์Šค ๋ณ‘๋ ฌ ์‹คํ–‰ (fan-out/fan-in)
3. โœ… **Subgraph**: ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ ํ•„ํ„ฐ๋ง ๋ฐ ์š”์•ฝ ํŒŒ์ดํ”„๋ผ์ธ
4. โœ… **Map-Reduce**: ๋‹ค์ค‘ ์งˆ๋ฌธ ์ฒ˜๋ฆฌ ์‹œ ๊ฐ ์งˆ๋ฌธ๋ณ„ ๋…๋ฆฝ ์‹คํ–‰ ํ›„ ๊ฒฐ๊ณผ ํ†ตํ•ฉ
5. โœ… **Checkpointing**: MemorySaver๋กœ ๋Œ€ํ™” ์ƒํƒœ ์ €์žฅ ๋ฐ ์žฌ๊ฐœ
6. โœ… **Pydantic Typed State**: ํƒ€์ž… ์•ˆ์ „ํ•œ ์ƒํƒœ ๊ด€๋ฆฌ
### GitHub
[ํ”„๋กœ์ ํŠธ ์†Œ์Šค์ฝ”๋“œ](https://github.com/shin-heewon/codeweaver)
""")
# ์‚ฌ์šฉ ๊ฐ€์ด๋“œ
with gr.Accordion("๐Ÿ’ก ์‚ฌ์šฉ ํŒ", open=False):
gr.Markdown("""
### 1. ๊ตฌ์ฒด์ ์œผ๋กœ ์งˆ๋ฌธํ•˜๊ธฐ
- โŒ "ํŒŒ์ด์ฌ ์—๋Ÿฌ"
- โœ… "ImportError: No module named 'requests' ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•"
### 2. ์งˆ๋ฌธ ์œ ํ˜•๋ณ„ ์˜ˆ์‹œ
- **๋””๋ฒ„๊น…**: "์ด ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋Š” ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋‚˜์š”?"
- **ํ•™์Šต**: "JPA N+1 ๋ฌธ์ œ๋Š” ์™œ ๋ฐœ์ƒํ•˜๋‚˜์š”?"
- **์ฝ”๋“œ ๋ฆฌ๋ทฐ**: "์ด ์ฝ”๋“œ๋ฅผ ๋” ํšจ์œจ์ ์œผ๋กœ ๊ฐœ์„ ํ•˜๋ ค๋ฉด?"
### 3. ๋‹ค์ค‘ ์งˆ๋ฌธ ์‚ฌ์šฉ๋ฒ•
- โœ… **2๊ฐœ๊นŒ์ง€ ๊ฐ€๋Šฅ**: "JWT๊ฐ€ ๋ญ์•ผ? CORS๋Š”?"
- โŒ **3๊ฐœ ์ด์ƒ ๋ถˆ๊ฐ€**: "JWT? CORS? Docker?" โ†’ ์•ˆ๋‚ด ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
- ๐Ÿ’ก **ํŒ**: ๊ด€๋ จ ์งˆ๋ฌธ์€ ํ•˜๋‚˜๋กœ ํ†ตํ•ฉํ•˜๊ฑฐ๋‚˜, ์ˆœ์ฐจ์ ์œผ๋กœ ์งˆ๋ฌธํ•˜์„ธ์š”
### 4. ๋Œ€ํ™” ๋งฅ๋ฝ ํ™œ์šฉ
- **ํ›„์† ์งˆ๋ฌธ**: "์ข€ ๋” ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด์ค˜", "์˜ˆ์ œ ์ฝ”๋“œ๋กœ ๋ณด์—ฌ์ค˜"
- **์ƒˆ ๊ฐœ๋… ์งˆ๋ฌธ**: ๋Œ€ํ™” ์ค‘์—๋„ "Event Listener๋Š” ๋ญ์•ผ?" ๊ฐ™์€ ๋…๋ฆฝ ์งˆ๋ฌธ ๊ฐ€๋Šฅ
- ๐Ÿ’ก **ํŒ**: ์ด์ „ ๋Œ€ํ™”๋ฅผ ์ฐธ๊ณ ํ•œ ๋‹ต๋ณ€์ด ํ•„์š”ํ•˜๋ฉด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์งˆ๋ฌธํ•˜์„ธ์š”
### 5. ์‘๋‹ต ์‹œ๊ฐ„
- **์ฒซ ์งˆ๋ฌธ**: 10~15์ดˆ ์†Œ์š” (๊ฒ€์ƒ‰ + ๋‹ต๋ณ€ ์ƒ์„ฑ)
- **์œ ์‚ฌ ์งˆ๋ฌธ**: ์ฆ‰์‹œ ๋‹ต๋ณ€ (์บ์‹œ ํ™œ์šฉ, ์ž„๊ณ„๊ฐ’ 0.85 ์ด์ƒ)
- **๋‹ค์ค‘ ์งˆ๋ฌธ**: ๊ฐ ์งˆ๋ฌธ๋ณ„ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋กœ ํšจ์œจ์ 
### 6. ๋” ๋‚˜์€ ๋‹ต๋ณ€์„ ์œ„ํ•œ ํŒ
- ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ํฌํ•จํ•ด์ฃผ์„ธ์š”
- ์‚ฌ์šฉ ์ค‘์ธ ์–ธ์–ด/ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ๋ช…์‹œํ•˜์„ธ์š”
- ์‹œ๋„ํ–ˆ๋˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ํ•จ๊ป˜ ์•Œ๋ ค์ฃผ์„ธ์š”
- ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ๋ถ€์กฑํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ฟผ๋ฆฌ๋ฅผ ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค (์ตœ๋Œ€ 1ํšŒ)
""")
return demo
# ์•ฑ ์ƒ์„ฑ
app = create_demo()
if __name__ == "__main__":
# ๋กœ์ปฌ ์‹คํ–‰
app.launch(
server_name="0.0.0.0",
server_port=7860,
share=False, # True๋กœ ํ•˜๋ฉด ๊ณต๊ฐœ URL ์ƒ์„ฑ
show_api=False, # Gradio 4.44.x ๋ฒ„๊ทธ ์šฐํšŒ์šฉ
)