Skip to content
Draft
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
75 changes: 73 additions & 2 deletions surfkit/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,15 @@ def create_agent(
local_keys: bool = typer.Option(
False, "--local-keys", "-l", help="Use local API keys."
),
provider: Optional[str] = typer.Option(
None,
"--provider",
"-p",
help="AI provider to use (openai, openrouter, azure, etc.)",
),
provider_base_url: Optional[str] = typer.Option(
None, "--provider-base-url", help="Base URL for the AI provider API"
),
debug: bool = typer.Option(False, help="Run the agent with debug logging"),
):
from surfkit.server.models import V1AgentType
Expand Down Expand Up @@ -476,13 +485,42 @@ def create_agent(
name = instance_name(agent_type)

env_vars = find_envs(agent_type, use_local=local_keys)

# Add provider configuration to environment variables if specified
if provider:
from surfkit.providers import ProviderConfig
from surfkit.config import GlobalConfig

# Get provider configuration
provider_config = ProviderConfig.get_provider(provider.lower())

# Set provider in global config
config = GlobalConfig.read()
config.provider = provider.lower()
if provider_base_url:
config.provider_base_url = provider_base_url
config.write()

# Add provider info to environment variables
env_vars["SURFKIT_PROVIDER"] = provider.lower()
if provider_base_url:
env_vars[f"{provider_config.env_key.split('_API_KEY')[0]}_BASE_URL"] = (
provider_base_url
)
elif provider_config.base_url:
env_vars[f"{provider_config.env_key.split('_API_KEY')[0]}_BASE_URL"] = (
provider_config.base_url
)

if type:
provider_info = f" with provider '{provider}'" if provider else ""
typer.echo(
f"Running agent '{type}' with runtime '{runtime}' and name '{name}'..."
f"Running agent '{type}' with runtime '{runtime}' and name '{name}'{provider_info}..."
)
else:
provider_info = f" with provider '{provider}'" if provider else ""
typer.echo(
f"Running agent '{file}' with runtime '{runtime}' and name '{name}'..."
f"Running agent '{file}' with runtime '{runtime}' and name '{name}'{provider_info}..."
)

try:
Expand Down Expand Up @@ -863,6 +901,31 @@ def find(help="Find an agent"):
list_types()


@app.command("providers")
def list_providers():
"""List available AI providers"""
from surfkit.providers import PROVIDERS

table = []
for provider_id, provider in PROVIDERS.items():
table.append(
[
provider_id,
provider["name"],
provider["base_url"] or "Custom URL required",
provider["env_key"],
]
)

print(
tabulate(
table,
headers=["ID", "Name", "Base URL", "API Key Environment Variable"],
)
)
print("")


@list_group.command("tasks")
def list_tasks(
remote: Optional[str] = typer.Option(
Expand Down Expand Up @@ -1462,6 +1525,12 @@ def solve(
local_keys: bool = typer.Option(
False, "--local-keys", "-l", help="Use local API keys."
),
provider: Optional[str] = typer.Option(
None, "--provider", help="AI provider to use (openai, openrouter, azure, etc.)"
),
provider_base_url: Optional[str] = typer.Option(
None, "--provider-base-url", help="Base URL for the AI provider API"
),
debug: bool = typer.Option(False, help="Run the agent with debug logging"),
):
from surfkit.client import solve
Expand All @@ -1486,6 +1555,8 @@ def solve(
starting_url=starting_url,
auth_enabled=auth_enabled,
local_keys=local_keys,
provider=provider,
provider_base_url=provider_base_url,
debug=debug,
interactive=True,
create_tracker=False,
Expand Down
3 changes: 0 additions & 3 deletions surfkit/cli/templates/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ def generate_pyproject(agent_name: str, description, git_user_ref: str) -> None:
def generate_agentfile(
name: str, description: str, image_repo: str, icon_url: str
) -> None:

out = f"""
api_version: v1
kind: TaskAgent
Expand Down Expand Up @@ -200,7 +199,6 @@ def generate_agentfile(


def generate_gitignore() -> None:

out = """
# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down Expand Up @@ -380,7 +378,6 @@ def generate_gitignore() -> None:


def generate_readme(agent_name: str, description: str) -> None:

out = f"""# {agent_name}

{description}
Expand Down
2 changes: 2 additions & 0 deletions surfkit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
class GlobalConfig:
api_key: Optional[str] = None
hub_address: str = AGENTSEA_HUB_URL
provider: Optional[str] = None
provider_base_url: Optional[str] = None

def write(self) -> None:
home = os.path.expanduser("~")
Expand Down
5 changes: 3 additions & 2 deletions surfkit/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
from sqlalchemy.orm import declarative_base
from sqlalchemy.inspection import inspect


def to_dict(instance):
return {
c.key: getattr(instance, c.key)
for c in inspect(instance).mapper.column_attrs
c.key: getattr(instance, c.key) for c in inspect(instance).mapper.column_attrs
}


Base = declarative_base()


Expand Down
94 changes: 94 additions & 0 deletions surfkit/docs/providers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# AI Provider Configuration

Surfkit supports multiple AI providers that are compatible with the OpenAI API format. You can specify which provider to use with the `--provider` flag.

## Supported Providers

- `openai` (default) - OpenAI API
- `openrouter` - OpenRouter API
- `azure` - Azure OpenAI API
- `gemini` - Google Gemini API
- `ollama` - Ollama API
- `mistral` - Mistral AI API
- `deepseek` - DeepSeek API
- `xai` - xAI API
- `groq` - Groq API
- `arceeai` - ArceeAI API
- Any custom provider that is compatible with the OpenAI API

## Using a Provider

You can specify a provider when creating an agent or solving a task:

```bash
# Create an agent using OpenRouter
surfkit create agent --provider openrouter

# Solve a task using Azure OpenAI
surfkit solve "Create a simple web app" --provider azure --provider-base-url "https://your-resource.openai.azure.com/openai"
```

## Environment Variables

For each provider, you need to set the corresponding API key as an environment variable:

```bash
# OpenAI (default)
export OPENAI_API_KEY="your-api-key-here"

# OpenRouter
export OPENROUTER_API_KEY="your-openrouter-key-here"

# Azure OpenAI
export AZURE_OPENAI_API_KEY="your-azure-api-key-here"
export AZURE_OPENAI_API_VERSION="2023-05-15" # Optional
```

## Custom Providers

For custom providers not in the predefined list, you need to specify both the API key and base URL:

```bash
# Set environment variables for a custom provider
export CUSTOM_API_KEY="your-custom-api-key"
export CUSTOM_BASE_URL="https://your-custom-api-endpoint.com/v1"

# Use the custom provider
surfkit solve "Create a simple web app" --provider custom
```

## Configuration File

You can also configure providers in a JSON configuration file at `~/.surfkit/config.json`:

```json
{
"model": "gpt-4o",
"provider": "openrouter",
"providers": {
"openai": {
"name": "OpenAI",
"baseURL": "https://api.openai.com/v1",
"envKey": "OPENAI_API_KEY"
},
"openrouter": {
"name": "OpenRouter",
"baseURL": "https://openrouter.ai/api/v1",
"envKey": "OPENROUTER_API_KEY"
},
"custom": {
"name": "Custom Provider",
"baseURL": "https://your-custom-api-endpoint.com/v1",
"envKey": "CUSTOM_API_KEY"
}
}
}
```

## Listing Available Providers

To see all available providers and their configuration:

```bash
surfkit providers
```
1 change: 0 additions & 1 deletion surfkit/env_opts.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@


def find_local_llm_keys(typ: AgentType) -> Optional[dict]:

env_vars = None

if typ.llm_providers and typ.llm_providers.preference:
Expand Down
26 changes: 26 additions & 0 deletions surfkit/examples/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"model": "gpt-4o",
"provider": "openrouter",
"providers": {
"openai": {
"name": "OpenAI",
"baseURL": "https://api.openai.com/v1",
"envKey": "OPENAI_API_KEY"
},
"openrouter": {
"name": "OpenRouter",
"baseURL": "https://openrouter.ai/api/v1",
"envKey": "OPENROUTER_API_KEY"
},
"azure": {
"name": "Azure OpenAI",
"baseURL": "https://your-resource-name.openai.azure.com/openai/deployments/your-deployment-name",
"envKey": "AZURE_OPENAI_API_KEY"
},
"custom": {
"name": "Custom Provider",
"baseURL": "https://your-custom-api-endpoint.com/v1",
"envKey": "CUSTOM_API_KEY"
}
}
}
1 change: 0 additions & 1 deletion surfkit/learn/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@


class Teacher(ABC):

@abstractmethod
def teach(self, *args, **kwargs):
pass
Expand Down
82 changes: 82 additions & 0 deletions surfkit/providers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from __future__ import annotations

import os
from dataclasses import dataclass
from typing import Dict, Optional

# Default provider configurations
PROVIDERS = {
"openai": {
"name": "OpenAI",
"base_url": "https://api.openai.com/v1",
"env_key": "OPENAI_API_KEY",
},
"openrouter": {
"name": "OpenRouter",
"base_url": "https://openrouter.ai/api/v1",
"env_key": "OPENROUTER_API_KEY",
},
"azure": {
"name": "Azure OpenAI",
"base_url": None, # Must be provided by user
"env_key": "AZURE_OPENAI_API_KEY",
},
"gemini": {
"name": "Google Gemini",
"base_url": "https://generativelanguage.googleapis.com/v1beta/openai",
"env_key": "GEMINI_API_KEY",
},
"ollama": {
"name": "Ollama",
"base_url": "http://localhost:11434/v1",
"env_key": "OLLAMA_API_KEY",
},
"mistral": {
"name": "Mistral AI",
"base_url": "https://api.mistral.ai/v1",
"env_key": "MISTRAL_API_KEY",
},
"deepseek": {
"name": "DeepSeek",
"base_url": "https://api.deepseek.com",
"env_key": "DEEPSEEK_API_KEY",
},
"xai": {"name": "xAI", "base_url": "https://api.x.ai/v1", "env_key": "XAI_API_KEY"},
"groq": {
"name": "Groq",
"base_url": "https://api.groq.com/openai/v1",
"env_key": "GROQ_API_KEY",
},
"arceeai": {
"name": "ArceeAI",
"base_url": "https://conductor.arcee.ai/v1",
"env_key": "ARCEEAI_API_KEY",
},
}


@dataclass
class ProviderConfig:
name: str
base_url: Optional[str]
env_key: str

@classmethod
def get_provider(cls, provider_name: str) -> ProviderConfig:
"""Get provider configuration by name"""
if provider_name not in PROVIDERS:
# For custom providers, use the name as the env key prefix
return ProviderConfig(
name=provider_name.capitalize(),
base_url=os.environ.get(f"{provider_name.upper()}_BASE_URL"),
env_key=f"{provider_name.upper()}_API_KEY",
)

config = PROVIDERS[provider_name]
return ProviderConfig(
name=config["name"], base_url=config["base_url"], env_key=config["env_key"]
)

def get_api_key(self) -> Optional[str]:
"""Get the API key for this provider from environment variables"""
return os.environ.get(self.env_key)
Loading