From 38b74ee3d4f79b1311d694f23477a9e14d04c9f0 Mon Sep 17 00:00:00 2001 From: jinubabu Date: Wed, 25 Jun 2025 00:42:13 +0530 Subject: [PATCH 1/6] new improvement logic --- .gitignore | 1 + agents/agent_bridge.py | 122 ++++++++++++++++++++++++++++++- agents/nanda.py | 77 +++++++++++++++++++ agents/pirate_langchain_agent.py | 90 +++++++++++++++++++++++ agents/sarcastic_crewai_agent.py | 104 ++++++++++++++++++++++++++ requirements_pirate.txt | 15 ++++ 6 files changed, 406 insertions(+), 3 deletions(-) create mode 100644 agents/nanda.py create mode 100644 agents/pirate_langchain_agent.py create mode 100644 agents/sarcastic_crewai_agent.py create mode 100644 requirements_pirate.txt diff --git a/.gitignore b/.gitignore index 68a37c1..9cadeba 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ jinoos/ agents/__pycache__/ conversation_logs/ agents/test.py +test.py \ No newline at end of file diff --git a/agents/agent_bridge.py b/agents/agent_bridge.py index 8307dce..2670ca0 100644 --- a/agents/agent_bridge.py +++ b/agents/agent_bridge.py @@ -204,6 +204,41 @@ def call_claude(prompt: str, additional_context: str, conversation_id: str, curr # Log the Claude response log_message(conversation_id, current_path, f"Claude {AGENT_ID}", response_text) + return response_text + except APIStatusError as e: + print(f"Agent {AGENT_ID}: Anthropic API error:", e.status_code, e.message, flush=True) + # If we hit a credit limit error, return a fallback message + if "credit balance is too low" in str(e): + return f"Agent {AGENT_ID} processed (API credit limit reached): {prompt}" + except Exception as e: + print(f"Agent {AGENT_ID}: Anthropic SDK error:", e, flush=True) + traceback.print_exc() + return None + +def call_claude_direct(message_text: str, system_prompt: str = None) -> Optional[str]: + """Wrapper that never raises: returns text or None on failure.""" + try: + # Use the specified system prompt or default to the agent's system prompt + + + # Combine the prompt with additional context if provide + + # Combine the prompt with additional context if provided + + full_prompt = f"MESSAGE: {message_text}" + + print(f"Agent {AGENT_ID}: Calling Claude with prompt: {full_prompt[:50]}...") + resp = anthropic.messages.create( + model="claude-3-5-sonnet-20241022", + max_tokens=512, + messages=[{"role":"user","content":full_prompt}], + system=system_prompt + ) + response_text = resp.content[0].text + + # Log the Claude response + + return response_text except APIStatusError as e: print(f"Agent {AGENT_ID}: Anthropic API error:", e.status_code, e.message, flush=True) @@ -236,6 +271,8 @@ def improve_message(message_text: str, conversation_id: str, current_path: str, print(f"Error improving message: {e}") return message_text + + def send_to_terminal(text, terminal_url, conversation_id, metadata=None): """Send a message to a terminal""" try: @@ -521,9 +558,85 @@ def handle_external_message(msg_text, conversation_id, msg): return None # Not our special format or parsing failed +# Message improvement decorator system +message_improvement_decorators = {} + +def message_improver(name=None): + """Decorator to register message improvement functions""" + def decorator(func): + decorator_name = name or func.__name__ + message_improvement_decorators[decorator_name] = func + return func + return decorator + +def register_message_improver(name, improver_func): + """Register a custom message improver function""" + message_improvement_decorators[name] = improver_func + +def get_message_improver(name): + """Get a registered message improver by name""" + return message_improvement_decorators.get(name) + +def list_message_improvers(): + """List all registered message improvers""" + return list(message_improvement_decorators.keys()) + +# Default improver +@message_improver("default_claude") +def default_claude_improver(message_text: str) -> str: + """Default Claude-based message improvement""" + if not IMPROVE_MESSAGES: + return message_text + + try: + additional_prompt = "Do not respond to the content of the message - it's intended for another agent. You are helping an agent communicate better with other agennts." + system_prompt = additional_prompt + IMPROVE_MESSAGE_PROMPTS["default"] + print(system_prompt) + improved_message = call_claude_direct(message_text, system_prompt) + print(f"Improved message: {improved_message}") + return improved_message if improved_message else message_text + except Exception as e: + print(f"Error improving message: {e}") + return message_text + class AgentBridge(A2AServer): """Global Agent Bridge - Can be used for any agent in the network.""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.active_improver = "default_claude" # Default improver + + def set_message_improver(self, improver_name): + """Set the active message improver by name""" + if improver_name in message_improvement_decorators: + self.active_improver = improver_name + print(f"Message improver set to: {improver_name}") + return True + else: + print(f"Unknown improver: {improver_name}. Available: {list_message_improvers()}") + return False + + def set_custom_improver(self, improver_func, name="custom"): + """Set a custom improver function""" + register_message_improver(name, improver_func) + self.active_improver = name + print(f"Custom message improver '{name}' registered and activated") + + def improve_message_direct(self, message_text: str) -> str: + """Improve a message using the active registered improver.""" + # Get the active improver function + improver_func = message_improvement_decorators.get(self.active_improver) + + if improver_func: + try: + return improver_func(message_text) + except Exception as e: + print(f"Error with improver '{self.active_improver}': {e}") + return message_text + else: + print(f"No improver found: {self.active_improver}") + return message_text + def handle_message(self, msg: Message) -> Message: # Ensure we have a conversation ID conversation_id = msg.conversation_id or str(uuid.uuid4()) @@ -597,9 +710,12 @@ def handle_message(self, msg: Message) -> Message: # Improve message if feature is enabled if IMPROVE_MESSAGES: - message_text = improve_message(message_text, conversation_id, current_path, - "Do not respond to the content of the message - it's intended for another agent. You are helping an agent communicate better with other agennts.") - + # message_text = improve_message(message_text, conversation_id, current_path, + # "Do not respond to the content of the message - it's intended for another agent. You are helping an agent communicate better with other agennts.") + message_text = self.improve_message_direct(message_text) + log_message(conversation_id, current_path, f"Claude {AGENT_ID}", message_text) + + print(f"#jinu - Target agent: {target_agent}") print(f"#jinu - Imoproved message text: {message_text}") # Send to the target agent's bridge diff --git a/agents/nanda.py b/agents/nanda.py new file mode 100644 index 0000000..b189346 --- /dev/null +++ b/agents/nanda.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +""" +NANDA - Custom Message Improvement for Agent Bridge +- Accepts any custom improvement logic function +- Creates agent_bridge server with custom improve_message_direct +""" + +import os +import sys + +# Handle different import contexts +try: + from agent_bridge import * +except ModuleNotFoundError: + # If running from parent directory, add current directory to path + current_dir = os.path.dirname(os.path.abspath(__file__)) + sys.path.insert(0, current_dir) + from agent_bridge import * + +class NANDA: + """NANDA class to create agent_bridge with custom improvement logic""" + + def __init__(self, improvement_logic): + """ + Initialize NANDA with custom improvement logic + + Args: + improvement_logic: Function that takes (message_text: str) -> str + """ + self.improvement_logic = improvement_logic + self.bridge = None + print(f"šŸ¤– NANDA initialized with custom improvement logic: {improvement_logic.__name__}") + + # Register the custom improvement logic + self.register_custom_improver() + + # Create agent bridge with custom logic + self.create_agent_bridge() + + def register_custom_improver(self): + """Register the custom improvement logic with agent_bridge""" + register_message_improver("nanda_custom", self.improvement_logic) + print(f"šŸ”§ Custom improvement logic '{self.improvement_logic.__name__}' registered") + + def create_agent_bridge(self): + """Create AgentBridge with custom improvement logic""" + # Create standard AgentBridge + self.bridge = AgentBridge() + + # Set custom improver as active (replaces improve_message_direct) + self.bridge.set_message_improver("nanda_custom") + print(f"āœ… AgentBridge created with custom improve_message_direct: {self.improvement_logic.__name__}") + + def start_server(self): + """Start the agent_bridge server with custom improvement logic""" + print("šŸš€ NANDA starting agent_bridge server with custom logic...") + + # Register with the registry if PUBLIC_URL is set + public_url = os.getenv("PUBLIC_URL") + api_url = os.getenv("API_URL") + if public_url: + register_with_registry(AGENT_ID, public_url, api_url) + else: + print("WARNING: PUBLIC_URL environment variable not set. Agent will not be registered.") + + + # Start the server + IMPROVE_MESSAGES = os.getenv("IMPROVE_MESSAGES", "true").lower() in ("true", "1", "yes", "y") + + print(f"\nšŸš€ Starting Agent {AGENT_ID} bridge on port {PORT}") + print(f"Agent terminal port: {TERMINAL_PORT}") + print(f"Message improvement feature is {'ENABLED' if IMPROVE_MESSAGES else 'DISABLED'}") + print(f"Logging conversations to {os.path.abspath(LOG_DIR)}") + print(f"šŸ”§ Using custom improvement logic: {self.improvement_logic.__name__}") + + # Run the agent bridge server + run_server(self.bridge, host="0.0.0.0", port=PORT) \ No newline at end of file diff --git a/agents/pirate_langchain_agent.py b/agents/pirate_langchain_agent.py new file mode 100644 index 0000000..92c83c2 --- /dev/null +++ b/agents/pirate_langchain_agent.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +""" +Pirate LangChain Agent +- Uses LangChain to improve messages to English pirate +- Uses NANDA to create agent_bridge server with pirate improvement +""" + +import os +from langchain_core.prompts import PromptTemplate +from langchain_core.output_parsers import StrOutputParser +from langchain_anthropic import ChatAnthropic +from nanda import NANDA + +# Configuration +ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") + +def create_pirate_improvement(): + """Create LangChain-based pirate improvement function""" + + # Check API key + if not ANTHROPIC_API_KEY: + raise ValueError("ANTHROPIC_API_KEY environment variable is required") + + # Setup LangChain LLM + llm = ChatAnthropic( + api_key=ANTHROPIC_API_KEY, + model="claude-3-haiku-20240307", + temperature=0.7, + max_tokens=300 + ) + + # Create pirate prompt template + prompt = PromptTemplate( + input_variables=["message"], + template="""Convert this message to authentic pirate English while keeping the original meaning and intent. + +Guidelines: +- Use pirate vocabulary (ahoy, matey, ye, yer, savvy, etc.) +- Replace "you" with "ye" or "yer" +- Replace "my" with "me" +- Keep the core message intact +- Don't make it too theatrical + +Message: {message} + +Pirate version:""" + ) + + # Create LangChain chain + chain = prompt | llm | StrOutputParser() + + def pirate_improvement_logic(message_text: str) -> str: + """LangChain function to improve message to English pirate""" + try: + print(f"šŸ“ā€ā˜ ļø Converting to pirate: {message_text[:50]}...") + result = chain.invoke({"message": message_text}) + pirate_msg = result.strip() + print(f"šŸ“ā€ā˜ ļø Pirate result: {pirate_msg[:50]}...") + return pirate_msg + except Exception as e: + print(f"āŒ LangChain error: {e}") + # Simple fallback + return f"Ahoy! {message_text}, matey!" + + return pirate_improvement_logic + +if __name__ == "__main__": + try: + print("šŸ“ā€ā˜ ļø Creating LangChain pirate improvement logic...") + + # Create the pirate improvement function + my_improvement_logic = create_pirate_improvement() + + print("šŸ¤– Initializing NANDA with pirate improvement...") + + # Create NANDA with the pirate improvement logic + nanda = NANDA(my_improvement_logic) + + print("šŸš€ Starting agent_bridge server with pirate LangChain improvement...") + + # Start the server + nanda.start_server() + + except ValueError as e: + print(f"āŒ {e}") + print("šŸ”§ Set ANTHROPIC_API_KEY environment variable") + except Exception as e: + print(f"āŒ Error: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/agents/sarcastic_crewai_agent.py b/agents/sarcastic_crewai_agent.py new file mode 100644 index 0000000..995e351 --- /dev/null +++ b/agents/sarcastic_crewai_agent.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 +""" +Sarcastic CrewAI Agent +- Uses CrewAI to improve messages to be sarcastic +- Uses NANDA to create agent_bridge server with sarcastic improvement +""" + +import os +from crewai import Agent, Task, Crew +from langchain_anthropic import ChatAnthropic +from nanda import NANDA + +# Configuration +ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") + +def create_sarcastic_improvement(): + """Create CrewAI-based sarcastic improvement function""" + + # Check API key + if not ANTHROPIC_API_KEY: + raise ValueError("ANTHROPIC_API_KEY environment variable is required") + + # Setup LLM for CrewAI + llm = ChatAnthropic( + api_key=ANTHROPIC_API_KEY, + model="claude-3-haiku-20240307", + temperature=0.9, + max_tokens=300 + ) + + # Create CrewAI Agent for sarcastic messaging + sarcastic_agent = Agent( + role="Sarcastic Message Transformer", + goal="Transform regular messages into witty, sarcastic versions", + backstory="""You are a witty, sarcastic communicator who loves to add clever irony and humor to messages. + You transform regular messages into sarcastic versions while keeping the core meaning intact. + You're clever with words and enjoy subtle mockery and dry humor.""", + llm=llm, + verbose=False + ) + + def sarcastic_improvement_logic(message_text: str) -> str: + """CrewAI function to improve message to be sarcastic""" + try: + print(f"šŸ˜ Making message sarcastic: {message_text[:50]}...") + + # Create task for the agent + sarcastic_task = Task( + description=f"""Transform this regular message into a sarcastic, witty version: + +Original message: "{message_text}" + +Make it sarcastic but keep the same meaning. Use irony, dry humor, and wit. +Add subtle mockery while still being clever and funny. +Don't be mean, just witty and sarcastic. +Return only the sarcastic version, no explanations.""", + agent=sarcastic_agent, + expected_output="A sarcastic, witty version of the original message" + ) + + # Create crew and execute + crew = Crew( + agents=[sarcastic_agent], + tasks=[sarcastic_task], + verbose=False + ) + + result = crew.kickoff() + sarcastic_msg = str(result).strip() + + print(f"šŸ˜ Sarcastic result: {sarcastic_msg[:50]}...") + return sarcastic_msg + + except Exception as e: + print(f"āŒ CrewAI error: {e}") + # Simple fallback + return f"Oh wow, {message_text.lower()}... how absolutely thrilling! šŸ™„" + + return sarcastic_improvement_logic + +if __name__ == "__main__": + try: + print("šŸ˜ Creating CrewAI sarcastic improvement logic...") + + # Create the sarcastic improvement function + my_improvement_logic = create_sarcastic_improvement() + + print("šŸ¤– Initializing NANDA with sarcastic improvement...") + + # Create NANDA with the sarcastic improvement logic + nanda = NANDA(my_improvement_logic) + + print("šŸš€ Starting agent_bridge server with sarcastic CrewAI improvement...") + + # Start the server + nanda.start_server() + + except ValueError as e: + print(f"āŒ {e}") + print("šŸ”§ Set ANTHROPIC_API_KEY environment variable") + except Exception as e: + print(f"āŒ Error: {e}") + import traceback + traceback.print_exc() \ No newline at end of file diff --git a/requirements_pirate.txt b/requirements_pirate.txt new file mode 100644 index 0000000..7aa06fe --- /dev/null +++ b/requirements_pirate.txt @@ -0,0 +1,15 @@ +# LangChain Core Dependencies +langchain-core>=0.1.0 +langchain>=0.1.0 + +# LLM Provider Dependencies +langchain-openai>=0.1.0 +langchain-anthropic>=0.1.0 + +# Optional: Additional LangChain integrations +langchain-community>=0.1.0 + +# Core dependencies (if not already installed) +openai>=1.0.0 +anthropic>=0.18.0 +pydantic>=2.0.0 \ No newline at end of file From 63da97721780bec7f3683ed625ac939dadae194b Mon Sep 17 00:00:00 2001 From: jinubabu Date: Sun, 29 Jun 2025 11:21:21 +0530 Subject: [PATCH 2/6] start server url --- .gitignore | 3 +- agents/nanda.py | 195 +++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 196 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 9cadeba..ee9ab9c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ jinoos/ agents/__pycache__/ conversation_logs/ agents/test.py -test.py \ No newline at end of file +test.py +test_send_message.py \ No newline at end of file diff --git a/agents/nanda.py b/agents/nanda.py index b189346..3857473 100644 --- a/agents/nanda.py +++ b/agents/nanda.py @@ -7,6 +7,10 @@ import os import sys +import subprocess +import time +import signal +import requests # Handle different import contexts try: @@ -17,6 +21,13 @@ sys.path.insert(0, current_dir) from agent_bridge import * +# Import the run_ui_agent_https module +try: + import run_ui_agent_https +except ImportError: + print("Error: run_ui_agent_https module not found") + sys.exit(1) + class NANDA: """NANDA class to create agent_bridge with custom improvement logic""" @@ -74,4 +85,186 @@ def start_server(self): print(f"šŸ”§ Using custom improvement logic: {self.improvement_logic.__name__}") # Run the agent bridge server - run_server(self.bridge, host="0.0.0.0", port=PORT) \ No newline at end of file + run_server(self.bridge, host="0.0.0.0", port=PORT) + + def start_server_api(self, anthropic_key, domain, agent_id=None, port=6000, api_port=6001, + registry=None, public_url=None, api_url=None, cert=None, key=None, ssl=True): + """ + Start NANDA API server using run_ui_agent_https module + + Args: + anthropic_key (str): Anthropic API key + domain (str): Domain name for the server + agent_id (str): Agent ID (default: "nanda_api") + port (int): Agent bridge port (default: 6000) + api_port (int): Flask API port (default: 5000) + registry (str): Registry URL (optional) + public_url (str): Public URL for the Agent Bridge (optional) + api_url (str): API URL for the User Client (optional) + cert (str): Path to SSL certificate file (optional) + key (str): Path to SSL key file (optional) + ssl (bool): Enable SSL (default: False) + """ + # Get the server IP address (assumes a public IP) + def get_server_ip(): + """Get the public IP address of the server""" + try: + print("🌐 Detecting server IP address...") + # Try first method + response = requests.get("http://checkip.amazonaws.com", timeout=10) + if response.status_code == 200: + server_ip = response.text.strip() + print(f"āœ… Detected server IP: {server_ip}") + return server_ip + except Exception as e: + print(f"āš ļø First IP detection method failed: {e}") + + try: + # Try second method + response = requests.get("http://ifconfig.me", timeout=10) + if response.status_code == 200: + server_ip = response.text.strip() + print(f"āœ… Detected server IP (fallback): {server_ip}") + return server_ip + except Exception as e: + print(f"āš ļø Second IP detection method failed: {e}") + + # If both methods fail, use localhost + server_ip = "localhost" + print(f"āš ļø Could not determine IP automatically, using default: {server_ip}") + return server_ip + + # Set up signal handlers for cleanup + def cleanup(signum=None, frame=None): + """Clean up processes on exit""" + print("Cleaning up processes...") + if hasattr(run_ui_agent_https, 'bridge_process') and run_ui_agent_https.bridge_process: + run_ui_agent_https.bridge_process.terminate() + sys.exit(0) + + signal.signal(signal.SIGINT, cleanup) + signal.signal(signal.SIGTERM, cleanup) + + # Get server IP + server_ip = get_server_ip() + + # Set default agent ID + + import random + # Generate 6-digit random number + random_number = random.randint(100000, 999999) + + # Check domain pattern for agent naming + if "nanda-registry.com" in domain: + agent_id = f"agentm{random_number}" + else: + agent_id = f"agents{random_number}" + + print(f"šŸ¤– Auto-generated agent ID: {agent_id}") + + # Set global variables in run_ui_agent_https module + run_ui_agent_https.agent_id = agent_id + run_ui_agent_https.agent_port = port + run_ui_agent_https.registry_url = registry + + # Set default URLs if not provided + if not public_url: + public_url = f"http://{server_ip}:{port}" + print(f"šŸ”— Auto-generated public URL: {public_url}") + + if not api_url: + protocol = "https" if ssl else "http" + api_url = f"{protocol}://{domain}:{api_port}" + + # Set environment variables for the agent bridge (same as run_ui_agent_https main()) + os.environ["ANTHROPIC_API_KEY"] = anthropic_key + os.environ["AGENT_ID"] = agent_id + os.environ["PORT"] = str(port) + os.environ["PUBLIC_URL"] = public_url + os.environ['API_URL'] = api_url + os.environ["REGISTRY_URL"] = run_ui_agent_https.get_registry_url() + os.environ["UI_MODE"] = "true" + os.environ["UI_CLIENT_URL"] = f"{api_url}/api/receive_message" + + # Create unique log directories for each agent + log_dir = f"logs_{agent_id}" + os.makedirs(log_dir, exist_ok=True) + os.environ["LOG_DIR"] = log_dir + + # Open log file + log_file = open(f"{log_dir}/bridge_run.txt", "a") + + # Start the agent bridge using the start_server method in a separate thread + import threading + + def start_bridge_server(): + """Start the bridge server in a separate thread""" + print(f"šŸš€ Starting agent bridge for {agent_id} on port {port}...") + self.start_server() + + # Start the bridge server in a daemon thread + bridge_thread = threading.Thread(target=start_bridge_server, daemon=True) + bridge_thread.start() + + # Give the bridge a moment to start + time.sleep(2) + + # Print server information + print("\n" + "="*50) + print(f"šŸ¤– Agent {agent_id} is running") + print(f"🌐 Server IP: {server_ip}") + print(f"Agent Bridge URL: http://localhost:{port}/a2a") + print(f"Public Client API URL: {public_url}") + print("="*50) + print("\nšŸ“” API Endpoints:") + print(f" GET {api_url}/api/health - Health check") + print(f" POST {api_url}/api/send - Send a message to the client") + print(f" GET {api_url}/api/agents/list - List all registered agents") + print(f" POST {api_url}/api/receive_message - Receive a message from agent") + print(f" GET {api_url}/api/render - Get the latest message") + print("\nšŸ›‘ Press Ctrl+C to stop all processes.") + + # Configure SSL context if needed + ssl_context = None + if ssl: + if cert and key: + if os.path.exists(cert) and os.path.exists(key): + ssl_context = (cert, key) + print(f"šŸ”’ Using SSL certificates from: {cert}, {key}") + else: + print("āŒ ERROR: Certificate files not found at specified paths") + print(f"Certificate path: {cert}") + print(f"Key path: {key}") + sys.exit(1) + else: + print("āŒ ERROR: SSL enabled but certificate paths not provided") + print("Please provide cert and key arguments") + sys.exit(1) + + try: + # Start the Flask API server (same as run_ui_agent_https) + run_ui_agent_https.app.run( + host='0.0.0.0', + port=api_port, + threaded=True, + ssl_context=ssl_context + ) + except KeyboardInterrupt: + print("\nšŸ›‘ Server stopped by user") + cleanup() + except Exception as e: + print(f"āŒ Error starting server: {e}") + cleanup() + + +if __name__ == "__main__": + # Example usage + if len(sys.argv) > 1 and sys.argv[1] == "api": + # Simple command line interface for testing + anthropic_key = os.getenv("ANTHROPIC_API_KEY", "your-key-here") + domain = os.getenv("DOMAIN_NAME", "localhost") + + # Create NANDA instance and start API server + nanda = NANDA() + nanda.start_server_api(anthropic_key, domain) + From 3508e90782c1aca26515bffd7c8551bfd820688c Mon Sep 17 00:00:00 2001 From: jinubabu Date: Sun, 29 Jun 2025 11:33:38 +0530 Subject: [PATCH 3/6] calling server api --- agents/pirate_langchain_agent.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/agents/pirate_langchain_agent.py b/agents/pirate_langchain_agent.py index 92c83c2..537b056 100644 --- a/agents/pirate_langchain_agent.py +++ b/agents/pirate_langchain_agent.py @@ -13,6 +13,7 @@ # Configuration ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") +DOMAIN_NAME = os.getenv("DOMAIN_NAME", "localhost") def create_pirate_improvement(): """Create LangChain-based pirate improvement function""" @@ -79,7 +80,8 @@ def pirate_improvement_logic(message_text: str) -> str: print("šŸš€ Starting agent_bridge server with pirate LangChain improvement...") # Start the server - nanda.start_server() + # nanda.start_server() + nanda.start_server_api(ANTHROPIC_API_KEY, DOMAIN_NAME) except ValueError as e: print(f"āŒ {e}") From c3f4793f612d6a2143b4afd2865f2a5b8820689b Mon Sep 17 00:00:00 2001 From: jinubabu Date: Sun, 29 Jun 2025 11:43:03 +0530 Subject: [PATCH 4/6] cert path --- agents/nanda.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/agents/nanda.py b/agents/nanda.py index 3857473..e69d77a 100644 --- a/agents/nanda.py +++ b/agents/nanda.py @@ -95,15 +95,15 @@ def start_server_api(self, anthropic_key, domain, agent_id=None, port=6000, api_ Args: anthropic_key (str): Anthropic API key domain (str): Domain name for the server - agent_id (str): Agent ID (default: "nanda_api") + agent_id (str): Agent ID (default: auto-generated based on domain) port (int): Agent bridge port (default: 6000) - api_port (int): Flask API port (default: 5000) + api_port (int): Flask API port (default: 6001) registry (str): Registry URL (optional) public_url (str): Public URL for the Agent Bridge (optional) api_url (str): API URL for the User Client (optional) - cert (str): Path to SSL certificate file (optional) - key (str): Path to SSL key file (optional) - ssl (bool): Enable SSL (default: False) + cert (str): Path to SSL certificate file (optional, defaults to Let's Encrypt path) + key (str): Path to SSL key file (optional, defaults to Let's Encrypt path) + ssl (bool): Enable SSL (default: True, uses Let's Encrypt certificates) """ # Get the server IP address (assumes a public IP) def get_server_ip(): @@ -227,18 +227,21 @@ def start_bridge_server(): # Configure SSL context if needed ssl_context = None if ssl: - if cert and key: - if os.path.exists(cert) and os.path.exists(key): - ssl_context = (cert, key) - print(f"šŸ”’ Using SSL certificates from: {cert}, {key}") - else: - print("āŒ ERROR: Certificate files not found at specified paths") - print(f"Certificate path: {cert}") - print(f"Key path: {key}") - sys.exit(1) + # Set default certificate paths based on domain if not provided + if not cert or not key: + cert = f"/etc/letsencrypt/live/{domain}/fullchain.pem" + key = f"/etc/letsencrypt/live/{domain}/privkey.pem" + print(f"šŸ”’ Using default Let's Encrypt certificates for domain: {domain}") + + if os.path.exists(cert) and os.path.exists(key): + ssl_context = (cert, key) + print(f"šŸ”’ Using SSL certificates from: {cert}, {key}") else: - print("āŒ ERROR: SSL enabled but certificate paths not provided") - print("Please provide cert and key arguments") + print("āŒ ERROR: Certificate files not found at specified paths") + print(f"Certificate path: {cert}") + print(f"Key path: {key}") + print(f"šŸ’” Make sure Let's Encrypt certificates exist for domain: {domain}") + print(f"šŸ’” You can generate them with: certbot --nginx -d {domain}") sys.exit(1) try: From d8ce059cb1a378990437b4c6b3fd3fdcbe8a9d5d Mon Sep 17 00:00:00 2001 From: jinubabu Date: Sun, 29 Jun 2025 14:32:59 +0530 Subject: [PATCH 5/6] agent id updated --- agents/nanda.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/agents/nanda.py b/agents/nanda.py index e69d77a..0ba0ae3 100644 --- a/agents/nanda.py +++ b/agents/nanda.py @@ -69,8 +69,12 @@ def start_server(self): # Register with the registry if PUBLIC_URL is set public_url = os.getenv("PUBLIC_URL") api_url = os.getenv("API_URL") + agent_id = os.getenv("AGENT_ID") + print(f"šŸ”§ Public URL: {public_url}") + print(f"šŸ”§ API URL: {api_url}") + print(f"šŸ”§ Agent ID: {agent_id}") if public_url: - register_with_registry(AGENT_ID, public_url, api_url) + register_with_registry(agent_id, public_url, api_url) else: print("WARNING: PUBLIC_URL environment variable not set. Agent will not be registered.") From 00712065d3f131ef2765ef5a490db412a2fc84a5 Mon Sep 17 00:00:00 2001 From: jinubabu Date: Tue, 1 Jul 2025 07:03:37 +0530 Subject: [PATCH 6/6] fix the reciepent issue --- agents/agent_bridge.py | 117 ++++++++++++++++++++--------------- agents/nanda.py | 25 +++++++- agents/run_ui_agent_https.py | 4 +- 3 files changed, 90 insertions(+), 56 deletions(-) diff --git a/agents/agent_bridge.py b/agents/agent_bridge.py index 2670ca0..9b39e52 100644 --- a/agents/agent_bridge.py +++ b/agents/agent_bridge.py @@ -10,7 +10,7 @@ from anthropic import Anthropic, APIStatusError from python_a2a import ( A2AServer, A2AClient, run_server, - Message, TextContent, MessageRole, ErrorContent + Message, TextContent, MessageRole, ErrorContent, Metadata ) # MongoDB from pymongo import MongoClient @@ -31,7 +31,9 @@ anthropic = Anthropic(api_key=ANTHROPIC_API_KEY) # Get agent configuration from environment variables -AGENT_ID = os.getenv("AGENT_ID", "default") # Default to 'default' if not specified +def get_agent_id(): + """Get AGENT_ID dynamically from environment variables""" + return os.getenv("AGENT_ID", "default") PORT = int(os.getenv("PORT", "6000")) TERMINAL_PORT = int(os.getenv("TERMINAL_PORT", "6010")) @@ -39,7 +41,7 @@ LOCAL_TERMINAL_URL = f"http://localhost:{TERMINAL_PORT}/a2a" # UI client support -UI_MODE = os.getenv("UI_MODE", "false").lower() in ("true", "1", "yes", "y") +UI_MODE = os.getenv("UI_MODE", "true").lower() in ("true", "1", "yes", "y") UI_CLIENT_URL = os.getenv("UI_CLIENT_URL", "") registered_ui_clients = set() @@ -192,7 +194,8 @@ def call_claude(prompt: str, additional_context: str, conversation_id: str, curr if additional_context and additional_context.strip(): full_prompt = f"ADDITIONAL CONTEXT FRseOM USER: {additional_context}\n\nMESSAGE: {prompt}" - print(f"Agent {AGENT_ID}: Calling Claude with prompt: {full_prompt[:50]}...") + agent_id = get_agent_id() + print(f"Agent {agent_id}: Calling Claude with prompt: {full_prompt[:50]}...") resp = anthropic.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=512, @@ -202,16 +205,16 @@ def call_claude(prompt: str, additional_context: str, conversation_id: str, curr response_text = resp.content[0].text # Log the Claude response - log_message(conversation_id, current_path, f"Claude {AGENT_ID}", response_text) + log_message(conversation_id, current_path, f"Claude {agent_id}", response_text) return response_text except APIStatusError as e: - print(f"Agent {AGENT_ID}: Anthropic API error:", e.status_code, e.message, flush=True) + print(f"Agent {agent_id}: Anthropic API error:", e.status_code, e.message, flush=True) # If we hit a credit limit error, return a fallback message if "credit balance is too low" in str(e): - return f"Agent {AGENT_ID} processed (API credit limit reached): {prompt}" + return f"Agent {agent_id} processed (API credit limit reached): {prompt}" except Exception as e: - print(f"Agent {AGENT_ID}: Anthropic SDK error:", e, flush=True) + print(f"Agent {agent_id}: Anthropic SDK error:", e, flush=True) traceback.print_exc() return None @@ -227,7 +230,8 @@ def call_claude_direct(message_text: str, system_prompt: str = None) -> Optional full_prompt = f"MESSAGE: {message_text}" - print(f"Agent {AGENT_ID}: Calling Claude with prompt: {full_prompt[:50]}...") + agent_id = get_agent_id() + print(f"Agent {agent_id}: Calling Claude with prompt: {full_prompt[:50]}...") resp = anthropic.messages.create( model="claude-3-5-sonnet-20241022", max_tokens=512, @@ -241,12 +245,12 @@ def call_claude_direct(message_text: str, system_prompt: str = None) -> Optional return response_text except APIStatusError as e: - print(f"Agent {AGENT_ID}: Anthropic API error:", e.status_code, e.message, flush=True) + print(f"Agent {agent_id}: Anthropic API error:", e.status_code, e.message, flush=True) # If we hit a credit limit error, return a fallback message if "credit balance is too low" in str(e): - return f"Agent {AGENT_ID} processed (API credit limit reached): {prompt}" + return f"Agent {agent_id} processed (API credit limit reached): {prompt}" except Exception as e: - print(f"Agent {AGENT_ID}: Anthropic SDK error:", e, flush=True) + print(f"Agent {agent_id}: Anthropic SDK error:", e, flush=True) traceback.print_exc() return None @@ -278,12 +282,12 @@ def send_to_terminal(text, terminal_url, conversation_id, metadata=None): try: print(f"Sending message to {terminal_url}: {text[:50]}...") terminal = A2AClient(terminal_url, timeout=30) - terminal.send_message_async( + terminal.send_message_threaded( Message( role=MessageRole.USER, content=TextContent(text=text), conversation_id=conversation_id, - metadata=metadata or {} + metadata=Metadata(custom_fields=metadata or {}) ) ) return True @@ -293,14 +297,18 @@ def send_to_terminal(text, terminal_url, conversation_id, metadata=None): def send_to_ui_client(message_text, from_agent, conversation_id): - if not UI_CLIENT_URL: + # Read UI_CLIENT_URL dynamically to get the latest value + ui_client_url = os.getenv("UI_CLIENT_URL", "") + print(f"šŸ” Dynamic UI_CLIENT_URL: '{ui_client_url}'") + + if not ui_client_url: print(f"No UI client URL configured. Cannot send message to UI client") return False try: print(f"Sending message to UI client: {message_text[:50]}...") response = requests.post( - UI_CLIENT_URL, + ui_client_url, json={ "message": message_text, "from_agent": from_agent, @@ -340,14 +348,15 @@ def send_to_agent(target_agent_id, message_text, conversation_id, metadata=None) # Use the URL directly (it already includes /a2a from registration) print(f"Sending message to {target_agent_id} at {target_bridge_url}") - formatted_message = f"__EXTERNAL_MESSAGE__\n__FROM_AGENT__{AGENT_ID}\n__TO_AGENT__{target_agent_id}\n__MESSAGE_START__\n{message_text}\n__MESSAGE_END__" + agent_id = get_agent_id() + formatted_message = f"__EXTERNAL_MESSAGE__\n__FROM_AGENT__{agent_id}\n__TO_AGENT__{target_agent_id}\n__MESSAGE_START__\n{message_text}\n__MESSAGE_END__" # Create simplified metadata try: # For python_a2a library compatibility, still try to set some metadata send_metadata = { 'is_external': True, - 'from_agent_id': AGENT_ID, + 'from_agent_id': agent_id, 'to_agent_id': target_agent_id } if metadata: @@ -369,7 +378,7 @@ def send_to_agent(target_agent_id, message_text, conversation_id, metadata=None) role=MessageRole.USER, content=TextContent(text=formatted_message), conversation_id=conversation_id, - metadata=send_metadata + metadata=Metadata(custom_fields=send_metadata) if send_metadata else None ) ) @@ -453,17 +462,17 @@ async def run_mcp_query(query: str, updated_url: str) -> str: error_msg = f"Error processing MCP query: {str(e)}" return error_msg -# Add the async method to the A2AClient class if it doesn't exist -if not hasattr(A2AClient, 'send_message_async'): - def send_message_async(self, message: Message): - """Send a message asynchronously without waiting for a response""" +# Add the threaded method to the A2AClient class if it doesn't exist +if not hasattr(A2AClient, 'send_message_threaded'): + def send_message_threaded(self, message: Message): + """Send a message in a separate thread without waiting for a response""" thread = threading.Thread(target=self.send_message, args=(message,)) thread.daemon = True thread.start() return thread # Add the method to the class - A2AClient.send_message_async = send_message_async + A2AClient.send_message_threaded = send_message_threaded # Update handle_message to detect this special format @@ -513,9 +522,10 @@ def handle_external_message(msg_text, conversation_id, msg): send_to_ui_client(formatted_text, from_agent, conversation_id) # Acknowledge receipt to sender + agent_id = get_agent_id() return Message( role=MessageRole.AGENT, - content=TextContent(text=f"Message received by Agent {AGENT_ID}"), + content=TextContent(text=f"Message received by Agent {agent_id}"), parent_message_id=msg.message_id, conversation_id=conversation_id ) @@ -523,24 +533,25 @@ def handle_external_message(msg_text, conversation_id, msg): else: try: terminal_client = A2AClient(LOCAL_TERMINAL_URL, timeout=10) - terminal_client.send_message_async( + terminal_client.send_message_threaded( Message( role=MessageRole.USER, content=TextContent(text=formatted_text), conversation_id=conversation_id, - metadata={ + metadata=Metadata(custom_fields={ 'is_from_peer': True, 'is_user_message': True, 'source_agent': from_agent, 'forwarded_by_bridge': True - } + }) ) ) # Acknowledge receipt to sender + agent_id = get_agent_id() return Message( role=MessageRole.AGENT, - content=TextContent(text=f"Message received by Agent {AGENT_ID}"), + content=TextContent(text=f"Message received by Agent {agent_id}"), parent_message_id=msg.message_id, conversation_id=conversation_id ) @@ -640,13 +651,14 @@ def improve_message_direct(self, message_text: str) -> str: def handle_message(self, msg: Message) -> Message: # Ensure we have a conversation ID conversation_id = msg.conversation_id or str(uuid.uuid4()) - print(f"Agent {AGENT_ID}: Received message with ID: {msg.message_id}") + agent_id = get_agent_id() + print(f"Agent {agent_id}: Received message with ID: {msg.message_id}") print(f"[DEBUG] Message type: {type(msg.content)}") print(f"[DEBUG] Message ID: {msg.message_id}") - print(f"Agent {AGENT_ID}: Message metadata: {msg.metadata}") + print(f"Agent {agent_id}: Message metadata: {msg.metadata}") user_text = msg.content.text - print(f"Agent {AGENT_ID}: Received text: {user_text[:50]}...") + print(f"Agent {agent_id}: Received text: {user_text[:50]}...") # Extract metadata if hasattr(msg.metadata, 'custom_fields'): @@ -666,12 +678,13 @@ def handle_message(self, msg: Message) -> Message: additional_context = metadata.get('additional_context', '') # Add current agent ID to the path - current_path = path + ('>' if path else '') + AGENT_ID - print(f"Agent {AGENT_ID}: Current path: {current_path}") + agent_id = get_agent_id() + current_path = path + ('>' if path else '') + agent_id + print(f"Agent {agent_id}: Current path: {current_path}") # Handle non-text content if not isinstance(msg.content, TextContent): - print(f"Agent {AGENT_ID}: Received non-text content. Returning error.") + print(f"Agent {agent_id}: Received non-text content. Returning error.") return Message( role = MessageRole.AGENT, content = ErrorContent(message="Only text payloads supported."), @@ -698,7 +711,7 @@ def handle_message(self, msg: Message) -> Message: ) else: # Message from local terminal user - log_message(conversation_id, current_path, f"Local user to Agent {AGENT_ID}", user_text) + log_message(conversation_id, current_path, f"Local user to Agent {agent_id}", user_text) print(f"#jinu - User text: {user_text}") # Check if this is a message to another agent (starts with @) if user_text.startswith("@"): @@ -713,7 +726,7 @@ def handle_message(self, msg: Message) -> Message: # message_text = improve_message(message_text, conversation_id, current_path, # "Do not respond to the content of the message - it's intended for another agent. You are helping an agent communicate better with other agennts.") message_text = self.improve_message_direct(message_text) - log_message(conversation_id, current_path, f"Claude {AGENT_ID}", message_text) + log_message(conversation_id, current_path, f"Claude {agent_id}", message_text) print(f"#jinu - Target agent: {target_agent}") @@ -721,13 +734,13 @@ def handle_message(self, msg: Message) -> Message: # Send to the target agent's bridge result = send_to_agent(target_agent, message_text, conversation_id, { 'path': current_path, - 'source_agent': AGENT_ID + 'source_agent': agent_id }) # Return result to user return Message( role=MessageRole.AGENT, - content=TextContent(text=f"[AGENT {AGENT_ID}]: {message_text}"), + content=TextContent(text=f"[AGENT {agent_id}]: {message_text}"), parent_message_id=msg.message_id, conversation_id=conversation_id ) @@ -735,7 +748,7 @@ def handle_message(self, msg: Message) -> Message: # Invalid @ command format return Message( role=MessageRole.AGENT, - content=TextContent(text=f"[AGENT {AGENT_ID}] Invalid format. Use '@agent_id message' to send a message."), + content=TextContent(text=f"[AGENT {agent_id}] Invalid format. Use '@agent_id message' to send a message."), parent_message_id=msg.message_id, conversation_id=conversation_id ) @@ -755,7 +768,7 @@ def handle_message(self, msg: Message) -> Message: if response is None: return Message( role=MessageRole.AGENT, - content=TextContent(text=f"[AGENT {AGENT_ID}] MCP server '{mcp_server_to_call}' not found in registry. Please check the server name and try again."), + content=TextContent(text=f"[AGENT {agent_id}] MCP server '{mcp_server_to_call}' not found in registry. Please check the server name and try again."), parent_message_id=msg.message_id, conversation_id=conversation_id ) @@ -768,7 +781,7 @@ def handle_message(self, msg: Message) -> Message: if mcp_server_final_url is None: return Message( role=MessageRole.AGENT, - content=TextContent(text=f"[AGENT {AGENT_ID}] Ensure the required API key for registery is in env file"), + content=TextContent(text=f"[AGENT {agent_id}] Ensure the required API key for registery is in env file"), parent_message_id=msg.message_id, conversation_id=conversation_id ) @@ -787,7 +800,7 @@ def handle_message(self, msg: Message) -> Message: # Invalid # command format return Message( role=MessageRole.AGENT, - content=TextContent(text=f"[AGENT {AGENT_ID}] Invalid format. Use '#registry_provider:mcp_server_name query' to send a query to an MCP server."), + content=TextContent(text=f"[AGENT {agent_id}] Invalid format. Use '#registry_provider:mcp_server_name query' to send a query to an MCP server."), parent_message_id=msg.message_id, conversation_id=conversation_id ) @@ -803,7 +816,7 @@ def handle_message(self, msg: Message) -> Message: # Quit command - acknowledge but let terminal handle the actual quitting return Message( role = MessageRole.AGENT, - content = TextContent(text=f"[AGENT {AGENT_ID}] Exiting session..."), + content = TextContent(text=f"[AGENT {agent_id}] Exiting session..."), parent_message_id = msg.message_id, conversation_id = conversation_id ) @@ -817,7 +830,7 @@ def handle_message(self, msg: Message) -> Message: @ [message] - Send a message to a specific agent""" return Message( role = MessageRole.AGENT, - content = TextContent(text=f"[AGENT {AGENT_ID}] {help_text}"), + content = TextContent(text=f"[AGENT {agent_id}] {help_text}"), parent_message_id = msg.message_id, conversation_id = conversation_id ) @@ -841,7 +854,7 @@ def handle_message(self, msg: Message) -> Message: print(f"Response preview: {claude_response[:50]}...") # Format and return the response - formatted_response = f"[AGENT {AGENT_ID}] {claude_response}" + formatted_response = f"[AGENT {agent_id}] {claude_response}" # Return to local terminal response_message = Message( @@ -856,7 +869,7 @@ def handle_message(self, msg: Message) -> Message: # No query text provided return Message( role = MessageRole.AGENT, - content = TextContent(text=f"[AGENT {AGENT_ID}] Please provide a query after the /query command."), + content = TextContent(text=f"[AGENT {agent_id}] Please provide a query after the /query command."), parent_message_id = msg.message_id, conversation_id = conversation_id ) @@ -869,7 +882,7 @@ def handle_message(self, msg: Message) -> Message: @ [message] - Send a message to a specific agent""" return Message( role = MessageRole.AGENT, - content = TextContent(text=f"[AGENT {AGENT_ID}] {help_text}"), + content = TextContent(text=f"[AGENT {agent_id}] {help_text}"), parent_message_id = msg.message_id, conversation_id = conversation_id ) @@ -877,7 +890,7 @@ def handle_message(self, msg: Message) -> Message: else: # Regular message - process locally claude_response = call_claude(user_text, additional_context, conversation_id, current_path) or user_text - formatted_response = f"[AGENT {AGENT_ID}] {claude_response}" + formatted_response = f"[AGENT {agent_id}] {claude_response}" # Return Claude's response to local terminal return Message( @@ -892,13 +905,15 @@ def handle_message(self, msg: Message) -> Message: public_url = os.getenv("PUBLIC_URL") api_url = os.getenv("API_URL") if public_url: - register_with_registry(AGENT_ID, public_url, api_url) + agent_id = get_agent_id() + register_with_registry(agent_id, public_url, api_url) else: print("WARNING: PUBLIC_URL environment variable not set. Agent will not be registered.") IMPROVE_MESSAGES = os.getenv("IMPROVE_MESSAGES", "true").lower() in ("true", "1", "yes", "y") - print(f"Starting Agent {AGENT_ID} bridge on port {PORT}") + agent_id = get_agent_id() + print(f"Starting Agent {agent_id} bridge on port {PORT}") print(f"Agent terminal port: {TERMINAL_PORT}") print(f"Message improvement feature is {'ENABLED' if IMPROVE_MESSAGES else 'DISABLED'}") print(f"Logging conversations to {os.path.abspath(LOG_DIR)}") diff --git a/agents/nanda.py b/agents/nanda.py index 0ba0ae3..e6c0f03 100644 --- a/agents/nanda.py +++ b/agents/nanda.py @@ -70,9 +70,26 @@ def start_server(self): public_url = os.getenv("PUBLIC_URL") api_url = os.getenv("API_URL") agent_id = os.getenv("AGENT_ID") - print(f"šŸ”§ Public URL: {public_url}") - print(f"šŸ”§ API URL: {api_url}") - print(f"šŸ”§ Agent ID: {agent_id}") + + ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") or "your key" + AGENT_ID = os.getenv("AGENT_ID", "default") # Default to 'default' if not specified + PORT = int(os.getenv("PORT", "6000")) + TERMINAL_PORT = int(os.getenv("TERMINAL_PORT", "6010")) + + + UI_MODE = os.getenv("UI_MODE", "true").lower() in ("true", "1", "yes", "y") + UI_CLIENT_URL = os.getenv("UI_CLIENT_URL", "") + print(f"šŸ”§ UI_CLIENT_URL: {UI_CLIENT_URL}") + + # os.environ["ANTHROPIC_API_KEY"] = ANTHROPIC_API_KEY + # os.environ["AGENT_ID"] = AGENT_ID + # os.environ["PORT"] = str(PORT) + # os.environ["PUBLIC_URL"] = public_url + # os.environ['API_URL'] = api_url + # os.environ["REGISTRY_URL"] = run_ui_agent_https.get_registry_url() + # os.environ["UI_MODE"] = "true" + # os.environ["UI_CLIENT_URL"] = f"{api_url}/api/receive_message" + if public_url: register_with_registry(agent_id, public_url, api_url) else: @@ -190,6 +207,8 @@ def cleanup(signum=None, frame=None): os.environ["UI_MODE"] = "true" os.environ["UI_CLIENT_URL"] = f"{api_url}/api/receive_message" + + # Create unique log directories for each agent log_dir = f"logs_{agent_id}" os.makedirs(log_dir, exist_ok=True) diff --git a/agents/run_ui_agent_https.py b/agents/run_ui_agent_https.py index 23872af..8601359 100644 --- a/agents/run_ui_agent_https.py +++ b/agents/run_ui_agent_https.py @@ -10,7 +10,7 @@ import json from flask import Flask, request, jsonify, Response, stream_with_context from flask_cors import CORS -from python_a2a import A2AClient, Message, TextContent, MessageRole +from python_a2a import A2AClient, Message, TextContent, MessageRole, Metadata from queue import Queue from threading import Event import ssl @@ -169,7 +169,7 @@ def send_message(): role=MessageRole.USER, content=TextContent(text=message_text), conversation_id=conversation_id, - metadata=metadata + metadata=Metadata(custom_fields=metadata) ) ) print(f"Response: {response}")