From 4dff4ce3f645d4d9268cf854db50477987b84314 Mon Sep 17 00:00:00 2001 From: 10234567Z <93607971+10234567Z@users.noreply.github.com> Date: Mon, 12 Jan 2026 19:13:39 +0530 Subject: [PATCH 1/6] Remove fallback/exposed API Keys --- nanda_adapter/core/agent_bridge.py | 4 ++-- nanda_adapter/core/mcp_utils.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nanda_adapter/core/agent_bridge.py b/nanda_adapter/core/agent_bridge.py index 26361d6..a0f57c0 100644 --- a/nanda_adapter/core/agent_bridge.py +++ b/nanda_adapter/core/agent_bridge.py @@ -20,7 +20,7 @@ sys.stdout.reconfigure(line_buffering=True) # Set API key through environment variable or directly in the code -ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") or "your key" +ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") # Toggle for message improvement feature IMPROVE_MESSAGES = os.getenv("IMPROVE_MESSAGES", "true").lower() in ("true", "1", "yes", "y") @@ -58,7 +58,7 @@ def get_agent_id(): "default": "Improve the following message to make it more clear, compelling, and professional without changing the core content or adding fictional information. Keep the same overall meaning but enhance the phrasing and structure. Don't make it too verbose - keep it concise but impactful. Return only the improved message without explanations or introductions." } -SMITHERY_API_KEY = os.getenv("SMITHERY_API_KEY") or "bfcb8cec-9d56-4957-8156-bced0bfca532" +SMITHERY_API_KEY = os.getenv("SMITHERY_API_KEY") def get_registry_url(): """Get the registry URL from file or use default""" diff --git a/nanda_adapter/core/mcp_utils.py b/nanda_adapter/core/mcp_utils.py index 6042c28..0d00851 100644 --- a/nanda_adapter/core/mcp_utils.py +++ b/nanda_adapter/core/mcp_utils.py @@ -38,7 +38,7 @@ class MCPClient: def __init__(self): self.session = None self.exit_stack = AsyncExitStack() - ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") or "your-key" + ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") self.anthropic = Anthropic(api_key=ANTHROPIC_API_KEY) async def connect_to_mcp_and_get_tools(self, mcp_server_url, transport_type="http"): From 548b49ca604a36ec5eec5649aee736a2d7bff550 Mon Sep 17 00:00:00 2001 From: 10234567Z <93607971+10234567Z@users.noreply.github.com> Date: Mon, 12 Jan 2026 19:16:16 +0530 Subject: [PATCH 2/6] Remove double record of anthropic library --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1f6101e..832fdb5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,7 +3,6 @@ anthropic requests python-a2a==0.5.6 mcp -anthropic python-dotenv flask-cors pymongo \ No newline at end of file From a52d9f4115b578a742fa32ea90eb9604e2235ec2 Mon Sep 17 00:00:00 2001 From: 10234567Z <93607971+10234567Z@users.noreply.github.com> Date: Mon, 12 Jan 2026 19:18:01 +0530 Subject: [PATCH 3/6] Remove unused pymongo library --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 832fdb5..942c0d8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,4 @@ requests python-a2a==0.5.6 mcp python-dotenv -flask-cors -pymongo \ No newline at end of file +flask-cors \ No newline at end of file From 54a320c4902c598c2b85116ecaea5e99e408fefb Mon Sep 17 00:00:00 2001 From: 10234567Z <93607971+10234567Z@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:05:48 +0530 Subject: [PATCH 4/6] Add support for Huggingface Inference --- .gitignore | 4 +- nanda_adapter/__init__.py | 23 ++- nanda_adapter/core/__init__.py | 19 +- nanda_adapter/core/agent_bridge.py | 71 +++---- nanda_adapter/core/llm_providers.py | 285 ++++++++++++++++++++++++++++ nanda_adapter/core/mcp_utils.py | 97 +++++++--- nanda_adapter/core/nanda.py | 31 ++- requirements.txt | 3 +- 8 files changed, 449 insertions(+), 84 deletions(-) create mode 100644 nanda_adapter/core/llm_providers.py diff --git a/.gitignore b/.gitignore index e254ce5..61a9a8d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ nanda_agent/__pycache__ dist/ *.egg-info/ -nanda_adapter/core/__pycache__ \ No newline at end of file +nanda_adapter/core/__pycache__ + +*/__pycache__/ \ No newline at end of file diff --git a/nanda_adapter/__init__.py b/nanda_adapter/__init__.py index bd166ad..b1b6469 100644 --- a/nanda_adapter/__init__.py +++ b/nanda_adapter/__init__.py @@ -4,6 +4,10 @@ This package provides a framework for creating customizable AI agents with pluggable message improvement logic, built on top of the python_a2a communication framework. + +Supports multiple LLM providers: +- Anthropic Claude (default) +- Hugging Face Inference API """ from .core.nanda import NANDA @@ -14,6 +18,15 @@ get_message_improver, list_message_improvers ) +from .core.llm_providers import ( + LLMProvider, + AnthropicProvider, + HuggingFaceProvider, + get_provider, + set_provider, + create_provider, + init_provider +) __version__ = "1.0.0" __author__ = "NANDA Team" @@ -26,5 +39,13 @@ "message_improver", "register_message_improver", "get_message_improver", - "list_message_improvers" + "list_message_improvers", + # LLM Providers + "LLMProvider", + "AnthropicProvider", + "HuggingFaceProvider", + "get_provider", + "set_provider", + "create_provider", + "init_provider" ] \ No newline at end of file diff --git a/nanda_adapter/core/__init__.py b/nanda_adapter/core/__init__.py index bea4fd4..086f526 100644 --- a/nanda_adapter/core/__init__.py +++ b/nanda_adapter/core/__init__.py @@ -13,6 +13,15 @@ get_message_improver, list_message_improvers ) +from .llm_providers import ( + LLMProvider, + AnthropicProvider, + HuggingFaceProvider, + get_provider, + set_provider, + create_provider, + init_provider +) __all__ = [ "NANDA", @@ -20,5 +29,13 @@ "message_improver", "register_message_improver", "get_message_improver", - "list_message_improvers" + "list_message_improvers", + # LLM Providers + "LLMProvider", + "AnthropicProvider", + "HuggingFaceProvider", + "get_provider", + "set_provider", + "create_provider", + "init_provider" ] \ No newline at end of file diff --git a/nanda_adapter/core/agent_bridge.py b/nanda_adapter/core/agent_bridge.py index a0f57c0..dff43e3 100644 --- a/nanda_adapter/core/agent_bridge.py +++ b/nanda_adapter/core/agent_bridge.py @@ -7,27 +7,27 @@ import requests from typing import Optional from datetime import datetime -from anthropic import Anthropic, APIStatusError from python_a2a import ( A2AServer, A2AClient, run_server, Message, TextContent, MessageRole, ErrorContent, Metadata ) import asyncio -from mcp_utils import MCPClient import base64 +# Handle different import contexts +try: + from .llm_providers import get_provider, init_provider + from .mcp_utils import MCPClient +except ImportError: + from llm_providers import get_provider, init_provider + from mcp_utils import MCPClient + import sys sys.stdout.reconfigure(line_buffering=True) -# Set API key through environment variable or directly in the code -ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") - # Toggle for message improvement feature IMPROVE_MESSAGES = os.getenv("IMPROVE_MESSAGES", "true").lower() in ("true", "1", "yes", "y") -# Create Anthropic client with explicit API key -anthropic = Anthropic(api_key=ANTHROPIC_API_KEY) - # Get agent configuration from environment variables def get_agent_id(): """Get AGENT_ID dynamically from environment variables""" @@ -59,6 +59,8 @@ def get_agent_id(): } SMITHERY_API_KEY = os.getenv("SMITHERY_API_KEY") +if not SMITHERY_API_KEY: + print("WARNING: SMITHERY_API_KEY not set - Smithery MCP servers will not work") def get_registry_url(): """Get the registry URL from file or use default""" @@ -153,7 +155,7 @@ def log_message(conversation_id, path, source, message_text): print(f"Logged message from {source} in conversation {conversation_id}") def call_claude(prompt: str, additional_context: str, conversation_id: str, current_path: str, system_prompt: str = None) -> Optional[str]: - """Wrapper that never raises: returns text or None on failure.""" + """Wrapper that never raises: returns text or None on failure. Uses configured LLM provider.""" try: # Use the specified system prompt or default to the agent's system prompt if system_prompt: @@ -165,60 +167,41 @@ def call_claude(prompt: str, additional_context: str, conversation_id: str, curr # Combine the prompt with additional context if provided full_prompt = prompt if additional_context and additional_context.strip(): - full_prompt = f"ADDITIONAL CONTEXT FRseOM USER: {additional_context}\n\nMESSAGE: {prompt}" + full_prompt = f"ADDITIONAL CONTEXT FROM USER: {additional_context}\n\nMESSAGE: {prompt}" 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, - messages=[{"role":"user","content":full_prompt}], - system=system - ) - response_text = resp.content[0].text + provider = get_provider() + print(f"Agent {agent_id}: Calling {provider.name} with prompt: {full_prompt[:50]}...") + + response_text = provider.complete(full_prompt, system=system, max_tokens=512) - # Log the Claude response - log_message(conversation_id, current_path, f"Claude {agent_id}", response_text) + if response_text: + # Log the LLM response + log_message(conversation_id, current_path, f"{provider.name} {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) + agent_id = get_agent_id() + print(f"Agent {agent_id}: LLM 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.""" + """Wrapper that never raises: returns text or None on failure. Uses configured LLM provider.""" try: - # Use the specified system prompt or default to the agent's system prompt - # Combine the prompt with additional context if provided full_prompt = f"MESSAGE: {message_text}" 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, - messages=[{"role":"user","content":full_prompt}], - system=system_prompt - ) - response_text = resp.content[0].text + provider = get_provider() + print(f"Agent {agent_id}: Calling {provider.name} with prompt: {full_prompt[:50]}...") - # Log the Claude response + response_text = provider.complete(full_prompt, system=system_prompt, max_tokens=512) 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): {message_text}" except Exception as e: - print(f"Agent {agent_id}: Anthropic SDK error:", e, flush=True) + agent_id = get_agent_id() + print(f"Agent {agent_id}: LLM error:", e, flush=True) traceback.print_exc() return None diff --git a/nanda_adapter/core/llm_providers.py b/nanda_adapter/core/llm_providers.py new file mode 100644 index 0000000..9a45c28 --- /dev/null +++ b/nanda_adapter/core/llm_providers.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python3 +""" +LLM Provider Abstraction for NANDA Agent Framework + +Supports multiple LLM backends: +- Anthropic Claude (default) +- Hugging Face Inference API +""" + +import os +import json +from typing import Optional, List, Dict, Any +from abc import ABC, abstractmethod + +import sys +sys.stdout.reconfigure(line_buffering=True) + + +class LLMProvider(ABC): + """Base class for LLM providers""" + + @abstractmethod + def complete(self, prompt: str, system: str = None, max_tokens: int = 512) -> Optional[str]: + """Generate a completion for the given prompt""" + raise NotImplementedError + + @abstractmethod + def complete_with_tools( + self, + messages: List[Dict[str, Any]], + tools: List[Dict[str, Any]], + max_tokens: int = 1024 + ) -> Dict[str, Any]: + """Generate a completion with tool/function calling support""" + raise NotImplementedError + + @property + @abstractmethod + def name(self) -> str: + """Provider name""" + raise NotImplementedError + + +class AnthropicProvider(LLMProvider): + """Anthropic Claude provider""" + + def __init__(self, api_key: str = None, model: str = None): + self.api_key = api_key or os.getenv("ANTHROPIC_API_KEY") + self.model = model or os.getenv("ANTHROPIC_MODEL", "claude-3-5-sonnet-20241022") + + if not self.api_key: + print("WARNING: ANTHROPIC_API_KEY not set") + self.client = None + else: + from anthropic import Anthropic + self.client = Anthropic(api_key=self.api_key) + + @property + def name(self) -> str: + return "anthropic" + + def complete(self, prompt: str, system: str = None, max_tokens: int = 512) -> Optional[str]: + """Generate a completion using Claude""" + if not self.client: + print("Anthropic client not initialized - API key missing") + return None + + try: + from anthropic import APIStatusError + + kwargs = { + "model": self.model, + "max_tokens": max_tokens, + "messages": [{"role": "user", "content": prompt}] + } + if system: + kwargs["system"] = system + + resp = self.client.messages.create(**kwargs) + return resp.content[0].text + + except APIStatusError as e: + print(f"Anthropic API error: {e.status_code} {e.message}") + if "credit balance is too low" in str(e): + return f"(API credit limit reached): {prompt[:100]}" + return None + except Exception as e: + print(f"Anthropic SDK error: {e}") + return None + + def complete_with_tools( + self, + messages: List[Dict[str, Any]], + tools: List[Dict[str, Any]], + max_tokens: int = 1024 + ) -> Dict[str, Any]: + """Generate a completion with tool calling using Claude""" + if not self.client: + return {"error": "Anthropic client not initialized"} + + try: + response = self.client.messages.create( + model=self.model, + max_tokens=max_tokens, + messages=messages, + tools=tools + ) + return { + "content": response.content, + "stop_reason": response.stop_reason, + "raw": response + } + except Exception as e: + print(f"Anthropic tool call error: {e}") + return {"error": str(e)} + + +class HuggingFaceProvider(LLMProvider): + """Hugging Face Inference API provider""" + + def __init__(self, api_key: str = None, model: str = None): + self.api_key = api_key or os.getenv("HUGGINGFACE_API_KEY") + self.model = model or os.getenv("HUGGINGFACE_MODEL", "meta-llama/Llama-3.3-70B-Instruct") + + if not self.api_key: + print("WARNING: HUGGINGFACE_API_KEY not set") + self.client = None + else: + from huggingface_hub import InferenceClient + self.client = InferenceClient(api_key=self.api_key) + + @property + def name(self) -> str: + return "huggingface" + + def complete(self, prompt: str, system: str = None, max_tokens: int = 512) -> Optional[str]: + """Generate a completion using Hugging Face Inference API""" + if not self.client: + print("HuggingFace client not initialized - API key missing") + return None + + try: + messages = [] + if system: + messages.append({"role": "system", "content": system}) + messages.append({"role": "user", "content": prompt}) + + response = self.client.chat.completions.create( + model=self.model, + messages=messages, + max_tokens=max_tokens + ) + + return response.choices[0].message.content + + except Exception as e: + print(f"HuggingFace API error: {e}") + return None + + def complete_with_tools( + self, + messages: List[Dict[str, Any]], + tools: List[Dict[str, Any]], + max_tokens: int = 1024 + ) -> Dict[str, Any]: + """Generate a completion with tool calling using HuggingFace""" + if not self.client: + return {"error": "HuggingFace client not initialized"} + + try: + # Convert Anthropic-style tools to OpenAI-style for HuggingFace + hf_tools = [] + for tool in tools: + hf_tools.append({ + "type": "function", + "function": { + "name": tool["name"], + "description": tool.get("description", ""), + "parameters": tool.get("input_schema", {}) + } + }) + + response = self.client.chat.completions.create( + model=self.model, + messages=messages, + tools=hf_tools if hf_tools else None, + max_tokens=max_tokens + ) + + # Convert HuggingFace response to unified format + content = [] + choice = response.choices[0] + + if choice.message.content: + content.append({ + "type": "text", + "text": choice.message.content + }) + + if choice.message.tool_calls: + for tool_call in choice.message.tool_calls: + content.append({ + "type": "tool_use", + "id": tool_call.id, + "name": tool_call.function.name, + "input": json.loads(tool_call.function.arguments) if isinstance(tool_call.function.arguments, str) else tool_call.function.arguments + }) + + return { + "content": content, + "stop_reason": choice.finish_reason, + "raw": response + } + + except Exception as e: + print(f"HuggingFace tool call error: {e}") + return {"error": str(e)} + + +# Global provider instance +_current_provider: Optional[LLMProvider] = None + + +def get_provider() -> LLMProvider: + """Get the current LLM provider instance""" + global _current_provider + if _current_provider is None: + # Default to Anthropic + _current_provider = create_provider("anthropic") + return _current_provider + + +def set_provider(provider: LLMProvider): + """Set the current LLM provider instance""" + global _current_provider + _current_provider = provider + print(f"🔧 LLM Provider set to: {provider.name} (model: {provider.model})") + + +def create_provider( + provider_name: str = "anthropic", + api_key: str = None, + model: str = None +) -> LLMProvider: + """ + Create an LLM provider instance + + Args: + provider_name: "anthropic" or "huggingface" + api_key: API key for the provider + model: Model name/ID to use + + Returns: + LLMProvider instance + """ + provider_name = provider_name.lower() if provider_name else "anthropic" + + if provider_name == "anthropic": + return AnthropicProvider(api_key=api_key, model=model) + elif provider_name in ("huggingface", "hf"): + return HuggingFaceProvider(api_key=api_key, model=model) + else: + print(f"Unknown provider '{provider_name}', defaulting to Anthropic") + return AnthropicProvider(api_key=api_key, model=model) + + +def init_provider( + provider_name: str = None, + api_key: str = None, + model: str = None +): + """ + Initialize and set the global LLM provider + + Args: + provider_name: "anthropic" or "huggingface" (defaults to env LLM_PROVIDER or "anthropic") + api_key: API key for the provider + model: Model name/ID to use + """ + if provider_name is None: + provider_name = os.getenv("LLM_PROVIDER", "anthropic") + + provider = create_provider(provider_name, api_key, model) + set_provider(provider) + return provider diff --git a/nanda_adapter/core/mcp_utils.py b/nanda_adapter/core/mcp_utils.py index 0d00851..110d522 100644 --- a/nanda_adapter/core/mcp_utils.py +++ b/nanda_adapter/core/mcp_utils.py @@ -11,7 +11,11 @@ import json import base64 -from anthropic import Anthropic +# Handle different import contexts +try: + from .llm_providers import get_provider +except ImportError: + from llm_providers import get_provider import sys @@ -38,8 +42,7 @@ class MCPClient: def __init__(self): self.session = None self.exit_stack = AsyncExitStack() - ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") - self.anthropic = Anthropic(api_key=ANTHROPIC_API_KEY) + self.provider = get_provider() async def connect_to_mcp_and_get_tools(self, mcp_server_url, transport_type="http"): """Connect to MCP server and return available tools @@ -89,28 +92,48 @@ async def process_query(self, query, mcp_server_url, transport_type="http"): # Initialize message history messages = [{"role": "user", "content": query}] - # Call Claude API - message = self.anthropic.messages.create( - model="claude-3-5-sonnet-20241022", - max_tokens=1024, - messages=messages, - tools=available_tools - ) + # Use the provider abstraction for tool calling + provider = self.provider + print(f"Using LLM provider: {provider.name}") + + response = provider.complete_with_tools(messages, available_tools, max_tokens=1024) + + if "error" in response: + return f"LLM Error: {response['error']}" # Keep processing until we get a final response without tool calls while True: has_tool_calls = False + content = response.get("content", []) + + # Handle different content formats (Anthropic raw vs unified) + if hasattr(content, '__iter__') and not isinstance(content, (str, dict)): + blocks = content + else: + blocks = [content] if content else [] # Process each block in the response - for block in message.content: - print(block) - print(block.type) + for block in blocks: + # Handle both dict format and object format + if isinstance(block, dict): + block_type = block.get("type", "text") + block_id = block.get("id", "") + block_name = block.get("name", "") + block_input = block.get("input", {}) + block_text = block.get("text", "") + else: + block_type = getattr(block, "type", "text") + block_id = getattr(block, "id", "") + block_name = getattr(block, "name", "") + block_input = getattr(block, "input", {}) + block_text = getattr(block, "text", "") + + print(f"Block type: {block_type}") - if block.type == "tool_use": + if block_type == "tool_use": has_tool_calls = True - # Extract tool name and arguments - tool_name = block.name - tool_args = block.input + tool_name = block_name + tool_args = block_input # Call the tool result = await self.session.call_tool(tool_name, tool_args) @@ -125,7 +148,7 @@ async def process_query(self, query, mcp_server_url, transport_type="http"): "role": "assistant", "content": [{ "type": "tool_use", - "id": block.id, + "id": block_id, "name": tool_name, "input": tool_args }] @@ -136,7 +159,7 @@ async def process_query(self, query, mcp_server_url, transport_type="http"): "role": "user", "content": [{ "type": "tool_result", - "tool_use_id": block.id, + "tool_use_id": block_id, "content": str(processed_result) }] }) @@ -145,25 +168,37 @@ async def process_query(self, query, mcp_server_url, transport_type="http"): if not has_tool_calls: break - print("Getting next response from Claude...") - # Get next response from Claude - message = self.anthropic.messages.create( - model="claude-3-5-sonnet-20241022", - max_tokens=1024, - messages=messages, - tools=available_tools - ) - print(message) + print(f"Getting next response from {provider.name}...") + response = provider.complete_with_tools(messages, available_tools, max_tokens=1024) + + if "error" in response: + return f"LLM Error: {response['error']}" + + print(f"Response: {response}") # Return the final response final_response = "" - for block in message.content: - if block.type == "text": - final_response += block.text + "\n" + content = response.get("content", []) + + if hasattr(content, '__iter__') and not isinstance(content, (str, dict)): + blocks = content + else: + blocks = [content] if content else [] + + for block in blocks: + if isinstance(block, dict): + if block.get("type") == "text": + final_response += block.get("text", "") + "\n" + else: + if getattr(block, "type", "") == "text": + final_response += getattr(block, "text", "") + "\n" + return parse_jsonrpc_response(final_response.strip()) if final_response else "No response generated" except Exception as e: print(f"Error processing query: {e}") + import traceback + traceback.print_exc() return f"Error: {str(e)}" async def __aenter__(self): diff --git a/nanda_adapter/core/nanda.py b/nanda_adapter/core/nanda.py index 9bc8dc7..09fd74d 100644 --- a/nanda_adapter/core/nanda.py +++ b/nanda_adapter/core/nanda.py @@ -18,12 +18,14 @@ try: from .agent_bridge import * from . import run_ui_agent_https + from .llm_providers import init_provider, get_provider except ImportError: # 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 * import run_ui_agent_https + from llm_providers import init_provider, get_provider class NANDA: """NANDA class to create agent_bridge with custom improvement logic""" @@ -68,7 +70,7 @@ def start_server(self): api_url = os.getenv("API_URL") agent_id = os.getenv("AGENT_ID") - ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY") or "your key" + ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_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")) @@ -105,13 +107,14 @@ def start_server(self): # Run the agent bridge server 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): + def start_server_api(self, anthropic_key=None, domain=None, agent_id=None, port=6000, api_port=6001, + registry=None, public_url=None, api_url=None, cert=None, key=None, ssl=True, + llm_provider=None, llm_model=None, llm_api_key=None): """ Start NANDA API server using run_ui_agent_https module Args: - anthropic_key (str): Anthropic API key + anthropic_key (str): Anthropic API key (used if llm_provider is 'anthropic' or None) domain (str): Domain name for the server agent_id (str): Agent ID (default: auto-generated based on domain) port (int): Agent bridge port (default: 6000) @@ -122,7 +125,23 @@ def start_server_api(self, anthropic_key, domain, agent_id=None, port=6000, api_ 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) + llm_provider (str): LLM provider to use - 'anthropic' (default) or 'huggingface' + llm_model (str): Model name/ID to use (optional, uses provider default) + llm_api_key (str): API key for the LLM provider (optional, overrides anthropic_key for non-anthropic providers) """ + # Initialize the LLM provider + effective_provider = llm_provider or "anthropic" + effective_api_key = llm_api_key if llm_api_key else anthropic_key + + print(f"🔧 Initializing LLM provider: {effective_provider}") + if llm_model: + print(f"🔧 Using model: {llm_model}") + + init_provider( + provider_name=effective_provider, + api_key=effective_api_key, + model=llm_model + ) # Get the server IP address (assumes a public IP) def get_server_ip(): """Get the public IP address of the server""" @@ -194,7 +213,9 @@ def cleanup(signum=None, frame=None): 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 + # Only set ANTHROPIC_API_KEY if it's provided (not needed for other providers) + if anthropic_key: + os.environ["ANTHROPIC_API_KEY"] = anthropic_key os.environ["AGENT_ID"] = agent_id os.environ["PORT"] = str(port) os.environ["PUBLIC_URL"] = public_url diff --git a/requirements.txt b/requirements.txt index 942c0d8..4e71e67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ requests python-a2a==0.5.6 mcp python-dotenv -flask-cors \ No newline at end of file +flask-cors +huggingface_hub \ No newline at end of file From 4dea3dcf0b2850a6b9774126bd46a07c004a9989 Mon Sep 17 00:00:00 2001 From: 10234567Z <93607971+10234567Z@users.noreply.github.com> Date: Tue, 13 Jan 2026 11:14:31 +0530 Subject: [PATCH 5/6] Update README to document HF --- README.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7ca262a..777deb4 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ https://docs.google.com/presentation/d/16ehp8yq4-QjEu55unsI9rHJ8BMK9MAi1/edit?us ## Features - **Multiple AI Frameworks**: Support for LangChain, CrewAI, and any custom logic. +- **Multiple LLM Providers**: Use Anthropic Claude, Hugging Face, or other providers. - **Multi-protocol Communication**: Built-in protocol that allows universal communication - **Global Index**: Automatic agent discovery via MIT NANDA Index - **SSL Support**: Production-ready with Let's Encrypt certificates @@ -52,6 +53,14 @@ pip install nanda-adapter > export DOMAIN_NAME=" +**Alternative: Use Hugging Face instead of Anthropic** + +> export HUGGINGFACE_API_KEY="hf_your-api-key-here" + +> export HUGGINGFACE_MODEL="meta-llama/Llama-3.3-70B-Instruct" + +> export DOMAIN_NAME="" + ### 5. Run an example agent (langchain_pirate.py) > nohup python3 langchain_pirate.py > out.log 2>&1 & @@ -187,6 +196,50 @@ domain = os.getenv("DOMAIN_NAME") nanda.start_server_api(anthropic_key, domain) ``` +### Deploy with Hugging Face (Alternative to Anthropic) + +You can use Hugging Face Inference API instead of Anthropic: + +```python +#!/usr/bin/env python3 +from nanda_adapter import NANDA +import os + +def create_simple_improvement(): + """Create a simple improvement function""" + def simple_improvement(message_text: str) -> str: + return f"[Enhanced] {message_text}" + return simple_improvement + +def main(): + nanda = NANDA(create_simple_improvement()) + + # Use Hugging Face instead of Anthropic + nanda.start_server_api( + anthropic_key=None, # Not needed for HuggingFace + domain=os.getenv("DOMAIN_NAME"), + llm_provider="huggingface", + llm_model="meta-llama/Llama-3.3-70B-Instruct", + llm_api_key=os.getenv("HUGGINGFACE_API_KEY") + ) + +if __name__ == "__main__": + main() +``` + +**For local development without SSL:** + +```python +nanda.start_server_api( + anthropic_key=None, + domain="localhost", + llm_provider="huggingface", + llm_model="meta-llama/Llama-3.3-70B-Instruct", + llm_api_key=os.getenv("HUGGINGFACE_API_KEY"), + ssl=False # Disable SSL for local testing +) +``` + ## Deploy from Scratch on a barebones machine (Ubuntu on Linode or Amazon Linux on EC2) ```bash @@ -242,14 +295,47 @@ The framework will automatically: ## Appendix: Configuration Details ### Environment Variables -You need the following environment details () -- `ANTHROPIC_API_KEY`: Your Anthropic API key (required) -- `DOMAIN_NAME`: Domain name for SSL certificates (required) +**Core Settings:** +- `DOMAIN_NAME`: Domain name for SSL certificates (required for production) - `AGENT_ID`: Custom agent ID (optional, auto-generated if not provided) - `PORT`: Agent bridge port (optional, default: 6000) - `IMPROVE_MESSAGES`: Enable/disable message improvement (optional, default: true) +**LLM Provider Settings (choose one):** + +*Anthropic (default):* +- `ANTHROPIC_API_KEY`: Your Anthropic API key +- `ANTHROPIC_MODEL`: Model to use (optional, default: claude-3-5-sonnet-20241022) + +*Hugging Face:* +- `HUGGINGFACE_API_KEY`: Your Hugging Face API key +- `HUGGINGFACE_MODEL`: Model to use (optional, default: meta-llama/Llama-3.3-70B-Instruct) + +*General:* +- `LLM_PROVIDER`: Provider to use - "anthropic" or "huggingface" (optional, default: anthropic) + +### start_server_api() Parameters + +```python +nanda.start_server_api( + anthropic_key, # Anthropic API key (or None if using other provider) + domain, # Domain name for the server + agent_id=None, # Custom agent ID (auto-generated if not provided) + port=6000, # Agent bridge port + api_port=6001, # Flask API port + registry=None, # Registry URL (optional) + public_url=None, # Public URL for Agent Bridge (optional) + api_url=None, # API URL for User Client (optional) + cert=None, # Path to SSL certificate (optional) + key=None, # Path to SSL key (optional) + ssl=True, # Enable SSL (default: True) + llm_provider=None, # "anthropic" or "huggingface" (default: anthropic) + llm_model=None, # Model name/ID (uses provider default if not set) + llm_api_key=None # API key for the LLM provider +) +``` + ### Production Deployment For production deployment with SSL: From 8dca7cd40cce88081e0751aa834650f07b9472ec Mon Sep 17 00:00:00 2001 From: Harsh Suthar <93607971+10234567Z@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:19:18 +0530 Subject: [PATCH 6/6] Update nanda_adapter/core/nanda.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- nanda_adapter/core/nanda.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nanda_adapter/core/nanda.py b/nanda_adapter/core/nanda.py index 09fd74d..77f435e 100644 --- a/nanda_adapter/core/nanda.py +++ b/nanda_adapter/core/nanda.py @@ -18,14 +18,14 @@ try: from .agent_bridge import * from . import run_ui_agent_https - from .llm_providers import init_provider, get_provider + from .llm_providers import init_provider except ImportError: # 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 * import run_ui_agent_https - from llm_providers import init_provider, get_provider + from llm_providers import init_provider class NANDA: """NANDA class to create agent_bridge with custom improvement logic"""