Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ attestation_sdk.log

minions-secure-attestation/

# Internal workflow
CLAUDE.md
.claude/
doc/
.autonomous/

*.csv
88 changes: 41 additions & 47 deletions minions/clients/__init__.py
Original file line number Diff line number Diff line change
@@ -1,70 +1,64 @@
# Core imports - should always work with base installation
from minions.clients.base import MinionsClient
from minions.clients.response import ChatResponse
from minions.clients.ollama import OllamaClient, OllamaTurboClient
from minions.clients.osaurus import OsaurusClient
from minions.clients.lemonade import LemonadeClient
from minions.clients.openai import OpenAIClient
from minions.clients.azure_openai import AzureOpenAIClient
from minions.clients.anthropic import AnthropicClient
from minions.clients.cohere import CohereClient
from minions.clients.together import TogetherClient
from minions.clients.perplexity import PerplexityAIClient
from minions.clients.openrouter import OpenRouterClient
from minions.clients.groq import GroqClient
from minions.clients.deepseek import DeepSeekClient
from minions.clients.qwen import QwenClient
from minions.clients.sambanova import SambanovaClient
from minions.clients.moonshot import MoonshotClient
from minions.clients.gemini import GeminiClient
from minions.clients.grok import GrokClient
from minions.clients.llama_api import LlamaApiClient
from minions.clients.mistral import MistralClient
from minions.clients.minimax import MiniMaxClient
from minions.clients.sarvam import SarvamClient
from minions.clients.docker_model_runner import DockerModelRunnerClient
from minions.clients.lemonade import LemonadeClient
from minions.clients.distributed_inference import DistributedInferenceClient
from minions.clients.novita import NovitaClient
from minions.clients.parallel import ParallelClient
from minions.clients.tencent import TencentClient
from minions.clients.cloudflare import CloudflareGatewayClient
from minions.clients.notdiamond import NotDiamondAIClient
from minions.clients.vercel_gateway import VercelGatewayClient
from minions.clients.exa import ExaClient

# Initialize __all__ with core clients
__all__ = [
"MinionsClient",
"ChatResponse",
"OllamaClient",
"OllamaTurboClient",
"OsaurusClient",
"LemonadeClient",
"OpenAIClient",
"AzureOpenAIClient",
"AnthropicClient",
"CohereClient",
"TogetherClient",
"PerplexityAIClient",
"OpenRouterClient",
"GroqClient",
"DeepSeekClient",
"QwenClient",
"SambanovaClient",
"MoonshotClient",
"GeminiClient",
"GrokClient",
"LlamaApiClient",
"MistralClient",
"MiniMaxClient",
"SarvamClient",
"DockerModelRunnerClient",
"DistributedInferenceClient",
"NovitaClient",
"ParallelClient",
"TencentClient",
"CloudflareGatewayClient",
"NotDiamondAIClient",
"VercelGatewayClient",
"ExaClient",
]

# Optional dependencies - clients that require additional packages
_optional_clients = [
("minions.clients.cohere", "CohereClient"),
("minions.clients.perplexity", "PerplexityAIClient"),
("minions.clients.openrouter", "OpenRouterClient"),
("minions.clients.groq", "GroqClient"),
("minions.clients.deepseek", "DeepSeekClient"),
("minions.clients.qwen", "QwenClient"),
("minions.clients.moonshot", "MoonshotClient"),
("minions.clients.grok", "GrokClient"),
("minions.clients.llama_api", "LlamaApiClient"),
("minions.clients.minimax", "MiniMaxClient"),
("minions.clients.sarvam", "SarvamClient"),
("minions.clients.docker_model_runner", "DockerModelRunnerClient"),
("minions.clients.distributed_inference", "DistributedInferenceClient"),
("minions.clients.novita", "NovitaClient"),
("minions.clients.tencent", "TencentClient"),
("minions.clients.cloudflare", "CloudflareGatewayClient"),
("minions.clients.notdiamond", "NotDiamondAIClient"),
("minions.clients.vercel_gateway", "VercelGatewayClient"),
("minions.clients.exa", "ExaClient"),
("minions.clients.together", "TogetherClient"),
("minions.clients.sambanova", "SambanovaClient"),
("minions.clients.gemini", "GeminiClient"),
("minions.clients.mistral", "MistralClient"),
]

