diff --git a/cookbook/anthropic_agent_sdk/README.md b/cookbook/anthropic_agent_sdk/README.md index 35fbee61711..f1132618091 100644 --- a/cookbook/anthropic_agent_sdk/README.md +++ b/cookbook/anthropic_agent_sdk/README.md @@ -28,6 +28,16 @@ python main.py That's it! You can now chat with the agent in your terminal. +### Chat Commands + +While chatting, you can use these commands: +- `models` - List all available models (fetched from your LiteLLM proxy) +- `model` - Switch to a different model +- `clear` - Start a new conversation +- `quit` or `exit` - End the chat + +The chat automatically fetches available models from your LiteLLM proxy's `/models` endpoint, so you'll always see what's currently configured. + ## Configuration Set these environment variables if needed: diff --git a/cookbook/anthropic_agent_sdk/main.py b/cookbook/anthropic_agent_sdk/main.py index 2dcda89506f..9bdd2f7364c 100644 --- a/cookbook/anthropic_agent_sdk/main.py +++ b/cookbook/anthropic_agent_sdk/main.py @@ -8,6 +8,7 @@ through the Claude Agent SDK by pointing it to the LiteLLM gateway. import os import asyncio +import httpx from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions @@ -24,6 +25,33 @@ class Config: LITELLM_MODEL = os.getenv("LITELLM_MODEL", "bedrock-claude-sonnet-4.5") +async def fetch_available_models(base_url: str, api_key: str) -> list[str]: + """ + Fetch available models from LiteLLM proxy /models endpoint + """ + try: + async with httpx.AsyncClient() as client: + response = await client.get( + f"{base_url}/models", + headers={"Authorization": f"Bearer {api_key}"}, + timeout=10.0 + ) + response.raise_for_status() + data = response.json() + return [model["id"] for model in data.get("data", [])] + except Exception as e: + print(f"āš ļø Warning: Could not fetch models from proxy: {e}") + print("Using default model list...") + # Fallback to default models + return [ + "bedrock-claude-sonnet-3.5", + "bedrock-claude-sonnet-4", + "bedrock-claude-sonnet-4.5", + "bedrock-claude-opus-4.5", + "bedrock-nova-premier", + ] + + async def interactive_chat(): """ Interactive CLI chat with the agent @@ -36,14 +64,21 @@ async def interactive_chat(): os.environ["ANTHROPIC_BASE_URL"] = litellm_base_url os.environ["ANTHROPIC_API_KEY"] = config.LITELLM_API_KEY + # Fetch available models from proxy + available_models = await fetch_available_models(litellm_base_url, config.LITELLM_API_KEY) + + current_model = config.LITELLM_MODEL + print("=" * 70) print("šŸ¤– Claude Agent SDK with LiteLLM Gateway - Interactive Chat") print("=" * 70) print(f"šŸš€ Connected to: {litellm_base_url}") - print(f"šŸ“¦ Using model: {config.LITELLM_MODEL}") + print(f"šŸ“¦ Current model: {current_model}") print("\nType your messages below. Commands:") print(" - 'quit' or 'exit' to end the conversation") print(" - 'clear' to start a new conversation") + print(" - 'model' to switch models") + print(" - 'models' to list available models") print("=" * 70) print() @@ -51,7 +86,7 @@ async def interactive_chat(): # Configure agent options for each conversation options = ClaudeAgentOptions( system_prompt="You are a helpful AI assistant. Be concise, accurate, and friendly.", - model=config.LITELLM_MODEL, + model=current_model, max_turns=50, ) @@ -77,17 +112,66 @@ async def interactive_chat(): conversation_active = False continue + if user_input.lower() == 'models': + print("\nšŸ“‹ Available models:") + for i, model in enumerate(available_models, 1): + marker = "āœ“" if model == current_model else " " + print(f" {marker} {i}. {model}") + continue + + if user_input.lower() == 'model': + print("\nšŸ“‹ Select a model:") + for i, model in enumerate(available_models, 1): + marker = "āœ“" if model == current_model else " " + print(f" {marker} {i}. {model}") + + try: + choice = input("\nEnter number (or press Enter to cancel): ").strip() + if choice: + idx = int(choice) - 1 + if 0 <= idx < len(available_models): + current_model = available_models[idx] + print(f"\nāœ… Switched to: {current_model}") + print("šŸ”„ Starting new conversation with new model...\n") + conversation_active = False + else: + print("āŒ Invalid choice") + except (ValueError, IndexError): + print("āŒ Invalid input") + continue + if not user_input: continue - # Send query to agent + # Send query to agent with loading indicator print("\nšŸ¤– Assistant: ", end='', flush=True) try: await client.query(user_input) + # Show loading indicator + print("ā³ thinking...", end='', flush=True) + # Stream the response + first_chunk = True async for msg in client.receive_response(): + # Clear loading indicator on first message + if first_chunk: + print("\ršŸ¤– Assistant: ", end='', flush=True) + first_chunk = False + + # Handle different message types + if hasattr(msg, 'type'): + if msg.type == 'content_block_delta': + # Streaming text delta + if hasattr(msg, 'delta') and hasattr(msg.delta, 'text'): + print(msg.delta.text, end='', flush=True) + elif msg.type == 'content_block_start': + # Start of content block + if hasattr(msg, 'content_block') and hasattr(msg.content_block, 'text'): + print(msg.content_block.text, end='', flush=True) + + # Fallback to original content handling if hasattr(msg, 'content'): for content_block in msg.content: if hasattr(content_block, 'text'): @@ -96,7 +180,7 @@ async def interactive_chat(): print() # New line after response except Exception as e: - print(f"\n\nāŒ Error: {e}") + print(f"\r\nāŒ Error: {e}") print("Please check your LiteLLM gateway is running and configured correctly.") diff --git a/cookbook/anthropic_agent_sdk/requirements.txt b/cookbook/anthropic_agent_sdk/requirements.txt index 472956607b8..1e810bb7d99 100644 --- a/cookbook/anthropic_agent_sdk/requirements.txt +++ b/cookbook/anthropic_agent_sdk/requirements.txt @@ -1 +1,2 @@ claude-agent-sdk +httpx>=0.27.0