Spaces:
Runtime error
Runtime error
deepagent-bot commited on
Commit Β·
09840af
1
Parent(s): ff25935
ci: sync dashboard from 14f3545
Browse filesSource: github.com/CouLiBaLy-B/gh-deepagents@14f3545190f0c5123a074d14a34077067ec20760
src/gh_deepagent/dashboard/pages/8_βοΈ_LLM_Settings.py
CHANGED
|
@@ -87,6 +87,25 @@ PROVIDERS = {
|
|
| 87 |
"spec_prefix": "groq:",
|
| 88 |
"doc": "https://console.groq.com/keys",
|
| 89 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
"ollama": {
|
| 91 |
"label": "Ollama (local, no API key)",
|
| 92 |
"env_key": "", # no key
|
|
|
|
| 87 |
"spec_prefix": "groq:",
|
| 88 |
"doc": "https://console.groq.com/keys",
|
| 89 |
},
|
| 90 |
+
"openrouter": {
|
| 91 |
+
"label": "OpenRouter (unified β Anthropic, OpenAI, Llama, Mistral, β¦)",
|
| 92 |
+
"env_key": "OPENROUTER_API_KEY",
|
| 93 |
+
"models": [
|
| 94 |
+
"anthropic/claude-sonnet-4-5",
|
| 95 |
+
"anthropic/claude-haiku-4",
|
| 96 |
+
"openai/gpt-4o-mini",
|
| 97 |
+
"openai/gpt-4o",
|
| 98 |
+
"openai/gpt-4.1-mini",
|
| 99 |
+
"google/gemini-2.5-flash",
|
| 100 |
+
"google/gemini-2.5-pro",
|
| 101 |
+
"meta-llama/llama-3.3-70b-instruct",
|
| 102 |
+
"mistralai/mistral-large-latest",
|
| 103 |
+
"qwen/qwen-2.5-coder-32b-instruct",
|
| 104 |
+
"deepseek/deepseek-chat",
|
| 105 |
+
],
|
| 106 |
+
"spec_prefix": "openrouter:",
|
| 107 |
+
"doc": "https://openrouter.ai/keys",
|
| 108 |
+
},
|
| 109 |
"ollama": {
|
| 110 |
"label": "Ollama (local, no API key)",
|
| 111 |
"env_key": "", # no key
|
src/gh_deepagent/models.py
CHANGED
|
@@ -2,9 +2,19 @@
|
|
| 2 |
|
| 3 |
Every model returned is wired with the ``CostCallback`` so token/cost metrics
|
| 4 |
flow through the observability stack automatically.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
"""
|
| 6 |
from __future__ import annotations
|
| 7 |
|
|
|
|
| 8 |
from typing import Any
|
| 9 |
|
| 10 |
from langchain.chat_models import init_chat_model
|
|
@@ -12,6 +22,9 @@ from langchain.chat_models import init_chat_model
|
|
| 12 |
from .config import get_settings
|
| 13 |
|
| 14 |
|
|
|
|
|
|
|
|
|
|
| 15 |
def _attach_callbacks(model):
|
| 16 |
"""Attach the cost callback to a chat model. Idempotent."""
|
| 17 |
try:
|
|
@@ -30,6 +43,38 @@ def _attach_callbacks(model):
|
|
| 30 |
return model.with_config({"callbacks": existing})
|
| 31 |
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
def build_model(model_spec: str | None = None, **overrides: Any):
|
| 34 |
"""Return a LangChain chat model with observability callbacks attached."""
|
| 35 |
settings = get_settings()
|
|
@@ -49,4 +94,9 @@ def build_model(model_spec: str | None = None, **overrides: Any):
|
|
| 49 |
params.update(overrides)
|
| 50 |
return _attach_callbacks(ChatOllama(**params))
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
return _attach_callbacks(init_chat_model(spec, temperature=0.0, **overrides))
|
|
|
|
| 2 |
|
| 3 |
Every model returned is wired with the ``CostCallback`` so token/cost metrics
|
| 4 |
flow through the observability stack automatically.
|
| 5 |
+
|
| 6 |
+
Supported model specs:
|
| 7 |
+
ollama:<model> β ChatOllama, OLLAMA_BASE_URL
|
| 8 |
+
openrouter:<model> β ChatOpenAI with base_url=openrouter.ai
|
| 9 |
+
anthropic:<model> β langchain init_chat_model
|
| 10 |
+
openai:<model> β langchain init_chat_model
|
| 11 |
+
google_genai:<model> β langchain init_chat_model
|
| 12 |
+
groq:<model> β langchain init_chat_model
|
| 13 |
+
<anything else with ":"> β forwarded to init_chat_model verbatim
|
| 14 |
"""
|
| 15 |
from __future__ import annotations
|
| 16 |
|
| 17 |
+
import os
|
| 18 |
from typing import Any
|
| 19 |
|
| 20 |
from langchain.chat_models import init_chat_model
|
|
|
|
| 22 |
from .config import get_settings
|
| 23 |
|
| 24 |
|
| 25 |
+
OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1"
|
| 26 |
+
|
| 27 |
+
|
| 28 |
def _attach_callbacks(model):
|
| 29 |
"""Attach the cost callback to a chat model. Idempotent."""
|
| 30 |
try:
|
|
|
|
| 43 |
return model.with_config({"callbacks": existing})
|
| 44 |
|
| 45 |
|
| 46 |
+
def _build_openrouter(model_name: str, **overrides: Any):
|
| 47 |
+
"""OpenRouter speaks the OpenAI HTTP API. Use ChatOpenAI with a custom base_url.
|
| 48 |
+
|
| 49 |
+
Recommended HTTP headers (`HTTP-Referer` + `X-Title`) help OpenRouter rank
|
| 50 |
+
your traffic; we set them from env vars when present.
|
| 51 |
+
"""
|
| 52 |
+
from langchain_openai import ChatOpenAI
|
| 53 |
+
|
| 54 |
+
api_key = os.getenv("OPENROUTER_API_KEY", "")
|
| 55 |
+
if not api_key:
|
| 56 |
+
raise RuntimeError(
|
| 57 |
+
"OPENROUTER_API_KEY is not set. Configure it via the dashboard's "
|
| 58 |
+
"βοΈ LLM Settings page or in the Space's Settings β Secrets."
|
| 59 |
+
)
|
| 60 |
+
extra_headers = {}
|
| 61 |
+
if site := os.getenv("OPENROUTER_HTTP_REFERER"):
|
| 62 |
+
extra_headers["HTTP-Referer"] = site
|
| 63 |
+
if title := os.getenv("OPENROUTER_X_TITLE", "gh-deepagent"):
|
| 64 |
+
extra_headers["X-Title"] = title
|
| 65 |
+
|
| 66 |
+
params: dict[str, Any] = dict(
|
| 67 |
+
model=model_name,
|
| 68 |
+
base_url=OPENROUTER_BASE_URL,
|
| 69 |
+
api_key=api_key,
|
| 70 |
+
temperature=0.0,
|
| 71 |
+
)
|
| 72 |
+
if extra_headers:
|
| 73 |
+
params["default_headers"] = extra_headers
|
| 74 |
+
params.update(overrides)
|
| 75 |
+
return ChatOpenAI(**params)
|
| 76 |
+
|
| 77 |
+
|
| 78 |
def build_model(model_spec: str | None = None, **overrides: Any):
|
| 79 |
"""Return a LangChain chat model with observability callbacks attached."""
|
| 80 |
settings = get_settings()
|
|
|
|
| 94 |
params.update(overrides)
|
| 95 |
return _attach_callbacks(ChatOllama(**params))
|
| 96 |
|
| 97 |
+
if spec.startswith("openrouter:"):
|
| 98 |
+
_, model_name = spec.split("openrouter:", 1)
|
| 99 |
+
return _attach_callbacks(_build_openrouter(model_name, **overrides))
|
| 100 |
+
|
| 101 |
+
# Everything else β LangChain's init_chat_model (handles anthropic, openai, β¦).
|
| 102 |
return _attach_callbacks(init_chat_model(spec, temperature=0.0, **overrides))
|
src/gh_deepagent/observability/cost.py
CHANGED
|
@@ -47,6 +47,19 @@ PRICE_CATALOG: dict[str, dict[str, float]] = {
|
|
| 47 |
# Google
|
| 48 |
"google_genai:gemini-2.5-pro": {"input": 1.25, "output": 10.00},
|
| 49 |
"google_genai:gemini-2.5-flash": {"input": 0.075, "output": 0.30},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
# Local β counted, not billed
|
| 51 |
"ollama:*": {"input": 0.0, "output": 0.0},
|
| 52 |
"vllm:*": {"input": 0.0, "output": 0.0},
|
|
|
|
| 47 |
# Google
|
| 48 |
"google_genai:gemini-2.5-pro": {"input": 1.25, "output": 10.00},
|
| 49 |
"google_genai:gemini-2.5-flash": {"input": 0.075, "output": 0.30},
|
| 50 |
+
# OpenRouter β they expose hundreds of models; we ship the most common ones
|
| 51 |
+
# at upstream prices. Anything else falls through to the openrouter:* wildcard
|
| 52 |
+
# at $0 (tokens still counted; check openrouter.ai/usage for the real bill).
|
| 53 |
+
"openrouter:anthropic/claude-sonnet-4-5": {"input": 3.00, "output": 15.00},
|
| 54 |
+
"openrouter:anthropic/claude-haiku-4": {"input": 0.80, "output": 4.00},
|
| 55 |
+
"openrouter:openai/gpt-4o": {"input": 2.50, "output": 10.00},
|
| 56 |
+
"openrouter:openai/gpt-4o-mini": {"input": 0.15, "output": 0.60},
|
| 57 |
+
"openrouter:openai/gpt-4.1-mini": {"input": 0.40, "output": 1.60},
|
| 58 |
+
"openrouter:google/gemini-2.5-flash": {"input": 0.075, "output": 0.30},
|
| 59 |
+
"openrouter:google/gemini-2.5-pro": {"input": 1.25, "output": 10.00},
|
| 60 |
+
"openrouter:meta-llama/llama-3.3-70b-instruct": {"input": 0.59, "output": 0.79},
|
| 61 |
+
"openrouter:mistralai/mistral-large-latest": {"input": 2.00, "output": 6.00},
|
| 62 |
+
"openrouter:*": {"input": 0.0, "output": 0.0},
|
| 63 |
# Local β counted, not billed
|
| 64 |
"ollama:*": {"input": 0.0, "output": 0.0},
|
| 65 |
"vllm:*": {"input": 0.0, "output": 0.0},
|