aiqtech commited on
Commit
505cfe8
Β·
verified Β·
1 Parent(s): 363b044

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +241 -481
app.py CHANGED
@@ -1,213 +1,160 @@
1
- # ν˜‘λ ₯적 LLM μ‹œμŠ€ν…œ - Python κ΅¬ν˜„
2
-
3
- ## 1. FastAPI λ°±μ—”λ“œ (backend.py)
4
-
5
- ```python
6
- from fastapi import FastAPI, HTTPException
7
- from fastapi.middleware.cors import CORSMiddleware
8
- from pydantic import BaseModel
9
- from typing import List, Optional, Dict, Any
10
  import os
11
- import httpx
12
- import asyncio
13
  import json
 
14
  from datetime import datetime
 
 
 
15
  import logging
16
 
17
  # λ‘œκΉ… μ„€μ •
18
  logging.basicConfig(level=logging.INFO)
19
  logger = logging.getLogger(__name__)
20
 
21
- app = FastAPI(title="Collaborative LLM System")
22
-
23
- # CORS μ„€μ •
24
- app.add_middleware(
25
- CORSMiddleware,
26
- allow_origins=["*"],
27
- allow_credentials=True,
28
- allow_methods=["*"],
29
- allow_headers=["*"],
30
- )
31
-
32
  # ν™˜κ²½ λ³€μˆ˜μ—μ„œ 토큰 κ°€μ Έμ˜€κΈ°
33
- FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN")
34
- if not FRIENDLI_TOKEN:
35
- raise ValueError("FRIENDLI_TOKEN ν™˜κ²½ λ³€μˆ˜κ°€ μ„€μ •λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€.")
36
-
37
  API_URL = "https://api.friendli.ai/dedicated/v1/chat/completions"
38
  MODEL_ID = "dep89a2fld32mcm"
39
 
40
- # μš”μ²­/응닡 λͺ¨λΈ
41
- class Message(BaseModel):
42
- role: str
43
- content: str
44
 
45
- class LLMRequest(BaseModel):
46
- messages: List[Message]
47
- role: str # 'supervisor' or 'executor'
48
- stream: bool = True
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
- class CollaborativeRequest(BaseModel):
51
- user_query: str
52
- max_iterations: int = 3
53
 
54
- class LLMResponse(BaseModel):
55
- content: str
56
- role: str
57
- timestamp: str
58
 
59
- # LLM 호좜 ν•¨μˆ˜
60
- async def call_llm(messages: List[Dict[str, str]], role: str, stream: bool = True):
61
- """LLM API 호좜"""
62
-
63
- # 역할에 λ”°λ₯Έ μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ
64
- system_prompts = {
65
- "supervisor": """당신은 κ±°μ‹œμ  κ΄€μ μ—μ„œ λΆ„μ„ν•˜κ³  μ§€λ„ν•˜λŠ” κ°λ…μž AIμž…λ‹ˆλ‹€.
66
- - 전체적인 λ°©ν–₯κ³Ό ν”„λ ˆμž„μ›Œν¬λ₯Ό μ œμ‹œν•©λ‹ˆλ‹€.
67
- - μ‹€ν–‰μž AI의 닡변을 κ²€ν† ν•˜κ³  κ°œμ„ μ μ„ μ œμ•ˆν•©λ‹ˆλ‹€.
68
- - ꡬ쑰적이고 체계적인 접근을 μΆ”κ΅¬ν•©λ‹ˆλ‹€.""",
69
-
70
- "executor": """당신은 세뢀적인 λ‚΄μš©μ„ κ΅¬ν˜„ν•˜λŠ” μ‹€ν–‰μž AIμž…λ‹ˆλ‹€.
71
- - κ°λ…μž AI의 지침에 따라 ꡬ체적인 λ‚΄μš©μ„ μž‘μ„±ν•©λ‹ˆλ‹€.
72
- - μ‹€μš©μ μ΄κ³  μ‹€ν–‰ κ°€λŠ₯ν•œ μ„ΈλΆ€ 사항에 μ§‘μ€‘ν•©λ‹ˆλ‹€.
73
- - ν•„μš”μ‹œ κ°λ…μžμ—κ²Œ μΆ”κ°€ 지침을 μš”μ²­ν•©λ‹ˆλ‹€."""
74
- }
75
-
76
- # λ©”μ‹œμ§€ ꡬ성
77
- full_messages = [
78
- {"role": "system", "content": system_prompts.get(role, "")},
79
- *messages
80
- ]
81
-
82
- headers = {
83
- "Authorization": f"Bearer {FRIENDLI_TOKEN}",
84
- "Content-Type": "application/json"
85
- }
86
 
87
- payload = {
88
- "model": MODEL_ID,
89
- "messages": full_messages,
90
- "max_tokens": 2048,
91
- "temperature": 0.7,
92
- "top_p": 0.8,
93
- "stream": stream,
94
- "stream_options": {"include_usage": True} if stream else None
95
- }
 
96
 
97
- async with httpx.AsyncClient(timeout=60.0) as client:
 
98
  try:
99
- if stream:
100
- response_text = ""
101
- async with client.stream("POST", API_URL, json=payload, headers=headers) as response:
102
- async for line in response.aiter_lines():
103
- if line.startswith("data: "):
104
- data = line[6:]
105
- if data == "[DONE]":
106
- break
107
- try:
108
- chunk = json.loads(data)
109
- if "choices" in chunk and chunk["choices"]:
110
- content = chunk["choices"][0].get("delta", {}).get("content", "")
111
- response_text += content
112
- yield content
113
- except json.JSONDecodeError:
114
- continue
115
- return response_text
116
- else:
117
- response = await client.post(API_URL, json=payload, headers=headers)
118
- response.raise_for_status()
 
 
 
 
 
 
 
 
 
119
  data = response.json()
120
  return data["choices"][0]["message"]["content"]
 
 
 
121
 
122
  except Exception as e:
123
- logger.error(f"LLM API 호좜 였λ₯˜: {str(e)}")
124
- raise HTTPException(status_code=500, detail=f"LLM API 호좜 μ‹€νŒ¨: {str(e)}")
125
-
126
- # API μ—”λ“œν¬μΈνŠΈ
127
- @app.post("/api/llm/single")
128
- async def single_llm_call(request: LLMRequest):
129
- """단일 LLM 호좜"""
130
- messages = [{"role": msg.role, "content": msg.content} for msg in request.messages]
131
-
132
- if request.stream:
133
- async def generate():
134
- async for chunk in call_llm(messages, request.role, stream=True):
135
- yield chunk
136
- return generate()
137
- else:
138
- content = await call_llm(messages, request.role, stream=False)
139
- return LLMResponse(
140
- content=content,
141
- role=request.role,
142
- timestamp=datetime.now().isoformat()
143
- )
144
-
145
- @app.post("/api/llm/collaborate")
146
- async def collaborative_process(request: CollaborativeRequest):
147
- """ν˜‘λ ₯적 LLM 처리"""
148
- user_query = request.user_query
149
- conversation_log = []
150
 
151
- try:
152
- # 1단계: κ°λ…μž AI의 초기 뢄석
153
- supervisor_prompt = f"""μ‚¬μš©μž 질문: {user_query}
154
-
155
- 이 μ§ˆλ¬Έμ— λŒ€ν•œ 전체적인 μ ‘κ·Ό λ°©ν–₯κ³Ό ν”„λ ˆμž„μ›Œν¬λ₯Ό μ œμ‹œν•΄μ£Όμ„Έμš”.
156
- 핡심 μš”μ†Œμ™€ 고렀사항을 κ΅¬μ‘°ν™”ν•˜μ—¬ μ„€λͺ…ν•΄μ£Όμ„Έμš”."""
157
-
158
- supervisor_response = ""
159
- async for chunk in call_llm([{"role": "user", "content": supervisor_prompt}], "supervisor"):
160
- supervisor_response += chunk
161
-
162
- conversation_log.append({
163
- "role": "supervisor",
164
- "stage": "initial_analysis",
165
- "content": supervisor_response,
166
- "timestamp": datetime.now().isoformat()
167
- })
168
 
169
- # 2단계: μ‹€ν–‰μž AI의 μ„ΈλΆ€ κ΅¬ν˜„
170
- executor_prompt = f"""κ°λ…μž AI의 μ§€μΉ¨:
171
- {supervisor_response}
172
-
173
- μ‚¬μš©μž 질문: {user_query}
174
-
175
- μœ„ 지침을 λ°”νƒ•μœΌλ‘œ ꡬ체적이고 μ‹€ν–‰ κ°€λŠ₯ν•œ μ„ΈλΆ€ λ‚΄μš©μ„ μž‘μ„±ν•΄μ£Όμ„Έμš”."""
176
-
177
- executor_response = ""
178
- async for chunk in call_llm([{"role": "user", "content": executor_prompt}], "executor"):
179
- executor_response += chunk
180
 
181
- conversation_log.append({
182
- "role": "executor",
183
- "stage": "implementation",
184
- "content": executor_response,
185
- "timestamp": datetime.now().isoformat()
186
- })
187
-
188
- # 3단계: κ°λ…μž AI의 κ²€ν†  및 ν”Όλ“œλ°±
189
- review_prompt = f"""μ‚¬μš©μž 질문: {user_query}
190
-
191
- μ‹€ν–‰μž AI의 λ‹΅λ³€:
192
- {executor_response}
193
-
194
- 이 닡변을 κ²€ν† ν•˜κ³  κ°œμ„ μ κ³Ό μΆ”κ°€ 고렀사항을 μ œμ‹œν•΄μ£Όμ„Έμš”."""
195
-
196
- review_response = ""
197
- async for chunk in call_llm([{"role": "user", "content": review_prompt}], "supervisor"):
198
- review_response += chunk
199
 
200
- conversation_log.append({
201
- "role": "supervisor",
202
- "stage": "review",
203
- "content": review_response,
204
- "timestamp": datetime.now().isoformat()
205
- })
206
-
207
- # 4단계: μ΅œμ’… μ’…ν•©
208
- final_summary = f"""## ν˜‘λ ₯적 AI μ‹œμŠ€ν…œ μ’…ν•© λ‹΅λ³€
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
 
210
- ### 🎯 μ‚¬μš©μž 질문
211
  {user_query}
212
 
213
  ### πŸ” κ±°μ‹œμ  뢄석 (κ°λ…μž AI)
@@ -221,348 +168,168 @@ async def collaborative_process(request: CollaborativeRequest):
221
 
222
  ---
223
  *이 닡변은 κ±°μ‹œμ  관점과 λ―Έμ‹œμ  세뢀사항을 μ’…ν•©ν•˜μ—¬ μž‘μ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.*"""
224
-
225
- return {
226
- "final_result": final_summary,
227
- "conversation_log": conversation_log,
228
- "timestamp": datetime.now().isoformat()
229
- }
230
-
231
- except Exception as e:
232
- logger.error(f"ν˜‘λ ₯ 처리 였λ₯˜: {str(e)}")
233
- raise HTTPException(status_code=500, detail=str(e))
234
-
235
- @app.get("/health")
236
- async def health_check():
237
- return {"status": "healthy", "timestamp": datetime.now().isoformat()}
238
-
239
- if __name__ == "__main__":
240
- import uvicorn
241
- uvicorn.run(app, host="0.0.0.0", port=8000)
242
- ```
243
-
244
- ## 2. Streamlit ν”„λ‘ νŠΈμ—”λ“œ (streamlit_app.py)
245
-
246
- ```python
247
- import streamlit as st
248
- import requests
249
- import json
250
- from datetime import datetime
251
- import time
252
- import os
253
-
254
- # νŽ˜μ΄μ§€ μ„€μ •
255
- st.set_page_config(
256
- page_title="ν˜‘λ ₯적 LLM μ‹œμŠ€ν…œ",
257
- page_icon="🀝",
258
- layout="wide",
259
- initial_sidebar_state="expanded"
260
- )
261
-
262
- # μŠ€νƒ€μΌ μ„€μ •
263
- st.markdown("""
264
- <style>
265
- .main-header {
266
- text-align: center;
267
- background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
268
- -webkit-background-clip: text;
269
- -webkit-text-fill-color: transparent;
270
- font-size: 3rem;
271
- font-weight: bold;
272
- margin-bottom: 2rem;
273
- }
274
- .llm-box {
275
- background-color: #f0f2f6;
276
- border-radius: 10px;
277
- padding: 1rem;
278
- margin-bottom: 1rem;
279
- border-left: 4px solid;
280
- }
281
- .supervisor-box {
282
- border-left-color: #667eea;
283
- }
284
- .executor-box {
285
- border-left-color: #764ba2;
286
- }
287
- .timestamp {
288
- font-size: 0.8rem;
289
- color: #666;
290
- }
291
- </style>
292
- """, unsafe_allow_html=True)
293
-
294
- # λ°±μ—”λ“œ URL
295
- BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:8000")
296
-
297
- # μ„Έμ…˜ μƒνƒœ μ΄ˆκΈ°ν™”
298
- if "conversation_history" not in st.session_state:
299
- st.session_state.conversation_history = []
300
- if "current_conversation" not in st.session_state:
301
- st.session_state.current_conversation = None
302
-
303
- # 헀더
304
- st.markdown('<h1 class="main-header">🀝 ν˜‘λ ₯적 LLM μ‹œμŠ€ν…œ</h1>', unsafe_allow_html=True)
305
- st.markdown("κ±°μ‹œμ  κ°λ…μž AI와 λ―Έμ‹œμ  μ‹€ν–‰μž AIκ°€ ν˜‘λ ₯ν•˜μ—¬ μ΅œμƒμ˜ 닡변을 λ§Œλ“€μ–΄λƒ…λ‹ˆλ‹€.")
306
-
307
- # μ‚¬μ΄λ“œλ°”
308
- with st.sidebar:
309
- st.header("βš™οΈ μ„€μ •")
310
-
311
- # λ°±μ—”λ“œ μƒνƒœ 확인
312
- try:
313
- response = requests.get(f"{BACKEND_URL}/health")
314
- if response.status_code == 200:
315
- st.success("βœ… λ°±μ—”λ“œ 연결됨")
316
- else:
317
- st.error("❌ λ°±μ—”λ“œ μ—°κ²° μ‹€νŒ¨")
318
- except:
319
- st.error("❌ λ°±μ—”λ“œ μ„œλ²„λ₯Ό μ‹œμž‘ν•΄μ£Όμ„Έμš”")
320
-
321
- st.divider()
322
-
323
- # λŒ€ν™” 기둝
324
- st.header("πŸ“ λŒ€ν™” 기둝")
325
- if st.session_state.conversation_history:
326
- for idx, conv in enumerate(reversed(st.session_state.conversation_history)):
327
- if st.button(f"πŸ’¬ {conv['query'][:30]}...", key=f"hist_{idx}"):
328
- st.session_state.current_conversation = conv
329
- else:
330
- st.info("λŒ€ν™” 기둝이 μ—†μŠ΅λ‹ˆλ‹€.")
331
-
332
- if st.button("πŸ—‘οΈ 기둝 μ΄ˆκΈ°ν™”"):
333
- st.session_state.conversation_history = []
334
- st.session_state.current_conversation = None
335
- st.rerun()
336
-
337
- # 메인 컨텐츠
338
- main_container = st.container()
339
-
340
- # 질문 μž…λ ₯
341
- with main_container:
342
- col1, col2 = st.columns([5, 1])
343
- with col1:
344
- user_query = st.text_input(
345
- "μ§ˆλ¬Έμ„ μž…λ ₯ν•˜μ„Έμš”:",
346
- placeholder="예: κΈ°κ³„ν•™μŠ΅ λͺ¨λΈμ˜ μ„±λŠ₯을 ν–₯μƒμ‹œν‚€λŠ” 방법은?",
347
- key="user_input"
348
- )
349
- with col2:
350
- process_button = st.button("πŸš€ 뢄석 μ‹œμž‘", type="primary", use_container_width=True)
351
-
352
- # 처리 둜직
353
- if process_button and user_query:
354
- with st.spinner("AI듀이 ν˜‘λ ₯ν•˜μ—¬ 닡변을 μƒμ„±ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€..."):
355
- try:
356
- # λ°±μ—”λ“œμ— μš”μ²­
357
- response = requests.post(
358
- f"{BACKEND_URL}/api/llm/collaborate",
359
- json={"user_query": user_query, "max_iterations": 3}
360
- )
361
 
362
- if response.status_code == 200:
363
- result = response.json()
364
-
365
- # λŒ€ν™” 기둝에 μΆ”κ°€
366
- conversation = {
367
- "query": user_query,
368
- "result": result,
369
- "timestamp": datetime.now().isoformat()
370
- }
371
- st.session_state.conversation_history.append(conversation)
372
- st.session_state.current_conversation = conversation
373
-
374
- st.success("βœ… 뢄석 μ™„λ£Œ!")
375
- else:
376
- st.error(f"였λ₯˜ λ°œμƒ: {response.status_code}")
377
-
378
  except Exception as e:
379
- st.error(f"처리 쀑 였λ₯˜ λ°œμƒ: {str(e)}")
380
-
381
- # κ²°κ³Ό ν‘œμ‹œ
382
- if st.session_state.current_conversation:
383
- conv = st.session_state.current_conversation
384
-
385
- # μ΅œμ’… κ²°κ³Ό
386
- st.markdown("### πŸ“Š μ΅œμ’… μ’…ν•© κ²°κ³Ό")
387
- with st.expander("펼치기", expanded=True):
388
- st.markdown(conv["result"]["final_result"])
389
-
390
- # λŒ€ν™” κ³Όμ •
391
- st.markdown("### πŸ”„ AI ν˜‘λ ₯ κ³Όμ •")
392
-
393
- # 두 개의 컬럼으둜 λΆ„ν• 
394
- col1, col2 = st.columns(2)
395
-
396
- supervisor_logs = [log for log in conv["result"]["conversation_log"] if log["role"] == "supervisor"]
397
- executor_logs = [log for log in conv["result"]["conversation_log"] if log["role"] == "executor"]
398
-
399
- with col1:
400
- st.markdown("#### 🧠 κ°λ…μž AI (κ±°μ‹œμ  뢄석)")
401
- for log in supervisor_logs:
402
- with st.container():
403
- st.markdown(f'<div class="llm-box supervisor-box">', unsafe_allow_html=True)
404
- st.markdown(f"**{log['stage']}**")
405
- st.text(log["content"][:500] + "..." if len(log["content"]) > 500 else log["content"])
406
- st.markdown(f'<span class="timestamp">{log["timestamp"]}</span>', unsafe_allow_html=True)
407
- st.markdown('</div>', unsafe_allow_html=True)
408
-
409
- with col2:
410
- st.markdown("#### πŸ‘οΈ μ‹€ν–‰μž AI (λ―Έμ‹œμ  κ΅¬ν˜„)")
411
- for log in executor_logs:
412
- with st.container():
413
- st.markdown(f'<div class="llm-box executor-box">', unsafe_allow_html=True)
414
- st.markdown(f"**{log['stage']}**")
415
- st.text(log["content"][:500] + "..." if len(log["content"]) > 500 else log["content"])
416
- st.markdown(f'<span class="timestamp">{log["timestamp"]}</span>', unsafe_allow_html=True)
417
- st.markdown('</div>', unsafe_allow_html=True)
418
-
419
- # ν•˜λ‹¨ 정보
420
- st.divider()
421
- st.markdown("""
422
- <div style="text-align: center; color: #666;">
423
- πŸ’‘ 이 μ‹œμŠ€ν…œμ€ 두 AIκ°€ μ„œλ‘œ λ‹€λ₯Έ κ΄€μ μ—μ„œ μ ‘κ·Όν•˜μ—¬ 더 λ‚˜μ€ 닡변을 λ§Œλ“€μ–΄λƒ…λ‹ˆλ‹€.<br>
424
- κ°λ…μž AIλŠ” 전체적인 λ°©ν–₯을, μ‹€ν–‰μž AIλŠ” ꡬ체적인 세뢀사항을 λ‹΄λ‹Ήν•©λ‹ˆλ‹€.
425
- </div>
426
- """, unsafe_allow_html=True)
427
- ```
428
 
