lm-deluge 0.0.56__tar.gz → 0.0.57__tar.gz
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.
Potentially problematic release.
This version of lm-deluge might be problematic. Click here for more details.
- {lm_deluge-0.0.56/src/lm_deluge.egg-info → lm_deluge-0.0.57}/PKG-INFO +1 -1
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/pyproject.toml +1 -1
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/__init__.py +2 -1
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/client.py +9 -12
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/prompt.py +6 -7
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/tool.py +338 -18
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/tracker.py +11 -5
- {lm_deluge-0.0.56 → lm_deluge-0.0.57/src/lm_deluge.egg-info}/PKG-INFO +1 -1
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge.egg-info/SOURCES.txt +0 -2
- lm_deluge-0.0.56/src/lm_deluge/agent.py +0 -0
- lm_deluge-0.0.56/src/lm_deluge/gemini_limits.py +0 -65
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/LICENSE +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/README.md +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/setup.cfg +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/__init__.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/anthropic.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/base.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/bedrock.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/common.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/deprecated/bedrock.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/deprecated/cohere.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/deprecated/deepseek.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/deprecated/mistral.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/deprecated/vertex.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/gemini.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/mistral.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/openai.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/api_requests/response.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/batches.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/built_in_tools/anthropic/__init__.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/built_in_tools/anthropic/bash.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/built_in_tools/anthropic/computer_use.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/built_in_tools/anthropic/editor.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/built_in_tools/base.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/built_in_tools/openai.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/cache.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/cli.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/config.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/embed.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/errors.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/file.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/image.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/llm_tools/__init__.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/llm_tools/classify.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/llm_tools/extract.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/llm_tools/locate.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/llm_tools/ocr.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/llm_tools/score.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/llm_tools/translate.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/__init__.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/anthropic.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/bedrock.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/cerebras.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/cohere.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/deepseek.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/fireworks.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/google.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/grok.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/groq.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/meta.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/mistral.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/openai.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/openrouter.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/models/together.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/presets/cerebras.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/presets/meta.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/request_context.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/rerank.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/usage.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/util/harmony.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/util/json.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/util/logprobs.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/util/spatial.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/util/validation.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/util/xml.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge.egg-info/dependency_links.txt +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge.egg-info/requires.txt +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge.egg-info/top_level.txt +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/tests/test_builtin_tools.py +0 -0
- {lm_deluge-0.0.56 → lm_deluge-0.0.57}/tests/test_native_mcp_server.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from .client import APIResponse, LLMClient, SamplingParams
|
|
2
2
|
from .file import File
|
|
3
3
|
from .prompt import Conversation, Message
|
|
4
|
-
from .tool import Tool
|
|
4
|
+
from .tool import Tool, ToolParams
|
|
5
5
|
|
|
6
6
|
# dotenv.load_dotenv() - don't do this, fucks with other packages
|
|
7
7
|
|
|
@@ -12,5 +12,6 @@ __all__ = [
|
|
|
12
12
|
"Conversation",
|
|
13
13
|
"Message",
|
|
14
14
|
"Tool",
|
|
15
|
+
"ToolParams",
|
|
15
16
|
"File",
|
|
16
17
|
]
|
|
@@ -618,23 +618,20 @@ class _LLMClient(BaseModel):
|
|
|
618
618
|
mcp_tools = await tool.to_tools()
|
|
619
619
|
expanded_tools.extend(mcp_tools)
|
|
620
620
|
|
|
621
|
-
|
|
621
|
+
response: APIResponse | None = None
|
|
622
622
|
|
|
623
623
|
for _ in range(max_rounds):
|
|
624
|
-
|
|
625
|
-
|
|
624
|
+
response = await self.start(
|
|
625
|
+
conversation,
|
|
626
626
|
tools=tools, # type: ignore
|
|
627
|
-
return_completions_only=False,
|
|
628
|
-
show_progress=show_progress,
|
|
629
627
|
)
|
|
630
628
|
|
|
631
|
-
|
|
632
|
-
if last_response is None or last_response.content is None:
|
|
629
|
+
if response is None or response.content is None:
|
|
633
630
|
break
|
|
634
631
|
|
|
635
|
-
conversation = conversation.with_message(
|
|
632
|
+
conversation = conversation.with_message(response.content)
|
|
636
633
|
|
|
637
|
-
tool_calls =
|
|
634
|
+
tool_calls = response.content.tool_calls
|
|
638
635
|
if not tool_calls:
|
|
639
636
|
break
|
|
640
637
|
|
|
@@ -657,12 +654,12 @@ class _LLMClient(BaseModel):
|
|
|
657
654
|
if not isinstance(result, (str, dict, list)):
|
|
658
655
|
result = str(result)
|
|
659
656
|
|
|
660
|
-
conversation.
|
|
657
|
+
conversation.with_tool_result(call.id, result) # type: ignore
|
|
661
658
|
|
|
662
|
-
if
|
|
659
|
+
if response is None:
|
|
663
660
|
raise RuntimeError("model did not return a response")
|
|
664
661
|
|
|
665
|
-
return conversation,
|
|
662
|
+
return conversation, response
|
|
666
663
|
|
|
667
664
|
def run_agent_loop_sync(
|
|
668
665
|
self,
|
|
@@ -144,8 +144,8 @@ class ToolResult:
|
|
|
144
144
|
def oa_chat(
|
|
145
145
|
self,
|
|
146
146
|
) -> dict: # OpenAI Chat Completions - tool results are separate messages
|
|
147
|
-
print("serializing toolresult with oa_chat...")
|
|
148
|
-
print("typeof self.result:", type(self.result))
|
|
147
|
+
# print("serializing toolresult with oa_chat...")
|
|
148
|
+
# print("typeof self.result:", type(self.result))
|
|
149
149
|
if isinstance(self.result, str):
|
|
150
150
|
return {
|
|
151
151
|
"role": "tool",
|
|
@@ -174,8 +174,7 @@ class ToolResult:
|
|
|
174
174
|
raise ValueError("result type not supported")
|
|
175
175
|
|
|
176
176
|
def oa_resp(self) -> dict: # OpenAI Responses
|
|
177
|
-
print("
|
|
178
|
-
print("typeof self.result:", type(self.result))
|
|
177
|
+
# print("typeof self.result:", type(self.result))
|
|
179
178
|
# if normal (not built-in just return the regular output
|
|
180
179
|
if not self.built_in:
|
|
181
180
|
result = (
|
|
@@ -466,7 +465,7 @@ class Message:
|
|
|
466
465
|
self.parts.append(ToolCall(id=id, name=name, arguments=arguments))
|
|
467
466
|
return self
|
|
468
467
|
|
|
469
|
-
def
|
|
468
|
+
def with_tool_result(
|
|
470
469
|
self, tool_call_id: str, result: str | list[ToolResultPart]
|
|
471
470
|
) -> "Message":
|
|
472
471
|
"""Append a tool result block and return self for chaining."""
|
|
@@ -1189,11 +1188,11 @@ class Conversation:
|
|
|
1189
1188
|
"""
|
|
1190
1189
|
if self.messages and self.messages[-1].role == "tool":
|
|
1191
1190
|
# Append to existing tool message (parallel tool calls)
|
|
1192
|
-
self.messages[-1].
|
|
1191
|
+
self.messages[-1].with_tool_result(tool_call_id, result)
|
|
1193
1192
|
else:
|
|
1194
1193
|
# Create new tool message
|
|
1195
1194
|
tool_msg = Message("tool", [])
|
|
1196
|
-
tool_msg.
|
|
1195
|
+
tool_msg.with_tool_result(tool_call_id, result)
|
|
1197
1196
|
self.messages.append(tool_msg)
|
|
1198
1197
|
return self
|
|
1199
1198
|
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import inspect
|
|
3
3
|
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
-
from typing import
|
|
4
|
+
from typing import (
|
|
5
|
+
Any,
|
|
6
|
+
Callable,
|
|
7
|
+
Coroutine,
|
|
8
|
+
Literal,
|
|
9
|
+
Type,
|
|
10
|
+
TypedDict,
|
|
11
|
+
get_args,
|
|
12
|
+
get_origin,
|
|
13
|
+
get_type_hints,
|
|
14
|
+
)
|
|
5
15
|
|
|
6
16
|
from fastmcp import Client # pip install fastmcp >= 2.0
|
|
7
17
|
from mcp.types import Tool as MCPTool
|
|
@@ -11,6 +21,196 @@ from lm_deluge.image import Image
|
|
|
11
21
|
from lm_deluge.prompt import Text, ToolResultPart
|
|
12
22
|
|
|
13
23
|
|
|
24
|
+
def _python_type_to_json_schema_enhanced(python_type: Any) -> dict[str, Any]:
|
|
25
|
+
"""
|
|
26
|
+
Convert Python type annotations to JSON Schema.
|
|
27
|
+
Handles: primitives, Optional, Literal, list[T], dict[str, T], Union.
|
|
28
|
+
"""
|
|
29
|
+
# Get origin and args for generic types
|
|
30
|
+
origin = get_origin(python_type)
|
|
31
|
+
args = get_args(python_type)
|
|
32
|
+
|
|
33
|
+
# Handle Optional[T] or T | None
|
|
34
|
+
if origin is type(None) or python_type is type(None):
|
|
35
|
+
return {"type": "null"}
|
|
36
|
+
|
|
37
|
+
# Handle Union types (including Optional)
|
|
38
|
+
if origin is Literal:
|
|
39
|
+
# Literal["a", "b"] -> enum
|
|
40
|
+
return {"type": "string", "enum": list(args)}
|
|
41
|
+
|
|
42
|
+
# Handle list[T]
|
|
43
|
+
if origin is list:
|
|
44
|
+
if args:
|
|
45
|
+
items_schema = _python_type_to_json_schema_enhanced(args[0])
|
|
46
|
+
return {"type": "array", "items": items_schema}
|
|
47
|
+
return {"type": "array"}
|
|
48
|
+
|
|
49
|
+
# Handle dict[str, T]
|
|
50
|
+
if origin is dict:
|
|
51
|
+
if len(args) >= 2:
|
|
52
|
+
# For dict[str, T], we can set additionalProperties
|
|
53
|
+
value_schema = _python_type_to_json_schema_enhanced(args[1])
|
|
54
|
+
return {"type": "object", "additionalProperties": value_schema}
|
|
55
|
+
return {"type": "object"}
|
|
56
|
+
|
|
57
|
+
# Handle basic types
|
|
58
|
+
if python_type is int:
|
|
59
|
+
return {"type": "integer"}
|
|
60
|
+
elif python_type is float:
|
|
61
|
+
return {"type": "number"}
|
|
62
|
+
elif python_type is str:
|
|
63
|
+
return {"type": "string"}
|
|
64
|
+
elif python_type is bool:
|
|
65
|
+
return {"type": "boolean"}
|
|
66
|
+
elif python_type is list:
|
|
67
|
+
return {"type": "array"}
|
|
68
|
+
elif python_type is dict:
|
|
69
|
+
return {"type": "object"}
|
|
70
|
+
else:
|
|
71
|
+
# Default to string for unknown types
|
|
72
|
+
return {"type": "string"}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ToolParams:
|
|
76
|
+
"""
|
|
77
|
+
Helper class for constructing tool parameters more easily.
|
|
78
|
+
|
|
79
|
+
Usage:
|
|
80
|
+
# Simple constructor with Python types
|
|
81
|
+
params = ToolParams({"city": str, "age": int})
|
|
82
|
+
|
|
83
|
+
# With extras (description, enum, etc)
|
|
84
|
+
params = ToolParams({
|
|
85
|
+
"operation": (str, {"enum": ["add", "sub"], "description": "Math operation"}),
|
|
86
|
+
"value": (int, {"description": "The value"})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
# From Pydantic model
|
|
90
|
+
params = ToolParams.from_pydantic(MyModel)
|
|
91
|
+
|
|
92
|
+
# From TypedDict
|
|
93
|
+
params = ToolParams.from_typed_dict(MyTypedDict)
|
|
94
|
+
|
|
95
|
+
# From existing JSON Schema
|
|
96
|
+
params = ToolParams.from_json_schema(schema_dict, required=["field1"])
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def __init__(self, spec: dict[str, Any]):
|
|
100
|
+
"""
|
|
101
|
+
Create ToolParams from a dict mapping parameter names to types or (type, extras) tuples.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
spec: Dict where values can be:
|
|
105
|
+
- A Python type (str, int, list[str], etc.)
|
|
106
|
+
- A tuple of (type, extras_dict) for additional JSON Schema properties
|
|
107
|
+
- An already-formed JSON Schema dict (passed through as-is)
|
|
108
|
+
"""
|
|
109
|
+
self.parameters: dict[str, Any] = {}
|
|
110
|
+
self.required: list[str] = []
|
|
111
|
+
|
|
112
|
+
for param_name, param_spec in spec.items():
|
|
113
|
+
# If it's a tuple, extract (type, extras)
|
|
114
|
+
if isinstance(param_spec, tuple):
|
|
115
|
+
param_type, extras = param_spec
|
|
116
|
+
schema = _python_type_to_json_schema_enhanced(param_type)
|
|
117
|
+
schema.update(extras)
|
|
118
|
+
self.parameters[param_name] = schema
|
|
119
|
+
# Mark as required unless explicitly marked as optional
|
|
120
|
+
if extras.get("optional") is not True:
|
|
121
|
+
self.required.append(param_name)
|
|
122
|
+
# If it's already a dict with "type" key, use as-is
|
|
123
|
+
elif isinstance(param_spec, dict) and "type" in param_spec:
|
|
124
|
+
self.parameters[param_name] = param_spec
|
|
125
|
+
# Assume required unless marked optional
|
|
126
|
+
if param_spec.get("optional") is not True:
|
|
127
|
+
self.required.append(param_name)
|
|
128
|
+
# Otherwise treat as a Python type
|
|
129
|
+
else:
|
|
130
|
+
self.parameters[param_name] = _python_type_to_json_schema_enhanced(
|
|
131
|
+
param_spec
|
|
132
|
+
)
|
|
133
|
+
self.required.append(param_name)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def from_pydantic(cls, model: Type[BaseModel]) -> "ToolParams":
|
|
137
|
+
"""
|
|
138
|
+
Create ToolParams from a Pydantic model.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
model: A Pydantic BaseModel class
|
|
142
|
+
"""
|
|
143
|
+
# Get the JSON schema from Pydantic
|
|
144
|
+
schema = model.model_json_schema()
|
|
145
|
+
properties = schema.get("properties", {})
|
|
146
|
+
required = schema.get("required", [])
|
|
147
|
+
|
|
148
|
+
return cls.from_json_schema(properties, required)
|
|
149
|
+
|
|
150
|
+
@classmethod
|
|
151
|
+
def from_typed_dict(cls, typed_dict: Type) -> "ToolParams":
|
|
152
|
+
"""
|
|
153
|
+
Create ToolParams from a TypedDict.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
typed_dict: A TypedDict class
|
|
157
|
+
"""
|
|
158
|
+
hints = get_type_hints(typed_dict)
|
|
159
|
+
|
|
160
|
+
# TypedDict doesn't have a built-in way to mark optional fields,
|
|
161
|
+
# but we can check for Optional in the type hints
|
|
162
|
+
params = {}
|
|
163
|
+
required = []
|
|
164
|
+
|
|
165
|
+
for field_name, field_type in hints.items():
|
|
166
|
+
# Check if it's Optional (Union with None)
|
|
167
|
+
origin = get_origin(field_type)
|
|
168
|
+
# args = get_args(field_type)
|
|
169
|
+
|
|
170
|
+
is_optional = False
|
|
171
|
+
actual_type = field_type
|
|
172
|
+
|
|
173
|
+
# Check for Union types (including Optional[T] which is Union[T, None])
|
|
174
|
+
if origin is type(None):
|
|
175
|
+
is_optional = True
|
|
176
|
+
actual_type = type(None)
|
|
177
|
+
|
|
178
|
+
# For now, treat all TypedDict fields as required unless they're explicitly Optional
|
|
179
|
+
schema = _python_type_to_json_schema_enhanced(actual_type)
|
|
180
|
+
params[field_name] = schema
|
|
181
|
+
|
|
182
|
+
if not is_optional:
|
|
183
|
+
required.append(field_name)
|
|
184
|
+
|
|
185
|
+
instance = cls.__new__(cls)
|
|
186
|
+
instance.parameters = params
|
|
187
|
+
instance.required = required
|
|
188
|
+
return instance
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
def from_json_schema(
|
|
192
|
+
cls, properties: dict[str, Any], required: list[str] | None = None
|
|
193
|
+
) -> "ToolParams":
|
|
194
|
+
"""
|
|
195
|
+
Create ToolParams from an existing JSON Schema properties dict.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
properties: The "properties" section of a JSON Schema
|
|
199
|
+
required: List of required field names
|
|
200
|
+
"""
|
|
201
|
+
instance = cls.__new__(cls)
|
|
202
|
+
instance.parameters = properties
|
|
203
|
+
instance.required = required or []
|
|
204
|
+
return instance
|
|
205
|
+
|
|
206
|
+
def to_dict(self) -> dict[str, Any]:
|
|
207
|
+
"""
|
|
208
|
+
Convert to a dict with 'parameters' and 'required' keys.
|
|
209
|
+
Useful for unpacking into Tool constructor.
|
|
210
|
+
"""
|
|
211
|
+
return {"parameters": self.parameters, "required": self.required}
|
|
212
|
+
|
|
213
|
+
|
|
14
214
|
async def _load_all_mcp_tools(client: Client) -> list["Tool"]:
|
|
15
215
|
metas: list[MCPTool] = await client.list_tools()
|
|
16
216
|
|
|
@@ -79,6 +279,24 @@ class Tool(BaseModel):
|
|
|
79
279
|
)
|
|
80
280
|
return v
|
|
81
281
|
|
|
282
|
+
@field_validator("parameters", mode="before")
|
|
283
|
+
@classmethod
|
|
284
|
+
def validate_parameters(cls, v: Any) -> dict[str, Any] | None:
|
|
285
|
+
"""Accept ToolParams objects and convert to dict for backwards compatibility."""
|
|
286
|
+
if isinstance(v, ToolParams):
|
|
287
|
+
return v.parameters
|
|
288
|
+
return v
|
|
289
|
+
|
|
290
|
+
def model_post_init(self, __context: Any) -> None:
|
|
291
|
+
"""
|
|
292
|
+
After validation, if parameters came from ToolParams, also update required list.
|
|
293
|
+
This is called by Pydantic after __init__.
|
|
294
|
+
"""
|
|
295
|
+
# This is a bit tricky - we need to capture the required list from ToolParams
|
|
296
|
+
# Since Pydantic has already converted it in the validator, we can't access it here
|
|
297
|
+
# Instead, we'll handle this differently in the convenience constructors
|
|
298
|
+
pass
|
|
299
|
+
|
|
82
300
|
def _is_async(self) -> bool:
|
|
83
301
|
return inspect.iscoroutinefunction(self.run)
|
|
84
302
|
|
|
@@ -143,7 +361,7 @@ class Tool(BaseModel):
|
|
|
143
361
|
param_type = type_hints.get(param_name, str)
|
|
144
362
|
|
|
145
363
|
# Convert Python types to JSON Schema types
|
|
146
|
-
json_type =
|
|
364
|
+
json_type = _python_type_to_json_schema_enhanced(param_type)
|
|
147
365
|
|
|
148
366
|
parameters[param_name] = json_type
|
|
149
367
|
|
|
@@ -209,6 +427,119 @@ class Tool(BaseModel):
|
|
|
209
427
|
return t
|
|
210
428
|
raise ValueError(f"Tool '{tool_name}' not found on that server")
|
|
211
429
|
|
|
430
|
+
@classmethod
|
|
431
|
+
def from_params(
|
|
432
|
+
cls,
|
|
433
|
+
name: str,
|
|
434
|
+
params: ToolParams,
|
|
435
|
+
*,
|
|
436
|
+
description: str | None = None,
|
|
437
|
+
run: Callable | None = None,
|
|
438
|
+
**kwargs,
|
|
439
|
+
) -> "Tool":
|
|
440
|
+
"""
|
|
441
|
+
Create a Tool from a ToolParams object.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
name: Tool name
|
|
445
|
+
params: ToolParams object defining the parameter schema
|
|
446
|
+
description: Optional description
|
|
447
|
+
run: Optional callable to execute the tool
|
|
448
|
+
**kwargs: Additional Tool arguments
|
|
449
|
+
|
|
450
|
+
Example:
|
|
451
|
+
params = ToolParams({"city": str, "age": int})
|
|
452
|
+
tool = Tool.from_params("get_user", params, run=my_function)
|
|
453
|
+
"""
|
|
454
|
+
return cls(
|
|
455
|
+
name=name,
|
|
456
|
+
description=description,
|
|
457
|
+
parameters=params.parameters,
|
|
458
|
+
required=params.required,
|
|
459
|
+
run=run,
|
|
460
|
+
**kwargs,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
@classmethod
|
|
464
|
+
def from_pydantic(
|
|
465
|
+
cls,
|
|
466
|
+
name: str,
|
|
467
|
+
model: Type[BaseModel],
|
|
468
|
+
*,
|
|
469
|
+
description: str | None = None,
|
|
470
|
+
run: Callable | None = None,
|
|
471
|
+
**kwargs,
|
|
472
|
+
) -> "Tool":
|
|
473
|
+
"""
|
|
474
|
+
Create a Tool from a Pydantic model.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
name: Tool name
|
|
478
|
+
model: Pydantic BaseModel class
|
|
479
|
+
description: Optional description (defaults to model docstring)
|
|
480
|
+
run: Optional callable to execute the tool
|
|
481
|
+
**kwargs: Additional Tool arguments
|
|
482
|
+
|
|
483
|
+
Example:
|
|
484
|
+
class UserQuery(BaseModel):
|
|
485
|
+
city: str
|
|
486
|
+
age: int
|
|
487
|
+
|
|
488
|
+
tool = Tool.from_pydantic("get_user", UserQuery, run=my_function)
|
|
489
|
+
"""
|
|
490
|
+
params = ToolParams.from_pydantic(model)
|
|
491
|
+
|
|
492
|
+
# Use model docstring as default description if not provided
|
|
493
|
+
if description is None and model.__doc__:
|
|
494
|
+
description = model.__doc__.strip()
|
|
495
|
+
|
|
496
|
+
return cls(
|
|
497
|
+
name=name,
|
|
498
|
+
description=description,
|
|
499
|
+
parameters=params.parameters,
|
|
500
|
+
required=params.required,
|
|
501
|
+
run=run,
|
|
502
|
+
**kwargs,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
@classmethod
|
|
506
|
+
def from_typed_dict(
|
|
507
|
+
cls,
|
|
508
|
+
name: str,
|
|
509
|
+
typed_dict: Type,
|
|
510
|
+
*,
|
|
511
|
+
description: str | None = None,
|
|
512
|
+
run: Callable | None = None,
|
|
513
|
+
**kwargs,
|
|
514
|
+
) -> "Tool":
|
|
515
|
+
"""
|
|
516
|
+
Create a Tool from a TypedDict.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
name: Tool name
|
|
520
|
+
typed_dict: TypedDict class
|
|
521
|
+
description: Optional description
|
|
522
|
+
run: Optional callable to execute the tool
|
|
523
|
+
**kwargs: Additional Tool arguments
|
|
524
|
+
|
|
525
|
+
Example:
|
|
526
|
+
class UserQuery(TypedDict):
|
|
527
|
+
city: str
|
|
528
|
+
age: int
|
|
529
|
+
|
|
530
|
+
tool = Tool.from_typed_dict("get_user", UserQuery, run=my_function)
|
|
531
|
+
"""
|
|
532
|
+
params = ToolParams.from_typed_dict(typed_dict)
|
|
533
|
+
|
|
534
|
+
return cls(
|
|
535
|
+
name=name,
|
|
536
|
+
description=description,
|
|
537
|
+
parameters=params.parameters,
|
|
538
|
+
required=params.required,
|
|
539
|
+
run=run,
|
|
540
|
+
**kwargs,
|
|
541
|
+
)
|
|
542
|
+
|
|
212
543
|
@staticmethod
|
|
213
544
|
def _tool_from_meta(meta: dict[str, Any], runner) -> "Tool":
|
|
214
545
|
props = meta["inputSchema"].get("properties", {})
|
|
@@ -225,22 +556,11 @@ class Tool(BaseModel):
|
|
|
225
556
|
|
|
226
557
|
@staticmethod
|
|
227
558
|
def _python_type_to_json_schema(python_type) -> dict[str, Any]:
|
|
228
|
-
"""
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
elif python_type is str:
|
|
234
|
-
return {"type": "string"}
|
|
235
|
-
elif python_type is bool:
|
|
236
|
-
return {"type": "boolean"}
|
|
237
|
-
elif python_type is list:
|
|
238
|
-
return {"type": "array"}
|
|
239
|
-
elif python_type is dict:
|
|
240
|
-
return {"type": "object"}
|
|
241
|
-
else:
|
|
242
|
-
# Default to string for unknown types
|
|
243
|
-
return {"type": "string"}
|
|
559
|
+
"""
|
|
560
|
+
Convert Python type to JSON Schema type definition.
|
|
561
|
+
Now delegates to enhanced version for better type support.
|
|
562
|
+
"""
|
|
563
|
+
return _python_type_to_json_schema_enhanced(python_type)
|
|
244
564
|
|
|
245
565
|
def _json_schema(
|
|
246
566
|
self, include_additional_properties=False, remove_defaults=False
|
|
@@ -171,20 +171,24 @@ class StatusTracker:
|
|
|
171
171
|
)
|
|
172
172
|
|
|
173
173
|
# Display cumulative usage stats if available
|
|
174
|
-
if
|
|
174
|
+
if (
|
|
175
|
+
self.total_cost > 0
|
|
176
|
+
or self.total_input_tokens > 0
|
|
177
|
+
or self.total_output_tokens > 0
|
|
178
|
+
):
|
|
175
179
|
usage_parts = []
|
|
176
180
|
if self.total_cost > 0:
|
|
177
|
-
usage_parts.append(f"Cost: ${self.total_cost:.4f}")
|
|
181
|
+
usage_parts.append(f"💰 Cost: ${self.total_cost:.4f}")
|
|
178
182
|
if self.total_input_tokens > 0 or self.total_output_tokens > 0:
|
|
179
183
|
usage_parts.append(
|
|
180
|
-
f"Tokens: {self.total_input_tokens:,} in / {self.total_output_tokens:,} out"
|
|
184
|
+
f"🔡 Tokens: {self.total_input_tokens:,} in / {self.total_output_tokens:,} out"
|
|
181
185
|
)
|
|
182
186
|
if self.total_cache_read_tokens > 0:
|
|
183
187
|
usage_parts.append(f"Cache: {self.total_cache_read_tokens:,} read")
|
|
184
188
|
if self.total_cache_write_tokens > 0:
|
|
185
189
|
usage_parts.append(f"{self.total_cache_write_tokens:,} write")
|
|
186
190
|
|
|
187
|
-
print("
|
|
191
|
+
print(" ", " • ".join(usage_parts))
|
|
188
192
|
|
|
189
193
|
@property
|
|
190
194
|
def pbar(self) -> tqdm | None:
|
|
@@ -288,7 +292,9 @@ class StatusTracker:
|
|
|
288
292
|
usage_text = f" [gold3]Usage:[/gold3] {' • '.join(usage_parts)}"
|
|
289
293
|
|
|
290
294
|
if usage_text:
|
|
291
|
-
display = Group(
|
|
295
|
+
display = Group(
|
|
296
|
+
self._rich_progress, in_progress, capacity_text, usage_text
|
|
297
|
+
)
|
|
292
298
|
else:
|
|
293
299
|
display = Group(self._rich_progress, in_progress, capacity_text)
|
|
294
300
|
live.update(display)
|
|
@@ -2,7 +2,6 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
4
|
src/lm_deluge/__init__.py
|
|
5
|
-
src/lm_deluge/agent.py
|
|
6
5
|
src/lm_deluge/batches.py
|
|
7
6
|
src/lm_deluge/cache.py
|
|
8
7
|
src/lm_deluge/cli.py
|
|
@@ -11,7 +10,6 @@ src/lm_deluge/config.py
|
|
|
11
10
|
src/lm_deluge/embed.py
|
|
12
11
|
src/lm_deluge/errors.py
|
|
13
12
|
src/lm_deluge/file.py
|
|
14
|
-
src/lm_deluge/gemini_limits.py
|
|
15
13
|
src/lm_deluge/image.py
|
|
16
14
|
src/lm_deluge/prompt.py
|
|
17
15
|
src/lm_deluge/request_context.py
|
|
File without changes
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
gemini_flash_limits = {
|
|
2
|
-
"asia-east1": 2000,
|
|
3
|
-
"asia-east2": 200,
|
|
4
|
-
"asia-northeast1": 200,
|
|
5
|
-
"asia-northeast3": 200,
|
|
6
|
-
"asia-south1": 200,
|
|
7
|
-
"asia-southeast1": 3_000,
|
|
8
|
-
"australia-southeast1": 200,
|
|
9
|
-
"europe-central2": 200,
|
|
10
|
-
"europe-north1": 200,
|
|
11
|
-
"europe-southwest1": 200,
|
|
12
|
-
"europe-west1": 10_000,
|
|
13
|
-
"europe-west2": 200,
|
|
14
|
-
"europe-west3": 200,
|
|
15
|
-
"europe-west4": 200,
|
|
16
|
-
"europe-west6": 200,
|
|
17
|
-
"europe-west8": 200,
|
|
18
|
-
"europe-west9": 200,
|
|
19
|
-
# 'me-central1': 200,
|
|
20
|
-
"me-central2": 200,
|
|
21
|
-
"me-west1": 200,
|
|
22
|
-
"northamerica-northeast1": 200,
|
|
23
|
-
"southamerica-east1": 200,
|
|
24
|
-
"us-central1": 5_000,
|
|
25
|
-
"us-east1": 3_000,
|
|
26
|
-
"us-east4": 200,
|
|
27
|
-
# 'us-east5': 200,
|
|
28
|
-
"us-south1": 3_000,
|
|
29
|
-
"us-west1": 5_000,
|
|
30
|
-
"us-west4": 200,
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
# total: 7_520
|
|
34
|
-
gemini_1_5_pro_limits = {
|
|
35
|
-
"asia-east1": 500,
|
|
36
|
-
"asia-east2": 500,
|
|
37
|
-
"asia-northeast1": 500,
|
|
38
|
-
# "asia-northeast2": 500,
|
|
39
|
-
"asia-northeast3": 500,
|
|
40
|
-
"asia-south1": 500,
|
|
41
|
-
"asia-southeast1": 500,
|
|
42
|
-
"australia-southeast1": 60,
|
|
43
|
-
"europe-central2": 500,
|
|
44
|
-
"europe-north1": 60,
|
|
45
|
-
"europe-southwest1": 60,
|
|
46
|
-
"europe-west1": 500,
|
|
47
|
-
"europe-west2": 60,
|
|
48
|
-
"europe-west3": 60,
|
|
49
|
-
"europe-west4": 60,
|
|
50
|
-
"europe-west6": 60,
|
|
51
|
-
"europe-west8": 60,
|
|
52
|
-
"europe-west9": 60,
|
|
53
|
-
"me-central1": 60,
|
|
54
|
-
"me-central2": 60,
|
|
55
|
-
"me-west1": 60,
|
|
56
|
-
"northamerica-northeast1": 60,
|
|
57
|
-
"southamerica-east1": 500,
|
|
58
|
-
"us-central1": 500,
|
|
59
|
-
"us-east1": 500,
|
|
60
|
-
"us-east4": 60,
|
|
61
|
-
# "us-east5": 60,
|
|
62
|
-
"us-south1": 60,
|
|
63
|
-
"us-west1": 500,
|
|
64
|
-
"us-west4": 60,
|
|
65
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lm_deluge-0.0.56 → lm_deluge-0.0.57}/src/lm_deluge/built_in_tools/anthropic/computer_use.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|