# Dynamically import optional clients
for module_path, class_name in _optional_clients:
try:
module = __import__(module_path, fromlist=[class_name])
globals()[class_name] = getattr(module, class_name)
__all__.append(class_name)
except ImportError:
pass

try:
from minions.clients.transformers import TransformersClient

Expand Down
37 changes: 21 additions & 16 deletions minions/clients/anthropic.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from minions.usage import Usage
from minions.clients.base import MinionsClient
from minions.clients.response import ChatResponse


class AnthropicClient(MinionsClient):
Expand Down Expand Up @@ -293,10 +294,11 @@ def chat(self, messages: List[Dict[str, Any]], **kwargs) -> Tuple[List[str], Usa

result_text = "\n\n".join(result_parts) if result_parts else ""

if self.local:
return [result_text], usage, ["stop"]
else:
return [result_text], usage
return ChatResponse(
responses=[result_text],
usage=usage,
done_reasons=["stop"] if self.local else None
)

else:
# Standard response handling for non-web-search or simple responses
Expand All @@ -307,21 +309,24 @@ def chat(self, messages: List[Dict[str, Any]], **kwargs) -> Tuple[List[str], Usa
):
if hasattr(response.content[0], "text"):
print(response.content[-1].text)
if self.local:
return [response.content[-1].text], usage, ["stop"]
else:
return [response.content[-1].text], usage
return ChatResponse(
responses=[response.content[-1].text],
usage=usage,
done_reasons=["stop"] if self.local else None
)
else:
self.logger.warning(
"Unexpected response format - missing text attribute"
)
if self.local:
return [str(response.content)], usage, ["stop"]
else:
return [str(response.content)], usage
return ChatResponse(
responses=[str(response.content)],
usage=usage,
done_reasons=["stop"] if self.local else None
)
else:
self.logger.warning("Unexpected response format - missing content list")
if self.local:
return [str(response)], usage, ["stop"]
else:
return [str(response)], usage
return ChatResponse(
responses=[str(response)],
usage=usage,
done_reasons=["stop"] if self.local else None
)
6 changes: 5 additions & 1 deletion minions/clients/azure_openai.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from minions.usage import Usage
from minions.clients.base import MinionsClient
from minions.clients.response import ChatResponse


class AzureOpenAIClient(MinionsClient):
Expand Down Expand Up @@ -98,4 +99,7 @@ def chat(self, messages: List[Dict[str, Any]], **kwargs) -> Tuple[List[str], Usa
)

# The content is now nested under message
return [choice.message.content for choice in response.choices], usage
return ChatResponse(
responses=[choice.message.content for choice in response.choices],
usage=usage
)
40 changes: 24 additions & 16 deletions minions/clients/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Any, Dict, List, Optional, Tuple, Union

from minions.usage import Usage
from minions.clients.response import ChatResponse


class MinionsClient(ABC):
Expand Down Expand Up @@ -63,29 +64,36 @@ def __init__(

@abstractmethod
def chat(
self,
messages: List[Dict[str, Any]],
self,
messages: List[Dict[str, Any]],
**kwargs
) -> Union[
Tuple[List[str], Usage],
Tuple[List[str], Usage, List[str]],
Tuple[List[str], Usage, List[str], List[Any]]
]:
) -> ChatResponse:
"""
Primary chat interface that all clients must implement.

Args:
messages: List of message dictionaries with 'role' and 'content' keys
**kwargs: Additional parameters specific to the client

Returns:
Tuple containing at minimum:
- List[str]: Generated responses
- Usage: Token usage information

May also include:
- List[str]: Finish reasons (optional)
- List[Any]: Tool calls (optional)
ChatResponse: Standardized response object with responses, usage,
and optional fields (done_reasons, tool_calls, audio, metadata)

Examples:
# Type-safe attribute access (recommended):
response = client.chat(messages)
print(response.responses[0])
print(response.usage.total_tokens)
if response.done_reasons:
print(response.done_reasons[0])

# Backward compatible unpacking (still supported):
responses, usage = client.chat(messages)
responses, usage, done_reasons = client.chat(messages)
responses, usage, done_reasons, tool_calls = client.chat(messages)

Raises:
NotImplementedError: If client doesn't support chat
"""
pass

Expand Down
10 changes: 6 additions & 4 deletions minions/clients/cartesia_mlx.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import mlx.core as mx
from minions.usage import Usage
from minions.clients.base import MinionsClient
from minions.clients.response import ChatResponse
from transformers import AutoTokenizer


Expand Down Expand Up @@ -137,7 +138,8 @@ def _generate(
completion_tokens=completion_tokens,
)

if self.local:
return [output_text], usage, "stop"
else:
return [output_text], usage
return ChatResponse(
responses=[output_text],
usage=usage,
done_reasons=["stop"] if self.local else None
)
19 changes: 11 additions & 8 deletions minions/clients/cerebras.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import Any, Dict, List, Optional, Tuple
from minions.usage import Usage
from minions.clients.base import MinionsClient
from minions.clients.response import ChatResponse
import logging
import os

Expand All @@ -12,7 +14,7 @@
)