429
- ## 3. Gradio ν”„λ‘ νŠΈμ—”λ“œ (gradio_app.py)
430
-
431
- ```python
432
- import gradio as gr
433
- import requests
434
- import json
435
- from datetime import datetime
436
- import os
437
- import asyncio
438
-
439
- # λ°±μ—”λ“œ URL
440
- BACKEND_URL = os.getenv("BACKEND_URL", "http://localhost:8000")
441
-
442
- # κΈ€λ‘œλ²Œ λ³€μˆ˜
443
- conversation_history = []
444
-
445
- def check_backend_status():
446
- """λ°±μ—”λ“œ μƒνƒœ 확인"""
447
- try:
448
- response = requests.get(f"{BACKEND_URL}/health")
449
- return response.status_code == 200
450
- except:
451
- return False
452
 
453
  def process_query(user_query, history):
454
- """μ‚¬μš©μž 쿼리 처리"""
455
  if not user_query:
456
- return history, "", "", "", "μ§ˆλ¬Έμ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."
 
 
 
457
 
458
  try:
459
- # λ°±μ—”λ“œμ— μš”μ²­
460
- response = requests.post(
461
- f"{BACKEND_URL}/api/llm/collaborate",
462
- json={"user_query": user_query, "max_iterations": 3}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  )
464
 
465
- if response.status_code == 200:
466
- result = response.json()
467
-
468
- # λŒ€ν™” 둜그 νŒŒμ‹±
469
- supervisor_text = ""
470
- executor_text = ""
471
-
472
- for log in result["conversation_log"]:
473
- if log["role"] == "supervisor":
474
- supervisor_text += f"[{log['stage']}]\n{log['content']}\n\n"
475
- else:
476
- executor_text += f"[{log['stage']}]\n{log['content']}\n\n"
477
-
478
- # νžˆμŠ€ν† λ¦¬ μ—…λ°μ΄νŠΈ
479
- new_history = history + [(user_query, result["final_result"])]
480
-
481
- return (
482
- new_history,
483
- supervisor_text,
484
- executor_text,
485
- result["final_result"],
486
- "βœ… 뢄석 μ™„λ£Œ!"
487
- )
488
- else:
489
- return history, "", "", "", f"❌ 였λ₯˜ λ°œμƒ: {response.status_code}"
490
-
491
  except Exception as e:
492
- return history, "", "", "", f"❌ 처리 쀑 였λ₯˜: {str(e)}"
 
493
 
494
  def clear_all():
495
  """λͺ¨λ“  λ‚΄μš© μ΄ˆκΈ°ν™”"""
496
- return [], "", "", "", ""
497
-
498
- # Gradio μΈν„°νŽ˜μ΄μŠ€ ꡬ성
499
- with gr.Blocks(title="ν˜‘λ ₯적 LLM μ‹œμŠ€ν…œ", theme=gr.themes.Soft()) as app:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
500
  gr.Markdown(
501
  """
502
  # 🀝 ν˜‘λ ₯적 LLM μ‹œμŠ€ν…œ
503
 
504
- κ±°μ‹œμ  κ°λ…μž AI와 λ―Έμ‹œμ  μ‹€ν–‰μž AIκ°€ ν˜‘λ ₯ν•˜μ—¬ μ΅œμƒμ˜ 닡변을 λ§Œλ“€μ–΄λƒ…λ‹ˆλ‹€.
 
 
 
 
 
505
  """
506
  )
507
 
508
- # λ°±μ—”λ“œ μƒνƒœ ν‘œμ‹œ
509
- with gr.Row():
510
- backend_status = gr.Markdown(
511
- "🟒 λ°±μ—”λ“œ 연결됨" if check_backend_status() else "πŸ”΄ λ°±μ—”λ“œ μ—°κ²° μ•ˆλ¨"
512
- )
513
-
514
  with gr.Row():
515
  # μ™Όμͺ½: μž…λ ₯ 및 μ±„νŒ… 기둝
516
  with gr.Column(scale=1):
517
  chatbot = gr.Chatbot(
518
  label="πŸ’¬ λŒ€ν™” 기둝",
519
- height=400,
520
- show_copy_button=True
 
521
  )
522
 
523
  user_input = gr.Textbox(
524
  label="질문 μž…λ ₯",
525
  placeholder="예: κΈ°κ³„ν•™μŠ΅ λͺ¨λΈμ˜ μ„±λŠ₯을 ν–₯μƒμ‹œν‚€λŠ” 방법은?",
526
- lines=2
527
  )
528
 
529
  with gr.Row():
530
- submit_btn = gr.Button("πŸš€ 뢄석 μ‹œμž‘", variant="primary")
531
- clear_btn = gr.Button("πŸ—‘οΈ μ΄ˆκΈ°ν™”")
532
 
533
  status_text = gr.Textbox(
534
  label="μƒνƒœ",
535
  interactive=False,
536
- value="λŒ€κΈ° 쀑..."
 
537
  )
538
 
539
  # 였λ₯Έμͺ½: AI 좜λ ₯
540
  with gr.Column(scale=2):
541
  # μ΅œμ’… κ²°κ³Ό
542
- final_output = gr.Markdown(
543
- label="πŸ“Š μ΅œμ’… μ’…ν•© κ²°κ³Ό",
544
- value="*μ§ˆλ¬Έμ„ μž…λ ₯ν•˜λ©΄ κ²°κ³Όκ°€ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€.*"
545
- )
546
 
547
  with gr.Row():
548
  # κ°λ…μž AI 좜λ ₯
549
  with gr.Column():
 
550
  supervisor_output = gr.Textbox(
551
- label="🧠 κ°λ…μž AI (κ±°μ‹œμ  뢄석)",
552
  lines=15,
553
  max_lines=20,
554
- interactive=False
 
555
  )
556
 
557
  # μ‹€ν–‰μž AI 좜λ ₯
558
  with gr.Column():
 
559
  executor_output = gr.Textbox(
560
- label="πŸ‘οΈ μ‹€ν–‰μž AI (λ―Έμ‹œμ  κ΅¬ν˜„)",
561
  lines=15,
562
  max_lines=20,
563
- interactive=False
 
564
  )
565
 
 
 
 
 
 
 
 
 
 
 
 
 
 
566
  # 이벀트 ν•Έλ“€λŸ¬
567
  submit_btn.click(
568
  fn=process_query,
@@ -587,24 +354,17 @@ with gr.Blocks(title="ν˜‘λ ₯적 LLM μ‹œμŠ€ν…œ", theme=gr.themes.Soft()) as app:
587
  outputs=[chatbot, supervisor_output, executor_output, final_output, status_text]
588
  )
589
 
590
- # 예제
591
- gr.Examples(
592
- examples=[
593
- "κΈ°κ³„ν•™μŠ΅ λͺ¨λΈμ˜ μ„±λŠ₯을 ν–₯μƒμ‹œν‚€λŠ” 방법은?",
594
- "효과적인 ν”„λ‘œμ νŠΈ 관리 μ „λž΅μ„ μˆ˜λ¦½ν•˜λŠ” 방법은?",
595
- "지속 κ°€λŠ₯ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ λͺ¨λΈμ„ κ΅¬μΆ•ν•˜λŠ” 방법은?",
596
- "λ³΅μž‘ν•œ 데이터λ₯Ό μ‹œκ°ν™”ν•˜λŠ” μ΅œμ„ μ˜ 방법은?"
597
- ],
598
- inputs=user_input
599
- )
600
-
601
  gr.Markdown(
602
  """
603
  ---
604
- πŸ’‘ **μ‹œμŠ€ν…œ μ„€λͺ…**
605
- - **κ°λ…μž AI**: 전체적인 λ°©ν–₯κ³Ό ν”„λ ˆμž„μ›Œν¬λ₯Ό μ œμ‹œν•©λ‹ˆλ‹€.
606
- - **μ‹€ν–‰μž AI**: ꡬ체적이고 μ‹€ν–‰ κ°€λŠ₯ν•œ 세뢀사항을 μž‘μ„±ν•©λ‹ˆλ‹€.
607
- - 두 AIκ°€ μƒν˜Έμž‘μš©ν•˜λ©° 졜적의 닡변을 λ„μΆœν•©λ‹ˆλ‹€.
 
 
 
 
608
  """
609
  )
610
 
@@ -612,6 +372,6 @@ if __name__ == "__main__":
612
  app.launch(
613
  server_name="0.0.0.0",
614
  server_port=7860,
615
- share=False,
616
  show_error=True
617
- )
 
1
+ import gradio as gr
 
 
 
 
 
 
 
 
2
  import os
 
 
3
  import json
4
+ import requests
5
  from datetime import datetime
6
+ import asyncio
7
+ import aiohttp
8
+ from typing import List, Dict, Any
9
  import logging
10
 
11
  # λ‘œκΉ… μ„€μ •
12
  logging.basicConfig(level=logging.INFO)
13
  logger = logging.getLogger(__name__)
14
 
 
 
 
 
 
 
 
 
 
 
 
15
  # ν™˜κ²½ λ³€μˆ˜μ—μ„œ 토큰 κ°€μ Έμ˜€κΈ°
16
+ FRIENDLI_TOKEN = os.getenv("FRIENDLI_TOKEN", "YOUR_FRIENDLI_TOKEN")
 
 
 
17
  API_URL = "https://api.friendli.ai/dedicated/v1/chat/completions"
18
  MODEL_ID = "dep89a2fld32mcm"
19
 
20
+ # μ „μ—­ λ³€μˆ˜
21
+ conversation_history = []
 
 
22
 
23
+ class LLMCollaborativeSystem:
24
+ def __init__(self):
25
+ self.token = FRIENDLI_TOKEN
26
+ self.api_url = API_URL
27
+ self.model_id = MODEL_ID
28
+
29
+ def create_headers(self):
30
+ """API 헀더 생성"""
31
+ return {
32
+ "Authorization": f"Bearer {self.token}",
33
+ "Content-Type": "application/json"
34
+ }
35
+
36
+ def create_supervisor_prompt(self, user_query: str, executor_response: str = None) -> str:
37
+ """κ°λ…μž AI ν”„λ‘¬ν”„νŠΈ 생성"""
38
+ if executor_response:
39
+ return f"""당신은 κ±°μ‹œμ  κ΄€μ μ—μ„œ λΆ„μ„ν•˜κ³  μ§€λ„ν•˜λŠ” κ°λ…μž AIμž…λ‹ˆλ‹€.
40
 
41
+ μ‚¬μš©μž 질문: {user_query}
 
 
42
 
43
+ μ‹€ν–‰μž AI의 λ‹΅λ³€:
44
+ {executor_response}
 
 
45
 
46
+ 이 닡변을 κ²€ν† ν•˜κ³  κ°œμ„ μ κ³Ό μΆ”κ°€ 고렀사항을 μ œμ‹œν•΄μ£Όμ„Έμš”. ꡬ쑰적이고 체계적인 ν”Όλ“œλ°±μ„ μ œκ³΅ν•˜μ„Έμš”."""
47
+ else:
48
+ return f"""당신은 κ±°μ‹œμ  κ΄€μ μ—μ„œ λΆ„μ„ν•˜κ³  μ§€λ„ν•˜λŠ” κ°λ…μž AIμž…λ‹ˆλ‹€.
49
+
50
+ μ‚¬μš©μž 질문: {user_query}
51
+
52
+ 이 μ§ˆλ¬Έμ— λŒ€ν•œ 전체적인 μ ‘κ·Ό λ°©ν–₯κ³Ό ν”„λ ˆμž„μ›Œν¬λ₯Ό μ œμ‹œν•΄μ£Όμ„Έμš”. 핡심 μš”μ†Œμ™€ 고렀사항을 κ΅¬μ‘°ν™”ν•˜μ—¬ μ„€λͺ…ν•΄μ£Όμ„Έμš”."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ def create_executor_prompt(self, user_query: str, supervisor_guidance: str) -> str:
55
+ """μ‹€ν–‰μž AI ν”„λ‘¬ν”„νŠΈ 생성"""
56
+ return f"""당신은 세뢀적인 λ‚΄μš©μ„ κ΅¬ν˜„ν•˜λŠ” μ‹€ν–‰μž AIμž…λ‹ˆλ‹€.
57
+
58
+ κ°λ…μž AI의 μ§€μΉ¨:
59
+ {supervisor_guidance}
60
+
61
+ μ‚¬μš©μž 질문: {user_query}
62
+
63
+ μœ„ 지침을 λ°”νƒ•μœΌλ‘œ ꡬ체적이고 μ‹€ν–‰ κ°€λŠ₯ν•œ μ„ΈλΆ€ λ‚΄μš©μ„ μž‘μ„±ν•΄μ£Όμ„Έμš”. μ‹€μš©μ μ΄κ³  κ΅¬ν˜„ κ°€λŠ₯ν•œ 해결책에 μ§‘μ€‘ν•˜μ„Έμš”."""
64
 
65
+ def call_llm_sync(self, messages: List[Dict[str, str]], role: str) -> str:
66
+ """동기식 LLM API 호좜"""
67
  try:
68
+ # 역할에 λ”°λ₯Έ μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈ
69
+ system_prompts = {
70
+ "supervisor": "당신은 κ±°μ‹œμ  κ΄€μ μ—μ„œ λΆ„μ„ν•˜κ³  μ§€λ„ν•˜λŠ” κ°λ…μž AIμž…λ‹ˆλ‹€.",
71
+ "executor": "당신은 세뢀적인 λ‚΄μš©μ„ κ΅¬ν˜„ν•˜λŠ” μ‹€ν–‰μž AIμž…λ‹ˆλ‹€."
72
+ }
73
+
74
+ # λ©”μ‹œμ§€ ꡬ성
75
+ full_messages = [
76
+ {"role": "system", "content": system_prompts.get(role, "")},
77
+ *messages
78
+ ]
79
+
80
+ payload = {
81
+ "model": self.model_id,
82
+ "messages": full_messages,
83
+ "max_tokens": 2048,
84
+ "temperature": 0.7,
85
+ "top_p": 0.8,
86
+ "stream": False
87
+ }
88
+
89
+ response = requests.post(
90
+ self.api_url,
91
+ headers=self.create_headers(),
92
+ json=payload,
93
+ timeout=60
94
+ )
95
+
96
+ if response.status_code == 200:
97
  data = response.json()
98
  return data["choices"][0]["message"]["content"]
99
+ else:
100
+ logger.error(f"API 였λ₯˜: {response.status_code}")
101
+ return f"API 호좜 였λ₯˜: {response.status_code}"
102
 
103
  except Exception as e:
104
+ logger.error(f"LLM 호좜 쀑 였λ₯˜: {str(e)}")
105
+ return f"였λ₯˜ λ°œμƒ: {str(e)}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
+ def process_collaborative(self, user_query: str) -> Dict[str, Any]:
108
+ """ν˜‘λ ₯적 처리 ν”„λ‘œμ„ΈμŠ€"""
109
+ conversation_log = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
+ try:
112
+ # 1단계: κ°λ…μž AI의 초기 뢄석
113
+ supervisor_prompt = self.create_supervisor_prompt(user_query)
114
+ supervisor_response = self.call_llm_sync(
115
+ [{"role": "user", "content": supervisor_prompt}],
116
+ "supervisor"
117
+ )
 
 
 
 
118
 
119
+ conversation_log.append({
120
+ "role": "supervisor",
121
+ "stage": "초기 뢄석",
122
+ "content": supervisor_response,
123
+ "timestamp": datetime.now().strftime("%H:%M:%S")
124
+ })
 
 
 
 
 
 
 
 
 
 
 
 
125
 
126
+ # 2단계: μ‹€ν–‰μž AI의 μ„ΈλΆ€ κ΅¬ν˜„
127
+ executor_prompt = self.create_executor_prompt(user_query, supervisor_response)
128
+ executor_response = self.call_llm_sync(
129
+ [{"role": "user", "content": executor_prompt}],
130
+ "executor"
131
+ )
132
+
133
+ conversation_log.append({
134
+ "role": "executor",
135
+ "stage": "μ„ΈλΆ€ κ΅¬ν˜„",
136
+ "content": executor_response,
137
+ "timestamp": datetime.now().strftime("%H:%M:%S")
138
+ })
139
+
140
+ # 3단계: κ°λ…μž AI의 κ²€ν†  및 ν”Όλ“œλ°±
141
+ review_prompt = self.create_supervisor_prompt(user_query, executor_response)
142
+ review_response = self.call_llm_sync(
143
+ [{"role": "user", "content": review_prompt}],
144
+ "supervisor"
145
+ )
146
+
147
+ conversation_log.append({
148
+ "role": "supervisor",
149
+ "stage": "κ²€ν†  및 ν”Όλ“œλ°±",
150
+ "content": review_response,
151
+ "timestamp": datetime.now().strftime("%H:%M:%S")
152
+ })
153
+
154
+ # 4단계: μ΅œμ’… μ’…ν•©
155
+ final_summary = f"""## 🀝 ν˜‘λ ₯적 AI μ‹œμŠ€ν…œ μ’…ν•© λ‹΅λ³€
156
 
157
+ ### πŸ“Œ μ‚¬μš©μž 질문
158
  {user_query}
159
 
160
  ### πŸ” κ±°μ‹œμ  뢄석 (κ°λ…μž AI)
 
168
 
169
  ---
170
  *이 닡변은 κ±°μ‹œμ  관점과 λ―Έμ‹œμ  세뢀사항을 μ’…ν•©ν•˜μ—¬ μž‘μ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.*"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
+ return {
173
+ "final_result": final_summary,
174
+ "conversation_log": conversation_log,
175
+ "supervisor_texts": [log["content"] for log in conversation_log if log["role"] == "supervisor"],
176
+ "executor_texts": [log["content"] for log in conversation_log if log["role"] == "executor"]
177
+ }
178
+
 
 
 
 
 
 
 
 
 
179
  except Exception as e:
180
+ logger.error(f"ν˜‘λ ₯ 처리 였λ₯˜: {str(e)}")
181
+ return {
182
+ "final_result": f"처리 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: {str(e)}",
183
+ "conversation_log": [],
184
+ "supervisor_texts": [],
185
+ "executor_texts": []
186
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
 
188
+ # μ‹œμŠ€ν…œ μΈμŠ€ν„΄μŠ€ 생성
189
+ llm_system = LLMCollaborativeSystem()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
 
191
  def process_query(user_query, history):
192
+ """Gradio μΈν„°νŽ˜μ΄μŠ€λ₯Ό μœ„ν•œ 쿼리 처리 ν•¨μˆ˜"""
193
  if not user_query:
194
+ return history, "", "", "", "❌ μ§ˆλ¬Έμ„ μž…λ ₯ν•΄μ£Όμ„Έμš”."
195
+
196
+ # 처리 μ‹œμž‘
197
+ status = "πŸ”„ AI듀이 ν˜‘λ ₯ν•˜μ—¬ 닡변을 μƒμ„±ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€..."
198
 
199
  try:
200
+ # ν˜‘λ ₯적 처리
201
+ result = llm_system.process_collaborative(user_query)
202
+
203
+ # κ°λ…μžμ™€ μ‹€ν–‰μž ν…μŠ€νŠΈ ν¬λ§·νŒ…
204
+ supervisor_text = "\n\n---\n\n".join([
205
+ f"[{log['stage']}] - {log['timestamp']}\n{log['content']}"
206
+ for log in result["conversation_log"] if log["role"] == "supervisor"
207
+ ])
208
+
209
+ executor_text = "\n\n---\n\n".join([
210
+ f"[{log['stage']}] - {log['timestamp']}\n{log['content']}"
211
+ for log in result["conversation_log"] if log["role"] == "executor"
212
+ ])
213
+
214
+ # νžˆμŠ€ν† λ¦¬ μ—…λ°μ΄νŠΈ
215
+ new_history = history + [(user_query, result["final_result"])]
216
+
217
+ return (
218
+ new_history,
219
+ supervisor_text,
220
+ executor_text,
221
+ result["final_result"],
222
+ "βœ… 뢄석 μ™„λ£Œ!"
223
  )
224
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  except Exception as e:
226
+ error_msg = f"❌ 처리 쀑 였λ₯˜: {str(e)}"
227
+ return history, "", "", error_msg, error_msg
228
 
229
  def clear_all():
230
  """λͺ¨λ“  λ‚΄μš© μ΄ˆκΈ°ν™”"""
231
+ return [], "", "", "", "πŸ”„ μ΄ˆκΈ°ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€."
232
+
233
+ # Gradio μΈν„°νŽ˜μ΄μŠ€
234
+ css = """
235
+ .gradio-container {
236
+ font-family: 'Arial', sans-serif;
237
+ }
238
+ .supervisor-box {
239
+ border-left: 4px solid #667eea;
240
+ padding-left: 10px;
241
+ }
242
+ .executor-box {
243
+ border-left: 4px solid #764ba2;
244
+ padding-left: 10px;
245
+ }
246
+ """
247
+
248
+ with gr.Blocks(title="ν˜‘λ ₯적 LLM μ‹œμŠ€ν…œ", theme=gr.themes.Soft(), css=css) as app:
249
  gr.Markdown(
250
  """
251
  # 🀝 ν˜‘λ ₯적 LLM μ‹œμŠ€ν…œ
252
 
253
+ > κ±°μ‹œμ  κ°λ…μž AI와 λ―Έμ‹œμ  μ‹€ν–‰μž AIκ°€ ν˜‘λ ₯ν•˜μ—¬ μ΅œμƒμ˜ 닡변을 λ§Œλ“€μ–΄λƒ…λ‹ˆλ‹€.
254
+
255
+ **μ‹œμŠ€ν…œ ꡬ쑰:**
256
+ - 🧠 **κ°λ…μž AI**: 전체적인 λ°©ν–₯κ³Ό ν”„λ ˆμž„μ›Œν¬λ₯Ό μ œμ‹œ
257
+ - πŸ‘οΈ **μ‹€ν–‰μž AI**: ꡬ체적이고 μ‹€ν–‰ κ°€λŠ₯ν•œ 세뢀사항을 μž‘μ„±
258
+ - πŸ”„ **ν˜‘λ ₯ ν”„λ‘œμ„ΈμŠ€**: μƒν˜Έ ν”Όλ“œλ°±μ„ ν†΅ν•œ λ‹΅λ³€ κ°œμ„ 
259
  """
260
  )
261
 
 
 
 
 
 
 
262
  with gr.Row():
263
  # μ™Όμͺ½: μž…λ ₯ 및 μ±„νŒ… 기둝
264
  with gr.Column(scale=1):
265
  chatbot = gr.Chatbot(
266
  label="πŸ’¬ λŒ€ν™” 기둝",
267
+ height=500,
268
+ show_copy_button=True,
269
+ bubble_full_width=False
270
  )
271
 
272
  user_input = gr.Textbox(
273
  label="질문 μž…λ ₯",
274
  placeholder="예: κΈ°κ³„ν•™μŠ΅ λͺ¨λΈμ˜ μ„±λŠ₯을 ν–₯μƒμ‹œν‚€λŠ” 방법은?",
275
+ lines=3
276
  )
277
 
278
  with gr.Row():
279
+ submit_btn = gr.Button("πŸš€ 뢄석 μ‹œμž‘", variant="primary", scale=2)
280
+ clear_btn = gr.Button("πŸ—‘οΈ μ΄ˆκΈ°ν™”", scale=1)
281
 
282
  status_text = gr.Textbox(
283
  label="μƒνƒœ",
284
  interactive=False,
285
+ value="λŒ€κΈ° 쀑...",
286
+ max_lines=1
287
  )
288
 
289
  # 였λ₯Έμͺ½: AI 좜λ ₯
290
  with gr.Column(scale=2):
291
  # μ΅œμ’… κ²°κ³Ό
292
+ with gr.Accordion("πŸ“Š μ΅œμ’… μ’…ν•© κ²°κ³Ό", open=True):
293
+ final_output = gr.Markdown(
294
+ value="*μ§ˆλ¬Έμ„ μž…λ ₯ν•˜λ©΄ κ²°κ³Όκ°€ 여기에 ν‘œμ‹œλ©λ‹ˆλ‹€.*"
295
+ )
296
 
297
  with gr.Row():
298
  # κ°λ…μž AI 좜λ ₯
299
  with gr.Column():
300
+ gr.Markdown("### 🧠 κ°λ…μž AI (κ±°μ‹œμ  뢄석)")
301
  supervisor_output = gr.Textbox(
302
+ label="",
303
  lines=15,
304
  max_lines=20,
305
+ interactive=False,
306
+ elem_classes=["supervisor-box"]
307
  )
308
 
309
  # μ‹€ν–‰μž AI 좜λ ₯
310
  with gr.Column():
311
+ gr.Markdown("### πŸ‘οΈ μ‹€ν–‰μž AI (λ―Έμ‹œμ  κ΅¬ν˜„)")
312
  executor_output = gr.Textbox(
313
+ label="",
314
  lines=15,
315
  max_lines=20,
316
+ interactive=False,
317
+ elem_classes=["executor-box"]
318
  )
319
 
320
+ # 예제
321
+ gr.Examples(
322
+ examples=[
323
+ "κΈ°κ³„ν•™μŠ΅ λͺ¨λΈμ˜ μ„±λŠ₯을 ν–₯μƒμ‹œν‚€λŠ” 방법은?",
324
+ "효과적인 ν”„λ‘œμ νŠΈ 관리 μ „λž΅μ„ μˆ˜λ¦½ν•˜λŠ” 방법은?",
325
+ "지속 κ°€λŠ₯ν•œ λΉ„μ¦ˆλ‹ˆμŠ€ λͺ¨λΈμ„ κ΅¬μΆ•ν•˜λŠ” 방법은?",
326
+ "λ³΅μž‘ν•œ 데이터λ₯Ό μ‹œκ°ν™”ν•˜λŠ” μ΅œμ„ μ˜ 방법은?",
327
+ "νŒ€μ˜ 생산성을 λ†’μ΄λŠ” 방법은?"
328
+ ],
329
+ inputs=user_input,
330
+ label="πŸ’‘ 예제 질문"
331
+ )
332
+
333
  # 이벀트 ν•Έλ“€λŸ¬
334
  submit_btn.click(
335
  fn=process_query,
 
354
  outputs=[chatbot, supervisor_output, executor_output, final_output, status_text]
355
  )
356
 
 
 
 
 
 
 
 
 
 
 
 
357
  gr.Markdown(
358
  """
359
  ---
360
+ ### πŸ“ μ‚¬μš© 방법
361
+ 1. μ§ˆλ¬Έμ„ μž…λ ₯ν•˜κ³  Enter λ˜λŠ” '뢄석 μ‹œμž‘' λ²„νŠΌμ„ ν΄λ¦­ν•˜μ„Έμš”.
362
+ 2. 두 AIκ°€ ν˜‘λ ₯ν•˜μ—¬ 닡변을 μƒμ„±ν•˜λŠ” 과정을 μ‹€μ‹œκ°„μœΌλ‘œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.
363
+ 3. μ΅œμ’… μ’…ν•© κ²°κ³ΌλŠ” 상단에 ν‘œμ‹œλ©λ‹ˆλ‹€.
364
+
365
+ ### βš™οΈ ν™˜κ²½ μ„€μ •
366
+ - `FRIENDLI_TOKEN` ν™˜κ²½ λ³€μˆ˜λ₯Ό μ„€μ •ν•˜μ„Έμš”: `export FRIENDLI_TOKEN="your_token"`
367
+ - λ˜λŠ” μ½”λ“œμ˜ `FRIENDLI_TOKEN` λ³€μˆ˜λ₯Ό 직접 μˆ˜μ •ν•˜μ„Έμš”.
368
  """
369
  )
370
 
 
372
  app.launch(
373
  server_name="0.0.0.0",
374
  server_port=7860,
375
+ share=True,
376
  show_error=True
377
+ )