Metadata-Version: 2.4
Name: dd-llm
Version: 0.2.0
Summary: Shared LLM abstraction layer for Digital Duck projects
Author-email: Digital Duck <p2p2learn@outlook.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/digital-duck/dd-llm
Project-URL: Repository, https://github.com/digital-duck/dd-llm
Project-URL: Issues, https://github.com/digital-duck/dd-llm/issues
Keywords: llm,digital-duck,ai,provider
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: openai
Requires-Dist: openai>=1.0.0; extra == "openai"
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.25.0; extra == "anthropic"
Provides-Extra: gemini
Requires-Dist: google-genai>=1.0.0; extra == "gemini"
Provides-Extra: momagrid
Requires-Dist: httpx>=0.27; extra == "momagrid"
Provides-Extra: bedrock
Requires-Dist: boto3>=1.34; extra == "bedrock"
Provides-Extra: vertex
Requires-Dist: google-genai>=1.0.0; extra == "vertex"
Provides-Extra: all
Requires-Dist: openai>=1.0.0; extra == "all"
Requires-Dist: anthropic>=0.25.0; extra == "all"
Requires-Dist: google-genai>=1.0.0; extra == "all"
Requires-Dist: httpx>=0.27; extra == "all"
Requires-Dist: boto3>=1.34; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Dynamic: license-file

# dd-llm

Shared LLM abstraction layer for Digital Duck projects.

Zero core dependencies. Adapters lazy-import their SDKs only when used.

## Install

```bash
pip install -e .              # zero deps — claude_cli adapter works out of the box
pip install -e ".[openai]"    # + OpenAI SDK (also covers openrouter, ollama)
pip install -e ".[anthropic]" # + Anthropic SDK
pip install -e ".[gemini]"    # + Google GenAI SDK
pip install -e ".[momagrid]"   # + httpx (Momagrid hub)
pip install -e ".[bedrock]"    # + boto3
pip install -e ".[all]"       # all provider SDKs
```

## Quick Start

```python
from dd_llm import call_llm

# Uses LLM_PROVIDER env var (default: "openai")
response = call_llm("What is 2+2?")

# Specify provider
response = call_llm("Hello", provider="claude_cli")

# With messages
response = call_llm(messages=[
    {"role": "system", "content": "You are helpful."},
    {"role": "user", "content": "Hi"},
])
```

## Built-in Adapters

| Name | Class | Needs | Notes |
|------|-------|-------|-------|
| `claude_cli` | `ClaudeCLIAdapter` | the `claude` CLI | Dev provider, $0 via Claude Code subscription. Prompt goes in via stdin; `ANTHROPIC_API_KEY` is stripped from the child so it cannot bill the paid API; limit errors carry `error_type == "ModelOverloaded"` |
| `gemini_cli` | `GeminiCLIAdapter` | the `gemini` CLI | Same idea for Gemini CLI |
| `openai` | `OpenAIAdapter` | `openai` | Direct OpenAI API |
| `anthropic` | `AnthropicAdapter` | `anthropic` | Direct Anthropic API |
| `gemini` | `GeminiAdapter` | `google-genai` | Direct Google API |
| `vertex` | `VertexAdapter` | `google-genai` | Gemini on Vertex AI (ADC auth) |
| `openrouter` | `OpenAIAdapter` (configured) | `openai` | OpenAI-compatible endpoint |
| `ollama` | `OpenAIAdapter` (configured) | `openai` | Local OpenAI-compatible endpoint |
| `deepseek` | `OpenAIAdapter` (configured) | `openai`, `DEEPSEEK_API_KEY` | DeepSeek API |
| `qwen` | `OpenAIAdapter` (configured) | `openai`, `DASHSCOPE_API_KEY` | Alibaba DashScope |
| `azure_openai` | `AzureOpenAIAdapter` | `openai`, `AZURE_OPENAI_*` | `model` is the deployment name |
| `bedrock` | `BedrockAdapter` | `boto3` | Converse API; estimates cost |
| `momagrid` | `MomagridAdapter` | `httpx` | Decentralised inference via a Momagrid hub; fills `response_worker` |
| `echo` | `EchoAdapter` | none | Returns the prompt; for tests |

`dd_llm.model_compatible(adapter, model)` says whether a model name is plausible for an adapter
(`claude_cli` only speaks `claude-*`, `openrouter` models contain a slash, ...), so callers can drop a
stray model hint instead of making a doomed call.

`dd_llm.parse_llm_spec("claude_cli:claude-haiku-5-5")` returns `("claude_cli", "claude-haiku-5-5")`. It is the
parser behind the `--llm ADAPTER:MODEL` flag of the CLIs built on dd-llm (`grafr`, `spl3`): only the first `:`
splits, so `ollama:gemma3:12b` and `openrouter:meta/llama-3` work, and a bare `claude_cli` gives `("claude_cli", None)`.

## Custom Adapters

```python
from dd_llm import LLMAdapter, LLMResponse, register_adapter, call_llm

class MyAdapter(LLMAdapter):
    def call(self, prompt="", *, messages=None, **kwargs):
        result = my_internal_api(prompt)
        return LLMResponse(content=result, success=True, provider="my_api", model="v1")

register_adapter("my_api", MyAdapter)

# Now usable everywhere
response = call_llm("hello", provider="my_api")
```

## UnifiedLLMProvider

Multi-provider client with retry (exponential backoff + jitter) and automatic
fallback to alternative providers.

```python
from dd_llm import UnifiedLLMProvider

provider = UnifiedLLMProvider(
    primary_provider="openai",
    fallback_providers=["anthropic", "ollama"],
    max_retries=3,
)

result = provider.call("Explain quantum computing")
if result.success:
    print(result.content)
    print(f"Provider: {result.provider}, Model: {result.model}")
    print(f"Tokens: {result.input_tokens} in, {result.output_tokens} out")
```

## Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `LLM_PROVIDER` | Primary provider name | `openai` |
| `LLM_MODEL` | Default model (all providers) | per-provider |
| `LLM_MODEL_OPENAI` | Override model for OpenAI | `gpt-4o` |
| `LLM_MODEL_ANTHROPIC` | Override model for Anthropic | `claude-sonnet-4-5-20250929` |
| `LLM_MODEL_GEMINI` | Override model for Gemini | `gemini-2.0-flash` |
| `LLM_MAX_RETRIES` | Max retries per provider | `3` |
| `LLM_INITIAL_WAIT` | Initial backoff (seconds) | `1` |
| `LLM_MAX_WAIT` | Max backoff (seconds) | `30` |
| `OPENAI_API_KEY` | OpenAI API key | — |
| `ANTHROPIC_API_KEY` | Anthropic API key | — |
| `GEMINI_API_KEY` | Google Gemini API key | — |
| `OPENROUTER_API_KEY` | OpenRouter API key | — |
| `OLLAMA_HOST` | Ollama base URL | `http://localhost:11434` |
| `DEEPSEEK_API_KEY`, `DASHSCOPE_API_KEY` | DeepSeek, Qwen keys | — |
| `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_API_VERSION` | Azure OpenAI | — |
| `MOMAGRID_HUB_URL`, `MOMAGRID_API_KEY` | Momagrid hub (else `~/.igrid/config.yaml`) | `http://localhost:9000` |
| `GOOGLE_CLOUD_PROJECT`, `GOOGLE_CLOUD_LOCATION` | Vertex AI | — |

## License

MIT
