openrouter-provider 1.0.1__py3-none-any.whl → 1.0.10__py3-none-any.whl
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.
- openrouter/__init__.py +5 -0
- llms.py → openrouter/llms.py +2 -3
- message.py → openrouter/message.py +5 -2
- openrouter.py → openrouter/openrouter.py +77 -41
- openrouter_provider.py → openrouter/openrouter_provider.py +56 -9
- {openrouter_provider-1.0.1.dist-info → openrouter_provider-1.0.10.dist-info}/METADATA +1 -1
- openrouter_provider-1.0.10.dist-info/RECORD +10 -0
- openrouter_provider-1.0.10.dist-info/top_level.txt +1 -0
- __init__.py +0 -0
- openrouter_provider-1.0.1.dist-info/RECORD +0 -10
- openrouter_provider-1.0.1.dist-info/top_level.txt +0 -6
- /tool.py → /openrouter/tool.py +0 -0
- {openrouter_provider-1.0.1.dist-info → openrouter_provider-1.0.10.dist-info}/WHEEL +0 -0
openrouter/__init__.py
ADDED
llms.py → openrouter/llms.py
RENAMED
|
@@ -34,9 +34,8 @@ claude_4_1_opus = LLMModel(name='anthropic/claude-opus-4.1', input_cost=15, outp
|
|
|
34
34
|
# Google
|
|
35
35
|
gemini_2_0_flash = LLMModel(name='google/gemini-2.0-flash-001', input_cost=0.1, output_cost=0.4)
|
|
36
36
|
gemini_2_5_flash_lite = LLMModel(name='google/gemini-2.5-flash-lite', input_cost=0.1, output_cost=0.4)
|
|
37
|
-
gemini_2_5_flash = LLMModel(name='google/gemini-2.5-flash
|
|
38
|
-
|
|
39
|
-
gemini_2_5_pro = LLMModel(name='google/gemini-2.5-pro-preview-03-25', input_cost=1.25, output_cost=10)
|
|
37
|
+
gemini_2_5_flash = LLMModel(name='google/gemini-2.5-flash', input_cost=0.3, output_cost=2.5)
|
|
38
|
+
gemini_2_5_pro = LLMModel(name='google/gemini-2.5-pro', input_cost=1.25, output_cost=10)
|
|
40
39
|
|
|
41
40
|
# Deepseek
|
|
42
41
|
deepseek_v3_free = LLMModel(name='deepseek/deepseek-chat-v3-0324:free', input_cost=0, output_cost=0)
|
|
@@ -3,12 +3,13 @@ from dataclasses import dataclass
|
|
|
3
3
|
from enum import Enum
|
|
4
4
|
from io import BytesIO
|
|
5
5
|
import base64
|
|
6
|
+
import uuid
|
|
6
7
|
from typing import Optional, Any
|
|
7
8
|
|
|
8
9
|
from PIL import Image
|
|
9
10
|
from openai.types.chat import ChatCompletion
|
|
10
11
|
|
|
11
|
-
from .llms import LLMModel
|
|
12
|
+
from openrouter.llms import LLMModel
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class Role(Enum):
|
|
@@ -34,8 +35,10 @@ class Message:
|
|
|
34
35
|
images: Optional[list[Image.Image]] = None,
|
|
35
36
|
role: Role = Role.user,
|
|
36
37
|
answered_by: Optional[LLMModel] = None,
|
|
37
|
-
raw_response: Optional[ChatCompletion] = None
|
|
38
|
+
raw_response: Optional[ChatCompletion] = None,
|
|
39
|
+
id: Optional[str] = None
|
|
38
40
|
) -> None:
|
|
41
|
+
self.id = id if id is not None else str(uuid.uuid4())
|
|
39
42
|
self.role = role
|
|
40
43
|
self.text = text
|
|
41
44
|
self.images = self._process_image(images)
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
import json
|
|
3
3
|
import time
|
|
4
|
+
from copy import deepcopy
|
|
4
5
|
from typing import Iterator, AsyncIterator
|
|
5
6
|
|
|
6
7
|
from dotenv import load_dotenv
|
|
7
8
|
from pydantic import BaseModel
|
|
8
9
|
|
|
9
|
-
from .llms import *
|
|
10
|
-
from .message import *
|
|
11
|
-
from .openrouter_provider import *
|
|
12
|
-
from .tool import *
|
|
10
|
+
from openrouter.llms import *
|
|
11
|
+
from openrouter.message import *
|
|
12
|
+
from openrouter.openrouter_provider import *
|
|
13
|
+
from openrouter.tool import *
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
_base_system_prompt = """
|
|
@@ -35,9 +36,57 @@ class OpenRouterClient:
|
|
|
35
36
|
system_prompt = _base_system_prompt
|
|
36
37
|
system_prompt = system_prompt.replace("[TIME]", f"{month}/{day}/{year}")
|
|
37
38
|
system_prompt = system_prompt.replace("[SYSTEM_INSTRUCTION]", prompt)
|
|
38
|
-
|
|
39
|
+
|
|
39
40
|
self._system_prompt = Message(text=system_prompt, role=Role.system)
|
|
40
|
-
|
|
41
|
+
|
|
42
|
+
def _execute_tools(self, reply: Message, tools: list[tool_model]) -> Message:
|
|
43
|
+
if not reply.tool_calls:
|
|
44
|
+
return reply
|
|
45
|
+
|
|
46
|
+
reply_copy = deepcopy(reply)
|
|
47
|
+
|
|
48
|
+
for requested_tool in reply_copy.tool_calls:
|
|
49
|
+
args = requested_tool.arguments
|
|
50
|
+
if isinstance(args, str):
|
|
51
|
+
args = json.loads(args)
|
|
52
|
+
|
|
53
|
+
for tool in tools:
|
|
54
|
+
if tool.name == requested_tool.name:
|
|
55
|
+
result = tool(**args)
|
|
56
|
+
requested_tool.result = result
|
|
57
|
+
break
|
|
58
|
+
|
|
59
|
+
return reply_copy
|
|
60
|
+
|
|
61
|
+
def execute_tool(self, reply: Message, tool_index: int, tools: List[tool_model] = []) -> Message:
|
|
62
|
+
if not reply.tool_calls:
|
|
63
|
+
return reply
|
|
64
|
+
|
|
65
|
+
if tool_index < 0 or tool_index >= len(reply.tool_calls):
|
|
66
|
+
raise IndexError(f"Tool index {tool_index} is out of range. Available tools: {len(reply.tool_calls)}")
|
|
67
|
+
|
|
68
|
+
requested_tool = reply.tool_calls[tool_index]
|
|
69
|
+
|
|
70
|
+
args = requested_tool.arguments
|
|
71
|
+
if isinstance(args, str):
|
|
72
|
+
args = json.loads(args)
|
|
73
|
+
|
|
74
|
+
all_tools = self.tools + tools
|
|
75
|
+
for tool in all_tools:
|
|
76
|
+
if tool.name == requested_tool.name:
|
|
77
|
+
result = tool(**args)
|
|
78
|
+
requested_tool.result = result
|
|
79
|
+
break
|
|
80
|
+
else:
|
|
81
|
+
raise ValueError(f"Tool '{requested_tool.name}' not found in registered tools")
|
|
82
|
+
|
|
83
|
+
for i, msg in enumerate(self._memory):
|
|
84
|
+
if msg.id == reply.id:
|
|
85
|
+
self._memory[i] = reply
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
return reply
|
|
89
|
+
|
|
41
90
|
def clear_memory(self) -> None:
|
|
42
91
|
self._memory = []
|
|
43
92
|
|
|
@@ -80,14 +129,17 @@ class OpenRouterClient:
|
|
|
80
129
|
def invoke(
|
|
81
130
|
self,
|
|
82
131
|
model: LLMModel,
|
|
83
|
-
query: Message,
|
|
132
|
+
query: Message = None,
|
|
84
133
|
tools: list[tool_model] = None,
|
|
85
134
|
provider: ProviderConfig = None,
|
|
86
|
-
temperature: float = 0.3
|
|
135
|
+
temperature: float = 0.3,
|
|
136
|
+
auto_tool_exec: bool = True
|
|
87
137
|
) -> Message:
|
|
88
138
|
tools = tools or []
|
|
89
|
-
|
|
139
|
+
if query is not None:
|
|
140
|
+
self._memory.append(query)
|
|
90
141
|
client = OpenRouterProvider()
|
|
142
|
+
|
|
91
143
|
reply = client.invoke(
|
|
92
144
|
model=model,
|
|
93
145
|
temperature=temperature,
|
|
@@ -99,28 +151,17 @@ class OpenRouterClient:
|
|
|
99
151
|
reply.answered_by = model
|
|
100
152
|
self._memory.append(reply)
|
|
101
153
|
|
|
102
|
-
if reply.tool_calls:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
args = json.loads(args)
|
|
107
|
-
|
|
108
|
-
for tool in (self.tools + tools):
|
|
109
|
-
if tool.name == requested_tool.name:
|
|
110
|
-
result = tool(**args)
|
|
111
|
-
requested_tool.result = result
|
|
112
|
-
break
|
|
113
|
-
else:
|
|
114
|
-
return reply
|
|
115
|
-
|
|
154
|
+
if auto_tool_exec and reply.tool_calls:
|
|
155
|
+
reply = self._execute_tools(reply, self.tools + tools)
|
|
156
|
+
self._memory[-1] = reply
|
|
157
|
+
|
|
116
158
|
reply = client.invoke(
|
|
117
159
|
model=model,
|
|
160
|
+
temperature=temperature,
|
|
118
161
|
system_prompt=self._system_prompt,
|
|
119
162
|
querys=self._memory,
|
|
120
|
-
tools=self.tools + tools,
|
|
121
163
|
provider=provider
|
|
122
164
|
)
|
|
123
|
-
|
|
124
165
|
reply.answered_by = model
|
|
125
166
|
self._memory.append(reply)
|
|
126
167
|
|
|
@@ -156,13 +197,15 @@ class OpenRouterClient:
|
|
|
156
197
|
async def async_invoke(
|
|
157
198
|
self,
|
|
158
199
|
model: LLMModel,
|
|
159
|
-
query: Message,
|
|
200
|
+
query: Message = None,
|
|
160
201
|
tools: list[tool_model] = None,
|
|
161
202
|
provider: ProviderConfig = None,
|
|
162
|
-
temperature: float = 0.3
|
|
203
|
+
temperature: float = 0.3,
|
|
204
|
+
auto_tool_exec: bool = True
|
|
163
205
|
) -> Message:
|
|
164
206
|
tools = tools or []
|
|
165
|
-
|
|
207
|
+
if query is not None:
|
|
208
|
+
self._memory.append(query)
|
|
166
209
|
client = OpenRouterProvider()
|
|
167
210
|
reply = await client.async_invoke(
|
|
168
211
|
model=model,
|
|
@@ -175,19 +218,8 @@ class OpenRouterClient:
|
|
|
175
218
|
reply.answered_by = model
|
|
176
219
|
self._memory.append(reply)
|
|
177
220
|
|
|
178
|
-
if reply.tool_calls:
|
|
179
|
-
|
|
180
|
-
args = requested_tool.arguments
|
|
181
|
-
if isinstance(args, str):
|
|
182
|
-
args = json.loads(args)
|
|
183
|
-
|
|
184
|
-
for tool in (self.tools + tools):
|
|
185
|
-
if tool.name == requested_tool.name:
|
|
186
|
-
result = tool(**args)
|
|
187
|
-
requested_tool.result = result
|
|
188
|
-
break
|
|
189
|
-
else:
|
|
190
|
-
return reply
|
|
221
|
+
if auto_tool_exec and reply.tool_calls:
|
|
222
|
+
reply = self._execute_tools(reply, self.tools + tools)
|
|
191
223
|
|
|
192
224
|
reply = await client.async_invoke(
|
|
193
225
|
model=model,
|
|
@@ -248,4 +280,8 @@ class OpenRouterClient:
|
|
|
248
280
|
provider=provider,
|
|
249
281
|
json_schema=json_schema
|
|
250
282
|
)
|
|
283
|
+
|
|
284
|
+
self._memory.append(Message(text=reply.model_dump_json(), role=Role.ai, answered_by=model))
|
|
285
|
+
|
|
251
286
|
return reply
|
|
287
|
+
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
import json
|
|
2
3
|
import os
|
|
3
4
|
from dataclasses import dataclass, asdict
|
|
4
5
|
from typing import List, Optional, Literal, Iterator, AsyncIterator
|
|
@@ -6,11 +7,11 @@ from typing import List, Optional, Literal, Iterator, AsyncIterator
|
|
|
6
7
|
from dotenv import load_dotenv
|
|
7
8
|
from openai import OpenAI, AsyncOpenAI
|
|
8
9
|
from openai.types.chat import ChatCompletionChunk
|
|
9
|
-
from pydantic import BaseModel
|
|
10
|
+
from pydantic import BaseModel, ValidationError
|
|
10
11
|
|
|
11
|
-
from .message import Message, Role, ToolCall
|
|
12
|
-
from .tool import tool_model
|
|
13
|
-
from .llms import LLMModel
|
|
12
|
+
from openrouter.message import Message, Role, ToolCall
|
|
13
|
+
from openrouter.tool import tool_model
|
|
14
|
+
from openrouter.llms import LLMModel
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
|
|
@@ -122,7 +123,7 @@ class OpenRouterProvider:
|
|
|
122
123
|
for tool in response.choices[0].message.tool_calls:
|
|
123
124
|
reply.tool_calls.append(ToolCall(id=tool.id, name=tool.function.name, arguments=tool.function.arguments))
|
|
124
125
|
return reply
|
|
125
|
-
|
|
126
|
+
|
|
126
127
|
def invoke_stream(
|
|
127
128
|
self,
|
|
128
129
|
model: LLMModel,
|
|
@@ -219,14 +220,60 @@ class OpenRouterProvider:
|
|
|
219
220
|
messages = self.make_prompt(system_prompt, querys)
|
|
220
221
|
provider_dict = provider.to_dict() if provider else None
|
|
221
222
|
|
|
223
|
+
schema = json_schema.model_json_schema()
|
|
224
|
+
|
|
225
|
+
def add_additional_properties_false(obj):
|
|
226
|
+
if isinstance(obj, dict):
|
|
227
|
+
if "properties" in obj:
|
|
228
|
+
obj["additionalProperties"] = False
|
|
229
|
+
for value in obj.values():
|
|
230
|
+
add_additional_properties_false(value)
|
|
231
|
+
elif isinstance(obj, list):
|
|
232
|
+
for item in obj:
|
|
233
|
+
add_additional_properties_false(item)
|
|
234
|
+
|
|
235
|
+
def ensure_required_properties(obj):
|
|
236
|
+
if isinstance(obj, dict):
|
|
237
|
+
properties = obj.get("properties")
|
|
238
|
+
if isinstance(properties, dict):
|
|
239
|
+
keys = list(properties.keys())
|
|
240
|
+
existing_required = obj.get("required")
|
|
241
|
+
if isinstance(existing_required, list):
|
|
242
|
+
required_set = set(existing_required)
|
|
243
|
+
else:
|
|
244
|
+
required_set = set()
|
|
245
|
+
required_set.update(keys)
|
|
246
|
+
obj["required"] = list(required_set)
|
|
247
|
+
for value in obj.values():
|
|
248
|
+
ensure_required_properties(value)
|
|
249
|
+
elif isinstance(obj, list):
|
|
250
|
+
for item in obj:
|
|
251
|
+
ensure_required_properties(item)
|
|
252
|
+
|
|
253
|
+
add_additional_properties_false(schema)
|
|
254
|
+
ensure_required_properties(schema)
|
|
255
|
+
|
|
222
256
|
response = self.client.chat.completions.create(
|
|
223
257
|
model=model.name,
|
|
224
258
|
temperature=temperature,
|
|
225
259
|
messages=messages,
|
|
226
|
-
response_format={"type": "json_schema", "json_schema": {"name": json_schema.__name__, "schema":
|
|
260
|
+
response_format={"type": "json_schema", "json_schema": {"name": json_schema.__name__, "schema": schema}},
|
|
227
261
|
extra_body={"provider": provider_dict},
|
|
228
262
|
)
|
|
229
263
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
264
|
+
content = response.choices[0].message.content
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
return json_schema.model_validate_json(content)
|
|
268
|
+
except ValidationError:
|
|
269
|
+
formatted_content = content
|
|
270
|
+
try:
|
|
271
|
+
parsed = json.loads(content)
|
|
272
|
+
formatted_content = json.dumps(parsed, indent=2, ensure_ascii=False)
|
|
273
|
+
except json.JSONDecodeError:
|
|
274
|
+
pass
|
|
275
|
+
print("structured_output validation failed, response content:")
|
|
276
|
+
print(formatted_content)
|
|
277
|
+
raise
|
|
278
|
+
|
|
279
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
openrouter/__init__.py,sha256=xuIzdm8-l3Tmc-zrNIXTicv05c9HCMxTS9xynKpWK-Q,123
|
|
2
|
+
openrouter/llms.py,sha256=zmujFW5BmQA0Fm6z8skuO3ipLpaUMy1VBZxYkPE9OcM,3391
|
|
3
|
+
openrouter/message.py,sha256=ESI4YT6x0TuPZ0AY29ZPlBCv72KrQad1-IeNmrfGD0w,2978
|
|
4
|
+
openrouter/openrouter.py,sha256=T1n3Mi26StAMzJ5yNNY5ww6fympiHBfp5M4pzYPmaV8,8993
|
|
5
|
+
openrouter/openrouter_provider.py,sha256=NHKZjpTTBKWTYcH67XTXh8vTotV-zsuuQl2xzAjXq24,9898
|
|
6
|
+
openrouter/tool.py,sha256=tUUNLosz1XhzPIwY1zHXWNM3ePs7hcVD1a_W5hWTCWk,1975
|
|
7
|
+
openrouter_provider-1.0.10.dist-info/METADATA,sha256=NFw7weaQNpo-sEJbxrsC_DqrkJsLSJ7_bk4APhRecVY,11504
|
|
8
|
+
openrouter_provider-1.0.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
+
openrouter_provider-1.0.10.dist-info/top_level.txt,sha256=0jnlCcRirGeYZLm5ZbWQRUonIp4tTPl_9mq-ds_1SEo,11
|
|
10
|
+
openrouter_provider-1.0.10.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openrouter
|
__init__.py
DELETED
|
File without changes
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
llms.py,sha256=IjG3WgmPDbNR8zvAOYDsZvSZNmDjYfPS4IpdtlI4OI8,3535
|
|
3
|
-
message.py,sha256=xTPzrgiVd651EeccUoxh5h1Xo4Xhu1VpAnb6NB-p9YY,2860
|
|
4
|
-
openrouter.py,sha256=zXa6huZ4QIzJpbk9GeX4fEKJsyYwZN09ZlmbkAef5ik,7857
|
|
5
|
-
openrouter_provider.py,sha256=dJe_kPENFfhzC9J7q4XyglpXYq-1KPFIwsgyIdHeml8,8021
|
|
6
|
-
tool.py,sha256=tUUNLosz1XhzPIwY1zHXWNM3ePs7hcVD1a_W5hWTCWk,1975
|
|
7
|
-
openrouter_provider-1.0.1.dist-info/METADATA,sha256=g4NGzvYX4bY8VU3c9gEAT8XWcfT0itTTY-HiOQVb3o4,11503
|
|
8
|
-
openrouter_provider-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
openrouter_provider-1.0.1.dist-info/top_level.txt,sha256=P1gRRxgxRCkgKpO2Eru8nR9svmkGy1YlKSVavfrNIFs,58
|
|
10
|
-
openrouter_provider-1.0.1.dist-info/RECORD,,
|
/tool.py → /openrouter/tool.py
RENAMED
|
File without changes
|
|
File without changes
|