Skip to main content
Glama
conversation_cli.py11.7 kB
"""CLI interface for the new ConversationClient.""" import asyncio import os import signal import sys from typing import Optional import typer from rich.console import Console from rich.markdown import Markdown from rich.panel import Panel from rich.progress import Progress, SpinnerColumn, TextColumn from tools.common import get_logger from tools.orchestration.config_loader import load_server_configs from .conversation_client import ConversationClient, create_conversation_client app = typer.Typer(help="Conversation client CLI using the new architecture") console = Console() logger = get_logger("ConversationCLI") # Global client for signal handling current_client: Optional[ConversationClient] = None def handle_interrupt(signum, frame): """Handle Ctrl+C gracefully.""" if current_client: console.print("\n[yellow]Shutting down gracefully...[/yellow]") asyncio.create_task(current_client.disconnect_from_servers()) sys.exit(0) signal.signal(signal.SIGINT, handle_interrupt) def print_step_info(step) -> None: """Print information about a completed conversation step.""" step_title = f"Step {step.step_number + 1}" if step.text and step.tool_calls: step_title += " (Text + Tool Calls)" elif step.tool_calls: step_title += " (Tool Calls Only)" elif step.text: step_title += " (Text Only)" # Print step header console.print(f"\n[bold blue]{step_title}[/bold blue]") # Print text if present if step.text: console.print(Panel(Markdown(step.text), title="Response", border_style="blue")) # Print tool calls and results if step.tool_calls: for i, tool_call in enumerate(step.tool_calls): tool_title = f"Tool Call {i + 1}: {tool_call.tool_name}" # Find corresponding result result = None for tool_result in step.tool_results: if tool_result.id == tool_call.id: result = tool_result break # Format tool call info info_parts = [f"**Tool:** {tool_call.tool_name}"] if tool_call.server_name: info_parts.append(f"**Server:** {tool_call.server_name}") if tool_call.arguments: info_parts.append(f"**Arguments:** `{tool_call.arguments}`") if result: if result.is_success: info_parts.append(f"**Result:** {result.result}") border_style = "green" elif result.is_error: info_parts.append(f"**Error:** {result.error}") border_style = "red" else: info_parts.append("**Status:** Pending") border_style = "yellow" else: info_parts.append("**Status:** No result") border_style = "yellow" console.print( Panel( "\n".join(info_parts), title=tool_title, border_style=border_style ) ) @app.command() def chat( config_path: str = typer.Option( "config/servers.yaml", "--config", "-c", help="Path to server configuration file", ), ai_provider: str = typer.Option( "claude", "--provider", "-p", help="AI provider (claude or openai)" ), max_steps: int = typer.Option( None, "--max-steps", "-s", help="Maximum number of steps (overrides environment variable)", ), api_key: Optional[str] = typer.Option( None, "--api-key", "-k", help="API key (optional, uses environment variables)" ), ): """Start an interactive chat session with AI and connected MCP servers.""" asyncio.run(async_chat(config_path, ai_provider, max_steps, api_key)) async def async_chat( config_path: str, ai_provider: str, max_steps: Optional[int], api_key: Optional[str] ): """Async chat implementation.""" global current_client try: # Load server configuration servers = load_server_configs(config_path) if not servers: console.print( "[red]No servers configured. Please check your configuration file.[/red]" ) return # Get max_steps from environment if not provided if max_steps is None: max_steps = int(os.getenv("LIGHTFAST_MAX_STEPS", "5")) console.print("[green]Loading servers and connecting...[/green]") # Create and connect client with Progress( SpinnerColumn(), TextColumn("[progress.description]{task.description}"), console=console, ) as progress: task = progress.add_task("Connecting to servers...", total=None) client_result = await create_conversation_client( servers=servers, ai_provider=ai_provider, api_key=api_key, max_steps=max_steps, ) if not client_result.is_success: console.print( f"[red]Failed to create client: {client_result.error}[/red]" ) return current_client = client_result.data progress.update(task, description="Connected!") # Display connected servers and tools if current_client is not None: connected_servers = current_client.get_connected_servers() if connected_servers: console.print( f"\n[green][OK] Connected to {len(connected_servers)} servers:[/green]" ) tools_by_server = current_client.get_available_tools() for server in connected_servers: tools = tools_by_server.get(server, []) console.print(f" • {server}: {len(tools)} tools") else: console.print( "[yellow]Warning: No servers connected successfully[/yellow]" ) else: console.print("[red]Error: Client is not available[/red]") console.print(f"\n[blue]AI Provider:[/blue] {ai_provider}") console.print(f"[blue]Max Steps:[/blue] {max_steps}") console.print( "\n[green]Chat started! Type 'quit' or 'exit' to end the session.[/green]" ) console.print("[dim]Use Ctrl+C to exit gracefully.[/dim]\n") # Interactive chat loop while True: try: # Get user input user_input = console.input("[bold cyan]You:[/bold cyan] ").strip() if user_input.lower() in ["quit", "exit", "q"]: break if not user_input: continue console.print("\n[yellow]🤖 Processing...[/yellow]") # Send message and get result if current_client is not None: chat_result = await current_client.chat(user_input) if not chat_result.is_success: console.print(f"[red]Error: {chat_result.error}[/red]") continue conversation_result = chat_result.data # Display the steps for step in conversation_result.steps: print_step_info(step) console.print("\n" + "=" * 50 + "\n") else: console.print("[red]Error: Client is not available[/red]") except KeyboardInterrupt: break except Exception as e: console.print(f"[red]Error during chat: {e}[/red]") logger.error(f"Chat error: {e}") except Exception as e: console.print(f"[red]Failed to start chat: {e}[/red]") logger.error(f"Setup error: {e}") finally: if current_client: console.print("[yellow]Disconnecting from servers...[/yellow]") await current_client.disconnect_from_servers() console.print("[green]Disconnected. Goodbye![/green]") @app.command() def test( config_path: str = typer.Option( "config/servers.yaml", "--config", "-c", help="Path to server configuration file", ), ai_provider: str = typer.Option( "claude", "--provider", "-p", help="AI provider (claude or openai)" ), max_steps: int = typer.Option( 3, "--max-steps", "-s", help="Maximum number of steps for test" ), message: str = typer.Option( "Hello! What tools do you have available?", "--message", "-m", help="Test message to send", ), ): """Test the conversation client with a single message.""" asyncio.run(async_test(config_path, ai_provider, max_steps, message)) async def async_test(config_path: str, ai_provider: str, max_steps: int, message: str): """Async test implementation.""" global current_client try: # Load server configuration servers = load_server_configs(config_path) if not servers: console.print("[red]No servers configured.[/red]") return console.print("[green]Testing conversation client...[/green]") # Create and connect client client_result = await create_conversation_client( servers=servers, ai_provider=ai_provider, max_steps=max_steps, ) if not client_result.is_success: console.print(f"[red]Failed to create client: {client_result.error}[/red]") return current_client = client_result.data # Display status if current_client is not None: connected_servers = current_client.get_connected_servers() console.print(f"Connected servers: {connected_servers}") tools_by_server = current_client.get_available_tools() console.print(f"Available tools: {tools_by_server}") else: console.print("[red]Error: Client is not available[/red]") return # Send test message console.print(f"\n[cyan]Sending message:[/cyan] {message}") if current_client is not None: chat_result = await current_client.chat(message) if not chat_result.is_success: console.print(f"[red]Chat failed: {chat_result.error}[/red]") return conversation_result = chat_result.data # Display results console.print( f"\n[green]Completed {len(conversation_result.steps)} steps:[/green]" ) for step in conversation_result.steps: print_step_info(step) # Display summary console.print("\n[blue]Conversation Summary:[/blue]") console.print( f" • Total tool calls: {conversation_result.total_tool_calls}" ) console.print( f" • Successful: {conversation_result.successful_tool_calls}" ) console.print(f" • Failed: {conversation_result.failed_tool_calls}") console.print(f" • Success rate: {conversation_result.success_rate:.1%}") else: console.print("[red]Error: Client is not available[/red]") except Exception as e: console.print(f"[red]Test failed: {e}[/red]") logger.error(f"Test error: {e}") finally: if current_client: await current_client.disconnect_from_servers() if __name__ == "__main__": app()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lightfastai/lightfast-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server