Spaces:
Running
Running
# ⚡ Model Registry Optimization (Fast Mode)
Browse files## Changes
- **Removed Llama 3.3 70B**: Deprecated due to stability issues in structured output (Ref: agno-agi/agno#4090).
- **Added Qwen 2.5 32B (`qwen-2.5-32b`)**: New default for Fast Mode. Chosen for its superior performance in JSON generation and logic reasoning at lower latency.
- **Added GPT-OSS 20B (`openai/gpt-oss-20b`)**: Lightweight alternative for ultra-fast data retrieval tasks.
-- update README.md
## Impact
- Improves "Fast Mode" stability for Tool Calling (Scout/Navigator).
- Reduces latency for intermediate reasoning steps.
- README.md +84 -0
- app.py +4 -4
- config.py +9 -0
- services/planner_service.py +33 -15
- ui/components/modals.py +48 -48
README.md
CHANGED
|
@@ -15,4 +15,88 @@ tags:
|
|
| 15 |
- mcp-in-action-track-creative
|
| 16 |
---
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 15 |
- mcp-in-action-track-creative
|
| 16 |
---
|
| 17 |
|
| 18 |
+
# ✨ LifeFlow AI: Intelligent Trip Planning System
|
| 19 |
+
|
| 20 |
+
> **Your journey, in perfect rhythm.** > An enterprise-grade, multi-agent system that orchestrates your daily schedule using real-world data, hybrid AI architecture, and mathematical optimization.
|
| 21 |
+
|
| 22 |
+
---
|
| 23 |
+
|
| 24 |
+
## 📖 Overview
|
| 25 |
+
|
| 26 |
+
**LifeFlow AI** is not just a chatbot; it's a **State Machine for Real-World Operations**. It solves the complexity of daily travel planning—considering traffic, weather, opening hours, and route optimization—by coordinating a team of specialized AI agents.
|
| 27 |
+
|
| 28 |
+
Unlike traditional AI planners that hallucinate locations, LifeFlow grounds every decision in **Real-Time Data** (Google Maps & OpenWeather) and uses **Mathematical Optimization** (TSP/OR-Tools) for routing.
|
| 29 |
+
|
| 30 |
+
## 🚀 Key Innovation: Hybrid AI Architecture
|
| 31 |
+
|
| 32 |
+
We solve the "Trilemma" of AI Agents: **Cost vs. Speed vs. Intelligence**.
|
| 33 |
+
|
| 34 |
+
### 1. Dual-Brain System 🧠 + ⚡
|
| 35 |
+
Instead of using one expensive model for everything, LifeFlow uses a tiered approach:
|
| 36 |
+
* **Primary Brain (The Leader):** Uses high-reasoning models (e.g., **GPT-5, Gemini 2.5 Pro**) for complex intent understanding, team orchestration, and final report generation.
|
| 37 |
+
* **Acceleration Layer (The Muscle):** Uses ultra-fast, low-cost models (e.g., **Groq/Llama-3, Qwen 2.5, Gemini Flash-lite, GPT mini**) for high-volume tool execution (searching POIs, checking weather).
|
| 38 |
+
|
| 39 |
+
### 2. Context-Offloading Protocol 📉
|
| 40 |
+
Traditional agents paste massive JSON search results into the chat context, burning thousands of tokens.
|
| 41 |
+
* **LifeFlow's Approach:** Agents treat data like "Hot Potatoes."
|
| 42 |
+
* **Mechanism:** Raw data (reviews, photos, coordinates) is offloaded to a structured database immediately. Agents only pass **Reference IDs** (e.g., `scout_result_123`) to the next agent.
|
| 43 |
+
* **Result:** Token consumption reduced by **75%** (from ~80k to ~20k per run).
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
## 🤖 The Agent Team
|
| 48 |
+
|
| 49 |
+
LifeFlow orchestrates 6 specialized agents working in a strict pipeline:
|
| 50 |
+
|
| 51 |
+
1. **📋 Planner:** Analyzes vague user requests (e.g., "I need to buy coffee and visit the bank") and converts them into structured JSON tasks.
|
| 52 |
+
2. **👨✈️ Team Leader:** The State Machine orchestrator. Enforces SOPs and handles error recovery.
|
| 53 |
+
3. **🗺️ Scout (Fast Mode):** Interacts with Google Places API to verify locations and retrieve coordinates.
|
| 54 |
+
4. **⚡ Optimizer (Fast Mode):** Uses routing algorithms to solve the *Traveling Salesperson Problem (TSP)* with time windows.
|
| 55 |
+
5. **🧭 Navigator (Fast Mode):** Calculates precise traffic impacts and generates polyline routes.
|
| 56 |
+
6. **🌤️ Weatherman (Fast Mode):** Checks hyper-local weather forecasts for specific arrival times.
|
| 57 |
+
7. **📊 Presenter:** Compiles all data (from the DB) into a human-readable, formatted report.
|
| 58 |
+
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
## 🛠️ Features
|
| 62 |
+
|
| 63 |
+
* **BYOK (Bring Your Own Key):** Secure client-side key management for Google Maps, OpenWeather, and LLMs.
|
| 64 |
+
* **Zero-Cost Validation:** Smart API testing mechanism that checks key validity without incurring charges.
|
| 65 |
+
* **Interactive Map:** Visualizes routes, stops, and alternative POIs using Folium.
|
| 66 |
+
* **Graceful Cancellation:** Cooperative signal handling to terminate background agents instantly.
|
| 67 |
+
* **Reactive UI:** Modern Gradio interface with real-time streaming and responsive layouts.
|
| 68 |
+
|
| 69 |
+
---
|
| 70 |
+
|
| 71 |
+
## ⚙️ Configuration
|
| 72 |
+
|
| 73 |
+
LifeFlow AI allows deep customization via the **Settings** panel:
|
| 74 |
+
|
| 75 |
+
### Supported Providers
|
| 76 |
+
* **Google Gemini:** 2.5 Pro, 2.5 Flash, 2.0 Flash.
|
| 77 |
+
* **OpenAI:** GPT-5, GPT-5-mini, GPT-4o-mini.
|
| 78 |
+
* **Groq:** Llama 3.3 70B, Qwen 2.5 32B (for Acceleration).
|
| 79 |
+
|
| 80 |
+
### Fast Mode (Hybrid)
|
| 81 |
+
Enable **Fast Mode** in settings to offload search and routing tasks to Groq. This significantly reduces latency and API costs while maintaining high-quality reasoning for the final output.
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## 📦 Tech Stack
|
| 86 |
+
|
| 87 |
+
* **Framework:** [Agno](https://github.com/agno-agi/agno) (formerly Phidata) for Agent Orchestration.
|
| 88 |
+
* **UI/UX:** Gradio 5.x with custom CSS themes. (update to Gradio 6.x soon)
|
| 89 |
+
* **Services:** Google Maps Platform (Places, Routes), OpenWeatherMap.
|
| 90 |
+
* **Infrastructure:** Python 3.11, Docker.
|
| 91 |
+
|
| 92 |
+
---
|
| 93 |
+
|
| 94 |
+
## 💻 Local Installation
|
| 95 |
+
|
| 96 |
+
To run LifeFlow AI locally:
|
| 97 |
+
|
| 98 |
+
```bash
|
| 99 |
+
TODO
|
| 100 |
+
```
|
| 101 |
+
|
| 102 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
CHANGED
|
@@ -15,7 +15,6 @@ from ui.components.modals import create_settings_modal, create_doc_modal
|
|
| 15 |
from ui.renderers import (
|
| 16 |
create_agent_dashboard,
|
| 17 |
create_summary_card,
|
| 18 |
-
create_task_card
|
| 19 |
)
|
| 20 |
from core.session import UserSession
|
| 21 |
from services.planner_service import PlannerService
|
|
@@ -254,7 +253,7 @@ class LifeFlowAI:
|
|
| 254 |
session.to_dict()
|
| 255 |
)
|
| 256 |
|
| 257 |
-
def save_settings(self, g, w, prov, m_key, m_sel, fast, g_key_in, s_data):
|
| 258 |
sess = UserSession.from_dict(s_data)
|
| 259 |
|
| 260 |
# 存入 Session
|
|
@@ -265,6 +264,7 @@ class LifeFlowAI:
|
|
| 265 |
'model_api_key': m_key, # 主模型 Key
|
| 266 |
'model': m_sel, # 主模型 ID
|
| 267 |
'enable_fast_mode': fast, # 🔥 Fast Mode 開關
|
|
|
|
| 268 |
'groq_api_key': g_key_in # 🔥 獨立 Groq Key
|
| 269 |
})
|
| 270 |
return gr.update(visible=False), sess.to_dict(), "✅ Configuration Saved"
|
|
@@ -570,7 +570,7 @@ class LifeFlowAI:
|
|
| 570 |
save_set.click(
|
| 571 |
fn=self.save_settings,
|
| 572 |
# 輸入參數對應上面的 create_settings_modal 回傳順序
|
| 573 |
-
inputs=[g_key, w_key, llm_provider, main_key, model_sel, fast_mode_chk, groq_key, session_state],
|
| 574 |
outputs=[settings_modal, session_state, status_bar]
|
| 575 |
)
|
| 576 |
|
|
@@ -584,7 +584,7 @@ class LifeFlowAI:
|
|
| 584 |
def main():
|
| 585 |
app = LifeFlowAI()
|
| 586 |
demo = app.build_interface()
|
| 587 |
-
demo.launch(server_name="0.0.0.0", server_port=
|
| 588 |
#7860
|
| 589 |
if __name__ == "__main__":
|
| 590 |
main()
|
|
|
|
| 15 |
from ui.renderers import (
|
| 16 |
create_agent_dashboard,
|
| 17 |
create_summary_card,
|
|
|
|
| 18 |
)
|
| 19 |
from core.session import UserSession
|
| 20 |
from services.planner_service import PlannerService
|
|
|
|
| 253 |
session.to_dict()
|
| 254 |
)
|
| 255 |
|
| 256 |
+
def save_settings(self, g, w, prov, m_key, m_sel, fast, g_key_in, f_sel, s_data):
|
| 257 |
sess = UserSession.from_dict(s_data)
|
| 258 |
|
| 259 |
# 存入 Session
|
|
|
|
| 264 |
'model_api_key': m_key, # 主模型 Key
|
| 265 |
'model': m_sel, # 主模型 ID
|
| 266 |
'enable_fast_mode': fast, # 🔥 Fast Mode 開關
|
| 267 |
+
'groq_fast_model': f_sel,
|
| 268 |
'groq_api_key': g_key_in # 🔥 獨立 Groq Key
|
| 269 |
})
|
| 270 |
return gr.update(visible=False), sess.to_dict(), "✅ Configuration Saved"
|
|
|
|
| 570 |
save_set.click(
|
| 571 |
fn=self.save_settings,
|
| 572 |
# 輸入參數對應上面的 create_settings_modal 回傳順序
|
| 573 |
+
inputs=[g_key, w_key, llm_provider, main_key, model_sel, fast_mode_chk, groq_key, groq_model_sel, session_state],
|
| 574 |
outputs=[settings_modal, session_state, status_bar]
|
| 575 |
)
|
| 576 |
|
|
|
|
| 584 |
def main():
|
| 585 |
app = LifeFlowAI()
|
| 586 |
demo = app.build_interface()
|
| 587 |
+
demo.launch(server_name="0.0.0.0", server_port=8080, share=True, show_error=True)
|
| 588 |
#7860
|
| 589 |
if __name__ == "__main__":
|
| 590 |
main()
|
config.py
CHANGED
|
@@ -7,6 +7,8 @@ import os
|
|
| 7 |
from pathlib import Path
|
| 8 |
|
| 9 |
# ===== 系統預設值 =====
|
|
|
|
|
|
|
| 10 |
DEFAULT_PROVIDER = "Gemini"
|
| 11 |
DEFAULT_MODEL = "gemini-2.5-flash"
|
| 12 |
|
|
@@ -40,6 +42,13 @@ MODEL_OPTIONS = {
|
|
| 40 |
]
|
| 41 |
}
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
# ===== Agent 資訊配置 (前端顯示用) =====
|
| 44 |
AGENTS_INFO = {
|
| 45 |
'planner': {
|
|
|
|
| 7 |
from pathlib import Path
|
| 8 |
|
| 9 |
# ===== 系統預設值 =====
|
| 10 |
+
BASE_DIR = Path(__file__).parent
|
| 11 |
+
|
| 12 |
DEFAULT_PROVIDER = "Gemini"
|
| 13 |
DEFAULT_MODEL = "gemini-2.5-flash"
|
| 14 |
|
|
|
|
| 42 |
]
|
| 43 |
}
|
| 44 |
|
| 45 |
+
GROQ_FAST_MODEL_OPTIONS = [
|
| 46 |
+
("GPT-OSS 20B", "openai/gpt-oss-20b"),
|
| 47 |
+
#("Llama 3.1 8B", "llama-3.1-8b-instant"),
|
| 48 |
+
#("Llama 4 scout", "llama-4-scout-17b-16e-instructe"),
|
| 49 |
+
("QWEN 3 32B", "qwen/qwen3-32b")
|
| 50 |
+
]
|
| 51 |
+
|
| 52 |
# ===== Agent 資訊配置 (前端顯示用) =====
|
| 53 |
AGENTS_INFO = {
|
| 54 |
'planner': {
|
services/planner_service.py
CHANGED
|
@@ -23,6 +23,7 @@ from core.visualizers import create_animated_map
|
|
| 23 |
from config import AGENTS_INFO
|
| 24 |
|
| 25 |
# 導入 Model API
|
|
|
|
| 26 |
from agno.models.google import Gemini
|
| 27 |
from agno.models.openai import OpenAIChat
|
| 28 |
from agno.models.groq import Groq
|
|
@@ -42,8 +43,16 @@ from src.tools import (
|
|
| 42 |
)
|
| 43 |
from src.infra.logger import get_logger
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
logger = get_logger(__name__)
|
| 46 |
-
max_retries =
|
| 47 |
|
| 48 |
|
| 49 |
@contextmanager
|
|
@@ -185,6 +194,7 @@ class PlannerService:
|
|
| 185 |
provider = settings.get("llm_provider", "Gemini")
|
| 186 |
main_api_key = settings.get("model_api_key")
|
| 187 |
selected_model_id = settings.get("model", "gemini-2.5-flash")
|
|
|
|
| 188 |
google_map_key = settings.get("google_maps_api_key")
|
| 189 |
weather_map_key = settings.get("openweather_api_key")
|
| 190 |
|
|
@@ -197,7 +207,13 @@ class PlannerService:
|
|
| 197 |
|
| 198 |
# 2. 初始化 "主模型 (Brain)" - 負責 Planner, Leader, Presenter
|
| 199 |
if provider.lower() == "gemini":
|
| 200 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 201 |
elif provider.lower() == "openai":
|
| 202 |
main_brain = OpenAIChat(id=selected_model_id, api_key=main_api_key, reasoning_effort="low")
|
| 203 |
elif provider.lower() == "groq":
|
|
@@ -210,11 +226,10 @@ class PlannerService:
|
|
| 210 |
|
| 211 |
# 🔥 判斷是否啟用 Fast Mode
|
| 212 |
if enable_fast_mode and groq_api_key:
|
| 213 |
-
model_logger["sub_model"] =
|
| 214 |
-
logger.info("⚡ Fast Mode ENABLED: Using Groq
|
| 215 |
-
# 強制使用 Llama 3 70B,並壓低 Temperature
|
| 216 |
helper_model = Groq(
|
| 217 |
-
id=
|
| 218 |
api_key=groq_api_key,
|
| 219 |
temperature=0.1
|
| 220 |
)
|
|
@@ -223,7 +238,7 @@ class PlannerService:
|
|
| 223 |
logger.info("🐢 Fast Mode DISABLED: Helpers using Main Provider.")
|
| 224 |
if provider.lower() == "gemini":
|
| 225 |
model_logger["sub_model"] = "gemini-2.5-flash-lite"
|
| 226 |
-
helper_model = Gemini(id="gemini-2.5-flash-lite", api_key=main_api_key)
|
| 227 |
elif provider.lower() == "openai":
|
| 228 |
model_logger["sub_model"] = "gpt-4o-mini"
|
| 229 |
helper_model = OpenAIChat(id="gpt-4o-mini", api_key=main_api_key)
|
|
@@ -597,7 +612,7 @@ class PlannerService:
|
|
| 597 |
|
| 598 |
if sid in self._cancelled_sessions:
|
| 599 |
logger.warning(f"🛑 Execution terminated by user for session {sid}")
|
| 600 |
-
self._cancelled_sessions.remove(sid)
|
| 601 |
yield {"type": "error", "message": "Plan cancelled by user."}
|
| 602 |
return
|
| 603 |
|
|
@@ -702,6 +717,10 @@ class PlannerService:
|
|
| 702 |
# 6. Team Complete
|
| 703 |
elif event.event == TeamRunEvent.run_completed:
|
| 704 |
self._add_reasoning(session, "team", "🎉 Planning process finished")
|
|
|
|
|
|
|
|
|
|
|
|
|
| 705 |
|
| 706 |
|
| 707 |
if not has_content:
|
|
@@ -714,11 +733,10 @@ class PlannerService:
|
|
| 714 |
break
|
| 715 |
|
| 716 |
finally:
|
| 717 |
-
logger.info(f"Total tokens: {event.metrics.total_tokens}")
|
| 718 |
-
logger.info(f"Input tokens: {event.metrics.input_tokens}")
|
| 719 |
-
logger.info(f"Output tokens: {event.metrics.output_tokens}")
|
| 720 |
logger.info(f"Run time (s): {time.perf_counter() - start_time}")
|
| 721 |
|
|
|
|
|
|
|
| 722 |
for agent in ["scout", "optimizer", "navigator", "weatherman", "presenter"]:
|
| 723 |
yield {
|
| 724 |
"type": "reasoning_update",
|
|
@@ -743,16 +761,16 @@ class PlannerService:
|
|
| 743 |
"agent_status": ("team", "complete", "Finished")
|
| 744 |
}
|
| 745 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 746 |
except Exception as e:
|
| 747 |
logger.error(f"Error in attempt {attempt}: {e}")
|
| 748 |
if attempt >= max_retries:
|
| 749 |
yield {"type": "error", "message": str(e), "session": session}
|
| 750 |
return
|
| 751 |
|
| 752 |
-
except Exception as e:
|
| 753 |
-
logger.error(f"Team run error: {e}", exc_info=True)
|
| 754 |
-
yield {"type": "error", "message": str(e), "session": session}
|
| 755 |
-
|
| 756 |
# ================= Step 4: Finalize =================
|
| 757 |
|
| 758 |
def run_step4_finalize(self, session: UserSession) -> Dict[str, Any]:
|
|
|
|
| 23 |
from config import AGENTS_INFO
|
| 24 |
|
| 25 |
# 導入 Model API
|
| 26 |
+
from google.genai.types import HarmCategory, HarmBlockThreshold
|
| 27 |
from agno.models.google import Gemini
|
| 28 |
from agno.models.openai import OpenAIChat
|
| 29 |
from agno.models.groq import Groq
|
|
|
|
| 43 |
)
|
| 44 |
from src.infra.logger import get_logger
|
| 45 |
|
| 46 |
+
|
| 47 |
+
gemini_safety_settings = [
|
| 48 |
+
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "BLOCK_NONE"},
|
| 49 |
+
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "BLOCK_NONE"},
|
| 50 |
+
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "BLOCK_NONE"},
|
| 51 |
+
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "BLOCK_NONE"},
|
| 52 |
+
]
|
| 53 |
+
|
| 54 |
logger = get_logger(__name__)
|
| 55 |
+
max_retries = 5
|
| 56 |
|
| 57 |
|
| 58 |
@contextmanager
|
|
|
|
| 194 |
provider = settings.get("llm_provider", "Gemini")
|
| 195 |
main_api_key = settings.get("model_api_key")
|
| 196 |
selected_model_id = settings.get("model", "gemini-2.5-flash")
|
| 197 |
+
helper_model_id = settings.get("groq_fast_model", "openai/gpt-oss-20b")
|
| 198 |
google_map_key = settings.get("google_maps_api_key")
|
| 199 |
weather_map_key = settings.get("openweather_api_key")
|
| 200 |
|
|
|
|
| 207 |
|
| 208 |
# 2. 初始化 "主模型 (Brain)" - 負責 Planner, Leader, Presenter
|
| 209 |
if provider.lower() == "gemini":
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
main_brain = Gemini(id=selected_model_id,
|
| 214 |
+
api_key=main_api_key,
|
| 215 |
+
thinking_budget=1024,
|
| 216 |
+
safety_settings=gemini_safety_settings)
|
| 217 |
elif provider.lower() == "openai":
|
| 218 |
main_brain = OpenAIChat(id=selected_model_id, api_key=main_api_key, reasoning_effort="low")
|
| 219 |
elif provider.lower() == "groq":
|
|
|
|
| 226 |
|
| 227 |
# 🔥 判斷是否啟用 Fast Mode
|
| 228 |
if enable_fast_mode and groq_api_key:
|
| 229 |
+
model_logger["sub_model"] = helper_model_id
|
| 230 |
+
logger.info(f"⚡ Fast Mode ENABLED: Using Groq - {helper_model_id} for helpers.")
|
|
|
|
| 231 |
helper_model = Groq(
|
| 232 |
+
id=helper_model_id,
|
| 233 |
api_key=groq_api_key,
|
| 234 |
temperature=0.1
|
| 235 |
)
|
|
|
|
| 238 |
logger.info("🐢 Fast Mode DISABLED: Helpers using Main Provider.")
|
| 239 |
if provider.lower() == "gemini":
|
| 240 |
model_logger["sub_model"] = "gemini-2.5-flash-lite"
|
| 241 |
+
helper_model = Gemini(id="gemini-2.5-flash-lite", api_key=main_api_key,safety_settings=gemini_safety_settings)
|
| 242 |
elif provider.lower() == "openai":
|
| 243 |
model_logger["sub_model"] = "gpt-4o-mini"
|
| 244 |
helper_model = OpenAIChat(id="gpt-4o-mini", api_key=main_api_key)
|
|
|
|
| 612 |
|
| 613 |
if sid in self._cancelled_sessions:
|
| 614 |
logger.warning(f"🛑 Execution terminated by user for session {sid}")
|
| 615 |
+
self._cancelled_sessions.remove(sid)
|
| 616 |
yield {"type": "error", "message": "Plan cancelled by user."}
|
| 617 |
return
|
| 618 |
|
|
|
|
| 717 |
# 6. Team Complete
|
| 718 |
elif event.event == TeamRunEvent.run_completed:
|
| 719 |
self._add_reasoning(session, "team", "🎉 Planning process finished")
|
| 720 |
+
if hasattr(event, 'metrics'):
|
| 721 |
+
logger.info(f"Total tokens: {event.metrics.total_tokens}")
|
| 722 |
+
logger.info(f"Input tokens: {event.metrics.input_tokens}")
|
| 723 |
+
logger.info(f"Output tokens: {event.metrics.output_tokens}")
|
| 724 |
|
| 725 |
|
| 726 |
if not has_content:
|
|
|
|
| 733 |
break
|
| 734 |
|
| 735 |
finally:
|
|
|
|
|
|
|
|
|
|
| 736 |
logger.info(f"Run time (s): {time.perf_counter() - start_time}")
|
| 737 |
|
| 738 |
+
|
| 739 |
+
|
| 740 |
for agent in ["scout", "optimizer", "navigator", "weatherman", "presenter"]:
|
| 741 |
yield {
|
| 742 |
"type": "reasoning_update",
|
|
|
|
| 761 |
"agent_status": ("team", "complete", "Finished")
|
| 762 |
}
|
| 763 |
|
| 764 |
+
except GeneratorExit:
|
| 765 |
+
logger.warning("⚠️ Generator closed by client (Gradio Stop).")
|
| 766 |
+
return # 靜默退出,不要報錯
|
| 767 |
+
|
| 768 |
except Exception as e:
|
| 769 |
logger.error(f"Error in attempt {attempt}: {e}")
|
| 770 |
if attempt >= max_retries:
|
| 771 |
yield {"type": "error", "message": str(e), "session": session}
|
| 772 |
return
|
| 773 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 774 |
# ================= Step 4: Finalize =================
|
| 775 |
|
| 776 |
def run_step4_finalize(self, session: UserSession) -> Dict[str, Any]:
|
ui/components/modals.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
# ui/components/modals.py
|
| 2 |
import gradio as gr
|
| 3 |
-
from config import MODEL_OPTIONS, DEFAULT_PROVIDER, DEFAULT_MODEL
|
| 4 |
-
|
| 5 |
|
| 6 |
def create_validated_input(label, placeholder, type="password"):
|
| 7 |
"""
|
|
@@ -66,18 +66,14 @@ def create_settings_modal():
|
|
| 66 |
gr.Markdown("Configure Groq for speed.", elem_classes="tab-desc")
|
| 67 |
|
| 68 |
fast_mode_chk = gr.Checkbox(
|
| 69 |
-
label="Enable Fast Mode",
|
| 70 |
value=False,
|
| 71 |
elem_classes="modern-checkbox"
|
| 72 |
)
|
| 73 |
|
| 74 |
groq_model_sel = gr.Dropdown(
|
| 75 |
-
choices=
|
| 76 |
-
|
| 77 |
-
("GPT-OSS 20B", "openai/gpt-oss-20b"),
|
| 78 |
-
("Llama 4 scout", "llama-4-scout-17b-16e-instructe")
|
| 79 |
-
],
|
| 80 |
-
value="llama-3.1-8b-instant",
|
| 81 |
label="Model",
|
| 82 |
elem_classes="modern-dropdown",
|
| 83 |
visible = False # <--- 預設隱藏
|
|
@@ -106,44 +102,48 @@ def create_settings_modal():
|
|
| 106 |
|
| 107 |
|
| 108 |
def create_doc_modal():
|
| 109 |
-
"""
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
""
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
|
| 149 |
return doc_modal, close_doc_btn
|
|
|
|
| 1 |
# ui/components/modals.py
|
| 2 |
import gradio as gr
|
| 3 |
+
from config import MODEL_OPTIONS, DEFAULT_PROVIDER, DEFAULT_MODEL, GROQ_FAST_MODEL_OPTIONS
|
| 4 |
+
from config import BASE_DIR
|
| 5 |
|
| 6 |
def create_validated_input(label, placeholder, type="password"):
|
| 7 |
"""
|
|
|
|
| 66 |
gr.Markdown("Configure Groq for speed.", elem_classes="tab-desc")
|
| 67 |
|
| 68 |
fast_mode_chk = gr.Checkbox(
|
| 69 |
+
label="Enable Fast Sub-Mode",
|
| 70 |
value=False,
|
| 71 |
elem_classes="modern-checkbox"
|
| 72 |
)
|
| 73 |
|
| 74 |
groq_model_sel = gr.Dropdown(
|
| 75 |
+
choices=GROQ_FAST_MODEL_OPTIONS,
|
| 76 |
+
value=GROQ_FAST_MODEL_OPTIONS[0][1],
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
label="Model",
|
| 78 |
elem_classes="modern-dropdown",
|
| 79 |
visible = False # <--- 預設隱藏
|
|
|
|
| 102 |
|
| 103 |
|
| 104 |
def create_doc_modal():
|
| 105 |
+
"""
|
| 106 |
+
創建文檔模態框
|
| 107 |
+
功能:自動讀取 README.md 並【過濾掉】YAML Front Matter
|
| 108 |
+
"""
|
| 109 |
+
|
| 110 |
+
readme_path = BASE_DIR / "README.md"
|
| 111 |
+
doc_content = ""
|
| 112 |
+
|
| 113 |
+
try:
|
| 114 |
+
if readme_path.exists():
|
| 115 |
+
with open(readme_path, "r", encoding="utf-8") as f:
|
| 116 |
+
raw_content = f.read()
|
| 117 |
+
|
| 118 |
+
# 🔥🔥🔥 [核心修正] 過濾 YAML Front Matter 🔥🔥🔥
|
| 119 |
+
# 邏輯:YAML 區塊通常夾在兩個 "---" 之間,且位於檔案最上方
|
| 120 |
+
if raw_content.startswith("---"):
|
| 121 |
+
# 使用 split 切割,限制切割次數為 2
|
| 122 |
+
# 結果會是 ['', 'yaml內容', '剩下的Markdown內容']
|
| 123 |
+
parts = raw_content.split("---", 2)
|
| 124 |
+
if len(parts) >= 3:
|
| 125 |
+
doc_content = parts[2].strip() # 取出真正的內容並去除首尾空白
|
| 126 |
+
else:
|
| 127 |
+
doc_content = raw_content # 格式不對,就顯示原文
|
| 128 |
+
else:
|
| 129 |
+
doc_content = raw_content
|
| 130 |
+
|
| 131 |
+
else:
|
| 132 |
+
doc_content = "## ⚠️ Documentation Not Found"
|
| 133 |
+
|
| 134 |
+
except Exception as e:
|
| 135 |
+
doc_content = f"## ❌ Error Loading Documentation\n\n{str(e)}"
|
| 136 |
+
|
| 137 |
+
# ... (後面的 UI 構建代碼保持不變) ...
|
| 138 |
+
with gr.Group(visible=False, elem_classes="modal-overlay", elem_id="doc-modal") as doc_modal:
|
| 139 |
+
with gr.Group(elem_classes="modal-box"):
|
| 140 |
+
with gr.Row(elem_classes="modal-header"):
|
| 141 |
+
gr.Markdown("### 📖 Documentation", elem_classes="modal-title")
|
| 142 |
+
|
| 143 |
+
with gr.Column(elem_classes="modal-content"):
|
| 144 |
+
gr.Markdown(doc_content) # 這裡現在只會顯示乾淨的 Markdown
|
| 145 |
+
|
| 146 |
+
with gr.Row(elem_classes="modal-footer"):
|
| 147 |
+
close_doc_btn = gr.Button("Close", variant="secondary", elem_classes="btn-cancel")
|
| 148 |
|
| 149 |
return doc_modal, close_doc_btn
|