aiqtech commited on
Commit
9dca33d
·
verified ·
1 Parent(s): 1090499

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +615 -491
app.py CHANGED
@@ -1,18 +1,21 @@
1
  """
2
- Multi-Agent RAG-Enhanced LLM System for Hugging Face Spaces with Streaming
3
- 감독자(Supervisor) -> 창의성 생성자(Creative) -> 비평자(Critic) -> 감독자(Final)
4
- 4단계 파이프라인을 통한 고품질 답변 생성 시스템 - 스트리밍 출력 지원
5
  """
6
 
7
  import os
8
  import json
9
  import time
10
  import asyncio
 
11
  from typing import Optional, List, Dict, Any, Tuple, Generator, AsyncGenerator
12
- from datetime import datetime
13
  from enum import Enum
 
14
  import threading
15
  import queue
 
 
16
 
17
  import requests
18
  import gradio as gr
@@ -35,44 +38,134 @@ class AgentRole(Enum):
35
  FINALIZER = "finalizer"
36
 
37
 
 
 
 
 
 
 
 
38
  class Message(BaseModel):
39
  role: str
40
  content: str
 
41
 
42
 
43
  class AgentResponse(BaseModel):
44
  role: AgentRole
45
  content: str
 
46
  metadata: Optional[Dict] = None
47
 
48
 
49
- class StreamingResponse(BaseModel):
50
- chunk: str
51
- agent_role: Optional[AgentRole] = None
52
- is_complete: bool = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
 
55
  # ============================================================================
56
- # Brave Search 클라이언트
57
  # ============================================================================
58
 
59
- class BraveSearchClient:
 
 
60
  def __init__(self, api_key: Optional[str] = None):
61
  self.api_key = api_key or os.getenv("BRAVE_SEARCH_API_KEY")
62
- if not self.api_key:
63
- print("⚠️ Warning: Brave Search API key not found. Search disabled.")
64
-
65
  self.base_url = "https://api.search.brave.com/res/v1/web/search"
66
- self.headers = {
67
- "Accept": "application/json",
68
- "X-Subscription-Token": self.api_key
69
- } if self.api_key else {}
70
 
71
- def search(self, query: str, count: int = 5) -> List[Dict]:
72
- """ 검색 수행"""
73
  if not self.api_key:
74
  return []
75
 
 
 
 
 
 
76
  params = {
77
  "q": query,
78
  "count": count,
@@ -82,37 +175,40 @@ class BraveSearchClient:
82
  }
83
 
84
  try:
85
- response = requests.get(
86
- self.base_url,
87
- headers=self.headers,
88
- params=params,
89
- timeout=10
90
- )
91
- response.raise_for_status()
92
- data = response.json()
93
-
94
- results = []
95
- if "web" in data and "results" in data["web"]:
96
- for item in data["web"]["results"][:count]:
97
- results.append({
98
- "title": item.get("title", ""),
99
- "url": item.get("url", ""),
100
- "description": item.get("description", ""),
101
- "age": item.get("age", "")
102
- })
103
-
104
- return results
105
-
106
- except Exception as e:
107
- print(f"Search error: {str(e)}")
108
  return []
 
 
109
 
110
 
111
  # ============================================================================
112
- # Fireworks LLM 클라이언트 (스트리밍 지원)
113
  # ============================================================================
114
 
115
- class FireworksClient:
 
 
116
  def __init__(self, api_key: Optional[str] = None):
117
  self.api_key = api_key or os.getenv("FIREWORKS_API_KEY")
118
  if not self.api_key:
@@ -124,42 +220,21 @@ class FireworksClient:
124
  "Content-Type": "application/json",
125
  "Authorization": f"Bearer {self.api_key}"
126
  }
127
-
128
- def chat(self, messages: List[Dict], **kwargs) -> str:
129
- """LLM과 대화 (일반)"""
130
- payload = {
131
- "model": kwargs.get("model", "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507"),
132
- "messages": messages,
133
- "max_tokens": kwargs.get("max_tokens", 4096),
134
- "temperature": kwargs.get("temperature", 0.7),
135
- "top_p": kwargs.get("top_p", 1.0),
136
- "top_k": kwargs.get("top_k", 40),
137
- "stream": False
138
- }
139
 
140
- try:
141
- response = requests.post(
142
- self.base_url,
143
- headers=self.headers,
144
- data=json.dumps(payload),
145
- timeout=60
146
- )
147
- response.raise_for_status()
148
- data = response.json()
149
-
150
- if "choices" in data and len(data["choices"]) > 0:
151
- return data["choices"][0]["message"]["content"]
152
- return "응답을 생성할 수 없습니다."
153
-
154
- except Exception as e:
155
- return f"오류 발생: {str(e)}"
156
 
157
- def chat_stream(self, messages: List[Dict], **kwargs) -> Generator[str, None, None]:
158
- """LLM과 대화 (스트리밍)"""
 
 
 
 
 
159
  payload = {
160
- "model": kwargs.get("model", "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507"),
161
  "messages": messages,
162
- "max_tokens": kwargs.get("max_tokens", 4096),
163
  "temperature": kwargs.get("temperature", 0.7),
164
  "top_p": kwargs.get("top_p", 1.0),
165
  "top_k": kwargs.get("top_k", 40),
@@ -167,516 +242,571 @@ class FireworksClient:
167
  }
168
 
169
  try:
170
- response = requests.post(
171
- self.base_url,
172
- headers={**self.headers, "Accept": "text/event-stream"},
173
- data=json.dumps(payload),
174
- stream=True,
175
- timeout=60
176
- )
177
- response.raise_for_status()
178
-
179
- for line in response.iter_lines():
180
- if line:
181
- line_str = line.decode('utf-8')
182
- if line_str.startswith("data: "):
183
- data_str = line_str[6:]
184
- if data_str == "[DONE]":
185
- break
186
- try:
187
- data = json.loads(data_str)
188
- if "choices" in data and len(data["choices"]) > 0:
189
- delta = data["choices"][0].get("delta", {})
190
- if "content" in delta:
191
- yield delta["content"]
192
- except json.JSONDecodeError:
193
- continue
194
-
195
  except Exception as e:
196
- yield f"오류 발생: {str(e)}"
197
 
198
 
199
  # ============================================================================
200
- # 멀티 에이전트 시스템 (스트리밍 지원)
201
  # ============================================================================
202
 
203
- class MultiAgentSystemStreaming:
204
- """스트리밍을 지원하는 4단계 멀티 에이전트 처리 시스템"""
205
-
206
- def __init__(self, llm_client: FireworksClient, search_client: BraveSearchClient):
207
- self.llm = llm_client
208
- self.search = search_client
209
- self.agent_configs = self._initialize_agent_configs()
210
 
