lm-deluge 0.0.87__py3-none-any.whl → 0.0.89__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/gemini.py +19 -7
- lm_deluge/models/google.py +13 -0
- lm_deluge/tool/prefab/__init__.py +9 -1
- lm_deluge/tool/prefab/full_text_search/__init__.py +285 -0
- lm_deluge/tool/prefab/full_text_search/tantivy_index.py +396 -0
- lm_deluge/tool/prefab/rlm/__init__.py +296 -0
- lm_deluge/tool/prefab/rlm/executor.py +349 -0
- lm_deluge/tool/prefab/rlm/parse.py +144 -0
- lm_deluge/tool/prefab/sandbox.py +908 -0
- {lm_deluge-0.0.87.dist-info → lm_deluge-0.0.89.dist-info}/METADATA +12 -1
- {lm_deluge-0.0.87.dist-info → lm_deluge-0.0.89.dist-info}/RECORD +14 -9
- {lm_deluge-0.0.87.dist-info → lm_deluge-0.0.89.dist-info}/WHEEL +0 -0
- {lm_deluge-0.0.87.dist-info → lm_deluge-0.0.89.dist-info}/licenses/LICENSE +0 -0
- {lm_deluge-0.0.87.dist-info → lm_deluge-0.0.89.dist-info}/top_level.txt +0 -0
lm_deluge/api_requests/gemini.py
CHANGED
|
@@ -52,6 +52,7 @@ async def _build_gemini_request(
|
|
|
52
52
|
|
|
53
53
|
# Handle reasoning models (thinking)
|
|
54
54
|
is_gemini_3 = "gemini-3" in model.name.lower()
|
|
55
|
+
is_gemini_3_flash = "gemini-3-flash" in model.name.lower()
|
|
55
56
|
if is_gemini_3:
|
|
56
57
|
# gemini3 MUST think
|
|
57
58
|
if not sampling_params.reasoning_effort:
|
|
@@ -62,13 +63,24 @@ async def _build_gemini_request(
|
|
|
62
63
|
if effort_key == "xhigh":
|
|
63
64
|
maybe_warn("WARN_XHIGH_TO_HIGH", model_name=model.name)
|
|
64
65
|
effort_key = "high"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
if is_gemini_3_flash:
|
|
67
|
+
# Flash supports minimal, low, medium, high
|
|
68
|
+
level_map = {
|
|
69
|
+
"none": "low",
|
|
70
|
+
"minimal": "minimal",
|
|
71
|
+
"low": "low",
|
|
72
|
+
"medium": "medium",
|
|
73
|
+
"high": "high",
|
|
74
|
+
}
|
|
75
|
+
else:
|
|
76
|
+
# Pro only supports low, high
|
|
77
|
+
level_map = {
|
|
78
|
+
"none": "low",
|
|
79
|
+
"minimal": "low",
|
|
80
|
+
"low": "low",
|
|
81
|
+
"medium": "high",
|
|
82
|
+
"high": "high",
|
|
83
|
+
}
|
|
72
84
|
effort = level_map[effort_key]
|
|
73
85
|
thinking_config = {"thinkingLevel": effort}
|
|
74
86
|
request_json["generationConfig"]["thinkingConfig"] = thinking_config
|
lm_deluge/models/google.py
CHANGED
|
@@ -153,6 +153,19 @@ GOOGLE_MODELS = {
|
|
|
153
153
|
# Note: >200k tokens pricing is $4/$18 per million
|
|
154
154
|
"reasoning_model": True,
|
|
155
155
|
},
|
|
156
|
+
"gemini-3-flash-preview": {
|
|
157
|
+
"id": "gemini-3-flash-preview",
|
|
158
|
+
"name": "gemini-3-flash-preview",
|
|
159
|
+
"api_base": "https://generativelanguage.googleapis.com/v1alpha",
|
|
160
|
+
"api_key_env_var": "GEMINI_API_KEY",
|
|
161
|
+
"supports_json": True,
|
|
162
|
+
"supports_logprobs": False,
|
|
163
|
+
"api_spec": "gemini",
|
|
164
|
+
"input_cost": 0.5,
|
|
165
|
+
"cached_input_cost": 0.125, # estimated
|
|
166
|
+
"output_cost": 3.0,
|
|
167
|
+
"reasoning_model": True,
|
|
168
|
+
},
|
|
156
169
|
# Gemini 2.5 Computer Use model
|
|
157
170
|
"gemini-2.5-computer-use": {
|
|
158
171
|
"id": "gemini-2.5-computer-use",
|
|
@@ -7,18 +7,23 @@ from .filesystem import (
|
|
|
7
7
|
from .batch_tool import BatchTool
|
|
8
8
|
from .tool_search import ToolSearchTool
|
|
9
9
|
from .otc import ToolComposer
|
|
10
|
-
from .
|
|
10
|
+
from .rlm import RLMManager, RLMPipeline, RLMResult
|
|
11
|
+
from .sandbox import DaytonaSandbox, DockerSandbox, FargateSandbox, ModalSandbox
|
|
11
12
|
from .docs import DocsManager
|
|
12
13
|
from .sheets import SheetsManager
|
|
13
14
|
from .random import RandomTools
|
|
14
15
|
from .subagents import SubAgentManager
|
|
15
16
|
from .todos import TodoItem, TodoManager, TodoPriority, TodoStatus
|
|
16
17
|
from .email import EmailManager
|
|
18
|
+
from .full_text_search import FullTextSearchManager
|
|
17
19
|
|
|
18
20
|
__all__ = [
|
|
19
21
|
"BatchTool",
|
|
20
22
|
"ToolSearchTool",
|
|
21
23
|
"ToolComposer",
|
|
24
|
+
"RLMManager",
|
|
25
|
+
"RLMPipeline",
|
|
26
|
+
"RLMResult",
|
|
22
27
|
"TodoItem",
|
|
23
28
|
"TodoManager",
|
|
24
29
|
"TodoPriority",
|
|
@@ -30,8 +35,11 @@ __all__ = [
|
|
|
30
35
|
"WorkspaceBackend",
|
|
31
36
|
"ModalSandbox",
|
|
32
37
|
"DaytonaSandbox",
|
|
38
|
+
"DockerSandbox",
|
|
39
|
+
"FargateSandbox",
|
|
33
40
|
"DocsManager",
|
|
34
41
|
"SheetsManager",
|
|
35
42
|
"RandomTools",
|
|
36
43
|
"EmailManager",
|
|
44
|
+
"FullTextSearchManager",
|
|
37
45
|
]
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""Full text search prefab tool using Tantivy."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import tempfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated, Any
|
|
7
|
+
|
|
8
|
+
from lm_deluge.tool import Tool
|
|
9
|
+
|
|
10
|
+
from .tantivy_index import SearchResult, TantivySearch
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FullTextSearchManager:
|
|
14
|
+
"""
|
|
15
|
+
Full-text search tools using Tantivy.
|
|
16
|
+
|
|
17
|
+
Provides two tools:
|
|
18
|
+
- search: Search the corpus and get document IDs + previews
|
|
19
|
+
- fetch: Get the full contents of specific documents by ID
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
corpus: List of document dicts to index. Each dict must have an "id" field.
|
|
23
|
+
search_fields: List of field names to search. If None, searches all fields.
|
|
24
|
+
preview_fields: Fields to include in search result previews.
|
|
25
|
+
index_path: Path to store the Tantivy index. If None, uses a temp directory.
|
|
26
|
+
search_tool_name: Name for the search tool (default: "search")
|
|
27
|
+
fetch_tool_name: Name for the fetch tool (default: "fetch")
|
|
28
|
+
max_results: Maximum number of search results to return (default: 10)
|
|
29
|
+
include_fields: Fields to include in the index (searchable). If None, includes all.
|
|
30
|
+
exclude_fields: Fields to exclude from the index (not searchable).
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
```python
|
|
34
|
+
corpus = [
|
|
35
|
+
{"id": "1", "title": "Hello World", "content": "This is a test document."},
|
|
36
|
+
{"id": "2", "title": "Another Doc", "content": "More content here."},
|
|
37
|
+
]
|
|
38
|
+
manager = FullTextSearchManager(
|
|
39
|
+
corpus=corpus,
|
|
40
|
+
search_fields=["title", "content"],
|
|
41
|
+
preview_fields=["title"],
|
|
42
|
+
)
|
|
43
|
+
tools = manager.get_tools()
|
|
44
|
+
```
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
corpus: list[dict[str, Any]],
|
|
50
|
+
*,
|
|
51
|
+
search_fields: list[str] | None = None,
|
|
52
|
+
preview_fields: list[str] | None = None,
|
|
53
|
+
index_path: str | Path | None = None,
|
|
54
|
+
search_tool_name: str = "search",
|
|
55
|
+
fetch_tool_name: str = "fetch",
|
|
56
|
+
max_results: int = 10,
|
|
57
|
+
include_fields: list[str] | None = None,
|
|
58
|
+
exclude_fields: list[str] | None = None,
|
|
59
|
+
deduplicate_by: str | None = None,
|
|
60
|
+
):
|
|
61
|
+
# Initialize _temp_dir early to avoid __del__ issues
|
|
62
|
+
self._temp_dir: str | None = None
|
|
63
|
+
|
|
64
|
+
self.corpus = corpus
|
|
65
|
+
self.search_fields = search_fields
|
|
66
|
+
self.preview_fields = preview_fields
|
|
67
|
+
self.search_tool_name = search_tool_name
|
|
68
|
+
self.fetch_tool_name = fetch_tool_name
|
|
69
|
+
self.max_results = max_results
|
|
70
|
+
self._tools: list[Tool] | None = None
|
|
71
|
+
|
|
72
|
+
# Validate corpus
|
|
73
|
+
if not corpus:
|
|
74
|
+
raise ValueError("Corpus cannot be empty")
|
|
75
|
+
|
|
76
|
+
# Ensure all documents have an id field
|
|
77
|
+
for i, doc in enumerate(corpus):
|
|
78
|
+
if "id" not in doc:
|
|
79
|
+
raise ValueError(f"Document at index {i} is missing 'id' field")
|
|
80
|
+
|
|
81
|
+
# Set up index path
|
|
82
|
+
if index_path is None:
|
|
83
|
+
self._temp_dir = tempfile.mkdtemp(prefix="tantivy_")
|
|
84
|
+
self._index_path = Path(self._temp_dir)
|
|
85
|
+
else:
|
|
86
|
+
self._temp_dir = None
|
|
87
|
+
self._index_path = Path(index_path)
|
|
88
|
+
|
|
89
|
+
# Determine search fields from corpus if not provided
|
|
90
|
+
if search_fields is None:
|
|
91
|
+
# Use all string fields except 'id'
|
|
92
|
+
sample = corpus[0]
|
|
93
|
+
self.search_fields = [
|
|
94
|
+
k for k, v in sample.items() if k != "id" and isinstance(v, str)
|
|
95
|
+
]
|
|
96
|
+
else:
|
|
97
|
+
self.search_fields = search_fields
|
|
98
|
+
|
|
99
|
+
# Initialize Tantivy index
|
|
100
|
+
self._index = TantivySearch(
|
|
101
|
+
index_path=str(self._index_path),
|
|
102
|
+
include_fields=include_fields,
|
|
103
|
+
exclude_fields=exclude_fields,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Build the index
|
|
107
|
+
self._index.build_index(
|
|
108
|
+
records=corpus,
|
|
109
|
+
deduplicate_by=deduplicate_by,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
# Cache documents for efficient fetch
|
|
113
|
+
self._index.cache_documents_for_fetch(corpus, id_column="id")
|
|
114
|
+
|
|
115
|
+
def _format_preview(self, result: SearchResult) -> dict[str, Any]:
|
|
116
|
+
"""Format a search result for preview."""
|
|
117
|
+
preview: dict[str, Any] = {
|
|
118
|
+
"id": result.id,
|
|
119
|
+
"score": round(result.score, 4),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# Add preview fields
|
|
123
|
+
if self.preview_fields:
|
|
124
|
+
for field in self.preview_fields:
|
|
125
|
+
if field in result.content:
|
|
126
|
+
value = result.content[field]
|
|
127
|
+
# Truncate long values
|
|
128
|
+
if isinstance(value, str) and len(value) > 200:
|
|
129
|
+
value = value[:200] + "..."
|
|
130
|
+
preview[field] = value
|
|
131
|
+
else:
|
|
132
|
+
# Include all fields with truncation
|
|
133
|
+
for field, value in result.content.items():
|
|
134
|
+
if field == "id":
|
|
135
|
+
continue
|
|
136
|
+
if isinstance(value, str) and len(value) > 200:
|
|
137
|
+
value = value[:200] + "..."
|
|
138
|
+
preview[field] = value
|
|
139
|
+
|
|
140
|
+
return preview
|
|
141
|
+
|
|
142
|
+
def _search(
|
|
143
|
+
self,
|
|
144
|
+
query: Annotated[str, "Search query to find relevant documents"],
|
|
145
|
+
limit: Annotated[int, "Maximum number of results to return"] = 10,
|
|
146
|
+
) -> str:
|
|
147
|
+
"""
|
|
148
|
+
Search the corpus for documents matching the query.
|
|
149
|
+
|
|
150
|
+
Returns a list of document previews with IDs and scores.
|
|
151
|
+
Use the fetch tool to get full document contents.
|
|
152
|
+
"""
|
|
153
|
+
try:
|
|
154
|
+
# Use the search fields
|
|
155
|
+
assert self.search_fields is not None
|
|
156
|
+
results = self._index.search(
|
|
157
|
+
queries=[query],
|
|
158
|
+
fields=self.search_fields,
|
|
159
|
+
limit=min(limit, self.max_results),
|
|
160
|
+
escape=True,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
previews = [self._format_preview(r) for r in results]
|
|
164
|
+
|
|
165
|
+
return json.dumps(
|
|
166
|
+
{
|
|
167
|
+
"status": "success",
|
|
168
|
+
"query": query,
|
|
169
|
+
"num_results": len(previews),
|
|
170
|
+
"results": previews,
|
|
171
|
+
},
|
|
172
|
+
indent=2,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
except Exception as e:
|
|
176
|
+
return json.dumps({"status": "error", "error": str(e)})
|
|
177
|
+
|
|
178
|
+
def _fetch(
|
|
179
|
+
self,
|
|
180
|
+
document_ids: Annotated[
|
|
181
|
+
list[str], "List of document IDs to fetch full contents for"
|
|
182
|
+
],
|
|
183
|
+
) -> str:
|
|
184
|
+
"""
|
|
185
|
+
Fetch the full contents of documents by their IDs.
|
|
186
|
+
|
|
187
|
+
Use search first to find relevant document IDs.
|
|
188
|
+
"""
|
|
189
|
+
try:
|
|
190
|
+
documents, found_ids, missing_ids = self._index.fetch(document_ids)
|
|
191
|
+
|
|
192
|
+
result: dict[str, Any] = {
|
|
193
|
+
"status": "success",
|
|
194
|
+
"found": len(found_ids),
|
|
195
|
+
"missing": len(missing_ids),
|
|
196
|
+
"documents": documents,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if missing_ids:
|
|
200
|
+
result["missing_ids"] = missing_ids
|
|
201
|
+
|
|
202
|
+
return json.dumps(result, indent=2)
|
|
203
|
+
|
|
204
|
+
except Exception as e:
|
|
205
|
+
return json.dumps({"status": "error", "error": str(e)})
|
|
206
|
+
|
|
207
|
+
def get_tools(self) -> list[Tool]:
|
|
208
|
+
"""Return the search and fetch tools."""
|
|
209
|
+
if self._tools is not None:
|
|
210
|
+
return self._tools
|
|
211
|
+
|
|
212
|
+
search_tool = Tool.from_function(self._search, name=self.search_tool_name)
|
|
213
|
+
fetch_tool = Tool.from_function(self._fetch, name=self.fetch_tool_name)
|
|
214
|
+
|
|
215
|
+
# Update descriptions for clarity
|
|
216
|
+
search_tool = search_tool.model_copy(
|
|
217
|
+
update={
|
|
218
|
+
"description": (
|
|
219
|
+
"Search the document corpus for relevant results. "
|
|
220
|
+
"Returns document IDs, relevance scores, and previews. "
|
|
221
|
+
"Use the fetch tool to get full document contents."
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
fetch_tool = fetch_tool.model_copy(
|
|
227
|
+
update={
|
|
228
|
+
"description": (
|
|
229
|
+
"Fetch the full contents of documents by their IDs. "
|
|
230
|
+
"Use after searching to get complete document text."
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
self._tools = [search_tool, fetch_tool]
|
|
236
|
+
return self._tools
|
|
237
|
+
|
|
238
|
+
def search(
|
|
239
|
+
self, query: str, limit: int = 10, fields: list[str] | None = None
|
|
240
|
+
) -> list[SearchResult]:
|
|
241
|
+
"""
|
|
242
|
+
Direct search method for programmatic use.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
query: Search query string
|
|
246
|
+
limit: Maximum number of results
|
|
247
|
+
fields: Fields to search (defaults to self.search_fields)
|
|
248
|
+
|
|
249
|
+
Returns:
|
|
250
|
+
List of SearchResult objects
|
|
251
|
+
"""
|
|
252
|
+
search_fields = fields or self.search_fields
|
|
253
|
+
assert search_fields is not None
|
|
254
|
+
return self._index.search(
|
|
255
|
+
queries=[query],
|
|
256
|
+
fields=search_fields,
|
|
257
|
+
limit=min(limit, self.max_results),
|
|
258
|
+
escape=True,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
def fetch(self, document_ids: list[str]) -> list[dict[str, Any]]:
|
|
262
|
+
"""
|
|
263
|
+
Direct fetch method for programmatic use.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
document_ids: List of document IDs to fetch
|
|
267
|
+
|
|
268
|
+
Returns:
|
|
269
|
+
List of document dicts
|
|
270
|
+
"""
|
|
271
|
+
documents, _, _ = self._index.fetch(document_ids)
|
|
272
|
+
return documents
|
|
273
|
+
|
|
274
|
+
def __del__(self):
|
|
275
|
+
"""Clean up temp directory if used."""
|
|
276
|
+
if self._temp_dir is not None:
|
|
277
|
+
import shutil
|
|
278
|
+
|
|
279
|
+
try:
|
|
280
|
+
shutil.rmtree(self._temp_dir, ignore_errors=True)
|
|
281
|
+
except Exception:
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
__all__ = ["FullTextSearchManager", "SearchResult"]
|