lm-deluge 0.0.70__py3-none-any.whl → 0.0.72__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.
- lm_deluge/api_requests/base.py +34 -11
- lm_deluge/mock_openai.py +66 -64
- lm_deluge/prompt.py +6 -3
- {lm_deluge-0.0.70.dist-info → lm_deluge-0.0.72.dist-info}/METADATA +1 -1
- {lm_deluge-0.0.70.dist-info → lm_deluge-0.0.72.dist-info}/RECORD +8 -8
- {lm_deluge-0.0.70.dist-info → lm_deluge-0.0.72.dist-info}/WHEEL +0 -0
- {lm_deluge-0.0.70.dist-info → lm_deluge-0.0.72.dist-info}/licenses/LICENSE +0 -0
- {lm_deluge-0.0.70.dist-info → lm_deluge-0.0.72.dist-info}/top_level.txt +0 -0
lm_deluge/api_requests/base.py
CHANGED
|
@@ -90,9 +90,32 @@ class APIRequestBase(ABC):
|
|
|
90
90
|
start -> poll -> result style of request.
|
|
91
91
|
"""
|
|
92
92
|
assert self.context.status_tracker, "no status tracker"
|
|
93
|
-
|
|
93
|
+
poll_interval = 5.0
|
|
94
|
+
attempt_start = time.monotonic()
|
|
95
|
+
deadline = attempt_start + self.context.request_timeout
|
|
96
|
+
response_id: str | None = None
|
|
97
|
+
last_status: str | None = None
|
|
98
|
+
|
|
94
99
|
async with aiohttp.ClientSession() as session:
|
|
95
|
-
|
|
100
|
+
|
|
101
|
+
async def cancel_response(reason: str) -> None:
|
|
102
|
+
nonlocal response_id
|
|
103
|
+
if not response_id:
|
|
104
|
+
return
|
|
105
|
+
cancel_url = f"{self.url}/{response_id}/cancel"
|
|
106
|
+
try:
|
|
107
|
+
async with session.post(
|
|
108
|
+
url=cancel_url,
|
|
109
|
+
headers=self.request_header,
|
|
110
|
+
) as cancel_response:
|
|
111
|
+
cancel_response.raise_for_status()
|
|
112
|
+
print(f"Background req {response_id} cancelled: {reason}")
|
|
113
|
+
except (
|
|
114
|
+
Exception
|
|
115
|
+
) as cancel_err: # pragma: no cover - best effort logging
|
|
116
|
+
print(
|
|
117
|
+
f"Failed to cancel background req {response_id}: {cancel_err}"
|
|
118
|
+
)
|
|
96
119
|
|
|
97
120
|
try:
|
|
98
121
|
self.context.status_tracker.total_requests += 1
|
|
@@ -109,14 +132,11 @@ class APIRequestBase(ABC):
|
|
|
109
132
|
last_status = data["status"]
|
|
110
133
|
|
|
111
134
|
while True:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
) as http_response:
|
|
118
|
-
http_response.raise_for_status()
|
|
119
|
-
|
|
135
|
+
now = time.monotonic()
|
|
136
|
+
remaining = deadline - now
|
|
137
|
+
if remaining <= 0:
|
|
138
|
+
elapsed = now - attempt_start
|
|
139
|
+
await cancel_response(f"timed out after {elapsed:.1f}s")
|
|
120
140
|
return APIResponse(
|
|
121
141
|
id=self.context.task_id,
|
|
122
142
|
model_internal=self.context.model_name,
|
|
@@ -128,8 +148,9 @@ class APIRequestBase(ABC):
|
|
|
128
148
|
content=None,
|
|
129
149
|
usage=None,
|
|
130
150
|
)
|
|
151
|
+
|
|
131
152
|
# poll for the response
|
|
132
|
-
await asyncio.sleep(
|
|
153
|
+
await asyncio.sleep(min(poll_interval, max(remaining, 0)))
|
|
133
154
|
async with session.get(
|
|
134
155
|
url=f"{self.url}/{response_id}",
|
|
135
156
|
headers=self.request_header,
|
|
@@ -146,6 +167,8 @@ class APIRequestBase(ABC):
|
|
|
146
167
|
return await self.handle_response(http_response)
|
|
147
168
|
|
|
148
169
|
except Exception as e:
|
|
170
|
+
if response_id:
|
|
171
|
+
await cancel_response(f"errored: {type(e).__name__}")
|
|
149
172
|
raise_if_modal_exception(e)
|
|
150
173
|
tb = traceback.format_exc()
|
|
151
174
|
print(tb)
|
lm_deluge/mock_openai.py
CHANGED
|
@@ -41,6 +41,8 @@ try:
|
|
|
41
41
|
from openai.types.chat.chat_completion import Choice as ChatCompletionChoice
|
|
42
42
|
from openai.types.chat.chat_completion_chunk import (
|
|
43
43
|
Choice as ChunkChoice,
|
|
44
|
+
)
|
|
45
|
+
from openai.types.chat.chat_completion_chunk import (
|
|
44
46
|
ChoiceDelta,
|
|
45
47
|
ChoiceDeltaToolCall,
|
|
46
48
|
ChoiceDeltaToolCallFunction,
|
|
@@ -63,56 +65,61 @@ __all__ = [
|
|
|
63
65
|
"RateLimitError",
|
|
64
66
|
]
|
|
65
67
|
|
|
66
|
-
from lm_deluge.client import LLMClient
|
|
67
|
-
from lm_deluge.prompt import Conversation, Message,
|
|
68
|
+
from lm_deluge.client import LLMClient, _LLMClient
|
|
69
|
+
from lm_deluge.prompt import CachePattern, Conversation, Message, Text, ToolCall
|
|
70
|
+
from lm_deluge.tool import Tool
|
|
68
71
|
|
|
69
72
|
|
|
70
|
-
def
|
|
71
|
-
"""
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
73
|
+
def _openai_tools_to_lm_deluge(tools: list[dict[str, Any]]) -> list[Tool]:
|
|
74
|
+
"""
|
|
75
|
+
Convert OpenAI tool format to lm-deluge Tool objects.
|
|
76
|
+
|
|
77
|
+
OpenAI format:
|
|
78
|
+
{
|
|
79
|
+
"type": "function",
|
|
80
|
+
"function": {
|
|
81
|
+
"name": "get_weather",
|
|
82
|
+
"description": "Get weather",
|
|
83
|
+
"parameters": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"properties": {...},
|
|
86
|
+
"required": [...]
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
lm-deluge format:
|
|
92
|
+
Tool(
|
|
93
|
+
name="get_weather",
|
|
94
|
+
description="Get weather",
|
|
95
|
+
parameters={...properties...},
|
|
96
|
+
required=[...]
|
|
97
|
+
)
|
|
98
|
+
"""
|
|
99
|
+
lm_tools = []
|
|
100
|
+
for tool in tools:
|
|
101
|
+
if tool.get("type") == "function":
|
|
102
|
+
func = tool["function"]
|
|
103
|
+
params_schema = func.get("parameters", {})
|
|
104
|
+
|
|
105
|
+
# Extract properties and required from the parameters schema
|
|
106
|
+
properties = params_schema.get("properties", {})
|
|
107
|
+
required = params_schema.get("required", [])
|
|
108
|
+
|
|
109
|
+
lm_tool = Tool(
|
|
110
|
+
name=func["name"],
|
|
111
|
+
description=func.get("description"),
|
|
112
|
+
parameters=properties if properties else None,
|
|
113
|
+
required=required,
|
|
114
|
+
)
|
|
115
|
+
lm_tools.append(lm_tool)
|
|
108
116
|
|
|
109
|
-
|
|
110
|
-
if role == "tool" and tool_call_id:
|
|
111
|
-
parts.append(ToolResult(tool_call_id=tool_call_id, result=content or ""))
|
|
117
|
+
return lm_tools
|
|
112
118
|
|
|
113
|
-
conv_messages.append(Message(role=role, parts=parts))
|
|
114
119
|
|
|
115
|
-
|
|
120
|
+
def _messages_to_conversation(messages: list[dict[str, Any]]) -> Conversation:
|
|
121
|
+
"""Convert OpenAI messages format to lm-deluge Conversation."""
|
|
122
|
+
return Conversation.from_openai_chat(messages)
|
|
116
123
|
|
|
117
124
|
|
|
118
125
|
def _response_to_chat_completion(
|
|
@@ -346,7 +353,7 @@ class MockCompletions:
|
|
|
346
353
|
ChatCompletion (non-streaming) or AsyncIterator[ChatCompletionChunk] (streaming)
|
|
347
354
|
"""
|
|
348
355
|
# Get or create client for this model
|
|
349
|
-
client = self._parent._get_or_create_client(model)
|
|
356
|
+
client: _LLMClient = self._parent._get_or_create_client(model)
|
|
350
357
|
|
|
351
358
|
# Convert messages to Conversation
|
|
352
359
|
conversation = _messages_to_conversation(messages)
|
|
@@ -377,26 +384,19 @@ class MockCompletions:
|
|
|
377
384
|
# Convert tools if provided
|
|
378
385
|
lm_tools = None
|
|
379
386
|
if tools:
|
|
380
|
-
#
|
|
381
|
-
lm_tools = tools
|
|
387
|
+
# Convert from OpenAI format to lm-deluge Tool objects
|
|
388
|
+
lm_tools = _openai_tools_to_lm_deluge(tools)
|
|
382
389
|
|
|
383
390
|
# Execute request
|
|
384
391
|
if stream:
|
|
385
|
-
|
|
386
|
-
request_id = f"chatcmpl-{uuid.uuid4().hex[:24]}"
|
|
387
|
-
# Note: client.stream() is an async generator, not a coroutine
|
|
388
|
-
# We can directly wrap it
|
|
389
|
-
stream_iter = client.stream(conversation, tools=lm_tools)
|
|
390
|
-
# Verify it's a generator, not a coroutine
|
|
391
|
-
if hasattr(stream_iter, "__anext__"):
|
|
392
|
-
return _AsyncStreamWrapper(stream_iter, model, request_id)
|
|
393
|
-
else:
|
|
394
|
-
# If it's a coroutine, we need to await it first
|
|
395
|
-
# But this shouldn't happen with the current implementation
|
|
396
|
-
raise TypeError(f"Expected async generator, got {type(stream_iter)}")
|
|
392
|
+
raise RuntimeError("streaming not supported")
|
|
397
393
|
else:
|
|
398
394
|
# Non-streaming mode
|
|
399
|
-
response = await client.start(
|
|
395
|
+
response = await client.start(
|
|
396
|
+
conversation,
|
|
397
|
+
tools=lm_tools, # type: ignore
|
|
398
|
+
cache=self._parent.cache_pattern, # type: ignore
|
|
399
|
+
)
|
|
400
400
|
return _response_to_chat_completion(response, model)
|
|
401
401
|
|
|
402
402
|
|
|
@@ -437,7 +437,7 @@ class MockTextCompletions:
|
|
|
437
437
|
Completion object
|
|
438
438
|
"""
|
|
439
439
|
# Get or create client for this model
|
|
440
|
-
client = self._parent._get_or_create_client(model)
|
|
440
|
+
client: _LLMClient = self._parent._get_or_create_client(model)
|
|
441
441
|
|
|
442
442
|
# Handle single prompt
|
|
443
443
|
if isinstance(prompt, list):
|
|
@@ -464,7 +464,7 @@ class MockTextCompletions:
|
|
|
464
464
|
client = self._parent._create_client_with_params(model, merged_params)
|
|
465
465
|
|
|
466
466
|
# Execute request
|
|
467
|
-
response = await client.start(conversation)
|
|
467
|
+
response = await client.start(conversation, cache=self._parent.cache_pattern) # type: ignore
|
|
468
468
|
|
|
469
469
|
# Convert to Completion format
|
|
470
470
|
completion_text = None
|
|
@@ -477,7 +477,7 @@ class MockTextCompletions:
|
|
|
477
477
|
choice = TextCompletionChoice(
|
|
478
478
|
index=0,
|
|
479
479
|
text=completion_text or "",
|
|
480
|
-
finish_reason=response.finish_reason or "stop",
|
|
480
|
+
finish_reason=response.finish_reason or "stop", # type: ignore
|
|
481
481
|
)
|
|
482
482
|
|
|
483
483
|
# Create usage
|
|
@@ -560,6 +560,7 @@ class MockAsyncOpenAI:
|
|
|
560
560
|
max_completion_tokens: int | None = None,
|
|
561
561
|
top_p: float | None = None,
|
|
562
562
|
seed: int | None = None,
|
|
563
|
+
cache_pattern: CachePattern | None = None,
|
|
563
564
|
**kwargs: Any,
|
|
564
565
|
):
|
|
565
566
|
# OpenAI-compatible attributes
|
|
@@ -571,6 +572,7 @@ class MockAsyncOpenAI:
|
|
|
571
572
|
self.max_retries = max_retries or 2
|
|
572
573
|
self.default_headers = default_headers
|
|
573
574
|
self.http_client = http_client
|
|
575
|
+
self.cache_pattern = cache_pattern
|
|
574
576
|
|
|
575
577
|
# Internal attributes
|
|
576
578
|
self._default_model = model or "gpt-4o-mini"
|
lm_deluge/prompt.py
CHANGED
|
@@ -848,14 +848,16 @@ class Conversation:
|
|
|
848
848
|
if content is None:
|
|
849
849
|
return parts
|
|
850
850
|
if isinstance(content, str):
|
|
851
|
-
|
|
851
|
+
if content.strip():
|
|
852
|
+
parts.append(Text(content))
|
|
852
853
|
return parts
|
|
853
854
|
|
|
854
855
|
for block in content:
|
|
855
856
|
block_type = block.get("type")
|
|
856
857
|
if block_type in text_types:
|
|
857
858
|
text_value = block.get("text") or block.get(block_type) or ""
|
|
858
|
-
|
|
859
|
+
if text_value.strip():
|
|
860
|
+
parts.append(Text(text_value))
|
|
859
861
|
elif block_type in image_types:
|
|
860
862
|
parts.append(_to_image_from_url(block))
|
|
861
863
|
elif block_type in file_types:
|
|
@@ -1001,7 +1003,8 @@ class Conversation:
|
|
|
1001
1003
|
)
|
|
1002
1004
|
)
|
|
1003
1005
|
|
|
1004
|
-
|
|
1006
|
+
if parts:
|
|
1007
|
+
conversation_messages.append(Message(mapped_role, parts))
|
|
1005
1008
|
|
|
1006
1009
|
return cls(conversation_messages)
|
|
1007
1010
|
|
|
@@ -8,8 +8,8 @@ lm_deluge/embed.py,sha256=CO-TOlC5kOTAM8lcnicoG4u4K664vCBwHF1vHa-nAGg,13382
|
|
|
8
8
|
lm_deluge/errors.py,sha256=oHjt7YnxWbh-eXMScIzov4NvpJMo0-2r5J6Wh5DQ1tk,209
|
|
9
9
|
lm_deluge/file.py,sha256=PTmlJQ-IaYcYUFun9V0bJ1NPVP84edJrR0hvCMWFylY,19697
|
|
10
10
|
lm_deluge/image.py,sha256=5AMXmn2x47yXeYNfMSMAOWcnlrOxxOel-4L8QCJwU70,8928
|
|
11
|
-
lm_deluge/mock_openai.py,sha256
|
|
12
|
-
lm_deluge/prompt.py,sha256=
|
|
11
|
+
lm_deluge/mock_openai.py,sha256=-u4kxSzwoxDt_2fLh5LaiqETnu0Jg_VDL7TWAAYHGNw,21762
|
|
12
|
+
lm_deluge/prompt.py,sha256=b93ZZHlK9luujgilcnSkwoPCD-U6r1wLWXxWJ4D4ZIE,63578
|
|
13
13
|
lm_deluge/request_context.py,sha256=cBayMFWupWhde2OjRugW3JH-Gin-WFGc6DK2Mb4Prdc,2576
|
|
14
14
|
lm_deluge/rerank.py,sha256=-NBAJdHz9OB-SWWJnHzkFmeVO4wR6lFV7Vw-SxG7aVo,11457
|
|
15
15
|
lm_deluge/tool.py,sha256=Kp2O5lDq_WVo_ASxjLQSHzVRbaxZkS6J0JIIskBjux0,28909
|
|
@@ -18,7 +18,7 @@ lm_deluge/usage.py,sha256=xz9tAw2hqaJvv9aAVhnQ6N1Arn7fS8Shb28VwCW26wI,5136
|
|
|
18
18
|
lm_deluge/warnings.py,sha256=nlDJMCw30VhDEFxqLO2-bfXH_Tv5qmlglzUSbokCSw8,1498
|
|
19
19
|
lm_deluge/api_requests/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
20
20
|
lm_deluge/api_requests/anthropic.py,sha256=QGq3G5jJIGcoM2HdRt73GgkvZs4GOViyjYexWex05Vk,8927
|
|
21
|
-
lm_deluge/api_requests/base.py,sha256=
|
|
21
|
+
lm_deluge/api_requests/base.py,sha256=mXEM85mcU_5LD-ugELpCl28tv-tpHKcaxerTIVLQZVo,10436
|
|
22
22
|
lm_deluge/api_requests/bedrock.py,sha256=Uppne03GcIEk1tVYzoGu7GXK2Sg94a_xvFTLDRN_phY,15412
|
|
23
23
|
lm_deluge/api_requests/chat_reasoning.py,sha256=sJvstvKFqsSBUjYcwxzGt2_FH4cEp3Z6gKcBPyPjGwk,236
|
|
24
24
|
lm_deluge/api_requests/common.py,sha256=BZ3vRO5TB669_UsNKugkkuFSzoLHOYJIKt4nV4sf4vc,422
|
|
@@ -69,8 +69,8 @@ lm_deluge/util/logprobs.py,sha256=UkBZakOxWluaLqHrjARu7xnJ0uCHVfLGHJdnYlEcutk,11
|
|
|
69
69
|
lm_deluge/util/spatial.py,sha256=BsF_UKhE-x0xBirc-bV1xSKZRTUhsOBdGqsMKme20C8,4099
|
|
70
70
|
lm_deluge/util/validation.py,sha256=hz5dDb3ebvZrZhnaWxOxbNSVMI6nmaOODBkk0htAUhs,1575
|
|
71
71
|
lm_deluge/util/xml.py,sha256=Ft4zajoYBJR3HHCt2oHwGfymGLdvp_gegVmJ-Wqk4Ck,10547
|
|
72
|
-
lm_deluge-0.0.
|
|
73
|
-
lm_deluge-0.0.
|
|
74
|
-
lm_deluge-0.0.
|
|
75
|
-
lm_deluge-0.0.
|
|
76
|
-
lm_deluge-0.0.
|
|
72
|
+
lm_deluge-0.0.72.dist-info/licenses/LICENSE,sha256=uNNXGXPCw2TC7CUs7SEBkA-Mz6QBQFWUUEWDMgEs1dU,1058
|
|
73
|
+
lm_deluge-0.0.72.dist-info/METADATA,sha256=Ffg1w5rphPj_MScOCYhA1cQmSKsc2XjBqJefXiZOtDk,13514
|
|
74
|
+
lm_deluge-0.0.72.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
75
|
+
lm_deluge-0.0.72.dist-info/top_level.txt,sha256=hqU-TJX93yBwpgkDtYcXyLr3t7TLSCCZ_reytJjwBaE,10
|
|
76
|
+
lm_deluge-0.0.72.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|