Skip to content

Commit 1328cc7

Browse files
committed
Add local LLM providers (Ollama, vLLM) with zero-cost tracking
1 parent 88c514e commit 1328cc7

4 files changed

Lines changed: 53 additions & 7 deletions

File tree

docs/acceptance.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -519,8 +519,4 @@ track p50/p95 latency across eval suites and detect regressions.*
519519

520520
## Next User Stories
521521

522-
The following stories remain unimplemented.
523-
524-
| ID | Story | Priority |
525-
|---|---|---|
526-
| AC-NEW-20 | Local model (Ollama/vLLM) with full governance | P2 |
522+
All currently tracked stories are implemented.

teaagent/llm/_config.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,22 @@
4343
base_url='https://openrouter.ai/api/v1',
4444
base_url_env='OPENROUTER_BASE_URL',
4545
),
46+
'ollama': ProviderConfig(
47+
name='ollama',
48+
api_key_env='OLLAMA_API_KEY',
49+
default_model='llama3.2',
50+
base_url='http://localhost:11434/v1',
51+
api_key='ollama',
52+
base_url_env='OLLAMA_BASE_URL',
53+
),
54+
'vllm': ProviderConfig(
55+
name='vllm',
56+
api_key_env='VLLM_API_KEY',
57+
default_model='meta-llama/Llama-3.1-8B-Instruct',
58+
base_url='http://localhost:8000/v1',
59+
api_key='vllm',
60+
base_url_env='VLLM_BASE_URL',
61+
),
4662
'opencodezen-go': ProviderConfig(
4763
name='opencodezen-go',
4864
api_key_env='OPENCODEZEN_API_KEY',
@@ -100,15 +116,19 @@ def check_llm_configuration(provider: str) -> tuple[bool, str]:
100116
'gpt': 0.00015,
101117
'gemini': 0.000075,
102118
'openrouter': 0.0005,
119+
'ollama': 0.0,
103120
'opencodezen-go': 0.0005,
121+
'vllm': 0.0,
104122
}
105123

106124
PROVIDER_COST_PER_1K_OUTPUT: dict[str, float] = {
107125
'claude': 0.015,
108126
'gpt': 0.0006,
109127
'gemini': 0.0003,
110128
'openrouter': 0.002,
129+
'ollama': 0.0,
111130
'opencodezen-go': 0.002,
131+
'vllm': 0.0,
112132
}
113133

114134

teaagent/tui/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def default_adapter_factory(provider: str, model: Optional[str]) -> LLMAdapter:
3232
HELP_TEXT = """Commands:
3333
help Show this help.
3434
doctor Check GraphQLite runtime.
35-
provider <name> Set model provider: claude, gpt, gemini, openrouter, opencodezen-go.
35+
provider <name> Set model provider: claude, gpt, gemini, openrouter, ollama, vllm, opencodezen-go.
3636
model <name|default> Set or clear model override.
3737
route-model <on|off> Enable or disable task-based model routing.
3838
route <task> Preview model route for a task.

tests/test_llm.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,37 @@ class LLMAdapterTests(unittest.TestCase):
3636
def test_available_providers_include_requested_adapters(self) -> None:
3737
self.assertEqual(
3838
available_providers(),
39-
['claude', 'gemini', 'gpt', 'opencodezen-go', 'openrouter'],
39+
[
40+
'claude',
41+
'gemini',
42+
'gpt',
43+
'ollama',
44+
'opencodezen-go',
45+
'openrouter',
46+
'vllm',
47+
],
48+
)
49+
50+
def test_ollama_is_openai_compatible_without_api_key(self) -> None:
51+
transport = FakeTransport({'choices': [{'message': {'content': 'ok'}}]})
52+
with patch.dict(os.environ, {}, clear=True):
53+
adapter = create_llm_adapter('ollama', transport=transport)
54+
response = adapter.complete(LLMRequest(messages=[LLMMessage('user', 'hi')]))
55+
56+
self.assertEqual(response.content, 'ok')
57+
self.assertEqual(
58+
transport.calls[0]['url'], 'http://localhost:11434/v1/chat/completions'
59+
)
60+
61+
def test_vllm_is_openai_compatible_without_api_key(self) -> None:
62+
transport = FakeTransport({'choices': [{'message': {'content': 'ok'}}]})
63+
with patch.dict(os.environ, {}, clear=True):
64+
adapter = create_llm_adapter('vllm', transport=transport)
65+
response = adapter.complete(LLMRequest(messages=[LLMMessage('user', 'hi')]))
66+
67+
self.assertEqual(response.content, 'ok')
68+
self.assertEqual(
69+
transport.calls[0]['url'], 'http://localhost:8000/v1/chat/completions'
4070
)
4171

4272
def test_gpt_adapter_uses_openai_chat_completions_shape(self) -> None:

0 commit comments

Comments
 (0)