Skip to content

Commit a4d58d9

Browse files
authored
feat: Add openrouter backend (#55) (#56)
* Update router to determine api provider * Add backend for openrouter * Update webui app * Update README * Update requirements for new openai python package
1 parent 67fa666 commit a4d58d9

File tree

5 files changed

+123
-4
lines changed

5 files changed

+123
-4
lines changed

Diff for: README.md

+2
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ Set up your OpenAI (or Anthropic) API key:
9999
export OPENAI_API_KEY=<your API key>
100100
# or
101101
export ANTHROPIC_API_KEY=<your API key>
102+
# or
103+
export OPENROUTER_API_KEY=<your API key>
102104
```
103105

104106
To run AIDE:

Diff for: aide/backend/__init__.py

+25-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
1-
from . import backend_anthropic, backend_openai
1+
from . import backend_anthropic, backend_openai, backend_openrouter
22
from .utils import FunctionSpec, OutputType, PromptType, compile_prompt_to_md
3+
import re
4+
import logging
5+
6+
logger = logging.getLogger("aide")
7+
8+
9+
def determine_provider(model: str) -> str:
10+
if model.startswith("gpt-") or re.match(r"^o\d", model):
11+
return "openai"
12+
elif model.startswith("claude-"):
13+
return "anthropic"
14+
# all other models are handle by openrouter
15+
else:
16+
return "openrouter"
17+
18+
19+
provider_to_query_func = {
20+
"openai": backend_openai.query,
21+
"anthropic": backend_anthropic.query,
22+
"openrouter": backend_openrouter.query,
23+
}
324

425

526
def query(
@@ -35,13 +56,14 @@ def query(
3556

3657
# Handle models with beta limitations
3758
# ref: https://platform.openai.com/docs/guides/reasoning/beta-limitations
38-
if model.startswith("o1"):
59+
if re.match(r"^o\d", model):
3960
if system_message:
4061
user_message = system_message
4162
system_message = None
4263
model_kwargs["temperature"] = 1
4364

44-
query_func = backend_anthropic.query if "claude-" in model else backend_openai.query
65+
provider = determine_provider(model)
66+
query_func = provider_to_query_func[provider]
4567
output, req_time, in_tok_count, out_tok_count, info = query_func(
4668
system_message=compile_prompt_to_md(system_message) if system_message else None,
4769
user_message=compile_prompt_to_md(user_message) if user_message else None,

Diff for: aide/backend/backend_openrouter.py

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
"""Backend for OpenRouter API"""
2+
3+
import logging
4+
import os
5+
import time
6+
7+
from funcy import notnone, once, select_values
8+
import openai
9+
10+
from .utils import FunctionSpec, OutputType, backoff_create
11+
12+
logger = logging.getLogger("aide")
13+
14+
_client: openai.OpenAI = None # type: ignore
15+
16+
OPENAI_TIMEOUT_EXCEPTIONS = (
17+
openai.RateLimitError,
18+
openai.APIConnectionError,
19+
openai.APITimeoutError,
20+
openai.InternalServerError,
21+
)
22+
23+
24+
@once
25+
def _setup_openrouter_client():
26+
global _client
27+
_client = openai.OpenAI(
28+
base_url="https://openrouter.ai/api/v1",
29+
api_key=os.getenv("OPENROUTER_API_KEY"),
30+
max_retries=0,
31+
)
32+
33+
34+
def query(
35+
system_message: str | None,
36+
user_message: str | None,
37+
func_spec: FunctionSpec | None = None,
38+
**model_kwargs,
39+
) -> tuple[OutputType, float, int, int, dict]:
40+
_setup_openrouter_client()
41+
filtered_kwargs: dict = select_values(notnone, model_kwargs) # type: ignore
42+
43+
if func_spec is not None:
44+
raise NotImplementedError(
45+
"We are not supporting function calling in OpenRouter for now."
46+
)
47+
48+
# in case some backends dont support system roles, just convert everything to user
49+
messages = [
50+
{"role": "user", "content": message}
51+
for message in [system_message, user_message]
52+
if message
53+
]
54+
55+
t0 = time.time()
56+
completion = backoff_create(
57+
_client.chat.completions.create,
58+
OPENAI_TIMEOUT_EXCEPTIONS,
59+
messages=messages,
60+
extra_body={
61+
"provider": {
62+
"order": ["Fireworks"],
63+
"ignore": ["Together", "DeepInfra", "Hyperbolic"],
64+
},
65+
},
66+
**filtered_kwargs,
67+
)
68+
req_time = time.time() - t0
69+
70+
output = completion.choices[0].message.content
71+
72+
in_tokens = completion.usage.prompt_tokens
73+
out_tokens = completion.usage.completion_tokens
74+
75+
info = {
76+
"system_fingerprint": completion.system_fingerprint,
77+
"model": completion.model,
78+
"created": completion.created,
79+
}
80+
81+
return output, req_time, in_tokens, out_tokens, info

Diff for: aide/webui/app.py

+14
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ def load_env_variables():
5151
return {
5252
"openai_key": os.getenv("OPENAI_API_KEY", ""),
5353
"anthropic_key": os.getenv("ANTHROPIC_API_KEY", ""),
54+
"openrouter_key": os.getenv("OPENROUTER_API_KEY", ""),
5455
}
5556

5657
@staticmethod
@@ -127,9 +128,20 @@ def render_sidebar(self):
127128
type="password",
128129
label_visibility="collapsed",
129130
)
131+
st.markdown(
132+
"<p style='text-align: center;'>OpenRouter API Key</p>",
133+
unsafe_allow_html=True,
134+
)
135+
openrouter_key = st.text_input(
136+
"OpenRouter API Key",
137+
value=self.env_vars["openrouter_key"],
138+
type="password",
139+
label_visibility="collapsed",
140+
)
130141
if st.button("Save API Keys", use_container_width=True):
131142
st.session_state.openai_key = openai_key
132143
st.session_state.anthropic_key = anthropic_key
144+
st.session_state.openrouter_key = openrouter_key
133145
st.success("API keys saved!")
134146

135147
def render_input_section(self, results_col):
@@ -340,6 +352,8 @@ def set_api_keys():
340352
os.environ["OPENAI_API_KEY"] = st.session_state.openai_key
341353
if st.session_state.get("anthropic_key"):
342354
os.environ["ANTHROPIC_API_KEY"] = st.session_state.anthropic_key
355+
if st.session_state.get("openrouter_key"):
356+
os.environ["OPENROUTER_API_KEY"] = st.session_state.openrouter_key
343357

344358
def prepare_input_directory(self, files):
345359
"""

Diff for: requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ funcy==2.0
44
humanize==4.8.0
55
jsonschema==4.19.2
66
numpy==1.26.2
7-
openai>=1.3.5
7+
openai>=1.69.0
88
anthropic>=0.20.0
99
pandas==2.1.4
1010
pytest==7.4.3

0 commit comments

Comments
 (0)