Yoon-gu Hwang commited on
Commit
1ebd829
·
1 Parent(s): 00237bb

update langgraph + gradio

Browse files
Files changed (3) hide show
  1. app_tool.py +420 -0
  2. pyproject.toml +3 -0
  3. uv.lock +0 -0
app_tool.py ADDED
@@ -0,0 +1,420 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from langgraph.graph import StateGraph, END
3
+ from typing import TypedDict, Annotated
4
+ import operator
5
+ from langchain_core.messages import HumanMessage, AIMessage, BaseMessage
6
+ from langchain_openai import ChatOpenAI
7
+ import asyncio
8
+ import time
9
+ import os
10
+
11
+ # 상태 정의
12
+ class ChatState(TypedDict):
13
+ messages: Annotated[list[BaseMessage], operator.add]
14
+ current_response: str
15
+ agent_type: str
16
+ context: dict
17
+ step_info: str
18
+
19
+ # LLM 초기화
20
+ llm = ChatOpenAI(
21
+ model="gpt-3.5-turbo",
22
+ temperature=0.7,
23
+ # api_key="your-openai-api-key" # 실제 API 키를 설정하세요
24
+ )
25
+
26
+ def step1_analyzer_node(state: ChatState) -> ChatState:
27
+ """1단계: 메시지 분석 노드"""
28
+ last_message = state["messages"][-1].content
29
+
30
+ # 분석 시뮬레이션 (실제로는 더 복잡한 로직)
31
+ analysis = {
32
+ "length": len(last_message),
33
+ "has_question": "?" in last_message,
34
+ "language": "korean" if any(ord(c) > 127 for c in last_message) else "english",
35
+ "sentiment": "positive" if any(word in last_message.lower() for word in ["좋", "감사", "고마워", "thanks", "good"]) else "neutral"
36
+ }
37
+
38
+ return {
39
+ "messages": [],
40
+ "current_response": "",
41
+ "agent_type": "",
42
+ "context": {"analysis": analysis},
43
+ "step_info": f"📊 **1단계 완료** - 메시지 분석\n- 길이: {analysis['length']}자\n- 질문 포함: {'예' if analysis['has_question'] else '아니오'}\n- 언어: {analysis['language']}\n- 감정: {analysis['sentiment']}"
44
+ }
45
+
46
+ def step2_classifier_node(state: ChatState) -> ChatState:
47
+ """2단계: 의도 분류 노드"""
48
+ last_message = state["messages"][-1].content.lower()
49
+ analysis = state["context"]["analysis"]
50
+
51
+ # 의도 분류 로직
52
+ if any(word in last_message for word in ["코드", "프로그래밍", "python", "개발", "함수", "클래스"]):
53
+ agent_type = "programmer"
54
+ confidence = 0.9
55
+ elif any(word in last_message for word in ["날씨", "뉴스", "정보", "검색", "찾아"]):
56
+ agent_type = "informer"
57
+ confidence = 0.8
58
+ elif any(word in last_message for word in ["계산", "수학", "더하기", "빼기", "곱하기", "나누기"]):
59
+ agent_type = "calculator"
60
+ confidence = 0.95
61
+ elif any(word in last_message for word in ["창작", "시", "소설", "이야기", "글"]):
62
+ agent_type = "creative"
63
+ confidence = 0.85
64
+ else:
65
+ agent_type = "general"
66
+ confidence = 0.7
67
+
68
+ context = state["context"]
69
+ context["classification"] = {
70
+ "agent_type": agent_type,
71
+ "confidence": confidence
72
+ }
73
+
74
+ return {
75
+ "messages": [],
76
+ "current_response": "",
77
+ "agent_type": agent_type,
78
+ "context": context,
79
+ "step_info": f"🎯 **2단계 완료** - 의도 분류\n- 분류 결과: {agent_type}\n- 신뢰도: {confidence:.1%}\n- 다음 단계: {'전문 처리' if confidence > 0.8 else '일반 처리'}"
80
+ }
81
+
82
+ def step3_context_enricher_node(state: ChatState) -> ChatState:
83
+ """3단계: 컨텍스트 강화 노드"""
84
+ agent_type = state["agent_type"]
85
+
86
+ # 에이전트 타입별 컨텍스트 강화
87
+ enriched_context = {
88
+ "programmer": {
89
+ "system_prompt": "당신은 경험이 풍부한 시니어 개발자입니다. 코드 예시와 함께 명확하고 실용적인 답변을 제공하세요.",
90
+ "tools": ["코드 실행", "문서 검색", "베스트 프랙티스"],
91
+ "style": "기술적이고 정확한"
92
+ },
93
+ "informer": {
94
+ "system_prompt": "당신은 정보 전문가입니다. 정확하고 최신의 정보를 구조화된 형태로 제공하세요.",
95
+ "tools": ["웹 검색", "팩트 체크", "데이터 분석"],
96
+ "style": "객관적이고 상세한"
97
+ },
98
+ "calculator": {
99
+ "system_prompt": "당신은 수학 전문가입니다. 계산 과정을 단계별로 설명하고 정확한 답을 제공하세요.",
100
+ "tools": ["수식 계산", "그래프 생성", "통계 분석"],
101
+ "style": "논리적이고 체계적인"
102
+ },
103
+ "creative": {
104
+ "system_prompt": "당신은 창작 전문가입니다. 상상력이 풍부하고 감성적인 콘텐츠를 제작하세요.",
105
+ "tools": ["스토리텔링", "시각적 묘사", "감정 표현"],
106
+ "style": "창의적이고 감성적인"
107
+ },
108
+ "general": {
109
+ "system_prompt": "당신은 친근하고 도움이 되는 AI 어시스턴트입니다. 자연스럽고 이해하기 쉬운 답변을 제공하세요.",
110
+ "tools": ["일반 대화", "정보 제공", "문제 해결"],
111
+ "style": "친근하고 자연스러운"
112
+ }
113
+ }
114
+
115
+ context = state["context"]
116
+ context["enriched"] = enriched_context.get(agent_type, enriched_context["general"])
117
+
118
+ return {
119
+ "messages": [],
120
+ "current_response": "",
121
+ "agent_type": agent_type,
122
+ "context": context,
123
+ "step_info": f"🔧 **3단계 완료** - 컨텍스트 강화\n- 에이전트: {agent_type}\n- 스타일: {context['enriched']['style']}\n- 활용 도구: {', '.join(context['enriched']['tools'][:2])}"
124
+ }
125
+
126
+ def step4_response_generator_node(state: ChatState) -> ChatState:
127
+ """4단계: 응답 생성 노드"""
128
+ enriched_context = state["context"]["enriched"]
129
+ system_prompt = enriched_context["system_prompt"]
130
+
131
+ # 시스템 프롬프트와 함께 메시지 구성
132
+ messages = [HumanMessage(content=system_prompt)] + state["messages"]
133
+
134
+ try:
135
+ response = llm.invoke(messages)
136
+
137
+ # 에이전트 타입에 따른 아이콘 설정
138
+ icons = {
139
+ "programmer": "💻",
140
+ "informer": "📚",
141
+ "calculator": "🔢",
142
+ "creative": "🎨",
143
+ "general": "💬"
144
+ }
145
+
146
+ icon = icons.get(state["agent_type"], "💬")
147
+ final_response = f"{icon} **[{state['agent_type'].upper()}]**\n\n{response.content}"
148
+
149
+ return {
150
+ "messages": [response],
151
+ "current_response": final_response,
152
+ "agent_type": state["agent_type"],
153
+ "context": state["context"],
154
+ "step_info": f"✅ **4단계 완료** - 응답 생성\n- 최종 응답 준비됨\n- 응답 길이: {len(response.content)}자"
155
+ }
156
+
157
+ except Exception as e:
158
+ error_msg = f"❌ 응답 생성 중 오류 발생: {str(e)}"
159
+ return {
160
+ "messages": [AIMessage(content=error_msg)],
161
+ "current_response": error_msg,
162
+ "agent_type": state["agent_type"],
163
+ "context": state["context"],
164
+ "step_info": f"❌ **4단계 실패** - 오류 발생\n- 오류: {str(e)}"
165
+ }
166
+
167
+ def should_continue_to_classifier(state: ChatState) -> str:
168
+ return "classifier"
169
+
170
+ def should_continue_to_enricher(state: ChatState) -> str:
171
+ return "enricher"
172
+
173
+ def should_continue_to_generator(state: ChatState) -> str:
174
+ return "generator"
175
+
176
+ def should_end(state: ChatState) -> str:
177
+ return END
178
+
179
+ # LangGraph 워크플로우 생성
180
+ def create_enhanced_workflow():
181
+ workflow = StateGraph(ChatState)
182
+
183
+ # 4개 노드 추가
184
+ workflow.add_node("analyzer", step1_analyzer_node)
185
+ workflow.add_node("classifier", step2_classifier_node)
186
+ workflow.add_node("enricher", step3_context_enricher_node)
187
+ workflow.add_node("generator", step4_response_generator_node)
188
+
189
+ # 시작점 설정
190
+ workflow.set_entry_point("analyzer")
191
+
192
+ # 순차적 엣지 추가
193
+ workflow.add_conditional_edges(
194
+ "analyzer",
195
+ should_continue_to_classifier,
196
+ {"classifier": "classifier"}
197
+ )
198
+
199
+ workflow.add_conditional_edges(
200
+ "classifier",
201
+ should_continue_to_enricher,
202
+ {"enricher": "enricher"}
203
+ )
204
+
205
+ workflow.add_conditional_edges(
206
+ "enricher",
207
+ should_continue_to_generator,
208
+ {"generator": "generator"}
209
+ )
210
+
211
+ workflow.add_conditional_edges(
212
+ "generator",
213
+ should_end,
214
+ {END: END}
215
+ )
216
+
217
+ return workflow.compile()
218
+
219
+ # 글로벌 워크플로우 인스턴스
220
+ enhanced_workflow = create_enhanced_workflow()
221
+
222
+ def stream_chatbot_response(message, history):
223
+ """각 단계를 누적해서 실시간 표시"""
224
+ if not message.strip():
225
+ yield "", history
226
+ return
227
+
228
+ # 메시지 히스토리를 LangChain 메시지로 변환
229
+ messages = []
230
+ for human_msg, ai_msg in history:
231
+ if human_msg and not "📊" in human_msg and not "🎯" in human_msg:
232
+ messages.append(HumanMessage(content=human_msg))
233
+ if ai_msg and not "📊" in ai_msg and not "🎯" in ai_msg and not "⚡" in ai_msg:
234
+ messages.append(AIMessage(content=ai_msg))
235
+
236
+ # 현재 메시지 추가
237
+ messages.append(HumanMessage(content=message))
238
+
239
+ try:
240
+ # 초기 상태 설정
241
+ initial_state = {
242
+ "messages": messages,
243
+ "current_response": "",
244
+ "agent_type": "",
245
+ "context": {},
246
+ "step_info": ""
247
+ }
248
+
249
+ # 워크플로우를 스트림으로 실행
250
+ current_history = history.copy()
251
+
252
+ # 누적될 단계 정보들
253
+ accumulated_steps = []
254
+ accumulated_steps.append("🔄 **AI 처리 과정 시작**\n")
255
+
256
+ current_history.append((message, "\n".join(accumulated_steps)))
257
+ yield "", current_history
258
+ time.sleep(0.3)
259
+
260
+ # 각 노드를 순차적으로 실행하면서 중간 결과를 누적 표시
261
+ step_counter = 1
262
+ for step_result in enhanced_workflow.stream(initial_state):
263
+ node_name = list(step_result.keys())[0]
264
+ node_result = step_result[node_name]
265
+
266
+ if "step_info" in node_result and node_result["step_info"]:
267
+ # 현재 단계 정보를 누적 리스트에 추가
268
+ step_info = node_result["step_info"]
269
+ accumulated_steps.append(f"\n{'='*50}")
270
+ accumulated_steps.append(step_info)
271
+
272
+ # 진행률 표시
273
+ progress_bar = "▓" * step_counter + "░" * (4 - step_counter)
274
+ accumulated_steps.append(f"\n📈 **진행률**: [{progress_bar}] {step_counter}/4 단계")
275
+
276
+ # 누적된 모든 단계 정보를 표시
277
+ full_message = "\n".join(accumulated_steps)
278
+ current_history[-1] = (message, full_message)
279
+ yield "", current_history
280
+ time.sleep(0.8) # 시각적 효과를 위한 지연
281
+ step_counter += 1
282
+
283
+ # 최종 응답을 위해 다시 워크플로우 실행 (결과 획득용)
284
+ final_result = None
285
+ for step_result in enhanced_workflow.stream(initial_state):
286
+ final_result = step_result
287
+
288
+ if final_result:
289
+ final_node_result = list(final_result.values())[0]
290
+ final_response = final_node_result.get("current_response", "응답을 생성할 수 없습니다.")
291
+
292
+ # 최종 응답을 누적 정보에 추가
293
+ accumulated_steps.append(f"\n{'='*50}")
294
+ accumulated_steps.append("🎉 **최종 응답**")
295
+ accumulated_steps.append(f"\n{final_response}")
296
+
297
+ # 완료된 전체 과정 표시
298
+ full_message = "\n".join(accumulated_steps)
299
+ current_history[-1] = (message, full_message)
300
+ yield "", current_history
301
+
302
+ except Exception as e:
303
+ error_response = f"❌ **오류 발생**\n{str(e)}"
304
+ current_history = history.copy()
305
+ current_history.append((message, error_response))
306
+ yield "", current_history
307
+
308
+ def clear_chat():
309
+ """채팅 히스토리 초기화"""
310
+ return []
311
+
312
+ # Gradio 인터페이스 생성
313
+ def create_enhanced_gradio_interface():
314
+ with gr.Blocks(title="Enhanced LangGraph 챗봇", theme=gr.themes.Soft()) as demo:
315
+ gr.Markdown(
316
+ """
317
+ # 🚀 Enhanced LangGraph + Gradio 챗봇
318
+
319
+ **4단계 처리 과정을 실시간으로 확인할 수 있는 AI 챗봇**
320
+
321
+ **처리 단계:**
322
+ 1. 📊 **메시지 분석** - 입력 메시지의 특성 분석
323
+ 2. 🎯 **의도 분류** - 사용자 의도에 따른 에이전트 선택
324
+ 3. 🔧 **컨텍스트 강화** - 전문 도메인별 컨텍스트 설정
325
+ 4. ✅ **응답 생성** - 최적화된 답변 생성
326
+
327
+ **지원 에이전트:** 💻 프로그래머 | 📚 정보전문가 | 🔢 계산기 | 🎨 창작가 | 💬 일반대화
328
+ """
329
+ )
330
+
331
+ # 챗봇 컴포넌트
332
+ chatbot = gr.Chatbot(
333
+ value=[],
334
+ height=600,
335
+ show_label=False,
336
+ container=True,
337
+ )
338
+
339
+ with gr.Row():
340
+ msg = gr.Textbox(
341
+ placeholder="메시지를 입력하세요... (각 처리 단계가 실시간으로 표시됩니다)",
342
+ show_label=False,
343
+ scale=4,
344
+ container=False
345
+ )
346
+ submit_btn = gr.Button("전송", scale=1, variant="primary")
347
+ clear_btn = gr.Button("초기화", scale=1, variant="secondary")
348
+
349
+ # 상태 표시
350
+ with gr.Row():
351
+ gr.Markdown("💡 **팁**: 다양한 주제로 대화해보세요. 각 단계별 처리 과정을 확인할 수 있습니다!")
352
+
353
+ # 이벤트 핸들러 - 스트리밍 방식으로 변경
354
+ submit_btn.click(
355
+ stream_chatbot_response,
356
+ inputs=[msg, chatbot],
357
+ outputs=[msg, chatbot]
358
+ )
359
+
360
+ msg.submit(
361
+ stream_chatbot_response,
362
+ inputs=[msg, chatbot],
363
+ outputs=[msg, chatbot]
364
+ )
365
+
366
+ # 초기화 버튼
367
+ clear_btn.click(
368
+ clear_chat,
369
+ outputs=[chatbot]
370
+ )
371
+
372
+ # 카테고리별 예제 질문들
373
+ with gr.Row():
374
+ with gr.Column():
375
+ gr.Markdown("### 💻 프로그래밍")
376
+ gr.Examples(
377
+ examples=[
378
+ "Python으로 피보나치 수열 함수 만드는 방법?",
379
+ "딕셔너리와 리스트의 차이점을 알려줘",
380
+ "클래스와 객체에 대해 설명해줘"
381
+ ],
382
+ inputs=msg
383
+ )
384
+
385
+ with gr.Column():
386
+ gr.Markdown("### 🔢 계산/수학")
387
+ gr.Examples(
388
+ examples=[
389
+ "25 곱하기 37은 얼마야?",
390
+ "복리 계산 방법을 알려줘",
391
+ "삼각함수에 대해 설명해줘"
392
+ ],
393
+ inputs=msg
394
+ )
395
+
396
+ with gr.Column():
397
+ gr.Markdown("### 🎨 창작")
398
+ gr.Examples(
399
+ examples=[
400
+ "봄에 대한 짧은 시를 써줘",
401
+ "우주 여행 이야기를 만들어줘",
402
+ "창의적인 아이디어를 제안해줘"
403
+ ],
404
+ inputs=msg
405
+ )
406
+
407
+ return demo
408
+
409
+ if __name__ == "__main__":
410
+ # 사용 방법:
411
+ # 1. OpenAI API 키를 설정하세요
412
+ # os.environ["OPENAI_API_KEY"] = "your-api-key-here"
413
+
414
+ # 2. 필요한 패키지 설치:
415
+ # pip install gradio langgraph langchain-openai langchain-core
416
+
417
+ # 3. 인터페이스 실행
418
+ demo = create_enhanced_gradio_interface()
419
+ demo.launch(
420
+ )
pyproject.toml CHANGED
@@ -6,8 +6,11 @@ readme = "README.md"
6
  requires-python = ">=3.13"
7
  dependencies = [
8
  "gradio[mcp]==5.37",
 
9
  "langchain-mcp-adapters>=0.1.9",
 
10
  "langchain[openai]>=0.3.27",
11
  "langgraph>=0.5.4",
 
12
  "textblob>=0.19.0",
13
  ]
 
6
  requires-python = ">=3.13"
7
  dependencies = [
8
  "gradio[mcp]==5.37",
9
+ "ipython>=9.4.0",
10
  "langchain-mcp-adapters>=0.1.9",
11
+ "langchain-teddynote>=0.3.45",
12
  "langchain[openai]>=0.3.27",
13
  "langgraph>=0.5.4",
14
+ "notebook>=7.4.4",
15
  "textblob>=0.19.0",
16
  ]
uv.lock CHANGED
The diff for this file is too large to render. See raw diff