211
- def _initialize_agent_configs(self) -> Dict:
212
- """각 에이전트별 설정 초기화"""
213
- return {
214
- AgentRole.SUPERVISOR: {
215
- "temperature": 0.3,
216
- "system_prompt": """당신은 감독자 에이전트입니다.
217
- 사용자의 질문과 검색 결과를 분석하여 답변의 전체적인 방향성과 구조를 제시해야 합니다.
218
-
219
- 역할:
220
- 1. 질문의 핵심 의도 파악
221
- 2. 검색 결과에서 핵심 정보 추출
222
- 3. 답변이 포함해야 할 주요 요소들 정의
223
- 4. 논리적 흐름과 구조 제시"""
224
- },
225
-
226
- AgentRole.CREATIVE: {
227
- "temperature": 0.9,
228
- "system_prompt": """당신은 창의성 생성자 에이전트입니다.
229
- 감독자의 지침을 바탕으로 창의적이고 흥미로운 답변을 생성해야 합니다.
230
-
231
- 역할:
232
- 1. 감독자의 구조를 따르되 창의적으로 확장
233
- 2. 예시, 비유, 스토리텔링 활용
234
- 3. 사용자 관점에서 이해하기 쉬운 설명 추가
235
- 4. 실용적이고 구체적인 조언 포함"""
236
  },
237
-
238
- AgentRole.CRITIC: {
239
- "temperature": 0.2,
240
- "system_prompt": """당신은 비평자 에이전트입니다.
241
- 창의성 생성자의 답변을 검토하고 개선점을 제시해야 합니다.
242
-
243
- 평가 기준:
244
- - 정확성: 사실과 데이터의 정확성
245
- - 완전성: 질문에 대한 충분한 답변 여부
246
- - 명확성: 이해하기 쉬운 설명인지
247
- - 유용성: 실제로 도움이 되는 정보인지"""
248
  },
249
-
250
- AgentRole.FINALIZER: {
251
- "temperature": 0.5,
252
- "system_prompt": """당신은 최종 감독자입니다.
253
- 모든 에이전트의 의견을 종합하여 최종 답변을 생성해야 합니다.
254
-
255
- 최종 답변 기준:
256
- - 정확성과 창의성의 균형
257
- - 명확한 구조와 논리적 흐름
258
- - 실용적이고 유용한 정보
259
- - 사용자 친화적인 톤"""
260
  }
261
  }
262
 
263
- def _format_search_results(self, results: List[Dict]) -> str:
264
- """검색 결과 포맷팅"""
265
- if not results:
266
- return "검색 결과 없음"
267
-
268
- formatted = []
269
- for i, result in enumerate(results, 1):
270
- formatted.append(f"""
271
- [검색결과 {i}]
272
- 제목: {result.get('title', 'N/A')}
273
- URL: {result.get('url', 'N/A')}
274
- 내용: {result.get('description', 'N/A')}""")
275
-
276
- return "\n".join(formatted)
277
 