class CerebrasClient:
class CerebrasClient(MinionsClient):
def __init__(
self,
model_name: str = "llama3.1-8b",
Expand Down Expand Up @@ -52,7 +54,7 @@ def __init__(

self.client = Cerebras(**client_kwargs)

def chat(self, messages: List[Dict[str, Any]], **kwargs) -> Tuple[List[str], Usage]:
def chat(self, messages: List[Dict[str, Any]], **kwargs) -> ChatResponse:
'''
Handle chat completions using the Cerebras API.

Expand All @@ -61,7 +63,7 @@ def chat(self, messages: List[Dict[str, Any]], **kwargs) -> Tuple[List[str], Usa
**kwargs: Additional arguments to pass to cerebras.chat.completions.create

Returns:
Tuple of (List[str], Usage) containing response strings and token usage
ChatResponse containing response strings, token usage, and finish reasons
'''
assert len(messages) > 0, "Messages cannot be empty."

Expand All @@ -88,9 +90,10 @@ def chat(self, messages: List[Dict[str, Any]], **kwargs) -> Tuple[List[str], Usa

# Extract finish reasons
finish_reasons = ["stop"] * len(response.choices)

# Extract response content
if self.local:
return [choice.message.content for choice in response.choices], usage, finish_reasons
else:
return [choice.message.content for choice in response.choices], usage
return ChatResponse(
responses=[choice.message.content for choice in response.choices],
usage=usage,
done_reasons=finish_reasons if self.local else None
)
7 changes: 5 additions & 2 deletions minions/clients/cloudflare.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import os
from openai import OpenAI
from minions.clients.openai import OpenAIClient
from minions.clients.response import ChatResponse

from minions.usage import Usage

Expand Down Expand Up @@ -126,8 +127,10 @@ def chat(self, messages: List[Dict[str, Any]], **kwargs) -> Tuple[List[str], Usa
)

# Return response content
return [choice.message.content for choice in response.choices], usage

return ChatResponse(
responses=[choice.message.content for choice in response.choices],
usage=usage
)
@staticmethod
def get_available_models() -> List[str]:
"""
Expand Down
10 changes: 6 additions & 4 deletions minions/clients/cohere.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from minions.usage import Usage
from minions.clients.base import MinionsClient
from minions.clients.response import ChatResponse


class CohereClient(MinionsClient):
Expand Down Expand Up @@ -93,10 +94,11 @@ def chat(self, messages: List[Dict[str, Any]], **kwargs) -> Tuple[List[str], Usa
)

# Extract response content
if self.local:
return [choice.message.content for choice in response.choices], usage, [choice.finish_reason for choice in response.choices]
else:
return [choice.message.content for choice in response.choices], usage
return ChatResponse(
responses=[choice.message.content for choice in response.choices],
usage=usage,
done_reasons=[choice.finish_reason for choice in response.choices] if self.local else None
)

def embed(
self,
Expand Down
Loading