bridgekit 0.3.9__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.9 → bridgekit-0.3.11}/PKG-INFO +103 -5
- {bridgekit-0.3.9 → bridgekit-0.3.11}/README.md +98 -4
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit/__init__.py +3 -2
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit/config.py +32 -3
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit/providers.py +40 -3
- bridgekit-0.3.11/bridgekit/summarize.py +72 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit.egg-info/PKG-INFO +103 -5
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit.egg-info/SOURCES.txt +3 -1
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit.egg-info/requires.txt +6 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/pyproject.toml +3 -1
- bridgekit-0.3.11/tests/test_providers.py +214 -0
- bridgekit-0.3.11/tests/test_summarize.py +212 -0
- bridgekit-0.3.9/tests/test_providers.py +0 -71
- {bridgekit-0.3.9 → bridgekit-0.3.11}/LICENSE +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit/cli.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit/compare.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit/planner.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit/redteam.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit/reviewer.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit/search.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit.egg-info/dependency_links.txt +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit.egg-info/entry_points.txt +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/bridgekit.egg-info/top_level.txt +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/setup.cfg +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/tests/test_cli.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/tests/test_compare.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/tests/test_config.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/tests/test_planner.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/tests/test_redteam.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/tests/test_reviewer.py +0 -0
- {bridgekit-0.3.9 → bridgekit-0.3.11}/tests/test_search.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
|
|
@@ -398,7 +414,7 @@ willing to commit to — and what's your confidence interval on that estimate?"
|
|
|
398
414
|
|
|
399
415
|
## Tool #5: Compare
|
|
400
416
|
|
|
401
|
-
Run the same tool through two providers and see both outputs side by side. Useful for evaluating which model works best for your use case — as a one-liner.
|
|
417
|
+
Run the same tool through two providers and see both outputs side by side. A synthesis summary at the top highlights where the models agreed, where they differed in severity, and which gave more actionable feedback — so you get the key insight without reading both outputs in full. Useful for evaluating which model works best for your use case — as a one-liner.
|
|
402
418
|
|
|
403
419
|
```python
|
|
404
420
|
from bridgekit import compare
|
|
@@ -437,6 +453,16 @@ print(compare(text, providers=["anthropic", "gemini"]))
|
|
|
437
453
|
BRIDGEKIT COMPARE: EVALUATE
|
|
438
454
|
─────────────────────────────────────────
|
|
439
455
|
|
|
456
|
+
SUMMARY
|
|
457
|
+
─────────────────────────────────────────
|
|
458
|
+
Both outputs rated Clarity as STRONG. They diverged on severity: Anthropic
|
|
459
|
+
rated Statistical Rigor as MISSING (harsher) while OpenAI called it NEEDS WORK.
|
|
460
|
+
Anthropic gave more specific feedback — naming the correlation-vs-causation
|
|
461
|
+
problem explicitly and suggesting concrete fixes. OpenAI's feedback stayed
|
|
462
|
+
more generic. Anthropic's bottom line targets the core analytical flaw;
|
|
463
|
+
OpenAI's restates the statistical point only.
|
|
464
|
+
|
|
465
|
+
|
|
440
466
|
ANTHROPIC claude-opus-4-8
|
|
441
467
|
─────────────────────────────────────────
|
|
442
468
|
BRIDGEKIT ANALYSIS REVIEW
|
|
@@ -458,13 +484,67 @@ BRIDGEKIT ANALYSIS REVIEW
|
|
|
458
484
|
...
|
|
459
485
|
```
|
|
460
486
|
|
|
461
|
-
Both providers are called in parallel, so the total wait time is the slower of the two — not the sum.
|
|
487
|
+
Both providers are called in parallel, so the total wait time is the slower of the two — not the sum. The synthesis summary is a third sequential call made after both outputs are ready.
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## Tool #6: Summarize
|
|
492
|
+
|
|
493
|
+
Turn a long analysis writeup, notebook, or report into a short executive summary — the gap between doing the analysis and communicating it to people who won't read the whole thing.
|
|
494
|
+
|
|
495
|
+
```python
|
|
496
|
+
from bridgekit import summarize
|
|
497
|
+
|
|
498
|
+
text = """
|
|
499
|
+
I analyzed 90 days of user behavior data to understand what drives subscription
|
|
500
|
+
upgrades. Users who engaged with the reporting feature within their first week
|
|
501
|
+
were 3x more likely to upgrade within 30 days. Sample size was 1,200 users
|
|
502
|
+
across two acquisition channels, with consistent results in both. I recommend
|
|
503
|
+
we prioritize onboarding users to reporting as a growth lever.
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
# Default — general business audience
|
|
507
|
+
print(summarize(text))
|
|
508
|
+
|
|
509
|
+
# Or specify an audience
|
|
510
|
+
print(summarize(text, audience="VP of Marketing"))
|
|
511
|
+
print(summarize(text, audience="board"))
|
|
512
|
+
|
|
513
|
+
# Override for longer summaries
|
|
514
|
+
print(summarize(text, max_tokens=2048))
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
**Output:**
|
|
518
|
+
```
|
|
519
|
+
BRIDGEKIT SUMMARY
|
|
520
|
+
─────────────────────────────────────────
|
|
521
|
+
AUDIENCE: VP of Marketing
|
|
522
|
+
|
|
523
|
+
KEY TAKEAWAY
|
|
524
|
+
Getting users into the reporting feature in their first week is our strongest
|
|
525
|
+
predictor of paid upgrades — users who engage with it are 3x more likely to
|
|
526
|
+
convert within 30 days.
|
|
527
|
+
|
|
528
|
+
WHAT WE FOUND
|
|
529
|
+
- Early reporting engagement (week 1) drives 3x higher upgrade rates within 30 days
|
|
530
|
+
- Finding holds across both acquisition channels, suggesting it's a genuine
|
|
531
|
+
behavior pattern, not channel-specific
|
|
532
|
+
- Analyzed 1,200 users over 90 days with sufficient scale to trust the result
|
|
533
|
+
|
|
534
|
+
SO WHAT
|
|
535
|
+
We should redesign onboarding to get users to reporting faster. This is a
|
|
536
|
+
high-confidence growth lever worth testing immediately.
|
|
537
|
+
|
|
538
|
+
─────────────────────────────────────────
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
`audience`, `provider`, `model`, `system_prompt`, and `max_tokens` are all optional — the more specific the audience, the more tailored the summary.
|
|
462
542
|
|
|
463
543
|
---
|
|
464
544
|
|
|
465
545
|
## Multi-Provider Support
|
|
466
546
|
|
|
467
|
-
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.
|
|
468
548
|
|
|
469
549
|
**Using different providers:**
|
|
470
550
|
|
|
@@ -477,27 +557,42 @@ print(evaluate("Your analysis here", provider="openai"))
|
|
|
477
557
|
# Use Google Gemini (default model: gemini-1.5-pro)
|
|
478
558
|
print(plan("Your question here", provider="gemini"))
|
|
479
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
|
+
|
|
480
566
|
# Use specific model
|
|
481
567
|
print(redteam("Your analysis here", model="gpt-4-turbo"))
|
|
482
568
|
print(ask("Your question here", source="reports/", model="claude-3-opus-20240229"))
|
|
483
569
|
```
|
|
484
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
|
+
|
|
485
573
|
**Provider auto-detection:**
|
|
486
574
|
Bridgekit automatically detects the provider from model names:
|
|
487
575
|
- Models starting with "claude" → Anthropic
|
|
488
576
|
- Models starting with "gpt" → OpenAI
|
|
489
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
|
|
490
580
|
|
|
491
581
|
**Default models by provider:**
|
|
492
582
|
- Anthropic: `claude-opus-4-8`
|
|
493
583
|
- OpenAI: `gpt-4o`
|
|
494
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.
|
|
495
589
|
|
|
496
590
|
All tools support the same `provider` and `model` parameters:
|
|
497
591
|
- `evaluate(text, provider=None, model=None, system_prompt=None)`
|
|
498
592
|
- `plan(question, provider=None, model=None, ..., system_prompt=None)`
|
|
499
593
|
- `ask(question, provider=None, model=None, ..., system_prompt=None)`
|
|
500
594
|
- `redteam(text, provider=None, model=None, ..., system_prompt=None)`
|
|
595
|
+
- `summarize(text, provider=None, model=None, ..., system_prompt=None)`
|
|
501
596
|
|
|
502
597
|
---
|
|
503
598
|
|
|
@@ -506,7 +601,7 @@ All tools support the same `provider` and `model` parameters:
|
|
|
506
601
|
Every tool accepts an optional `system_prompt` parameter to override the default persona. Use this to adapt the tone or focus to a specific domain without changing anything else.
|
|
507
602
|
|
|
508
603
|
```python
|
|
509
|
-
from bridgekit import evaluate, plan, ask, redteam
|
|
604
|
+
from bridgekit import evaluate, plan, ask, redteam, summarize
|
|
510
605
|
|
|
511
606
|
# Narrow the reviewer to a specific domain
|
|
512
607
|
print(evaluate("my analysis", system_prompt="You are a skeptical PhD statistician focused only on methodology"))
|
|
@@ -519,6 +614,9 @@ print(redteam("my analysis", system_prompt="You are a hostile regulator looking
|
|
|
519
614
|
|
|
520
615
|
# Change the answering style for ask
|
|
521
616
|
print(ask("my question", text="...", system_prompt="You are a financial analyst. Answer only in terms of revenue impact."))
|
|
617
|
+
|
|
618
|
+
# Replace the summarizer persona entirely
|
|
619
|
+
print(summarize("my analysis", system_prompt="You are a data journalist writing a one-paragraph news brief."))
|
|
522
620
|
```
|
|
523
621
|
|
|
524
622
|
When `system_prompt` is not provided, each tool uses its built-in default — existing behavior is unchanged.
|
|
@@ -55,6 +55,18 @@ pip install bridgekit[gemini]
|
|
|
55
55
|
export GOOGLE_API_KEY=your_key_here
|
|
56
56
|
```
|
|
57
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
|
+
|
|
58
70
|
---
|
|
59
71
|
|
|
60
72
|
## Getting Started
|
|
@@ -366,7 +378,7 @@ willing to commit to — and what's your confidence interval on that estimate?"
|
|
|
366
378
|
|
|
367
379
|
## Tool #5: Compare
|
|
368
380
|
|
|
369
|
-
Run the same tool through two providers and see both outputs side by side. Useful for evaluating which model works best for your use case — as a one-liner.
|
|
381
|
+
Run the same tool through two providers and see both outputs side by side. A synthesis summary at the top highlights where the models agreed, where they differed in severity, and which gave more actionable feedback — so you get the key insight without reading both outputs in full. Useful for evaluating which model works best for your use case — as a one-liner.
|
|
370
382
|
|
|
371
383
|
```python
|
|
372
384
|
from bridgekit import compare
|
|
@@ -405,6 +417,16 @@ print(compare(text, providers=["anthropic", "gemini"]))
|
|
|
405
417
|
BRIDGEKIT COMPARE: EVALUATE
|
|
406
418
|
─────────────────────────────────────────
|
|
407
419
|
|
|
420
|
+
SUMMARY
|
|
421
|
+
─────────────────────────────────────────
|
|
422
|
+
Both outputs rated Clarity as STRONG. They diverged on severity: Anthropic
|
|
423
|
+
rated Statistical Rigor as MISSING (harsher) while OpenAI called it NEEDS WORK.
|
|
424
|
+
Anthropic gave more specific feedback — naming the correlation-vs-causation
|
|
425
|
+
problem explicitly and suggesting concrete fixes. OpenAI's feedback stayed
|
|
426
|
+
more generic. Anthropic's bottom line targets the core analytical flaw;
|
|
427
|
+
OpenAI's restates the statistical point only.
|
|
428
|
+
|
|
429
|
+
|
|
408
430
|
ANTHROPIC claude-opus-4-8
|
|
409
431
|
─────────────────────────────────────────
|
|
410
432
|
BRIDGEKIT ANALYSIS REVIEW
|
|
@@ -426,13 +448,67 @@ BRIDGEKIT ANALYSIS REVIEW
|
|
|
426
448
|
...
|
|
427
449
|
```
|
|
428
450
|
|
|
429
|
-
Both providers are called in parallel, so the total wait time is the slower of the two — not the sum.
|
|
451
|
+
Both providers are called in parallel, so the total wait time is the slower of the two — not the sum. The synthesis summary is a third sequential call made after both outputs are ready.
|
|
452
|
+
|
|
453
|
+
---
|
|
454
|
+
|
|
455
|
+
## Tool #6: Summarize
|
|
456
|
+
|
|
457
|
+
Turn a long analysis writeup, notebook, or report into a short executive summary — the gap between doing the analysis and communicating it to people who won't read the whole thing.
|
|
458
|
+
|
|
459
|
+
```python
|
|
460
|
+
from bridgekit import summarize
|
|
461
|
+
|
|
462
|
+
text = """
|
|
463
|
+
I analyzed 90 days of user behavior data to understand what drives subscription
|
|
464
|
+
upgrades. Users who engaged with the reporting feature within their first week
|
|
465
|
+
were 3x more likely to upgrade within 30 days. Sample size was 1,200 users
|
|
466
|
+
across two acquisition channels, with consistent results in both. I recommend
|
|
467
|
+
we prioritize onboarding users to reporting as a growth lever.
|
|
468
|
+
"""
|
|
469
|
+
|
|
470
|
+
# Default — general business audience
|
|
471
|
+
print(summarize(text))
|
|
472
|
+
|
|
473
|
+
# Or specify an audience
|
|
474
|
+
print(summarize(text, audience="VP of Marketing"))
|
|
475
|
+
print(summarize(text, audience="board"))
|
|
476
|
+
|
|
477
|
+
# Override for longer summaries
|
|
478
|
+
print(summarize(text, max_tokens=2048))
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
**Output:**
|
|
482
|
+
```
|
|
483
|
+
BRIDGEKIT SUMMARY
|
|
484
|
+
─────────────────────────────────────────
|
|
485
|
+
AUDIENCE: VP of Marketing
|
|
486
|
+
|
|
487
|
+
KEY TAKEAWAY
|
|
488
|
+
Getting users into the reporting feature in their first week is our strongest
|
|
489
|
+
predictor of paid upgrades — users who engage with it are 3x more likely to
|
|
490
|
+
convert within 30 days.
|
|
491
|
+
|
|
492
|
+
WHAT WE FOUND
|
|
493
|
+
- Early reporting engagement (week 1) drives 3x higher upgrade rates within 30 days
|
|
494
|
+
- Finding holds across both acquisition channels, suggesting it's a genuine
|
|
495
|
+
behavior pattern, not channel-specific
|
|
496
|
+
- Analyzed 1,200 users over 90 days with sufficient scale to trust the result
|
|
497
|
+
|
|
498
|
+
SO WHAT
|
|
499
|
+
We should redesign onboarding to get users to reporting faster. This is a
|
|
500
|
+
high-confidence growth lever worth testing immediately.
|
|
501
|
+
|
|
502
|
+
─────────────────────────────────────────
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
`audience`, `provider`, `model`, `system_prompt`, and `max_tokens` are all optional — the more specific the audience, the more tailored the summary.
|
|
430
506
|
|
|
431
507
|
---
|
|
432
508
|
|
|
433
509
|
## Multi-Provider Support
|
|
434
510
|
|
|
435
|
-
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.
|
|
436
512
|
|
|
437
513
|
**Using different providers:**
|
|
438
514
|
|
|
@@ -445,27 +521,42 @@ print(evaluate("Your analysis here", provider="openai"))
|
|
|
445
521
|
# Use Google Gemini (default model: gemini-1.5-pro)
|
|
446
522
|
print(plan("Your question here", provider="gemini"))
|
|
447
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
|
+
|
|
448
530
|
# Use specific model
|
|
449
531
|
print(redteam("Your analysis here", model="gpt-4-turbo"))
|
|
450
532
|
print(ask("Your question here", source="reports/", model="claude-3-opus-20240229"))
|
|
451
533
|
```
|
|
452
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
|
+
|
|
453
537
|
**Provider auto-detection:**
|
|
454
538
|
Bridgekit automatically detects the provider from model names:
|
|
455
539
|
- Models starting with "claude" → Anthropic
|
|
456
540
|
- Models starting with "gpt" → OpenAI
|
|
457
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
|
|
458
544
|
|
|
459
545
|
**Default models by provider:**
|
|
460
546
|
- Anthropic: `claude-opus-4-8`
|
|
461
547
|
- OpenAI: `gpt-4o`
|
|
462
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.
|
|
463
553
|
|
|
464
554
|
All tools support the same `provider` and `model` parameters:
|
|
465
555
|
- `evaluate(text, provider=None, model=None, system_prompt=None)`
|
|
466
556
|
- `plan(question, provider=None, model=None, ..., system_prompt=None)`
|
|
467
557
|
- `ask(question, provider=None, model=None, ..., system_prompt=None)`
|
|
468
558
|
- `redteam(text, provider=None, model=None, ..., system_prompt=None)`
|
|
559
|
+
- `summarize(text, provider=None, model=None, ..., system_prompt=None)`
|
|
469
560
|
|
|
470
561
|
---
|
|
471
562
|
|
|
@@ -474,7 +565,7 @@ All tools support the same `provider` and `model` parameters:
|
|
|
474
565
|
Every tool accepts an optional `system_prompt` parameter to override the default persona. Use this to adapt the tone or focus to a specific domain without changing anything else.
|
|
475
566
|
|
|
476
567
|
```python
|
|
477
|
-
from bridgekit import evaluate, plan, ask, redteam
|
|
568
|
+
from bridgekit import evaluate, plan, ask, redteam, summarize
|
|
478
569
|
|
|
479
570
|
# Narrow the reviewer to a specific domain
|
|
480
571
|
print(evaluate("my analysis", system_prompt="You are a skeptical PhD statistician focused only on methodology"))
|
|
@@ -487,6 +578,9 @@ print(redteam("my analysis", system_prompt="You are a hostile regulator looking
|
|
|
487
578
|
|
|
488
579
|
# Change the answering style for ask
|
|
489
580
|
print(ask("my question", text="...", system_prompt="You are a financial analyst. Answer only in terms of revenue impact."))
|
|
581
|
+
|
|
582
|
+
# Replace the summarizer persona entirely
|
|
583
|
+
print(summarize("my analysis", system_prompt="You are a data journalist writing a one-paragraph news brief."))
|
|
490
584
|
```
|
|
491
585
|
|
|
492
586
|
When `system_prompt` is not provided, each tool uses its built-in default — existing behavior is unchanged.
|
|
@@ -3,6 +3,7 @@ from .search import ask
|
|
|
3
3
|
from .planner import plan
|
|
4
4
|
from .redteam import redteam
|
|
5
5
|
from .compare import compare
|
|
6
|
+
from .summarize import summarize
|
|
6
7
|
|
|
7
|
-
__version__ = "0.3.
|
|
8
|
-
__all__ = ["evaluate", "ask", "plan", "redteam", "compare"]
|
|
8
|
+
__version__ = "0.3.10"
|
|
9
|
+
__all__ = ["evaluate", "ask", "plan", "redteam", "compare", "summarize"]
|
|
@@ -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
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from .config import DEFAULT_MODEL, parse_provider, get_default_model
|
|
2
|
+
from .providers import create_message
|
|
3
|
+
|
|
4
|
+
DEFAULT_AUDIENCE = "a general business audience with no technical or data science background"
|
|
5
|
+
|
|
6
|
+
SYSTEM_PROMPT_TEMPLATE = """You are a senior data scientist preparing an executive summary of a technical analysis for {audience}.
|
|
7
|
+
|
|
8
|
+
Your job is to translate technical detail into what matters for decision-making. Cut jargon, cut methodology detail unless it's essential to trust the conclusion, and lead with the takeaway. Write for someone who will skim this in 30 seconds.
|
|
9
|
+
|
|
10
|
+
Format your response exactly like this:
|
|
11
|
+
|
|
12
|
+
BRIDGEKIT SUMMARY
|
|
13
|
+
─────────────────────────────────────────
|
|
14
|
+
AUDIENCE: {audience_label}
|
|
15
|
+
|
|
16
|
+
KEY TAKEAWAY
|
|
17
|
+
[1-2 sentences: the single most important finding or recommendation]
|
|
18
|
+
|
|
19
|
+
WHAT WE FOUND
|
|
20
|
+
[3-5 bullet points of the supporting findings, in plain language]
|
|
21
|
+
|
|
22
|
+
SO WHAT
|
|
23
|
+
[1-2 sentences on the business implication or recommended next step]
|
|
24
|
+
|
|
25
|
+
─────────────────────────────────────────
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def summarize(text: str, audience: str = None, provider: str = None, model: str = None, system_prompt: str = None, max_tokens: int = 1024) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Turn a long analysis, notebook, or report into a short executive summary.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
text: The long-form analysis writeup, notebook output, or report as a plain string.
|
|
35
|
+
audience: Optional. Who the summary is for (e.g. "VP of Marketing", "board",
|
|
36
|
+
"engineering team"). Defaults to a general business audience.
|
|
37
|
+
provider: Optional. The AI provider to use ("anthropic", "openai", "gemini").
|
|
38
|
+
If not specified, defaults to "anthropic" or infers from model.
|
|
39
|
+
model: Optional. The specific model to use. If not specified, uses the provider's default.
|
|
40
|
+
system_prompt: Optional. A custom system prompt to fully override the default summarizer persona.
|
|
41
|
+
When provided, the audience parameter is ignored.
|
|
42
|
+
max_tokens: Optional. Maximum tokens in the response. Defaults to 1024.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
A short executive summary covering the key takeaway, supporting findings,
|
|
46
|
+
and the business implication.
|
|
47
|
+
"""
|
|
48
|
+
if not text or not text.strip():
|
|
49
|
+
raise ValueError("Text cannot be empty.")
|
|
50
|
+
|
|
51
|
+
# Parse provider and determine model
|
|
52
|
+
provider_enum = parse_provider(provider, model)
|
|
53
|
+
if model is None:
|
|
54
|
+
model = get_default_model(provider_enum)
|
|
55
|
+
|
|
56
|
+
if system_prompt is None:
|
|
57
|
+
audience_label = audience if audience else "General Business Audience"
|
|
58
|
+
audience_desc = audience if audience else DEFAULT_AUDIENCE
|
|
59
|
+
system_prompt = SYSTEM_PROMPT_TEMPLATE.format(
|
|
60
|
+
audience=audience_desc,
|
|
61
|
+
audience_label=audience_label
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
user_message = f"Summarize this analysis:\n\n{text}"
|
|
65
|
+
|
|
66
|
+
return create_message(
|
|
67
|
+
provider=provider_enum,
|
|
68
|
+
system_prompt=system_prompt,
|
|
69
|
+
user_message=user_message,
|
|
70
|
+
model=model,
|
|
71
|
+
max_tokens=max_tokens
|
|
72
|
+
)
|