278
- def process_with_streaming(
279
- self,
280
- query: str,
281
- search_results: List[Dict],
282
- config: Dict,
283
- show_agent_thoughts: bool = False
284
- ) -> Generator[Tuple[str, str], None, None]:
285
- """스트리밍으로 멀티 에이전트 파이프라인 실행"""
286
-
287
- search_context = self._format_search_results(search_results)
288
- accumulated_response = ""
289
- agent_thoughts_display = ""
290
-
291
- # 에이전트 역할 이모지
292
- role_emoji = {
293
- AgentRole.SUPERVISOR: "👔",
294
- AgentRole.CREATIVE: "🎨",
295
- AgentRole.CRITIC: "🔍",
296
- AgentRole.FINALIZER: "✅"
297
- }
298
-
299
- role_name = {
300
- AgentRole.SUPERVISOR: "감독자",
301
- AgentRole.CREATIVE: "창의성 생성자",
302
- AgentRole.CRITIC: "비평자",
303
- AgentRole.FINALIZER: "최종 감독자"
304
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
- # 저장할 에이전트 응답들
307
- agent_responses = {}
308
-
309
- # 1단계: 감독자
310
- if show_agent_thoughts:
311
- agent_thoughts_display += f"### {role_emoji[AgentRole.SUPERVISOR]} {role_name[AgentRole.SUPERVISOR]} 분석 중...\n\n"
312
- yield accumulated_response, agent_thoughts_display
313
 
314
- supervisor_prompt = f"""
315
- 사용자 질문: {query}
316
 
317
- 검색 결과:
318
- {search_context}
 
319
 
320
- 정보를 바탕으로 답변의 방향성과 구조를 제시하세요."""
 
 
 
 
 
 
 
 
 
 
 
 
 
321
 
322
- supervisor_response = ""
323
- for chunk in self.llm.chat_stream(
324
- messages=[
325
- {"role": "system", "content": self.agent_configs[AgentRole.SUPERVISOR]["system_prompt"]},
326
- {"role": "user", "content": supervisor_prompt}
327
- ],
328
- temperature=self.agent_configs[AgentRole.SUPERVISOR]["temperature"],
329
- max_tokens=config.get("max_tokens", 1000)
330
- ):
331
- supervisor_response += chunk
332
- if show_agent_thoughts:
333
- # 감독자 응답을 실시간으로 표시 (처음 300자만)
334
- display_text = supervisor_response[:300] + ("..." if len(supervisor_response) > 300 else "")
335
- agent_thoughts_display = f"### {role_emoji[AgentRole.SUPERVISOR]} {role_name[AgentRole.SUPERVISOR]}\n\n{display_text}\n\n"
336
- yield accumulated_response, agent_thoughts_display
337
-
338
- agent_responses[AgentRole.SUPERVISOR] = supervisor_response
339
-
340
- # 2단계: 창의성 생성자
341
- if show_agent_thoughts:
342
- agent_thoughts_display += f"### {role_emoji[AgentRole.CREATIVE]} {role_name[AgentRole.CREATIVE]} 생성 중...\n\n"
343
- yield accumulated_response, agent_thoughts_display
344
-
345
- creative_prompt = f"""
346
- 사용자 질문: {query}
347
-
348
- 감독자 지침:
349
- {supervisor_response}
350
-
351
- 검색 결과:
352
- {search_context}
353
-
354
- 위 지침과 정보를 바탕으로 창의적이고 유용한 답변을 생성하세요."""
355
-
356
- creative_response = ""
357
- for chunk in self.llm.chat_stream(
358
- messages=[
359
- {"role": "system", "content": self.agent_configs[AgentRole.CREATIVE]["system_prompt"]},
360
- {"role": "user", "content": creative_prompt}
361
- ],
362
- temperature=self.agent_configs[AgentRole.CREATIVE]["temperature"],
363
- max_tokens=config.get("max_tokens", 2000)
364
- ):
365
- creative_response += chunk
366
- if show_agent_thoughts:
367
- display_text = creative_response[:400] + ("..." if len(creative_response) > 400 else "")
368
- prev_supervisor = f"### {role_emoji[AgentRole.SUPERVISOR]} {role_name[AgentRole.SUPERVISOR]}\n\n{supervisor_response[:200]}...\n\n"
369
- agent_thoughts_display = prev_supervisor + f"### {role_emoji[AgentRole.CREATIVE]} {role_name[AgentRole.CREATIVE]}\n\n{display_text}\n\n"
370
- yield accumulated_response, agent_thoughts_display
371
-
372
- agent_responses[AgentRole.CREATIVE] = creative_response
373
-
374
- # 3단계: 비평자
375
- if show_agent_thoughts:
376
- agent_thoughts_display += f"### {role_emoji[AgentRole.CRITIC]} {role_name[AgentRole.CRITIC]} 검토 중...\n\n"
377
- yield accumulated_response, agent_thoughts_display
378
-
379
- critic_prompt = f"""
380
- 원본 질문: {query}
381
-
382
- 창의성 생성자의 답변:
383
- {creative_response}
384
-
385
- 검색 결과:
386
- {search_context}
387
-
388
- 위 답변을 검토하고 개선점을 제시하세요."""
389
-
390
- critic_response = ""
391
- for chunk in self.llm.chat_stream(
392
- messages=[
393
- {"role": "system", "content": self.agent_configs[AgentRole.CRITIC]["system_prompt"]},
394
- {"role": "user", "content": critic_prompt}
395
- ],
396
- temperature=self.agent_configs[AgentRole.CRITIC]["temperature"],
397
- max_tokens=config.get("max_tokens", 1000)
398
- ):
399
- critic_response += chunk
400
- if show_agent_thoughts:
401
- display_text = critic_response[:300] + ("..." if len(critic_response) > 300 else "")
402
- # 이전 에이전트들 요약
403
- prev_content = f"### {role_emoji[AgentRole.SUPERVISOR]} {role_name[AgentRole.SUPERVISOR]}\n{supervisor_response[:150]}...\n\n"
404
- prev_content += f"### {role_emoji[AgentRole.CREATIVE]} {role_name[AgentRole.CREATIVE]}\n{creative_response[:200]}...\n\n"
405
- agent_thoughts_display = prev_content + f"### {role_emoji[AgentRole.CRITIC]} {role_name[AgentRole.CRITIC]}\n\n{display_text}\n\n"
406
- yield accumulated_response, agent_thoughts_display
407
-
408
- agent_responses[AgentRole.CRITIC] = critic_response
409
-
410
- # 4단계: 최종 감독자 - 이제 최종 답변을 스트리밍으로 출력
411
- if show_agent_thoughts:
412
- # 모든 에이전트 사고 과정 최종 정리
413
- final_thoughts = "## 🤖 에이전트 협업 완료\n\n"
414
- for role in [AgentRole.SUPERVISOR, AgentRole.CREATIVE, AgentRole.CRITIC]:
415
- final_thoughts += f"### {role_emoji[role]} {role_name[role]}\n"
416
- final_thoughts += f"{agent_responses[role][:250]}...\n\n"
417
 
418
- final_thoughts += f"### {role_emoji[AgentRole.FINALIZER]} {role_name[AgentRole.FINALIZER]} 최종 답변 생성 중...\n\n"
419
- agent_thoughts_display = final_thoughts
420
- yield accumulated_response, agent_thoughts_display
 
 
 
421
 
422
- # 최종 답변 프롬프트
423
- final_prompt = f"""
424
- 사용자 질문: {query}
425
-
426
- 창의성 생성자의 답변:
427
- {creative_response}
428
 
429
- 비평자의 피드백:
430
- {critic_response}
431
 
432
- 초기 감독자 지침:
433
- {supervisor_response}
434
-
435
- 검색 결과:
436
- {search_context}
437
 
438
- 모든 의견을 종합하여 최종 답변을 생성하세요."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
 
440
- # 최종 답변 스트리밍
 
441
  accumulated_response = ""
442
- for chunk in self.llm.chat_stream(
443
- messages=[
444
- {"role": "system", "content": self.agent_configs[AgentRole.FINALIZER]["system_prompt"]},
445
- {"role": "user", "content": final_prompt}
446
- ],
447
- temperature=self.agent_configs[AgentRole.FINALIZER]["temperature"],
448
- max_tokens=config.get("max_tokens", 3000)
449
- ):
450
- accumulated_response += chunk
451
- yield accumulated_response, agent_thoughts_display
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
 
453
 
454
  # ============================================================================
455
- # Gradio UI (스트리밍 지원)
456
  # ============================================================================
457
 
458
- def create_gradio_interface():
459
- """Gradio 인터페이스 생성 (스트리밍 지원)"""
460
 
461
- # 클라이언트 초기화
462
- try:
463
- llm_client = FireworksClient()
464
- search_client = BraveSearchClient()
465
- multi_agent_system = MultiAgentSystemStreaming(llm_client, search_client)
466
- system_ready = True
467
- except Exception as e:
468
- print(f"⚠️ System initialization error: {e}")
469
- multi_agent_system = None
470
- search_client = None
471
- system_ready = False
472
 
473
- def process_query_streaming(
474
  message: str,
475
  history: List[Dict],
476
  use_search: bool,
477
  show_agent_thoughts: bool,
478
- search_count: int,
479
- temperature: float,
480
- max_tokens: int
481
  ):
482
- """스트리밍 쿼리 처리 함수"""
483
 
484
- if not message or not system_ready:
485
  yield history, "", ""
486
  return
487
 
488
  try:
489
- # 검색 수행
490
  search_results = []
491
  search_display = ""
492
 
493
- if use_search and search_client and search_client.api_key:
494
- # 검색 표시
495
  history_with_message = history + [
496
  {"role": "user", "content": message},
497
- {"role": "assistant", "content": "🔍 검색 중..."}
498
  ]
499
  yield history_with_message, "", ""
500
 
501
- search_results = search_client.search(message, count=search_count)
 
502
 
503
- # 검색 결과 포맷팅
504
  if search_results:
505
  search_display = "## 📚 참고 자료\n\n"
506
- for i, result in enumerate(search_results, 1):
507
- search_display += f"**{i}. [{result['title']}]({result['url']})**\n"
508
  search_display += f" {result['description'][:100]}...\n\n"
509
 
510
- # 설정
511
- config = {
512
- "temperature": temperature,
513
- "max_tokens": max_tokens
514
- }
515
-
516
  # 사용자 메시지 추가
517
  current_history = history + [{"role": "user", "content": message}]
518
 
519
- # 멀티 에이전트 스트리밍 처리
520
- assistant_message = ""
521
- agent_thoughts = ""
522
-
523
- for response_chunk, thoughts_chunk in multi_agent_system.process_with_streaming(
524
  query=message,
525
  search_results=search_results,
526
- config=config,
527
- show_agent_thoughts=show_agent_thoughts
528
  ):
529
- assistant_message = response_chunk
530
- agent_thoughts = thoughts_chunk
531
-
532
- # 히스토리 업데이트
533
- updated_history = current_history + [{"role": "assistant", "content": assistant_message}]
534
-
535
- yield updated_history, agent_thoughts, search_display
536
-
537
- # 최종 처리 시간 추가
538
- final_message = assistant_message + "\n\n---\n✨ *답변 생성 완료*"
539
- final_history = current_history + [{"role": "assistant", "content": final_message}]
540
-
541
- yield final_history, agent_thoughts, search_display
542
 
543
  except Exception as e:
544
- error_msg = f"❌ 오류 발생: {str(e)}"
545
  error_history = history + [
546
  {"role": "user", "content": message},
547
- {"role": "assistant", "content": error_msg}
548
  ]
549
  yield error_history, "", ""
550
 
551
  # Gradio 인터페이스
552
  with gr.Blocks(
553
- title="Multi-Agent RAG System with Streaming",
554
  theme=gr.themes.Soft(),
555
  css="""
556
  .gradio-container {
557
  max-width: 1400px !important;
558
  margin: auto !important;
559
  }
560
- .message {
561
- font-size: 1.1em !important;
562
- }
563
  """
564
  ) as demo:
565
  gr.Markdown("""
566
- # 🧠 Multi-Agent RAG System (Streaming)
567
- ### 실시간 스트리밍으로 4단계 에이전트 협업 답변 생성
568
-
569
- **처리 과정:** 감독자(구조화) → 창의성 생성자(창의적 답변) → 비평자(검증) → 최종 감독자(종합)
 
 
 
 
570
  """)
571
 
572
- if not system_ready:
573
- gr.Markdown("""
574
- ⚠️ **시스템 초기화 실패**: API 키를 확인해주세요.
575
- - FIREWORKS_API_KEY 필요
576
- - BRAVE_SEARCH_API_KEY (선택사항)
577
- """)
578
-
579
  with gr.Row():
580
- # 메인 채팅 영역
581
  with gr.Column(scale=3):
582
  chatbot = gr.Chatbot(
583
  height=500,
584
  label="💬 대화",
585
- type="messages",
586
- show_copy_button=True
587
  )
588
 
589
  msg = gr.Textbox(
590
- label="질문 입력",
591
- placeholder="질문을 입력하세요... (실시간으로 답변이 생성됩니다)",
592
  lines=3
593
  )
594
 
595
  with gr.Row():
596
- submit = gr.Button("🚀 전송", variant="primary")
597
  clear = gr.Button("🔄 초기화")
598
- stop = gr.Button("⏹️ 중지", variant="stop")
599
 
600
- # 에이전트 사고 과정
601
- with gr.Accordion("🤖 에이전트 사고 과정", open=False):
602
  agent_thoughts = gr.Markdown()
603
 
604
- # 검색 결과
605
  with gr.Accordion("📚 검색 소스", open=False):
606
  search_sources = gr.Markdown()
607
 
608
- # 설정 패널
609
  with gr.Column(scale=1):
610
  gr.Markdown("### ⚙️ 설정")
611
 
612
- with gr.Group():
613
- use_search = gr.Checkbox(
614
- label="🔍 웹 검색 사용",
615
- value=True
616
- )
617
-
618
- show_agent_thoughts = gr.Checkbox(
619
- label="🧠 에이전트 사고과정 표시",
620
- value=True
621
- )
622
-
623
- search_count = gr.Slider(
624
- minimum=1,
625
- maximum=10,
626
- value=5,
627
- step=1,
628
- label="검색 결과 수"
629
- )
630
-
631
- temperature = gr.Slider(
632
- minimum=0,
633
- maximum=1,
634
- value=0.6,
635
- step=0.1,
636
- label="Temperature",
637
- info="낮을수록 일관성, 높을수록 창의성"
638
- )
639
-
640
- max_tokens = gr.Slider(
641
- minimum=500,
642
- maximum=4000,
643
- value=2000,
644
- step=100,
645
- label="Max Tokens"
646
- )
647
 
648
  gr.Markdown("""
649
- ### 📊 시스템 정보
650
 
651
- **🎭 에이전트 역할:**
652
- - 👔 **감독자**: 구조 설계
653
- - 🎨 **창의성**: 창의적 생성
654
- - 🔍 **비평자**: 검증/개선
655
- - ✅ **최종**: 종합/완성
 
656
 
657
- **✨ 특징:**
658
- - 실시간 스트리밍 출력
659
- - 다단계 검증 시스템
660
- - RAG 기반 정확성
661
  """)
662
 
663
- # 예제
664
  gr.Examples(
665
  examples=[
666
- "양자 컴퓨터의 원리를 초등학생도 이해할 있게 설명해줘",
667
- "2024년 AI 기술 트렌드와 미래 전망은?",
668
- "효과적인 프로그래밍 학습 방법을 단계별로 알려줘",
669
- "기후 변화가 한국 경제에 미치는 영향 분석해줘",
670
- "스타트업 창업 고려해야 핵심 요소들은?"
671
  ],
672
  inputs=msg
673
  )
674
 
675
- # 이벤트 바인딩 (스트리밍)
676
- submit_event = submit.click(
677
- process_query_streaming,
678
- inputs=[msg, chatbot, use_search, show_agent_thoughts,
679
- search_count, temperature, max_tokens],
 
 
 
 
 
 
 
 
 
 
 
 
 
680
  outputs=[chatbot, agent_thoughts, search_sources]
681
  ).then(
682
  lambda: "",
@@ -684,10 +814,9 @@ def create_gradio_interface():
684
  msg
685
  )
686
 
687
- msg_event = msg.submit(
688
- process_query_streaming,
689
- inputs=[msg, chatbot, use_search, show_agent_thoughts,
690
- search_count, temperature, max_tokens],
691
  outputs=[chatbot, agent_thoughts, search_sources]
692
  ).then(
693
  lambda: "",
@@ -695,14 +824,6 @@ def create_gradio_interface():
695
  msg
696
  )
697
 
698
- # 중지 버튼
699
- stop.click(
700
- None,
701
- None,
702
- None,
703
- cancels=[submit_event, msg_event]
704
- )
705
-
706
  clear.click(
707
  lambda: ([], "", ""),
708
  None,
@@ -719,31 +840,34 @@ def create_gradio_interface():
719
  if __name__ == "__main__":
720
  print("""
721
  ╔══════════════════════════════════════════════════════════════╗
722
- 🧠 Multi-Agent RAG System with Streaming Output 🧠
 
 
723
  ║ ║
724
- 감독자 → 창의성 생성자 → 비평자 → 최종 감독자
725
- 실시간 스트리밍으로 고품질 답변 생성
 
 
 
 
726
  ╚══════════════════════════════════════════════════════════════╝
727
  """)
728
 
729
  # API 키 확인
730
  if not os.getenv("FIREWORKS_API_KEY"):
731
  print("\n⚠️ FIREWORKS_API_KEY가 설정되지 않았습니다.")
732
- print("Hugging Face Spaces Settings에서 설정해주세요.")
733
 
734
  if not os.getenv("BRAVE_SEARCH_API_KEY"):
735
  print("\n⚠️ BRAVE_SEARCH_API_KEY가 설정되지 않았습니다.")
736
- print("검색 기능이 비활성화됩니다.")
737
 
738
  # Gradio 앱 실행
739
- demo = create_gradio_interface()
740
 
741
- # Hugging Face Spaces 환경 확인
742
  is_hf_spaces = os.getenv("SPACE_ID") is not None
743
 
744
  if is_hf_spaces:
745
- print("\n🤗 Hugging Face Spaces에서 실행 중...")
746
  demo.launch(server_name="0.0.0.0", server_port=7860)
747
  else:
748
- print("\n💻 로컬 환경에서 실행 중...")
749
  demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
 
1
  """
2
+ ⚡ Speed-Optimized Multi-Agent RAG System for Complex Questions
3
+ 병렬 처리, 스마트 캐싱, 동적 파이프라인으로 복잡한 질문도 빠르게 처리
 
4
  """
5
 
6
  import os
7
  import json
8
  import time
9
  import asyncio
10
+ import hashlib
11
  from typing import Optional, List, Dict, Any, Tuple, Generator, AsyncGenerator
12
+ from datetime import datetime, timedelta
13
  from enum import Enum
14
+ from collections import deque
15
  import threading
16
  import queue
17
+ from concurrent.futures import ThreadPoolExecutor, as_completed
18
+ import aiohttp
19
 
20
  import requests
21
  import gradio as gr
 
38
  FINALIZER = "finalizer"
39
 
40
 
41
+ class ExecutionMode(Enum):
42
+ """실행 모드 정의"""
43
+ PARALLEL = "parallel" # 병렬 처리
44
+ SEQUENTIAL = "sequential" # 순차 처리
45
+ HYBRID = "hybrid" # 하이브리드
46
+
47
+
48
  class Message(BaseModel):
49
  role: str
50
  content: str
51
+ timestamp: Optional[datetime] = None
52
 
53
 
54
  class AgentResponse(BaseModel):
55
  role: AgentRole
56
  content: str
57
+ processing_time: float
58
  metadata: Optional[Dict] = None
59
 
60
 
61
+ # ============================================================================
62
+ # 스마트 캐싱 시스템
63
+ # ============================================================================
64
+
65
+ class SmartCache:
66
+ """지능형 캐싱 시스템"""
67
+
68
+ def __init__(self, max_size: int = 100, ttl_hours: int = 24):
69
+ self.cache = {}
70
+ self.access_count = {}
71
+ self.timestamps = {}
72
+ self.max_size = max_size
73
+ self.ttl = timedelta(hours=ttl_hours)
74
+ self.reasoning_patterns = self._init_reasoning_patterns()
75
+
76
+ def _init_reasoning_patterns(self) -> Dict:
77
+ """자주 사용되는 추론 패턴 초기화"""
78
+ return {
79
+ "analysis": {
80
+ "structure": ["현황 분석", "핵심 요인", "영향 평가", "전략 제안"],
81
+ "keywords": ["분석", "평가", "영향", "전략"]
82
+ },
83
+ "comparison": {
84
+ "structure": ["대상 정의", "비교 기준", "장단점 분석", "결론"],
85
+ "keywords": ["비교", "차이", "장단점", "vs"]
86
+ },
87
+ "creative": {
88
+ "structure": ["문제 정의", "창의적 접근", "구현 방법", "예상 효과"],
89
+ "keywords": ["창의적", "혁신적", "새로운", "아이디어"]
90
+ },
91
+ "technical": {
92
+ "structure": ["기술 개요", "핵심 원리", "구현 상세", "실용 예시"],
93
+ "keywords": ["기술", "구현", "코드", "시스템"]
94
+ }
95
+ }
96
+
97
+ def get_query_hash(self, query: str) -> str:
98
+ """쿼리 해시 생성"""
99
+ return hashlib.md5(query.encode()).hexdigest()
100
+
101
+ def get(self, query: str) -> Optional[Dict]:
102
+ """캐시에서 조회"""
103
+ query_hash = self.get_query_hash(query)
104
+
105
+ if query_hash in self.cache:
106
+ # TTL 체크
107
+ if datetime.now() - self.timestamps[query_hash] < self.ttl:
108
+ self.access_count[query_hash] += 1
109
+ return self.cache[query_hash]
110
+ else:
111
+ # 만료된 캐시 삭제
112
+ del self.cache[query_hash]
113
+ del self.timestamps[query_hash]
114
+ del self.access_count[query_hash]
115
+
116
+ return None
117
+
118
+ def set(self, query: str, response: Dict):
119
+ """캐시에 저장"""
120
+ query_hash = self.get_query_hash(query)
121
+
122
+ # 캐시 크기 관리
123
+ if len(self.cache) >= self.max_size:
124
+ # LRU 정책: 가장 적게 사용된 항목 제거
125
+ least_used = min(self.access_count, key=self.access_count.get)
126
+ del self.cache[least_used]
127
+ del self.timestamps[least_used]
128
+ del self.access_count[least_used]
129
+
130
+ self.cache[query_hash] = response
131
+ self.timestamps[query_hash] = datetime.now()
132
+ self.access_count[query_hash] = 1
133
+
134
+ def get_reasoning_pattern(self, query: str) -> Optional[Dict]:
135
+ """쿼리에 적합한 추론 패턴 반환"""
136
+ query_lower = query.lower()
137
+
138
+ for pattern_type, pattern_data in self.reasoning_patterns.items():
139
+ if any(keyword in query_lower for keyword in pattern_data["keywords"]):
140
+ return {
141
+ "type": pattern_type,
142
+ "structure": pattern_data["structure"]
143
+ }
144
+
145
+ return None
146
 
147
 
148
  # ============================================================================
149
+ # 병렬 처리 최적화 Brave Search
150
  # ============================================================================
151
 
152
+ class AsyncBraveSearch:
153
+ """비동기 Brave 검색 클라이언트"""
154
+
155
  def __init__(self, api_key: Optional[str] = None):
156
  self.api_key = api_key or os.getenv("BRAVE_SEARCH_API_KEY")
 
 
 
157
  self.base_url = "https://api.search.brave.com/res/v1/web/search"
 
 
 
 
158
 
159
+ async def search_async(self, query: str, count: int = 5) -> List[Dict]:
160
+ """비동기 검색"""
161
  if not self.api_key:
162
  return []
163
 
164
+ headers = {
165
+ "Accept": "application/json",
166
+ "X-Subscription-Token": self.api_key
167
+ }
168
+
169
  params = {
170
  "q": query,
171
  "count": count,
 
175
  }
176
 
177
  try:
178
+ async with aiohttp.ClientSession() as session:
179
+ async with session.get(
180
+ self.base_url,
181
+ headers=headers,
182
+ params=params,
183
+ timeout=aiohttp.ClientTimeout(total=5)
184
+ ) as response:
185
+ if response.status == 200:
186
+ data = await response.json()
187
+
188
+ results = []
189
+ if "web" in data and "results" in data["web"]:
190
+ for item in data["web"]["results"][:count]:
191
+ results.append({
192
+ "title": item.get("title", ""),
193
+ "url": item.get("url", ""),
194
+ "description": item.get("description", ""),
195
+ "age": item.get("age", "")
196
+ })
197
+
198
+ return results
199
+ except:
 
200
  return []
201
+
202
+ return []
203
 
204
 
205
  # ============================================================================
206
+ # 최적화된 Fireworks 클라이언트
207
  # ============================================================================
208
 
209
+ class OptimizedFireworksClient:
210
+ """최적화된 LLM 클라이언트"""
211
+
212
  def __init__(self, api_key: Optional[str] = None):
213
  self.api_key = api_key or os.getenv("FIREWORKS_API_KEY")
214
  if not self.api_key:
 
220
  "Content-Type": "application/json",
221
  "Authorization": f"Bearer {self.api_key}"
222
  }
 
 
 
 
 
 
 
 
 
 
 
 
223
 
224
+ # 항상 최고 성능 ���델 사용 (복잡한 질문 전제)
225
+ self.model = "accounts/fireworks/models/qwen3-235b-a22b-instruct-2507"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
 
227
+ async def chat_stream_async(
228
+ self,
229
+ messages: List[Dict],
230
+ **kwargs
231
+ ) -> AsyncGenerator[str, None]:
232
+ """비동기 스트리밍 대화"""
233
+
234
  payload = {
235
+ "model": self.model,
236
  "messages": messages,
237
+ "max_tokens": kwargs.get("max_tokens", 2000),
238
  "temperature": kwargs.get("temperature", 0.7),
239
  "top_p": kwargs.get("top_p", 1.0),
240
  "top_k": kwargs.get("top_k", 40),
 
242
  }
243
 
244
  try:
245
+ async with aiohttp.ClientSession() as session:
246
+ async with session.post(
247
+ self.base_url,
248
+ headers={**self.headers, "Accept": "text/event-stream"},
249
+ json=payload,
250
+ timeout=aiohttp.ClientTimeout(total=30)
251
+ ) as response:
252
+ async for line in response.content:
253
+ line_str = line.decode('utf-8').strip()
254
+ if line_str.startswith("data: "):
255
+ data_str = line_str[6:]
256
+ if data_str == "[DONE]":
257
+ break
258
+ try:
259
+ data = json.loads(data_str)
260
+ if "choices" in data and len(data["choices"]) > 0:
261
+ delta = data["choices"][0].get("delta", {})
262
+ if "content" in delta:
263
+ yield delta["content"]
264
+ except json.JSONDecodeError:
265
+ continue
 
 
 
 
266
  except Exception as e:
267
+ yield f"오류: {str(e)}"
268
 
269
 
270
  # ============================================================================
271
+ # 경량화된 추론 체인
272
  # ============================================================================
273
 
274
+ class LightweightReasoningChain:
275
+ """빠른 추론을 위한 템플릿 기반 시스템"""
 
 
 
 
 
276
 
277
+ def __init__(self):
278
+ self.templates = {
279
+ "problem_solving": {
280
+ "steps": ["문제 분해", "핵심 요인", "해결 방안", "구현 전략"],
281
+ "prompt": "체계적으로 단계별로 분석하고 해결책을 제시하세요."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  },
283
+ "creative_thinking": {
284
+ "steps": ["기존 접근", "창의적 대안", "혁신 포인트", "실행 방법"],
285
+ "prompt": "기존 방식을 넘어선 창의적이고 혁신적인 접근을 제시하세요."
 
 
 
 
 
 
 
 
286
  },
287
+ "critical_analysis": {
288
+ "steps": ["현황 평가", "강점/약점", "기회/위협", "개선 방향"],
289
+ "prompt": "비판적 관점에서 철저히 분석하고 개선점을 도출하세요."
 
 
 
 
 
 
 
 
290
  }
291
  }
292
 
293
+ def get_reasoning_structure(self, query_type: str) -> Dict:
294
+ """쿼리 유형에 맞는 추론 구조 반환"""
295
+ # 기본값은 problem_solving
296
+ return self.templates.get(query_type, self.templates["problem_solving"])
297
+
298
+
299
+ # ============================================================================
300
+ # 조기 종료 메커니즘
301
+ # ============================================================================
302
+
303
+ class QualityChecker:
304
+ """품질 체크 및 조기 종료 결정"""
 
 
305
 
306
+ def __init__(self, min_quality: float = 0.75):
307
+ self.min_quality = min_quality
308
+ self.quality_metrics = {
309
+ "length": 0.2,
310
+ "structure": 0.3,
311
+ "completeness": 0.3,
312
+ "clarity": 0.2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  }
314
+
315
+ def evaluate_response(self, response: str, query: str) -> Tuple[float, bool]:
316
+ """응답 품질 평가"""
317
+ scores = {}
318
+
319
+ # 길이 평가
320
+ scores["length"] = min(len(response) / 1000, 1.0) # 1000자 기준
321
+
322
+ # 구조 평가
323
+ structure_markers = ["1.", "2.", "•", "-", "첫째", "둘째", "결론", "요약"]
324
+ scores["structure"] = sum(1 for m in structure_markers if m in response) / len(structure_markers)
325
+
326
+ # 완전성 평가 (쿼리 키워드 포함 여부)
327
+ query_words = set(query.split())
328
+ response_words = set(response.split())
329
+ scores["completeness"] = len(query_words & response_words) / max(len(query_words), 1)
330
+
331
+ # 명확성 평가 (문장 구조)
332
+ sentences = response.split('.')
333
+ avg_sentence_length = sum(len(s.split()) for s in sentences) / max(len(sentences), 1)
334
+ scores["clarity"] = min(avg_sentence_length / 20, 1.0) # 20단어 기준
335
+
336
+ # 가중 평균 계산
337
+ total_score = sum(
338
+ scores[metric] * weight
339
+ for metric, weight in self.quality_metrics.items()
340
+ )
341
 
342
+ should_continue = total_score < self.min_quality
 
 
 
 
 
 
343
 
344
+ return total_score, should_continue
345
+
346
 
347
+ # ============================================================================
348
+ # 스트리밍 최적화
349
+ # ============================================================================
350
 
351
+ class OptimizedStreaming:
352
+ """스트리밍 버퍼 최적화"""
353
+
354
+ def __init__(self, chunk_size: int = 100, flush_interval: float = 0.1):
355
+ self.chunk_size = chunk_size
356
+ self.flush_interval = flush_interval
357
+ self.buffer = ""
358
+ self.last_flush = time.time()
359
+
360
+ async def buffer_and_yield(
361
+ self,
362
+ stream: AsyncGenerator[str, None]
363
+ ) -> AsyncGenerator[str, None]:
364
+ """버퍼링된 스트리밍"""
365
 
366
+ async for chunk in stream:
367
+ self.buffer += chunk
368
+ current_time = time.time()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
369
 
370
+ if (len(self.buffer) >= self.chunk_size or
371
+ current_time - self.last_flush >= self.flush_interval):
372
+
373
+ yield self.buffer
374
+ self.buffer = ""
375
+ self.last_flush = current_time
376
 
377
+ # 남은 버퍼 플러시
378
+ if self.buffer:
379
+ yield self.buffer
 
 
 
380
 
 
 
381
 
382
+ # ============================================================================
383
+ # 통합 최적화 멀티 에이전트 시스템
384
+ # ============================================================================
 
 
385
 
386
+ class SpeedOptimizedMultiAgentSystem:
387
+ """속도 최적화된 멀티 에이전트 시스템"""
388
+
389
+ def __init__(self):
390
+ self.llm = OptimizedFireworksClient()
391
+ self.search = AsyncBraveSearch()
392
+ self.cache = SmartCache()
393
+ self.reasoning = LightweightReasoningChain()
394
+ self.quality_checker = QualityChecker()
395
+ self.streaming = OptimizedStreaming()
396
+
397
+ # 컴팩트 프롬프트
398
+ self.compact_prompts = self._init_compact_prompts()
399
+
400
+ # 병렬 처리 풀
401
+ self.executor = ThreadPoolExecutor(max_workers=4)
402
+
403
+ def _init_compact_prompts(self) -> Dict:
404
+ """압축된 고효율 프롬프트"""
405
+ return {
406
+ AgentRole.SUPERVISOR: """[감독자-구조설계]
407
+ 즉시분석: 핵심의도+필요정보+답변구조
408
+ 출력: 5개 핵심포인트(각 1문장)
409
+ 추론체계 명시""",
410
+
411
+ AgentRole.CREATIVE: """[창의성생성자]
412
+ 입력구조 따라 창의적 확장
413
+ 실용예시+혁신접근+구체조언
414
+ 불필요설명 제거""",
415
+
416
+ AgentRole.CRITIC: """[비평자-검증]
417
+ 신속검토: 정확성/논리성/실용성
418
+ 개선포인트 3개만
419
+ 각 2문장 이내""",
420
+
421
+ AgentRole.FINALIZER: """[최종통합]
422
+ 모든의견 종합→최적답변
423
+ 명확구조+실용정보+창의균형
424
+ 핵심먼저+상세는후순위"""
425
+ }
426
+
427
+ async def parallel_process_agents(
428
+ self,
429
+ query: str,
430
+ search_results: List[Dict],
431
+ show_progress: bool = True
432
+ ) -> AsyncGenerator[Tuple[str, str], None]:
433
+ """병렬 처리 파이프라인"""
434
 
435
+ start_time = time.time()
436
+ search_context = self._format_search_results(search_results)
437
  accumulated_response = ""
438
+ agent_thoughts = ""
439
+
440
+ # 캐시 확인
441
+ cached = self.cache.get(query)
442
+ if cached:
443
+ yield cached["response"], "✨ 캐시에서 즉시 로드"
444
+ return
445
+
446
+ # 추론 패턴 결정
447
+ reasoning_pattern = self.cache.get_reasoning_pattern(query)
448
+
449
+ try:
450
+ # === 1단계: 감독자 + 검색 병렬 실행 ===
451
+ if show_progress:
452
+ agent_thoughts = "### 🚀 병렬 처리 시작\n"
453
+ agent_thoughts += "👔 감독자 분석 + 🔍 추가 검색 동시 진행...\n\n"
454
+ yield accumulated_response, agent_thoughts
455
+
456
+ # 감독자 프롬프트
457
+ supervisor_prompt = f"""
458
+ 질문: {query}
459
+ 검색결과: {search_context}
460
+ 추론패턴: {reasoning_pattern}
461
+ 즉시 핵심구조 5개 제시"""
462
+
463
+ supervisor_response = ""
464
+ supervisor_task = self.llm.chat_stream_async(
465
+ messages=[
466
+ {"role": "system", "content": self.compact_prompts[AgentRole.SUPERVISOR]},
467
+ {"role": "user", "content": supervisor_prompt}
468
+ ],
469
+ temperature=0.3,
470
+ max_tokens=500
471
+ )
472
+
473
+ # 감독자 스트리밍 (버퍼링)
474
+ async for chunk in self.streaming.buffer_and_yield(supervisor_task):
475
+ supervisor_response += chunk
476
+ if show_progress and len(supervisor_response) < 300:
477
+ agent_thoughts = f"### 👔 감독자 분석\n{supervisor_response[:300]}...\n\n"
478
+ yield accumulated_response, agent_thoughts
479
+
480
+ # === 2단계: 창의성 + 비평 준비 병렬 ===
481
+ if show_progress:
482
+ agent_thoughts += "### 🎨 창의성 생성자 + 🔍 비평자 준비...\n\n"
483
+ yield accumulated_response, agent_thoughts
484
+
485
+ # 창의성 생성 시작
486
+ creative_prompt = f"""
487
+ 질문: {query}
488
+ 감독자구조: {supervisor_response}
489
+ 검색결과: {search_context}
490
+ 창의적+실용적 답변 즉시생성"""
491
+
492
+ creative_response = ""
493
+ creative_partial = "" # 비평자용 부분 응답
494
+ critic_started = False
495
+ critic_response = ""
496
+
497
+ creative_task = self.llm.chat_stream_async(
498
+ messages=[
499
+ {"role": "system", "content": self.compact_prompts[AgentRole.CREATIVE]},
500
+ {"role": "user", "content": creative_prompt}
501
+ ],
502
+ temperature=0.8,
503
+ max_tokens=1500
504
+ )
505
+
506
+ # 창의성 스트리밍 + 비평자 조기 시작
507
+ async for chunk in self.streaming.buffer_and_yield(creative_task):
508
+ creative_response += chunk
509
+ creative_partial += chunk
510
+
511
+ # 창의성 응답이 500자 넘으면 비평자 시작
512
+ if len(creative_partial) > 500 and not critic_started:
513
+ critic_started = True
514
+
515
+ # 비평자 비동기 시작
516
+ critic_prompt = f"""
517
+ 원본질문: {query}
518
+ 창의성답변(일부): {creative_partial}
519
+ 신속검토→개선점3개"""
520
+
521
+ critic_task = asyncio.create_task(
522
+ self._run_critic_async(critic_prompt)
523
+ )
524
+
525
+ if show_progress:
526
+ display_creative = creative_response[:400] + "..." if len(creative_response) > 400 else creative_response
527
+ agent_thoughts = f"### 🎨 창의성 생성자\n{display_creative}\n\n"
528
+ yield accumulated_response, agent_thoughts
529
+
530
+ # 비평자 결과 대기
531
+ if critic_started:
532
+ critic_response = await critic_task
533
+
534
+ if show_progress:
535
+ agent_thoughts += f"### 🔍 비평자 검토\n{critic_response[:200]}...\n\n"
536
+ yield accumulated_response, agent_thoughts
537
+
538
+ # === 3단계: 품질 체크 및 조기 종료 ===
539
+ quality_score, need_more = self.quality_checker.evaluate_response(
540
+ creative_response, query
541
+ )
542
+
543
+ if not need_more and quality_score > 0.85:
544
+ # 품질이 충분히 높으면 바로 반환
545
+ accumulated_response = creative_response
546
+
547
+ if show_progress:
548
+ agent_thoughts += f"### ✅ 품질 충족 (점수: {quality_score:.2f})\n조기 완료!\n"
549
+
550
+ # 캐시 저장
551
+ self.cache.set(query, {
552
+ "response": accumulated_response,
553
+ "timestamp": datetime.now()
554
+ })
555
+
556
+ yield accumulated_response, agent_thoughts
557
+ return
558
+
559
+ # === 4단계: 최종 통합 (스트리밍) ===
560
+ if show_progress:
561
+ agent_thoughts += "### ✅ 최종 통합 중...\n\n"
562
+ yield accumulated_response, agent_thoughts
563
+
564
+ final_prompt = f"""
565
+ 질문: {query}
566
+ 창의성답변: {creative_response}
567
+ 비평피드백: {critic_response}
568
+ 감독자구조: {supervisor_response}
569
+ 최종통합→완벽답변"""
570
+
571
+ final_task = self.llm.chat_stream_async(
572
+ messages=[
573
+ {"role": "system", "content": self.compact_prompts[AgentRole.FINALIZER]},
574
+ {"role": "user", "content": final_prompt}
575
+ ],
576
+ temperature=0.5,
577
+ max_tokens=2500
578
+ )
579
+
580
+ # 최종 답변 스트리밍
581
+ accumulated_response = ""
582
+ async for chunk in self.streaming.buffer_and_yield(final_task):
583
+ accumulated_response += chunk
584
+ yield accumulated_response, agent_thoughts
585
+
586
+ # 처리 시간 추가
587
+ processing_time = time.time() - start_time
588
+ accumulated_response += f"\n\n---\n⚡ 처리 시간: {processing_time:.1f}초"
589
+
590
+ # 캐시 저장
591
+ self.cache.set(query, {
592
+ "response": accumulated_response,
593
+ "timestamp": datetime.now()
594
+ })
595
+
596
+ yield accumulated_response, agent_thoughts
597
+
598
+ except Exception as e:
599
+ error_msg = f"❌ 오류 발생: {str(e)}"
600
+ yield error_msg, agent_thoughts
601
+
602
+ async def _run_critic_async(self, prompt: str) -> str:
603
+ """비평자 비동기 실행"""
604
+ try:
605
+ response = ""
606
+ async for chunk in self.llm.chat_stream_async(
607
+ messages=[
608
+ {"role": "system", "content": self.compact_prompts[AgentRole.CRITIC]},
609
+ {"role": "user", "content": prompt}
610
+ ],
611
+ temperature=0.2,
612
+ max_tokens=500
613
+ ):
614
+ response += chunk
615
+ return response
616
+ except:
617
+ return "비평 처리 중 오류"
618
+
619
+ def _format_search_results(self, results: List[Dict]) -> str:
620
+ """검색 결과 압축 포맷"""
621
+ if not results:
622
+ return "검색결과없음"
623
+
624
+ formatted = []
625
+ for i, r in enumerate(results[:3], 1): # 상위 3개만
626
+ formatted.append(f"[{i}]{r.get('title','')[:50]}:{r.get('description','')[:100]}")
627
+
628
+ return " | ".join(formatted)
629
 
630
 
631
  # ============================================================================
632
+ # Gradio UI (최적화 버전)
633
  # ============================================================================
634
 
635
+ def create_optimized_gradio_interface():
636
+ """최적화된 Gradio 인터페이스"""
637
 
638
+ # 시스템 초기화
639
+ system = SpeedOptimizedMultiAgentSystem()
 
 
 
 
 
 
 
 
 
640
 
641
+ async def process_query_optimized(
642
  message: str,
643
  history: List[Dict],
644
  use_search: bool,
645
  show_agent_thoughts: bool,
646
+ search_count: int
 
 
647
  ):
648
+ """최적화된 쿼리 처리"""
649
 
650
+ if not message:
651
  yield history, "", ""
652
  return
653
 
654
  try:
655
+ # 검색 수행 (비동기)
656
  search_results = []
657
  search_display = ""
658
 
659
+ if use_search:
660
+ # 검색 상태 표시
661
  history_with_message = history + [
662
  {"role": "user", "content": message},
663
+ {"role": "assistant", "content": " 고속 처리 중..."}
664
  ]
665
  yield history_with_message, "", ""
666
 
667
+ # 비동기 검색
668
+ search_results = await system.search.search_async(message, count=search_count)
669
 
 
670
  if search_results:
671
  search_display = "## 📚 참고 자료\n\n"
672
+ for i, result in enumerate(search_results[:3], 1):
673
+ search_display += f"**{i}. [{result['title'][:50]}]({result['url']})**\n"
674
  search_display += f" {result['description'][:100]}...\n\n"
675
 
 
 
 
 
 
 
676
  # 사용자 메시지 추가
677
  current_history = history + [{"role": "user", "content": message}]
678
 
679
+ # 병렬 처리 실행
680
+ async for response, thoughts in system.parallel_process_agents(
 
 
 
681
  query=message,
682
  search_results=search_results,
683
+ show_progress=show_agent_thoughts
 
684
  ):
685
+ updated_history = current_history + [
686
+ {"role": "assistant", "content": response}
687
+ ]
688
+ yield updated_history, thoughts, search_display
 
 
 
 
 
 
 
 
 
689
 
690
  except Exception as e:
 
691
  error_history = history + [
692
  {"role": "user", "content": message},
693
+ {"role": "assistant", "content": f"❌ 오류: {str(e)}"}
694
  ]
695
  yield error_history, "", ""
696
 
697
  # Gradio 인터페이스
698
  with gr.Blocks(
699
+ title="⚡ Speed-Optimized Multi-Agent System",
700
  theme=gr.themes.Soft(),
701
  css="""
702
  .gradio-container {
703
  max-width: 1400px !important;
704
  margin: auto !important;
705
  }
 
 
 
706
  """
707
  ) as demo:
708
  gr.Markdown("""
709
+ # 고속 Multi-Agent RAG System
710
+ ### 복잡한 질문도 5초 이내 처리 목표
711
+
712
+ **최적화 기술:**
713
+ - 🚀 병렬 처리: 에이전트 동시 실행
714
+ - 💾 스마트 캐싱: 자주 묻는 패턴 즉시 응답
715
+ - ⚡ 스트리밍 버퍼: 네트워크 최적화
716
+ - 🎯 조기 종료: 품질 충족 시 즉시 완료
717
  """)
718
 
 
 
 
 
 
 
 
719
  with gr.Row():
 
720
  with gr.Column(scale=3):
721
  chatbot = gr.Chatbot(
722
  height=500,
723
  label="💬 대화",
724
+ type="messages"
 
725
  )
726
 
727
  msg = gr.Textbox(
728
+ label="복잡한 질문 입력",
729
+ placeholder="분석, 전략, 창의적 해결이 필요한 복잡한 질문을 입력하세요...",
730
  lines=3
731
  )
732
 
733
  with gr.Row():
734
+ submit = gr.Button(" 고속 처리", variant="primary")
735
  clear = gr.Button("🔄 초기화")
 
736
 
737
+ with gr.Accordion("🤖 에이전트 처리 과정", open=False):
 
738
  agent_thoughts = gr.Markdown()
739
 
 
740
  with gr.Accordion("📚 검색 소스", open=False):
741
  search_sources = gr.Markdown()
742
 
 
743
  with gr.Column(scale=1):
744
  gr.Markdown("### ⚙️ 설정")
745
 
746
+ use_search = gr.Checkbox(
747
+ label="🔍 웹 검색 사용",
748
+ value=True
749
+ )
750
+
751
+ show_agent_thoughts = gr.Checkbox(
752
+ label="🧠 처리 과정 표시",
753
+ value=True
754
+ )
755
+
756
+ search_count = gr.Slider(
757
+ minimum=3,
758
+ maximum=10,
759
+ value=5,
760
+ step=1,
761
+ label="검색 결과 수"
762
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
763
 
764
  gr.Markdown("""
765
+ ### 최적화 상태
766
 
767
+ **활성화된 최적화:**
768
+ - 병렬 처리
769
+ - 스마트 캐싱
770
+ - 버퍼 스트리밍
771
+ - ✅ 조기 종료
772
+ - ✅ 압축 프롬프트
773
 
774
+ **예상 처리 시간:**
775
+ - 캐시 히트: < 1초
776
+ - 일반 질문: 3-5초
777
+ - 복잡한 질문: 5-8초
778
  """)
779
 
780
+ # 복잡한 질문 예제
781
  gr.Examples(
782
  examples=[
783
+ "AI 기술이 향후 10년간 한국 경제에 미칠 영향을 다각도로 분석하고 대응 전략을 제시해줘",
784
+ "스타트업이 대기업과 경쟁하기 위한 혁신적인 전략을 단계별로 수립해줘",
785
+ "기후변화 대응을 위한 창의적인 비즈니스 모델 5가지를 구체적으로 설계해줘",
786
+ "양자컴퓨터가 현재 암호화 체계에 미칠 영향과 대안을 기술적으로 분석해줘",
787
+ "메타버스 시대의 교육 혁신 방안을 실제 구현 가능한 수준으로 제안해줘"
788
  ],
789
  inputs=msg
790
  )
791
 
792
+ # 이벤트 바인딩
793
+ def process_wrapper(message, history, use_search, show_thoughts, search_count):
794
+ """동기 래퍼 for Gradio"""
795
+ loop = asyncio.new_event_loop()
796
+ asyncio.set_event_loop(loop)
797
+
798
+ async def run():
799
+ async for result in process_query_optimized(
800
+ message, history, use_search, show_thoughts, search_count
801
+ ):
802
+ yield result
803
+
804
+ for result in loop.run_until_complete(run()):
805
+ yield result
806
+
807
+ submit.click(
808
+ process_wrapper,
809
+ inputs=[msg, chatbot, use_search, show_agent_thoughts, search_count],
810
  outputs=[chatbot, agent_thoughts, search_sources]
811
  ).then(
812
  lambda: "",
 
814
  msg
815
  )
816
 
817
+ msg.submit(
818
+ process_wrapper,
819
+ inputs=[msg, chatbot, use_search, show_agent_thoughts, search_count],
 
820
  outputs=[chatbot, agent_thoughts, search_sources]
821
  ).then(
822
  lambda: "",
 
824
  msg
825
  )
826
 
 
 
 
 
 
 
 
 
827
  clear.click(
828
  lambda: ([], "", ""),
829
  None,
 
840
  if __name__ == "__main__":
841
  print("""
842
  ╔══════════════════════════════════════════════════════════════╗
843
+ Speed-Optimized Multi-Agent RAG System
844
+ ║ ║
845
+ ║ 복잡한 질문도 5초 이내 처리하는 고속 AI 시스템 ║
846
  ║ ║
847
+ 최적화 기술:
848
+ 병렬 처리 파이프라인
849
+ ║ • 스마트 캐싱 시스템 ║
850
+ ║ • 스트리밍 버퍼 최적화 ║
851
+ ║ • 품질 기반 조기 종료 ║
852
+ ║ • 압축 프롬프트 엔지니어링 ║
853
  ╚══════════════════════════════════════════════════════════════╝
854
  """)
855
 
856
  # API 키 확인
857
  if not os.getenv("FIREWORKS_API_KEY"):
858
  print("\n⚠️ FIREWORKS_API_KEY가 설정되지 않았습니다.")
 
859
 
860
  if not os.getenv("BRAVE_SEARCH_API_KEY"):
861
  print("\n⚠️ BRAVE_SEARCH_API_KEY가 설정되지 않았습니다.")
 
862
 
863
  # Gradio 앱 실행
864
+ demo = create_optimized_gradio_interface()
865
 
 
866
  is_hf_spaces = os.getenv("SPACE_ID") is not None
867
 
868
  if is_hf_spaces:
869
+ print("\n🤗 Hugging Face Spaces에서 최적화 모드로 실행 중...")
870
  demo.launch(server_name="0.0.0.0", server_port=7860)
871
  else:
872
+ print("\n💻 로컬 환경에서 최적화 모드로 실행 중...")
873
  demo.launch(server_name="0.0.0.0", server_port=7860, share=False)