deepagent-bot commited on
Commit
09840af
Β·
1 Parent(s): ff25935

ci: sync dashboard from 14f3545

Browse files

Source: 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},