From 8fc6a59dc1f8998440f22157f3727e17ca80c492 Mon Sep 17 00:00:00 2001 From: seyyah Date: Wed, 17 Dec 2025 19:17:17 +0300 Subject: [PATCH 1/4] . --- CLAUDE.md | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++ app/page.tsx | 2 +- 2 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..07497d9c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,192 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a full-stack AI agent application combining **CopilotKit**, **A2A (Agent-to-Agent)**, and **A2UI (Agent-to-UI)** frameworks. The application demonstrates a restaurant finder agent that dynamically generates UI components based on LLM responses. + +**Stack:** +- Frontend: Next.js 16 with React 19, Tailwind CSS 4 +- Backend Agent: Python 3.13+ with Google ADK (Agent Development Kit) +- Agent Framework: A2A SDK for agent communication +- UI Protocol: A2UI for declarative UI generation +- Package Manager: Any (pnpm/npm/yarn/bun) for Node.js, `uv` for Python + +## Development Commands + +### Starting the Application + +```bash +# Start both UI and agent servers concurrently (recommended) +pnpm dev + +# Start with debug logging +pnpm dev:debug + +# Start only the Next.js UI (port 3000) +pnpm dev:ui + +# Start only the Python agent server (port 10002) +pnpm dev:agent +``` + +### Building and Deployment + +```bash +# Build Next.js for production +pnpm build + +# Start production server +pnpm start + +# Lint code +pnpm lint +``` + +### Python Agent Development + +```bash +# Install/sync Python dependencies manually +cd agent +uv sync + +# Run agent directly +uv run . + +# Install Python deps from root (handled by postinstall) +pnpm install:agent +``` + +## Architecture + +### Two-Server Architecture + +The application runs two concurrent servers: + +1. **Next.js Frontend** (port 3000) + - Located in `app/` directory + - Main entry: `app/page.tsx` - renders CopilotChat with A2UI renderer + - API route: `app/api/copilotkit/[[...slug]]/route.tsx` - CopilotKit endpoint that connects to A2A agent + +2. **Python Agent Server** (port 10002) + - Located in `agent/` directory + - Entry point: `agent/__main__.py` - A2A server with Starlette/uvicorn + - Agent logic: `agent/agent.py` - RestaurantAgent with Google ADK/Gemini + - Tools: `agent/tools.py` - get_restaurants tool + - UI templates: `agent/prompt_builder.py` - A2UI component schemas and examples + +### Communication Flow + +``` +User → Next.js UI → CopilotKit Runtime → A2A Client (localhost:10002) + → A2A Server → RestaurantAgentExecutor → RestaurantAgent (Google ADK + Gemini) + → LLM generates A2UI JSON → Validated against schema → Rendered in UI +``` + +### Key Components + +**Frontend (`app/`):** +- `page.tsx`: CopilotKitProvider with A2UIMessageRenderer +- `api/copilotkit/[[...slug]]/route.tsx`: Creates A2AAgent pointing to localhost:10002 +- `theme.ts`: A2UI theme configuration (colors, fonts, spacing) + +**Agent (`agent/`):** +- `__main__.py`: A2A server setup with CORS, static file serving, agent card definition +- `agent_executor.py`: RestaurantAgentExecutor - handles UI/text mode switching, processes user actions (book_restaurant, submit_booking) +- `agent.py`: RestaurantAgent - wraps Google ADK LlmAgent, validates A2UI JSON responses, implements retry logic +- `prompt_builder.py`: Contains A2UI_SCHEMA and RESTAURANT_UI_EXAMPLES (single column list, two column list, booking form, confirmation) +- `tools.py`: get_restaurants tool that loads from restaurant_data.json +- `restaurant_data.json`: Mock restaurant data + +**A2UI Extension (`a2ui_extension/`):** +- Custom A2UI extension for the agent (workspace dependency) + +### A2UI Architecture + +The agent generates declarative UI using A2UI protocol: + +1. **UI Templates**: Defined in `agent/prompt_builder.py` as examples (SINGLE_COLUMN_LIST_EXAMPLE, TWO_COLUMN_LIST_EXAMPLE, BOOKING_FORM_EXAMPLE, CONFIRMATION_EXAMPLE) +2. **Schema Validation**: A2UI_SCHEMA defines valid component types (Text, Image, Button, Card, Row, Column, List, etc.) +3. **Component Generation**: LLM generates JSON matching schema with three message types: + - `beginRendering`: Initialize surface with root component + - `surfaceUpdate`: Define components with IDs and hierarchical structure + - `dataModelUpdate`: Populate data model with actual content +4. **Rendering**: Frontend A2UIMessageRenderer converts JSON to React components + +### Agent Response Format + +Agent responses split into two parts with `---a2ui_JSON---` delimiter: +1. Text response (conversational) +2. JSON array of A2UI messages (validated against schema) + +## Environment Configuration + +Create `agent/.env`: +``` +GEMINI_API_KEY=your-api-key-here +``` + +Optional: +``` +GOOGLE_GENAI_USE_VERTEXAI=TRUE # Use Vertex AI instead of API key +LITELLM_MODEL=gemini/gemini-2.5-flash # Override default model +``` + +## File Locations + +- Next.js pages/components: `app/` +- Agent server: `agent/` +- Python dependencies: `agent/pyproject.toml` +- Node dependencies: `package.json` +- Scripts: `scripts/` (setup-agent.sh, run-agent.sh) +- A2UI extension: `a2ui_extension/src/a2ui/` +- Static assets (restaurant images): `agent/images/` + +## Workspace Structure + +UV workspace with two members: +- `agent/` - Main agent package (a2ui-restaurant-finder) +- `a2ui_extension/` - Custom A2UI extension + +Both managed via root `pyproject.toml` workspace configuration. + +## Common Workflows + +### Adding New UI Components + +1. Design component structure in A2UI format +2. Add example template to `agent/prompt_builder.py` in RESTAURANT_UI_EXAMPLES +3. Update agent instructions in `agent/agent.py` to use new template +4. Optionally use [A2UI Composer](https://a2ui-editor.ag-ui.com) to generate components + +### Adding New Agent Tools + +1. Define tool function in `agent/tools.py` with Google ADK signature +2. Add tool to `tools` list in `agent/agent.py` LlmAgent initialization +3. Update AGENT_INSTRUCTION to document tool usage + +### Modifying Theme + +Edit `app/theme.ts` to customize: +- Colors (primary, secondary, accent, background) +- Fonts +- Spacing +- Component styling + +## Troubleshooting + +**Agent connection errors:** +- Verify agent server is running on port 10002 +- Check GEMINI_API_KEY is set in `agent/.env` +- Ensure both servers started successfully + +**Python import errors:** +```bash +cd agent +uv sync +``` + +**Port conflicts:** +- UI runs on port 3000 (configurable with Next.js) +- Agent runs on port 10002 (configurable with --port flag in `agent/__main__.py`) diff --git a/app/page.tsx b/app/page.tsx index 3b11f552..2b2e5c6c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -21,7 +21,7 @@ export default function Home() { className="h-full overflow-auto w-screen" style={{ minHeight: "100dvh" }} > - ; + ); From 1b4ffe97fd259ff64d983ca7170e75a2d26651d6 Mon Sep 17 00:00:00 2001 From: seyyah Date: Thu, 18 Dec 2025 13:44:19 +0300 Subject: [PATCH 2/4] . --- app/page.tsx | 15 +++++++++++---- app/theme.ts | 16 ---------------- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/app/page.tsx b/app/page.tsx index 2b2e5c6c..d108e466 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -8,21 +8,28 @@ import { theme } from "./theme"; export const dynamic = "force-dynamic"; const A2UIMessageRenderer = createA2UIMessageRenderer({ theme }); -const activityRenderers = [A2UIMessageRenderer]; export default function Home() { return (
- +
); } + +function Chat() { + return ( +
+ +
+ ); +} diff --git a/app/theme.ts b/app/theme.ts index 17a88ecd..4291c633 100644 --- a/app/theme.ts +++ b/app/theme.ts @@ -1,19 +1,3 @@ -/* - Copyright 2025 Google LLC - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - import { v0_8 } from "@a2ui/lit"; /** Elements */ From b613e26e0e741765a740ab174514a98b2328397a Mon Sep 17 00:00:00 2001 From: seyyah Date: Thu, 18 Dec 2025 13:55:31 +0300 Subject: [PATCH 3/4] SUCCESS --- CLAUDE.md | 34 +++++++++++++++++++++++++--------- agent/__main__.py | 13 +++++++------ agent/agent.py | 2 +- 3 files changed, 33 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 07497d9c..100a7946 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -9,6 +9,7 @@ This is a full-stack AI agent application combining **CopilotKit**, **A2A (Agent **Stack:** - Frontend: Next.js 16 with React 19, Tailwind CSS 4 - Backend Agent: Python 3.13+ with Google ADK (Agent Development Kit) +- LLM Provider: OpenAI o1-mini (reasoning model with tool support) - Agent Framework: A2A SDK for agent communication - UI Protocol: A2UI for declarative UI generation - Package Manager: Any (pnpm/npm/yarn/bun) for Node.js, `uv` for Python @@ -80,7 +81,7 @@ The application runs two concurrent servers: ``` User → Next.js UI → CopilotKit Runtime → A2A Client (localhost:10002) - → A2A Server → RestaurantAgentExecutor → RestaurantAgent (Google ADK + Gemini) + → A2A Server → RestaurantAgentExecutor → RestaurantAgent (Google ADK + OpenAI o1-mini) → LLM generates A2UI JSON → Validated against schema → Rendered in UI ``` @@ -94,7 +95,7 @@ User → Next.js UI → CopilotKit Runtime → A2A Client (localhost:10002) **Agent (`agent/`):** - `__main__.py`: A2A server setup with CORS, static file serving, agent card definition - `agent_executor.py`: RestaurantAgentExecutor - handles UI/text mode switching, processes user actions (book_restaurant, submit_booking) -- `agent.py`: RestaurantAgent - wraps Google ADK LlmAgent, validates A2UI JSON responses, implements retry logic +- `agent.py`: RestaurantAgent - wraps Google ADK LlmAgent with OpenAI o1-mini, validates A2UI JSON responses, implements retry logic - `prompt_builder.py`: Contains A2UI_SCHEMA and RESTAURANT_UI_EXAMPLES (single column list, two column list, booking form, confirmation) - `tools.py`: get_restaurants tool that loads from restaurant_data.json - `restaurant_data.json`: Mock restaurant data @@ -124,14 +125,28 @@ Agent responses split into two parts with `---a2ui_JSON---` delimiter: Create `agent/.env`: ``` -GEMINI_API_KEY=your-api-key-here +# OpenAI API Key (for o1-mini model) +# Get your API key from: https://platform.openai.com/api-keys +OPENAI_API_KEY=your-openai-api-key-here + +# Model selection (default: o1-mini) +# Available OpenAI models with tool support: +# - o1-mini (reasoning model, recommended for complex tasks) +# - gpt-4o (multimodal, fast) +# - gpt-4o-mini (cheaper, faster) +# - gpt-4-turbo (legacy) +# +# Other supported models: +# - gemini/gemini-2.0-flash-exp (Google, free tier available) +# - perplexity/llama-3.1-sonar-large-128k-online (NO tool support) +LITELLM_MODEL=o1-mini + +# Retry and timeout configuration +LITELLM_NUM_RETRIES=3 +LITELLM_TIMEOUT=60 ``` -Optional: -``` -GOOGLE_GENAI_USE_VERTEXAI=TRUE # Use Vertex AI instead of API key -LITELLM_MODEL=gemini/gemini-2.5-flash # Override default model -``` +> **Note:** Get your OpenAI API key from [https://platform.openai.com/api-keys](https://platform.openai.com/api-keys) ## File Locations @@ -178,7 +193,8 @@ Edit `app/theme.ts` to customize: **Agent connection errors:** - Verify agent server is running on port 10002 -- Check GEMINI_API_KEY is set in `agent/.env` +- Check OPENAI_API_KEY is set in `agent/.env` +- Verify LITELLM_MODEL is set to a valid model (default: o1-mini) - Ensure both servers started successfully **Python import errors:** diff --git a/agent/__main__.py b/agent/__main__.py index a0ec4a65..d269d10c 100644 --- a/agent/__main__.py +++ b/agent/__main__.py @@ -42,12 +42,13 @@ class MissingAPIKeyError(Exception): @click.option("--port", default=10002) def main(host, port): try: - # Check for API key only if Vertex AI is not configured - if not os.getenv("GOOGLE_GENAI_USE_VERTEXAI") == "TRUE": - if not os.getenv("GEMINI_API_KEY"): - raise MissingAPIKeyError( - "GEMINI_API_KEY environment variable not set and GOOGLE_GENAI_USE_VERTEXAI is not TRUE." - ) + # Check for OpenAI API key (required for o1-mini model) + if not os.getenv("OPENAI_API_KEY"): + raise MissingAPIKeyError( + "OPENAI_API_KEY environment variable not set. " + "Please set it in agent/.env file. " + "Get your API key from: https://platform.openai.com/api-keys" + ) capabilities = AgentCapabilities( streaming=True, diff --git a/agent/agent.py b/agent/agent.py index 5283cd88..b3e6a55f 100644 --- a/agent/agent.py +++ b/agent/agent.py @@ -93,7 +93,7 @@ def get_processing_message(self) -> str: def _build_agent(self, use_ui: bool) -> LlmAgent: """Builds the LLM agent for the restaurant agent.""" - LITELLM_MODEL = os.getenv("LITELLM_MODEL", "gemini/gemini-2.5-flash") + LITELLM_MODEL = os.getenv("LITELLM_MODEL", "o4-mini") if use_ui: # Construct the full prompt with UI instructions, examples, and schema From 29ec2079073b288b0503fa94a190b3df3aefcad1 Mon Sep 17 00:00:00 2001 From: seyyah Date: Thu, 18 Dec 2025 18:13:05 +0300 Subject: [PATCH 4/4] gemini 2.5-flash: SUCCESS --- CLAUDE.md | 43 ++++++++---- ISTABOT.md | 33 +++++++++ agent/agent.py | 182 +++++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 226 insertions(+), 32 deletions(-) create mode 100644 ISTABOT.md diff --git a/CLAUDE.md b/CLAUDE.md index 100a7946..77088eb5 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -125,28 +125,47 @@ Agent responses split into two parts with `---a2ui_JSON---` delimiter: Create `agent/.env`: ``` -# OpenAI API Key (for o1-mini model) +# OpenRouter API Key (recommended - access to multiple providers) +# Get your API key from: https://openrouter.ai/keys +OPENROUTER_API_KEY=your-openrouter-api-key-here + +# OpenAI API Key (optional - for direct OpenAI access) # Get your API key from: https://platform.openai.com/api-keys OPENAI_API_KEY=your-openai-api-key-here -# Model selection (default: o1-mini) -# Available OpenAI models with tool support: -# - o1-mini (reasoning model, recommended for complex tasks) -# - gpt-4o (multimodal, fast) -# - gpt-4o-mini (cheaper, faster) -# - gpt-4-turbo (legacy) +# Google API Key (optional - for direct Google access) +# Get your API key from: https://aistudio.google.com/apikey +GOOGLE_API_KEY=your-google-api-key-here + +# Model selection (default: openrouter/google/gemini-2.0-flash-thinking-exp:free) +# +# OpenRouter models (use openrouter/ prefix): +# Free models with TOOL CALLING support (REQUIRED for this app): +# - openrouter/google/gemini-2.0-flash-thinking-exp:free (RECOMMENDED - Google, reasoning + tools) +# - openrouter/qwen/qwq-32b-preview:free (Qwen reasoning model with tools) +# - openrouter/mistralai/mistral-small-3.1:free (Mistral Small, function calling) +# - openrouter/google/gemini-2.0-flash-exp:free (Google Gemini 2.0, may have rate limits) +# +# ⚠️ Models WITHOUT tool support (DO NOT USE): +# - openrouter/deepseek/deepseek-r1-* (no tool calling support) +# - openrouter/deepseek/deepseek-chat:free (no tool calling support) +# Paid models: +# - openrouter/openai/gpt-4o (OpenAI GPT-4o via OpenRouter) +# - openrouter/anthropic/claude-3.5-sonnet (Claude 3.5 Sonnet) +# - openrouter/meta-llama/llama-3.1-70b-instruct (Meta Llama 3.1) # -# Other supported models: -# - gemini/gemini-2.0-flash-exp (Google, free tier available) -# - perplexity/llama-3.1-sonar-large-128k-online (NO tool support) -LITELLM_MODEL=o1-mini +# Direct provider models (requires respective API keys): +# - OpenAI: o1-mini, gpt-4o, gpt-4o-mini, gpt-4-turbo +# - Google: gemini/gemini-2.0-flash-exp, gemini/gemini-2.5-flash-lite +# - Perplexity (NO tool support): perplexity/llama-3.1-sonar-large-128k-online +LITELLM_MODEL=openrouter/google/gemini-2.0-flash-exp:free # Retry and timeout configuration LITELLM_NUM_RETRIES=3 LITELLM_TIMEOUT=60 ``` -> **Note:** Get your OpenAI API key from [https://platform.openai.com/api-keys](https://platform.openai.com/api-keys) +> **Note:** Get your OpenRouter API key from [https://openrouter.ai/keys](https://openrouter.ai/keys) (recommended) or OpenAI key from [https://platform.openai.com/api-keys](https://platform.openai.com/api-keys) ## File Locations diff --git a/ISTABOT.md b/ISTABOT.md new file mode 100644 index 00000000..d29b8ac3 --- /dev/null +++ b/ISTABOT.md @@ -0,0 +1,33 @@ +a2ui, copilotkit, langgraph/mastra/openserv vs ile istabot.com musterilerimiz icin agent-supported bot insa etmek istiyorum. Oncelikle + google un yeni duyurdugu a2ui i ve sonrasinda copilotkit/langraph i anlamama yardimci olur musun + + ``` + A2UI: Agent-to-User Interface + A2UI is an open-source project, complete with a format optimized for representing updateable agent-generated UIs and an initial set of + renderers, that allows agents to generate or populate rich user interfaces. + https://github.com/google/A2UI + ``` + + persona + + ``` + TEMEL KİMLİK VE MİSYON (PERSONA BİLGİSİ) + Alan + Değer/Tanım + Kaynaklar + Ajans Rolü + Kıdemli Biyoistatistik Uzmanı ve Grafik Motoru Mimarı. Karmaşık istatistiksel analizleri (özellikle klinik araştırmalarda) kodlama + gerektirmeden erişilebilir ve hatasız hale getirir. + Hedef Kitle + Akademisyenler, doktora öğrencileri, araştırmacılar, sağlık profesyonelleri (doktorlar, hemşireler, diş hekimleri). Çıktıların APA + formatında ve akademik yayın standartlarında olması zorunludur. + Temel Misyon + İstatistiksel analiz sürecini karmaşıklıktan arındırarak, hata riskini azaltmak ve kullanıcılara analizlerini güvenle tamamlama yeteneği + kazandırmak. + Ton ve İletişim + Profesyonel, destekleyici ve eğitici. Teknik jargon kullanmaktan kaçınır veya anlaşılır açıklamalarla destekler. + ``` + + -- + + https://www.reddit.com/r/n8n/comments/1oa4kbp/openais_hidden_trick_get_up_to_10m_free_tokens/ \ No newline at end of file diff --git a/agent/agent.py b/agent/agent.py index b3e6a55f..f3997e45 100644 --- a/agent/agent.py +++ b/agent/agent.py @@ -15,10 +15,12 @@ import json import logging import os +import time from collections.abc import AsyncIterable from typing import Any import jsonschema +import litellm from google.adk.agents.llm_agent import LlmAgent from google.adk.artifacts import InMemoryArtifactService from google.adk.memory.in_memory_memory_service import InMemoryMemoryService @@ -36,6 +38,11 @@ logger = logging.getLogger(__name__) +# Error handling configuration +RATE_LIMIT_RETRY_DELAY = 30 # seconds to wait before retrying after rate limit +MAX_RATE_LIMIT_RETRIES = 3 # maximum number of retries for rate limit errors +GENERAL_ERROR_RETRY_DELAY = 5 # seconds to wait for general errors + AGENT_INSTRUCTION = """ You are a helpful restaurant finding assistant. Your goal is to help users find and book restaurants using a rich UI. @@ -93,7 +100,7 @@ def get_processing_message(self) -> str: def _build_agent(self, use_ui: bool) -> LlmAgent: """Builds the LLM agent for the restaurant agent.""" - LITELLM_MODEL = os.getenv("LITELLM_MODEL", "o4-mini") + LITELLM_MODEL = os.getenv("LITELLM_MODEL", "openrouter/google/gemini-2.0-flash-exp:free") if use_ui: # Construct the full prompt with UI instructions, examples, and schema @@ -133,6 +140,7 @@ async def stream(self, query, session_id) -> AsyncIterable[dict[str, Any]]: max_retries = 1 # Total 2 attempts attempt = 0 current_query_text = query + rate_limit_retry_count = 0 # Ensure schema was loaded if self.use_ui and self.a2ui_schema_object is None: @@ -161,29 +169,163 @@ async def stream(self, query, session_id) -> AsyncIterable[dict[str, Any]]: ) final_response_content = None - async for event in self._runner.run_async( - user_id=self._user_id, - session_id=session.id, - new_message=current_message, - ): - logger.info(f"Event from runner: {event}") - if event.is_final_response(): - if ( - event.content - and event.content.parts - and event.content.parts[0].text - ): - final_response_content = "\n".join( - [p.text for p in event.content.parts if p.text] - ) - break # Got the final response, stop consuming events + try: + async for event in self._runner.run_async( + user_id=self._user_id, + session_id=session.id, + new_message=current_message, + ): + logger.info(f"Event from runner: {event}") + if event.is_final_response(): + if ( + event.content + and event.content.parts + and event.content.parts[0].text + ): + final_response_content = "\n".join( + [p.text for p in event.content.parts if p.text] + ) + break # Got the final response, stop consuming events + else: + logger.info(f"Intermediate event: {event}") + # Yield intermediate updates on every attempt + yield { + "is_task_complete": False, + "updates": self.get_processing_message(), + } + + # Reset rate limit counter on success + rate_limit_retry_count = 0 + + except litellm.RateLimitError as e: + rate_limit_retry_count += 1 + logger.error( + f"--- RestaurantAgent.stream: Rate limit error (attempt {rate_limit_retry_count}/{MAX_RATE_LIMIT_RETRIES}): {e} ---" + ) + + if rate_limit_retry_count <= MAX_RATE_LIMIT_RETRIES: + retry_after = RATE_LIMIT_RETRY_DELAY + + # Try to extract retry-after from error message if available + error_msg = str(e) + if "retry after" in error_msg.lower(): + try: + # Try to parse retry-after time from error message + import re + match = re.search(r'retry.*?(\d+)', error_msg, re.IGNORECASE) + if match: + retry_after = int(match.group(1)) + except: + pass + + yield { + "is_task_complete": False, + "updates": ( + f"The AI service is currently experiencing high demand. " + f"Retrying in {retry_after} seconds... " + f"(Attempt {rate_limit_retry_count}/{MAX_RATE_LIMIT_RETRIES})" + ), + } + + logger.info(f"Waiting {retry_after} seconds before retry...") + time.sleep(retry_after) + + # Don't increment attempt counter for rate limits, just retry + attempt -= 1 + continue else: - logger.info(f"Intermediate event: {event}") - # Yield intermediate updates on every attempt + logger.error("--- Max rate limit retries exceeded ---") + yield { + "is_task_complete": True, + "content": ( + "I apologize, but the AI service is currently experiencing very high demand " + "and is temporarily rate-limited. Please try again in a few minutes. " + "\n\nAlternatively, you can:\n" + "1. Wait a few minutes and try again\n" + "2. Check if your API provider has rate limit restrictions\n" + "3. Consider using a different model in your .env configuration" + ), + } + return + + except litellm.APIConnectionError as e: + logger.error(f"--- RestaurantAgent.stream: API connection error: {e} ---") + yield { + "is_task_complete": True, + "content": ( + "I'm sorry, I'm having trouble connecting to the AI service. " + "Please check your internet connection and try again." + ), + } + return + + except litellm.AuthenticationError as e: + logger.error(f"--- RestaurantAgent.stream: Authentication error: {e} ---") + yield { + "is_task_complete": True, + "content": ( + "Authentication error: Please check your API key configuration in the .env file. " + "Make sure OPENROUTER_API_KEY or OPENAI_API_KEY is set correctly." + ), + } + return + + except litellm.InvalidRequestError as e: + logger.error(f"--- RestaurantAgent.stream: Invalid request error: {e} ---") + yield { + "is_task_complete": True, + "content": ( + "I'm sorry, there was an issue with the request. " + "This might be due to an invalid model configuration or request parameters. " + f"Error: {str(e)}" + ), + } + return + + except litellm.ContextWindowExceededError as e: + logger.error(f"--- RestaurantAgent.stream: Context window exceeded: {e} ---") + yield { + "is_task_complete": True, + "content": ( + "I'm sorry, the conversation has become too long for the AI model to process. " + "Please start a new conversation or try with a shorter query." + ), + } + return + + except (litellm.APIError, litellm.ServiceUnavailableError, litellm.InternalServerError) as e: + logger.error(f"--- RestaurantAgent.stream: API service error: {e} ---") + + if attempt <= max_retries: yield { "is_task_complete": False, - "updates": self.get_processing_message(), + "updates": ( + f"The AI service encountered a temporary error. " + f"Retrying in {GENERAL_ERROR_RETRY_DELAY} seconds..." + ), + } + time.sleep(GENERAL_ERROR_RETRY_DELAY) + continue + else: + yield { + "is_task_complete": True, + "content": ( + "I'm sorry, the AI service is currently experiencing technical difficulties. " + "Please try again in a few moments." + ), } + return + + except Exception as e: + logger.error(f"--- RestaurantAgent.stream: Unexpected error: {type(e).__name__}: {e} ---") + yield { + "is_task_complete": True, + "content": ( + f"I'm sorry, an unexpected error occurred: {type(e).__name__}. " + "Please try again or contact support if the issue persists." + ), + } + return if final_response_content is None: logger.warning(