bridgekit 0.3.10__tar.gz → 0.3.11__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bridgekit-0.3.10/bridgekit.egg-info → bridgekit-0.3.11}/PKG-INFO +32 -2
- bridgekit-0.3.10/PKG-INFO → bridgekit-0.3.11/README.md +27 -33
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit/config.py +32 -3
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit/providers.py +40 -3
- bridgekit-0.3.10/README.md → bridgekit-0.3.11/bridgekit.egg-info/PKG-INFO +63 -1
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit.egg-info/requires.txt +6 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/pyproject.toml +3 -1
- bridgekit-0.3.11/tests/test_providers.py +214 -0
- bridgekit-0.3.10/tests/test_providers.py +0 -71
- {bridgekit-0.3.10 → bridgekit-0.3.11}/LICENSE +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit/__init__.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit/cli.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit/compare.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit/planner.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit/redteam.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit/reviewer.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit/search.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit/summarize.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit.egg-info/SOURCES.txt +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit.egg-info/dependency_links.txt +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit.egg-info/entry_points.txt +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/bridgekit.egg-info/top_level.txt +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/setup.cfg +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/tests/test_cli.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/tests/test_compare.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/tests/test_config.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/tests/test_planner.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/tests/test_redteam.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/tests/test_reviewer.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/tests/test_search.py +0 -0
- {bridgekit-0.3.10 → bridgekit-0.3.11}/tests/test_summarize.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bridgekit
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.11
|
|
4
4
|
Summary: AI tools that make you a better data scientist, not a redundant one.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://usebridgekit.com
|
|
@@ -25,6 +25,10 @@ Provides-Extra: openai
|
|
|
25
25
|
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
26
26
|
Provides-Extra: gemini
|
|
27
27
|
Requires-Dist: google-generativeai>=0.3.0; extra == "gemini"
|
|
28
|
+
Provides-Extra: ollama
|
|
29
|
+
Requires-Dist: ollama>=0.3.0; extra == "ollama"
|
|
30
|
+
Provides-Extra: openrouter
|
|
31
|
+
Requires-Dist: openai>=1.0.0; extra == "openrouter"
|
|
28
32
|
Provides-Extra: dev
|
|
29
33
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
30
34
|
Requires-Dist: pytest-mock>=3.0.0; extra == "dev"
|
|
@@ -87,6 +91,18 @@ pip install bridgekit[gemini]
|
|
|
87
91
|
export GOOGLE_API_KEY=your_key_here
|
|
88
92
|
```
|
|
89
93
|
|
|
94
|
+
**Ollama (local models, free, no API key):**
|
|
95
|
+
```bash
|
|
96
|
+
pip install bridgekit[ollama]
|
|
97
|
+
# Make sure Ollama is running locally: https://ollama.com
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**OpenRouter (free and paid models via one API):**
|
|
101
|
+
```bash
|
|
102
|
+
pip install bridgekit[openrouter]
|
|
103
|
+
export OPENROUTER_API_KEY=your_key_here
|
|
104
|
+
```
|
|
105
|
+
|
|
90
106
|
---
|
|
91
107
|
|
|
92
108
|
## Getting Started
|
|
@@ -528,7 +544,7 @@ high-confidence growth lever worth testing immediately.
|
|
|
528
544
|
|
|
529
545
|
## Multi-Provider Support
|
|
530
546
|
|
|
531
|
-
Bridgekit now supports multiple AI providers so you're not locked into one API. You can use Anthropic, OpenAI,
|
|
547
|
+
Bridgekit now supports multiple AI providers so you're not locked into one API. You can use Anthropic, OpenAI, Google Gemini, local models via Ollama, or any model on OpenRouter with any tool.
|
|
532
548
|
|
|
533
549
|
**Using different providers:**
|
|
534
550
|
|
|
@@ -541,21 +557,35 @@ print(evaluate("Your analysis here", provider="openai"))
|
|
|
541
557
|
# Use Google Gemini (default model: gemini-1.5-pro)
|
|
542
558
|
print(plan("Your question here", provider="gemini"))
|
|
543
559
|
|
|
560
|
+
# Use a local model via Ollama - free, private, no API key (default model: llama3.2)
|
|
561
|
+
print(evaluate("Your analysis here", provider="ollama"))
|
|
562
|
+
|
|
563
|
+
# Use OpenRouter to access a wide range of models, including free tiers
|
|
564
|
+
print(evaluate("Your analysis here", provider="openrouter", model="deepseek/deepseek-v4-flash-0731:free"))
|
|
565
|
+
|
|
544
566
|
# Use specific model
|
|
545
567
|
print(redteam("Your analysis here", model="gpt-4-turbo"))
|
|
546
568
|
print(ask("Your question here", source="reports/", model="claude-3-opus-20240229"))
|
|
547
569
|
```
|
|
548
570
|
|
|
571
|
+
**Ollama setup:** install and start [Ollama](https://ollama.com) locally, then pull a model (e.g. `ollama pull llama3.2`). By default Bridgekit connects to `http://localhost:11434`; set `OLLAMA_HOST` to point at a different host.
|
|
572
|
+
|
|
549
573
|
**Provider auto-detection:**
|
|
550
574
|
Bridgekit automatically detects the provider from model names:
|
|
551
575
|
- Models starting with "claude" → Anthropic
|
|
552
576
|
- Models starting with "gpt" → OpenAI
|
|
553
577
|
- Models starting with "gemini" → Google Gemini
|
|
578
|
+
- Model names containing "/" (e.g. `deepseek/deepseek-v4-flash-0731:free`) → OpenRouter
|
|
579
|
+
- Common local model families (e.g. "llama", "mistral", "mixtral", "gemma", "phi", "qwen") → Ollama
|
|
554
580
|
|
|
555
581
|
**Default models by provider:**
|
|
556
582
|
- Anthropic: `claude-opus-4-8`
|
|
557
583
|
- OpenAI: `gpt-4o`
|
|
558
584
|
- Gemini: `gemini-1.5-pro`
|
|
585
|
+
- Ollama: `llama3.2`
|
|
586
|
+
- OpenRouter: `deepseek/deepseek-v4-flash-0731:free`
|
|
587
|
+
|
|
588
|
+
> **Note:** OpenRouter's free-tier model lineup rotates over time. Check the current list at [openrouter.ai/models?max_price=0](https://openrouter.ai/models?max_price=0) if a `:free` model stops working, and pass `model=` explicitly with whichever slug is currently free.
|
|
559
589
|
|
|
560
590
|
All tools support the same `provider` and `model` parameters:
|
|
561
591
|
- `evaluate(text, provider=None, model=None, system_prompt=None)`
|
|
@@ -1,35 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: bridgekit
|
|
3
|
-
Version: 0.3.10
|
|
4
|
-
Summary: AI tools that make you a better data scientist, not a redundant one.
|
|
5
|
-
License: MIT
|
|
6
|
-
Project-URL: Homepage, https://usebridgekit.com
|
|
7
|
-
Project-URL: Issues, https://github.com/getbridgekit/bridgekit/issues
|
|
8
|
-
Keywords: data science,AI,analysis,evaluation,anthropic
|
|
9
|
-
Classifier: Development Status :: 3 - Alpha
|
|
10
|
-
Classifier: Intended Audience :: Science/Research
|
|
11
|
-
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
-
Classifier: Programming Language :: Python :: 3
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Requires-Python: >=3.9
|
|
15
|
-
Description-Content-Type: text/markdown
|
|
16
|
-
License-File: LICENSE
|
|
17
|
-
Requires-Dist: anthropic>=0.20.0
|
|
18
|
-
Requires-Dist: chromadb>=0.4.0
|
|
19
|
-
Requires-Dist: sentence-transformers>=2.0.0
|
|
20
|
-
Requires-Dist: pypdf>=3.0.0
|
|
21
|
-
Requires-Dist: python-docx>=1.0.0
|
|
22
|
-
Requires-Dist: python-pptx>=0.6.0
|
|
23
|
-
Requires-Dist: nbformat>=5.0.0
|
|
24
|
-
Provides-Extra: openai
|
|
25
|
-
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
26
|
-
Provides-Extra: gemini
|
|
27
|
-
Requires-Dist: google-generativeai>=0.3.0; extra == "gemini"
|
|
28
|
-
Provides-Extra: dev
|
|
29
|
-
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
30
|
-
Requires-Dist: pytest-mock>=3.0.0; extra == "dev"
|
|
31
|
-
Dynamic: license-file
|
|
32
|
-
|
|
33
1
|
<p align="center">
|
|
34
2
|
<img src="https://raw.githubusercontent.com/getbridgekit/bridgekit/main/assets/logo.png" alt="Bridgekit" width="200"/>
|
|
35
3
|
</p>
|
|
@@ -87,6 +55,18 @@ pip install bridgekit[gemini]
|
|
|
87
55
|
export GOOGLE_API_KEY=your_key_here
|
|
88
56
|
```
|
|
89
57
|
|
|
58
|
+
**Ollama (local models, free, no API key):**
|
|
59
|
+
```bash
|
|
60
|
+
pip install bridgekit[ollama]
|
|
61
|
+
# Make sure Ollama is running locally: https://ollama.com
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**OpenRouter (free and paid models via one API):**
|
|
65
|
+
```bash
|
|
66
|
+
pip install bridgekit[openrouter]
|
|
67
|
+
export OPENROUTER_API_KEY=your_key_here
|
|
68
|
+
```
|
|
69
|
+
|
|
90
70
|
---
|
|
91
71
|
|
|
92
72
|
## Getting Started
|
|
@@ -528,7 +508,7 @@ high-confidence growth lever worth testing immediately.
|
|
|
528
508
|
|
|
529
509
|
## Multi-Provider Support
|
|
530
510
|
|
|
531
|
-
Bridgekit now supports multiple AI providers so you're not locked into one API. You can use Anthropic, OpenAI,
|
|
511
|
+
Bridgekit now supports multiple AI providers so you're not locked into one API. You can use Anthropic, OpenAI, Google Gemini, local models via Ollama, or any model on OpenRouter with any tool.
|
|
532
512
|
|
|
533
513
|
**Using different providers:**
|
|
534
514
|
|
|
@@ -541,21 +521,35 @@ print(evaluate("Your analysis here", provider="openai"))
|
|
|
541
521
|
# Use Google Gemini (default model: gemini-1.5-pro)
|
|
542
522
|
print(plan("Your question here", provider="gemini"))
|
|
543
523
|
|
|
524
|
+
# Use a local model via Ollama - free, private, no API key (default model: llama3.2)
|
|
525
|
+
print(evaluate("Your analysis here", provider="ollama"))
|
|
526
|
+
|
|
527
|
+
# Use OpenRouter to access a wide range of models, including free tiers
|
|
528
|
+
print(evaluate("Your analysis here", provider="openrouter", model="deepseek/deepseek-v4-flash-0731:free"))
|
|
529
|
+
|
|
544
530
|
# Use specific model
|
|
545
531
|
print(redteam("Your analysis here", model="gpt-4-turbo"))
|
|
546
532
|
print(ask("Your question here", source="reports/", model="claude-3-opus-20240229"))
|
|
547
533
|
```
|
|
548
534
|
|
|
535
|
+
**Ollama setup:** install and start [Ollama](https://ollama.com) locally, then pull a model (e.g. `ollama pull llama3.2`). By default Bridgekit connects to `http://localhost:11434`; set `OLLAMA_HOST` to point at a different host.
|
|
536
|
+
|
|
549
537
|
**Provider auto-detection:**
|
|
550
538
|
Bridgekit automatically detects the provider from model names:
|
|
551
539
|
- Models starting with "claude" → Anthropic
|
|
552
540
|
- Models starting with "gpt" → OpenAI
|
|
553
541
|
- Models starting with "gemini" → Google Gemini
|
|
542
|
+
- Model names containing "/" (e.g. `deepseek/deepseek-v4-flash-0731:free`) → OpenRouter
|
|
543
|
+
- Common local model families (e.g. "llama", "mistral", "mixtral", "gemma", "phi", "qwen") → Ollama
|
|
554
544
|
|
|
555
545
|
**Default models by provider:**
|
|
556
546
|
- Anthropic: `claude-opus-4-8`
|
|
557
547
|
- OpenAI: `gpt-4o`
|
|
558
548
|
- Gemini: `gemini-1.5-pro`
|
|
549
|
+
- Ollama: `llama3.2`
|
|
550
|
+
- OpenRouter: `deepseek/deepseek-v4-flash-0731:free`
|
|
551
|
+
|
|
552
|
+
> **Note:** OpenRouter's free-tier model lineup rotates over time. Check the current list at [openrouter.ai/models?max_price=0](https://openrouter.ai/models?max_price=0) if a `:free` model stops working, and pass `model=` explicitly with whichever slug is currently free.
|
|
559
553
|
|
|
560
554
|
All tools support the same `provider` and `model` parameters:
|
|
561
555
|
- `evaluate(text, provider=None, model=None, system_prompt=None)`
|
|
@@ -7,25 +7,39 @@ class Provider(Enum):
|
|
|
7
7
|
ANTHROPIC = "anthropic"
|
|
8
8
|
OPENAI = "openai"
|
|
9
9
|
GEMINI = "gemini"
|
|
10
|
+
OLLAMA = "ollama"
|
|
11
|
+
OPENROUTER = "openrouter"
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
# Default models for each provider
|
|
13
15
|
DEFAULT_MODELS = {
|
|
14
16
|
Provider.ANTHROPIC: "claude-opus-4-8",
|
|
15
17
|
Provider.OPENAI: "gpt-4o",
|
|
16
|
-
Provider.GEMINI: "gemini-1.5-pro"
|
|
18
|
+
Provider.GEMINI: "gemini-1.5-pro",
|
|
19
|
+
Provider.OLLAMA: "llama3.2",
|
|
20
|
+
Provider.OPENROUTER: "deepseek/deepseek-v4-flash-0731:free",
|
|
17
21
|
}
|
|
18
22
|
|
|
19
23
|
# Legacy support
|
|
20
24
|
DEFAULT_MODEL = DEFAULT_MODELS[Provider.ANTHROPIC]
|
|
21
25
|
|
|
26
|
+
# Common local model family names served by Ollama. Used to infer the
|
|
27
|
+
# provider from a bare model name when no explicit provider is given.
|
|
28
|
+
OLLAMA_MODEL_PREFIXES = (
|
|
29
|
+
"llama", "mistral", "mixtral", "gemma", "phi", "qwen",
|
|
30
|
+
"codellama", "vicuna", "deepseek", "tinyllama",
|
|
31
|
+
)
|
|
22
32
|
|
|
23
|
-
|
|
33
|
+
|
|
34
|
+
def require_api_key(provider: Provider = Provider.ANTHROPIC) -> Optional[str]:
|
|
24
35
|
"""Return the API key for the specified provider from the environment, or raise a clear error.
|
|
25
36
|
|
|
26
37
|
Each Bridgekit tool calls this before constructing a client so
|
|
27
38
|
users get the same friendly message instead of whatever the SDK surfaces
|
|
28
39
|
when the key is missing.
|
|
40
|
+
|
|
41
|
+
Ollama runs locally and doesn't use an API key, so this returns None
|
|
42
|
+
for that provider instead of raising.
|
|
29
43
|
"""
|
|
30
44
|
if provider == Provider.ANTHROPIC:
|
|
31
45
|
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
|
@@ -48,6 +62,15 @@ def require_api_key(provider: Provider = Provider.ANTHROPIC) -> str:
|
|
|
48
62
|
"GOOGLE_API_KEY not found. Set it with: export GOOGLE_API_KEY=your_key_here"
|
|
49
63
|
)
|
|
50
64
|
return api_key
|
|
65
|
+
elif provider == Provider.OLLAMA:
|
|
66
|
+
return None
|
|
67
|
+
elif provider == Provider.OPENROUTER:
|
|
68
|
+
api_key = os.environ.get("OPENROUTER_API_KEY")
|
|
69
|
+
if not api_key:
|
|
70
|
+
raise EnvironmentError(
|
|
71
|
+
"OPENROUTER_API_KEY not found. Set it with: export OPENROUTER_API_KEY=your_key_here"
|
|
72
|
+
)
|
|
73
|
+
return api_key
|
|
51
74
|
else:
|
|
52
75
|
raise ValueError(f"Unsupported provider: {provider}")
|
|
53
76
|
|
|
@@ -73,7 +96,13 @@ def parse_provider(provider: Optional[str] = None, model: Optional[str] = None)
|
|
|
73
96
|
return Provider.OPENAI
|
|
74
97
|
elif model.startswith("gemini"):
|
|
75
98
|
return Provider.GEMINI
|
|
76
|
-
|
|
99
|
+
elif "/" in model:
|
|
100
|
+
# OpenRouter model names use a "vendor/model" format,
|
|
101
|
+
# e.g. "deepseek/deepseek-v4-flash-0731:free"
|
|
102
|
+
return Provider.OPENROUTER
|
|
103
|
+
elif model.startswith(OLLAMA_MODEL_PREFIXES):
|
|
104
|
+
return Provider.OLLAMA
|
|
105
|
+
|
|
77
106
|
# Default to Anthropic for backward compatibility
|
|
78
107
|
return Provider.ANTHROPIC
|
|
79
108
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Provider client factory for Bridgekit multi-provider support."""
|
|
2
2
|
|
|
3
|
+
import os
|
|
3
4
|
from typing import Union, Dict, Any, List
|
|
4
5
|
from .config import Provider, require_api_key, get_default_model
|
|
5
6
|
|
|
@@ -39,11 +40,14 @@ class AnthropicClient(BaseProviderClient):
|
|
|
39
40
|
|
|
40
41
|
class OpenAIClient(BaseProviderClient):
|
|
41
42
|
"""OpenAI provider client."""
|
|
42
|
-
|
|
43
|
-
def __init__(self, api_key: str):
|
|
43
|
+
|
|
44
|
+
def __init__(self, api_key: str, base_url: str = None):
|
|
44
45
|
super().__init__(api_key)
|
|
45
46
|
import openai
|
|
46
|
-
|
|
47
|
+
if base_url:
|
|
48
|
+
self.client = openai.OpenAI(api_key=api_key, base_url=base_url)
|
|
49
|
+
else:
|
|
50
|
+
self.client = openai.OpenAI(api_key=api_key)
|
|
47
51
|
|
|
48
52
|
def create_message(self, system_prompt: str, user_message: str, model: str, max_tokens: int = 1024) -> str:
|
|
49
53
|
"""Create a message using OpenAI's API."""
|
|
@@ -86,6 +90,35 @@ class GeminiClient(BaseProviderClient):
|
|
|
86
90
|
return response.text
|
|
87
91
|
|
|
88
92
|
|
|
93
|
+
class OllamaClient(BaseProviderClient):
|
|
94
|
+
"""Ollama provider client for locally-hosted models. No API key required."""
|
|
95
|
+
|
|
96
|
+
def __init__(self, api_key: str = None):
|
|
97
|
+
super().__init__(api_key)
|
|
98
|
+
import ollama
|
|
99
|
+
host = os.environ.get("OLLAMA_HOST", "http://localhost:11434")
|
|
100
|
+
self.client = ollama.Client(host=host)
|
|
101
|
+
|
|
102
|
+
def create_message(self, system_prompt: str, user_message: str, model: str, max_tokens: int = 1024) -> str:
|
|
103
|
+
"""Create a message using a locally-hosted Ollama model."""
|
|
104
|
+
response = self.client.chat(
|
|
105
|
+
model=model,
|
|
106
|
+
messages=[
|
|
107
|
+
{"role": "system", "content": system_prompt},
|
|
108
|
+
{"role": "user", "content": user_message}
|
|
109
|
+
],
|
|
110
|
+
options={"num_predict": max_tokens}
|
|
111
|
+
)
|
|
112
|
+
return response["message"]["content"]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class OpenRouterClient(OpenAIClient):
|
|
116
|
+
"""OpenRouter provider client. OpenAI-compatible API routing to many models, including free tiers."""
|
|
117
|
+
|
|
118
|
+
def __init__(self, api_key: str):
|
|
119
|
+
super().__init__(api_key, base_url="https://openrouter.ai/api/v1")
|
|
120
|
+
|
|
121
|
+
|
|
89
122
|
def create_client(provider: Provider, model: str = None) -> BaseProviderClient:
|
|
90
123
|
"""Create a client for the specified provider."""
|
|
91
124
|
if model is None:
|
|
@@ -99,6 +132,10 @@ def create_client(provider: Provider, model: str = None) -> BaseProviderClient:
|
|
|
99
132
|
return OpenAIClient(api_key)
|
|
100
133
|
elif provider == Provider.GEMINI:
|
|
101
134
|
return GeminiClient(api_key)
|
|
135
|
+
elif provider == Provider.OLLAMA:
|
|
136
|
+
return OllamaClient(api_key)
|
|
137
|
+
elif provider == Provider.OPENROUTER:
|
|
138
|
+
return OpenRouterClient(api_key)
|
|
102
139
|
else:
|
|
103
140
|
raise ValueError(f"Unsupported provider: {provider}")
|
|
104
141
|
|
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bridgekit
|
|
3
|
+
Version: 0.3.11
|
|
4
|
+
Summary: AI tools that make you a better data scientist, not a redundant one.
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://usebridgekit.com
|
|
7
|
+
Project-URL: Issues, https://github.com/getbridgekit/bridgekit/issues
|
|
8
|
+
Keywords: data science,AI,analysis,evaluation,anthropic
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: anthropic>=0.20.0
|
|
18
|
+
Requires-Dist: chromadb>=0.4.0
|
|
19
|
+
Requires-Dist: sentence-transformers>=2.0.0
|
|
20
|
+
Requires-Dist: pypdf>=3.0.0
|
|
21
|
+
Requires-Dist: python-docx>=1.0.0
|
|
22
|
+
Requires-Dist: python-pptx>=0.6.0
|
|
23
|
+
Requires-Dist: nbformat>=5.0.0
|
|
24
|
+
Provides-Extra: openai
|
|
25
|
+
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
26
|
+
Provides-Extra: gemini
|
|
27
|
+
Requires-Dist: google-generativeai>=0.3.0; extra == "gemini"
|
|
28
|
+
Provides-Extra: ollama
|
|
29
|
+
Requires-Dist: ollama>=0.3.0; extra == "ollama"
|
|
30
|
+
Provides-Extra: openrouter
|
|
31
|
+
Requires-Dist: openai>=1.0.0; extra == "openrouter"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-mock>=3.0.0; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
1
37
|
<p align="center">
|
|
2
38
|
<img src="https://raw.githubusercontent.com/getbridgekit/bridgekit/main/assets/logo.png" alt="Bridgekit" width="200"/>
|
|
3
39
|
</p>
|
|
@@ -55,6 +91,18 @@ pip install bridgekit[gemini]
|
|
|
55
91
|
export GOOGLE_API_KEY=your_key_here
|
|
56
92
|
```
|
|
57
93
|
|
|
94
|
+
**Ollama (local models, free, no API key):**
|
|
95
|
+
```bash
|
|
96
|
+
pip install bridgekit[ollama]
|
|
97
|
+
# Make sure Ollama is running locally: https://ollama.com
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**OpenRouter (free and paid models via one API):**
|
|
101
|
+
```bash
|
|
102
|
+
pip install bridgekit[openrouter]
|
|
103
|
+
export OPENROUTER_API_KEY=your_key_here
|
|
104
|
+
```
|
|
105
|
+
|
|
58
106
|
---
|
|
59
107
|
|
|
60
108
|
## Getting Started
|
|
@@ -496,7 +544,7 @@ high-confidence growth lever worth testing immediately.
|
|
|
496
544
|
|
|
497
545
|
## Multi-Provider Support
|
|
498
546
|
|
|
499
|
-
Bridgekit now supports multiple AI providers so you're not locked into one API. You can use Anthropic, OpenAI,
|
|
547
|
+
Bridgekit now supports multiple AI providers so you're not locked into one API. You can use Anthropic, OpenAI, Google Gemini, local models via Ollama, or any model on OpenRouter with any tool.
|
|
500
548
|
|
|
501
549
|
**Using different providers:**
|
|
502
550
|
|
|
@@ -509,21 +557,35 @@ print(evaluate("Your analysis here", provider="openai"))
|
|
|
509
557
|
# Use Google Gemini (default model: gemini-1.5-pro)
|
|
510
558
|
print(plan("Your question here", provider="gemini"))
|
|
511
559
|
|
|
560
|
+
# Use a local model via Ollama - free, private, no API key (default model: llama3.2)
|
|
561
|
+
print(evaluate("Your analysis here", provider="ollama"))
|
|
562
|
+
|
|
563
|
+
# Use OpenRouter to access a wide range of models, including free tiers
|
|
564
|
+
print(evaluate("Your analysis here", provider="openrouter", model="deepseek/deepseek-v4-flash-0731:free"))
|
|
565
|
+
|
|
512
566
|
# Use specific model
|
|
513
567
|
print(redteam("Your analysis here", model="gpt-4-turbo"))
|
|
514
568
|
print(ask("Your question here", source="reports/", model="claude-3-opus-20240229"))
|
|
515
569
|
```
|
|
516
570
|
|
|
571
|
+
**Ollama setup:** install and start [Ollama](https://ollama.com) locally, then pull a model (e.g. `ollama pull llama3.2`). By default Bridgekit connects to `http://localhost:11434`; set `OLLAMA_HOST` to point at a different host.
|
|
572
|
+
|
|
517
573
|
**Provider auto-detection:**
|
|
518
574
|
Bridgekit automatically detects the provider from model names:
|
|
519
575
|
- Models starting with "claude" → Anthropic
|
|
520
576
|
- Models starting with "gpt" → OpenAI
|
|
521
577
|
- Models starting with "gemini" → Google Gemini
|
|
578
|
+
- Model names containing "/" (e.g. `deepseek/deepseek-v4-flash-0731:free`) → OpenRouter
|
|
579
|
+
- Common local model families (e.g. "llama", "mistral", "mixtral", "gemma", "phi", "qwen") → Ollama
|
|
522
580
|
|
|
523
581
|
**Default models by provider:**
|
|
524
582
|
- Anthropic: `claude-opus-4-8`
|
|
525
583
|
- OpenAI: `gpt-4o`
|
|
526
584
|
- Gemini: `gemini-1.5-pro`
|
|
585
|
+
- Ollama: `llama3.2`
|
|
586
|
+
- OpenRouter: `deepseek/deepseek-v4-flash-0731:free`
|
|
587
|
+
|
|
588
|
+
> **Note:** OpenRouter's free-tier model lineup rotates over time. Check the current list at [openrouter.ai/models?max_price=0](https://openrouter.ai/models?max_price=0) if a `:free` model stops working, and pass `model=` explicitly with whichever slug is currently free.
|
|
527
589
|
|
|
528
590
|
All tools support the same `provider` and `model` parameters:
|
|
529
591
|
- `evaluate(text, provider=None, model=None, system_prompt=None)`
|
|
@@ -7,7 +7,7 @@ include = ["bridgekit*"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "bridgekit"
|
|
10
|
-
version = "0.3.
|
|
10
|
+
version = "0.3.11"
|
|
11
11
|
description = "AI tools that make you a better data scientist, not a redundant one."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = ">=3.9"
|
|
@@ -33,6 +33,8 @@ dependencies = [
|
|
|
33
33
|
[project.optional-dependencies]
|
|
34
34
|
openai = ["openai>=1.0.0"]
|
|
35
35
|
gemini = ["google-generativeai>=0.3.0"]
|
|
36
|
+
ollama = ["ollama>=0.3.0"]
|
|
37
|
+
openrouter = ["openai>=1.0.0"]
|
|
36
38
|
dev = [
|
|
37
39
|
"pytest>=7.0.0",
|
|
38
40
|
"pytest-mock>=3.0.0",
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import types
|
|
4
|
+
import pytest
|
|
5
|
+
from unittest.mock import MagicMock, patch
|
|
6
|
+
|
|
7
|
+
from bridgekit.config import parse_provider, require_api_key, Provider
|
|
8
|
+
from bridgekit.providers import OllamaClient, OpenRouterClient, create_client
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestParseProvider:
|
|
12
|
+
"""parse_provider() returns the correct Provider enum."""
|
|
13
|
+
|
|
14
|
+
def test_explicit_anthropic(self):
|
|
15
|
+
assert parse_provider(provider="anthropic") == Provider.ANTHROPIC
|
|
16
|
+
|
|
17
|
+
def test_explicit_openai(self):
|
|
18
|
+
assert parse_provider(provider="openai") == Provider.OPENAI
|
|
19
|
+
|
|
20
|
+
def test_explicit_gemini(self):
|
|
21
|
+
assert parse_provider(provider="gemini") == Provider.GEMINI
|
|
22
|
+
|
|
23
|
+
def test_infer_anthropic_from_model(self):
|
|
24
|
+
assert parse_provider(model="claude-opus-4-6") == Provider.ANTHROPIC
|
|
25
|
+
|
|
26
|
+
def test_infer_openai_from_model(self):
|
|
27
|
+
assert parse_provider(model="gpt-4o") == Provider.OPENAI
|
|
28
|
+
|
|
29
|
+
def test_infer_gemini_from_model(self):
|
|
30
|
+
assert parse_provider(model="gemini-1.5-pro") == Provider.GEMINI
|
|
31
|
+
|
|
32
|
+
def test_infer_openrouter_from_model(self):
|
|
33
|
+
assert parse_provider(model="deepseek/deepseek-v4-flash-0731:free") == Provider.OPENROUTER
|
|
34
|
+
|
|
35
|
+
def test_infer_ollama_from_model(self):
|
|
36
|
+
assert parse_provider(model="llama3.2") == Provider.OLLAMA
|
|
37
|
+
|
|
38
|
+
def test_infer_ollama_from_mistral_model(self):
|
|
39
|
+
assert parse_provider(model="mistral") == Provider.OLLAMA
|
|
40
|
+
|
|
41
|
+
def test_defaults_to_anthropic(self):
|
|
42
|
+
assert parse_provider() == Provider.ANTHROPIC
|
|
43
|
+
|
|
44
|
+
def test_raises_on_unsupported_provider(self):
|
|
45
|
+
with pytest.raises(ValueError, match="Unsupported provider"):
|
|
46
|
+
parse_provider(provider="cohere")
|
|
47
|
+
|
|
48
|
+
def test_provider_takes_precedence_over_model(self):
|
|
49
|
+
assert parse_provider(provider="openai", model="claude-opus-4-6") == Provider.OPENAI
|
|
50
|
+
|
|
51
|
+
def test_explicit_ollama(self):
|
|
52
|
+
assert parse_provider(provider="ollama") == Provider.OLLAMA
|
|
53
|
+
|
|
54
|
+
def test_explicit_openrouter(self):
|
|
55
|
+
assert parse_provider(provider="openrouter") == Provider.OPENROUTER
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TestRequireApiKey:
|
|
59
|
+
"""require_api_key() returns the correct key or raises EnvironmentError."""
|
|
60
|
+
|
|
61
|
+
def test_returns_anthropic_key(self):
|
|
62
|
+
with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}, clear=True):
|
|
63
|
+
assert require_api_key(Provider.ANTHROPIC) == "sk-ant-test"
|
|
64
|
+
|
|
65
|
+
def test_returns_openai_key(self):
|
|
66
|
+
with patch.dict(os.environ, {"OPENAI_API_KEY": "sk-openai-test"}, clear=True):
|
|
67
|
+
assert require_api_key(Provider.OPENAI) == "sk-openai-test"
|
|
68
|
+
|
|
69
|
+
def test_returns_gemini_key(self):
|
|
70
|
+
with patch.dict(os.environ, {"GOOGLE_API_KEY": "gemini-test"}, clear=True):
|
|
71
|
+
assert require_api_key(Provider.GEMINI) == "gemini-test"
|
|
72
|
+
|
|
73
|
+
def test_raises_when_anthropic_key_missing(self):
|
|
74
|
+
env = {k: v for k, v in os.environ.items() if k != "ANTHROPIC_API_KEY"}
|
|
75
|
+
with patch.dict(os.environ, env, clear=True):
|
|
76
|
+
with pytest.raises(EnvironmentError, match="ANTHROPIC_API_KEY"):
|
|
77
|
+
require_api_key(Provider.ANTHROPIC)
|
|
78
|
+
|
|
79
|
+
def test_raises_when_openai_key_missing(self):
|
|
80
|
+
env = {k: v for k, v in os.environ.items() if k != "OPENAI_API_KEY"}
|
|
81
|
+
with patch.dict(os.environ, env, clear=True):
|
|
82
|
+
with pytest.raises(EnvironmentError, match="OPENAI_API_KEY"):
|
|
83
|
+
require_api_key(Provider.OPENAI)
|
|
84
|
+
|
|
85
|
+
def test_raises_when_gemini_key_missing(self):
|
|
86
|
+
env = {k: v for k, v in os.environ.items() if k != "GOOGLE_API_KEY"}
|
|
87
|
+
with patch.dict(os.environ, env, clear=True):
|
|
88
|
+
with pytest.raises(EnvironmentError, match="GOOGLE_API_KEY"):
|
|
89
|
+
require_api_key(Provider.GEMINI)
|
|
90
|
+
|
|
91
|
+
def test_returns_openrouter_key(self):
|
|
92
|
+
with patch.dict(os.environ, {"OPENROUTER_API_KEY": "sk-or-test"}, clear=True):
|
|
93
|
+
assert require_api_key(Provider.OPENROUTER) == "sk-or-test"
|
|
94
|
+
|
|
95
|
+
def test_raises_when_openrouter_key_missing(self):
|
|
96
|
+
env = {k: v for k, v in os.environ.items() if k != "OPENROUTER_API_KEY"}
|
|
97
|
+
with patch.dict(os.environ, env, clear=True):
|
|
98
|
+
with pytest.raises(EnvironmentError, match="OPENROUTER_API_KEY"):
|
|
99
|
+
require_api_key(Provider.OPENROUTER)
|
|
100
|
+
|
|
101
|
+
def test_ollama_does_not_require_a_key(self):
|
|
102
|
+
env = {k: v for k, v in os.environ.items() if k != "OLLAMA_API_KEY"}
|
|
103
|
+
with patch.dict(os.environ, env, clear=True):
|
|
104
|
+
assert require_api_key(Provider.OLLAMA) is None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class TestOllamaClient:
|
|
108
|
+
"""OllamaClient talks to a local Ollama server and needs no API key."""
|
|
109
|
+
|
|
110
|
+
def _fake_ollama_module(self):
|
|
111
|
+
fake_module = types.ModuleType("ollama")
|
|
112
|
+
fake_module.Client = MagicMock()
|
|
113
|
+
return fake_module
|
|
114
|
+
|
|
115
|
+
def test_no_api_key_required(self):
|
|
116
|
+
fake_ollama = self._fake_ollama_module()
|
|
117
|
+
with patch.dict(sys.modules, {"ollama": fake_ollama}):
|
|
118
|
+
client = OllamaClient()
|
|
119
|
+
assert client.api_key is None
|
|
120
|
+
|
|
121
|
+
def test_uses_default_host_when_env_var_not_set(self):
|
|
122
|
+
fake_ollama = self._fake_ollama_module()
|
|
123
|
+
env = {k: v for k, v in os.environ.items() if k != "OLLAMA_HOST"}
|
|
124
|
+
with patch.dict(os.environ, env, clear=True), patch.dict(sys.modules, {"ollama": fake_ollama}):
|
|
125
|
+
OllamaClient()
|
|
126
|
+
fake_ollama.Client.assert_called_once_with(host="http://localhost:11434")
|
|
127
|
+
|
|
128
|
+
def test_uses_ollama_host_env_var_when_set(self):
|
|
129
|
+
fake_ollama = self._fake_ollama_module()
|
|
130
|
+
with patch.dict(os.environ, {"OLLAMA_HOST": "http://remote-box:11434"}), \
|
|
131
|
+
patch.dict(sys.modules, {"ollama": fake_ollama}):
|
|
132
|
+
OllamaClient()
|
|
133
|
+
fake_ollama.Client.assert_called_once_with(host="http://remote-box:11434")
|
|
134
|
+
|
|
135
|
+
def test_create_message_returns_response_content(self):
|
|
136
|
+
fake_ollama = self._fake_ollama_module()
|
|
137
|
+
mock_client_instance = MagicMock()
|
|
138
|
+
mock_client_instance.chat.return_value = {"message": {"content": "hello from llama"}}
|
|
139
|
+
fake_ollama.Client.return_value = mock_client_instance
|
|
140
|
+
|
|
141
|
+
with patch.dict(sys.modules, {"ollama": fake_ollama}):
|
|
142
|
+
client = OllamaClient()
|
|
143
|
+
result = client.create_message("system prompt", "user message", model="llama3.2", max_tokens=50)
|
|
144
|
+
|
|
145
|
+
assert result == "hello from llama"
|
|
146
|
+
mock_client_instance.chat.assert_called_once_with(
|
|
147
|
+
model="llama3.2",
|
|
148
|
+
messages=[
|
|
149
|
+
{"role": "system", "content": "system prompt"},
|
|
150
|
+
{"role": "user", "content": "user message"}
|
|
151
|
+
],
|
|
152
|
+
options={"num_predict": 50}
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _fake_openai_module():
|
|
157
|
+
"""A stand-in for the `openai` package so these tests don't require it
|
|
158
|
+
to actually be installed - it's an optional extra, and CI only installs
|
|
159
|
+
`.[dev]`."""
|
|
160
|
+
fake_module = types.ModuleType("openai")
|
|
161
|
+
fake_module.OpenAI = MagicMock()
|
|
162
|
+
return fake_module
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class TestOpenRouterClient:
|
|
166
|
+
"""OpenRouterClient reuses OpenAI's SDK pointed at OpenRouter's base URL."""
|
|
167
|
+
|
|
168
|
+
def test_uses_openrouter_base_url(self):
|
|
169
|
+
fake_openai = _fake_openai_module()
|
|
170
|
+
with patch.dict(sys.modules, {"openai": fake_openai}):
|
|
171
|
+
OpenRouterClient(api_key="or-test-key")
|
|
172
|
+
fake_openai.OpenAI.assert_called_once_with(api_key="or-test-key", base_url="https://openrouter.ai/api/v1")
|
|
173
|
+
|
|
174
|
+
def test_create_message_returns_response_content(self):
|
|
175
|
+
fake_openai = _fake_openai_module()
|
|
176
|
+
mock_response = MagicMock()
|
|
177
|
+
mock_response.choices[0].message.content = "response text"
|
|
178
|
+
mock_client_instance = MagicMock()
|
|
179
|
+
mock_client_instance.chat.completions.create.return_value = mock_response
|
|
180
|
+
fake_openai.OpenAI.return_value = mock_client_instance
|
|
181
|
+
|
|
182
|
+
with patch.dict(sys.modules, {"openai": fake_openai}):
|
|
183
|
+
client = OpenRouterClient(api_key="or-test-key")
|
|
184
|
+
result = client.create_message(
|
|
185
|
+
"system prompt", "user message", model="deepseek/deepseek-v4-flash-0731:free", max_tokens=100
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
assert result == "response text"
|
|
189
|
+
mock_client_instance.chat.completions.create.assert_called_once_with(
|
|
190
|
+
model="deepseek/deepseek-v4-flash-0731:free",
|
|
191
|
+
messages=[
|
|
192
|
+
{"role": "system", "content": "system prompt"},
|
|
193
|
+
{"role": "user", "content": "user message"}
|
|
194
|
+
],
|
|
195
|
+
max_tokens=100
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class TestCreateClientRouting:
|
|
200
|
+
"""create_client() routes to the right client class for the new providers."""
|
|
201
|
+
|
|
202
|
+
def test_routes_ollama(self):
|
|
203
|
+
fake_ollama = types.ModuleType("ollama")
|
|
204
|
+
fake_ollama.Client = MagicMock()
|
|
205
|
+
with patch.dict(sys.modules, {"ollama": fake_ollama}):
|
|
206
|
+
client = create_client(Provider.OLLAMA, model="llama3.2")
|
|
207
|
+
assert isinstance(client, OllamaClient)
|
|
208
|
+
|
|
209
|
+
def test_routes_openrouter(self):
|
|
210
|
+
fake_openai = _fake_openai_module()
|
|
211
|
+
with patch.dict(os.environ, {"OPENROUTER_API_KEY": "sk-or-test"}, clear=True), \
|
|
212
|
+
patch.dict(sys.modules, {"openai": fake_openai}):
|
|
213
|
+
client = create_client(Provider.OPENROUTER, model="deepseek/deepseek-v4-flash-0731:free")
|
|
214
|
+
assert isinstance(client, OpenRouterClient)
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import pytest
|
|
3
|
-
from unittest.mock import patch
|
|
4
|
-
|
|
5
|
-
from bridgekit.config import parse_provider, require_api_key, Provider
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TestParseProvider:
|
|
9
|
-
"""parse_provider() returns the correct Provider enum."""
|
|
10
|
-
|
|
11
|
-
def test_explicit_anthropic(self):
|
|
12
|
-
assert parse_provider(provider="anthropic") == Provider.ANTHROPIC
|
|
13
|
-
|
|
14
|
-
def test_explicit_openai(self):
|
|
15
|
-
assert parse_provider(provider="openai") == Provider.OPENAI
|
|
16
|
-
|
|
17
|
-
def test_explicit_gemini(self):
|
|
18
|
-
assert parse_provider(provider="gemini") == Provider.GEMINI
|
|
19
|
-
|
|
20
|
-
def test_infer_anthropic_from_model(self):
|
|
21
|
-
assert parse_provider(model="claude-opus-4-6") == Provider.ANTHROPIC
|
|
22
|
-
|
|
23
|
-
def test_infer_openai_from_model(self):
|
|
24
|
-
assert parse_provider(model="gpt-4o") == Provider.OPENAI
|
|
25
|
-
|
|
26
|
-
def test_infer_gemini_from_model(self):
|
|
27
|
-
assert parse_provider(model="gemini-1.5-pro") == Provider.GEMINI
|
|
28
|
-
|
|
29
|
-
def test_defaults_to_anthropic(self):
|
|
30
|
-
assert parse_provider() == Provider.ANTHROPIC
|
|
31
|
-
|
|
32
|
-
def test_raises_on_unsupported_provider(self):
|
|
33
|
-
with pytest.raises(ValueError, match="Unsupported provider"):
|
|
34
|
-
parse_provider(provider="cohere")
|
|
35
|
-
|
|
36
|
-
def test_provider_takes_precedence_over_model(self):
|
|
37
|
-
assert parse_provider(provider="openai", model="claude-opus-4-6") == Provider.OPENAI
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
class TestRequireApiKey:
|
|
41
|
-
"""require_api_key() returns the correct key or raises EnvironmentError."""
|
|
42
|
-
|
|
43
|
-
def test_returns_anthropic_key(self):
|
|
44
|
-
with patch.dict(os.environ, {"ANTHROPIC_API_KEY": "sk-ant-test"}, clear=True):
|
|
45
|
-
assert require_api_key(Provider.ANTHROPIC) == "sk-ant-test"
|
|
46
|
-
|
|
47
|
-
def test_returns_openai_key(self):
|
|
48
|
-
with patch.dict(os.environ, {"OPENAI_API_KEY": "sk-openai-test"}, clear=True):
|
|
49
|
-
assert require_api_key(Provider.OPENAI) == "sk-openai-test"
|
|
50
|
-
|
|
51
|
-
def test_returns_gemini_key(self):
|
|
52
|
-
with patch.dict(os.environ, {"GOOGLE_API_KEY": "gemini-test"}, clear=True):
|
|
53
|
-
assert require_api_key(Provider.GEMINI) == "gemini-test"
|
|
54
|
-
|
|
55
|
-
def test_raises_when_anthropic_key_missing(self):
|
|
56
|
-
env = {k: v for k, v in os.environ.items() if k != "ANTHROPIC_API_KEY"}
|
|
57
|
-
with patch.dict(os.environ, env, clear=True):
|
|
58
|
-
with pytest.raises(EnvironmentError, match="ANTHROPIC_API_KEY"):
|
|
59
|
-
require_api_key(Provider.ANTHROPIC)
|
|
60
|
-
|
|
61
|
-
def test_raises_when_openai_key_missing(self):
|
|
62
|
-
env = {k: v for k, v in os.environ.items() if k != "OPENAI_API_KEY"}
|
|
63
|
-
with patch.dict(os.environ, env, clear=True):
|
|
64
|
-
with pytest.raises(EnvironmentError, match="OPENAI_API_KEY"):
|
|
65
|
-
require_api_key(Provider.OPENAI)
|
|
66
|
-
|
|
67
|
-
def test_raises_when_gemini_key_missing(self):
|
|
68
|
-
env = {k: v for k, v in os.environ.items() if k != "GOOGLE_API_KEY"}
|
|
69
|
-
with patch.dict(os.environ, env, clear=True):
|
|
70
|
-
with pytest.raises(EnvironmentError, match="GOOGLE_API_KEY"):
|
|
71
|
-
require_api_key(Provider.GEMINI)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|