lm-deluge 0.0.80__tar.gz → 0.0.81__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.
Files changed (104) hide show
  1. {lm_deluge-0.0.80/src/lm_deluge.egg-info → lm_deluge-0.0.81}/PKG-INFO +2 -3
  2. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/README.md +1 -2
  3. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/pyproject.toml +1 -1
  4. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/__init__.py +1 -2
  5. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/anthropic.py +2 -1
  6. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/base.py +13 -0
  7. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/gemini.py +1 -1
  8. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/openai.py +3 -2
  9. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/client.py +11 -11
  10. lm_deluge-0.0.81/src/lm_deluge/llm_tools/__init__.py +25 -0
  11. {lm_deluge-0.0.80/src/lm_deluge/llm_tools → lm_deluge-0.0.81/src/lm_deluge/pipelines}/__init__.py +0 -7
  12. {lm_deluge-0.0.80/src/lm_deluge/llm_tools → lm_deluge-0.0.81/src/lm_deluge/pipelines}/score.py +2 -2
  13. {lm_deluge-0.0.80/src/lm_deluge/llm_tools → lm_deluge-0.0.81/src/lm_deluge/pipelines}/translate.py +5 -3
  14. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/prompt.py +105 -0
  15. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/request_context.py +2 -2
  16. lm_deluge-0.0.80/src/lm_deluge/tool.py → lm_deluge-0.0.81/src/lm_deluge/tool/__init__.py +531 -314
  17. lm_deluge-0.0.81/src/lm_deluge/tool/prefab/__init__.py +29 -0
  18. lm_deluge-0.0.81/src/lm_deluge/tool/prefab/batch_tool.py +156 -0
  19. {lm_deluge-0.0.80/src/lm_deluge/llm_tools → lm_deluge-0.0.81/src/lm_deluge/tool/prefab}/filesystem.py +1 -1
  20. lm_deluge-0.0.81/src/lm_deluge/tool/prefab/memory.py +190 -0
  21. lm_deluge-0.0.81/src/lm_deluge/tool/prefab/otc/__init__.py +165 -0
  22. lm_deluge-0.0.81/src/lm_deluge/tool/prefab/otc/executor.py +281 -0
  23. lm_deluge-0.0.81/src/lm_deluge/tool/prefab/otc/parse.py +188 -0
  24. {lm_deluge-0.0.80/src/lm_deluge/llm_tools → lm_deluge-0.0.81/src/lm_deluge/tool/prefab}/sandbox.py +251 -61
  25. {lm_deluge-0.0.80/src/lm_deluge/llm_tools → lm_deluge-0.0.81/src/lm_deluge/tool/prefab}/todos.py +1 -1
  26. lm_deluge-0.0.81/src/lm_deluge/tool/prefab/tool_search.py +169 -0
  27. {lm_deluge-0.0.80 → lm_deluge-0.0.81/src/lm_deluge.egg-info}/PKG-INFO +2 -3
  28. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge.egg-info/SOURCES.txt +23 -14
  29. lm_deluge-0.0.81/tests/test_batch_tool.py +98 -0
  30. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/tests/test_filesystem.py +8 -6
  31. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/tests/test_filesystem_live.py +1 -1
  32. lm_deluge-0.0.81/tests/test_otc.py +117 -0
  33. lm_deluge-0.0.81/tests/test_tool_search.py +86 -0
  34. lm_deluge-0.0.80/src/lm_deluge/presets/cerebras.py +0 -17
  35. lm_deluge-0.0.80/src/lm_deluge/presets/meta.py +0 -13
  36. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/LICENSE +0 -0
  37. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/setup.cfg +0 -0
  38. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/__init__.py +0 -0
  39. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/bedrock.py +0 -0
  40. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/chat_reasoning.py +0 -0
  41. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/common.py +0 -0
  42. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/deprecated/bedrock.py +0 -0
  43. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/deprecated/cohere.py +0 -0
  44. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/deprecated/deepseek.py +0 -0
  45. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/deprecated/mistral.py +0 -0
  46. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/deprecated/vertex.py +0 -0
  47. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/mistral.py +0 -0
  48. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/api_requests/response.py +0 -0
  49. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/batches.py +0 -0
  50. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/built_in_tools/anthropic/__init__.py +0 -0
  51. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/built_in_tools/anthropic/bash.py +0 -0
  52. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/built_in_tools/anthropic/computer_use.py +0 -0
  53. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/built_in_tools/anthropic/editor.py +0 -0
  54. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/built_in_tools/base.py +0 -0
  55. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/built_in_tools/openai.py +0 -0
  56. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/cache.py +0 -0
  57. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/cli.py +0 -0
  58. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/config.py +0 -0
  59. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/embed.py +0 -0
  60. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/errors.py +0 -0
  61. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/file.py +0 -0
  62. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/image.py +0 -0
  63. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/mock_openai.py +0 -0
  64. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/__init__.py +0 -0
  65. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/anthropic.py +0 -0
  66. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/bedrock.py +0 -0
  67. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/cerebras.py +0 -0
  68. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/cohere.py +0 -0
  69. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/deepseek.py +0 -0
  70. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/fireworks.py +0 -0
  71. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/google.py +0 -0
  72. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/grok.py +0 -0
  73. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/groq.py +0 -0
  74. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/kimi.py +0 -0
  75. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/meta.py +0 -0
  76. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/minimax.py +0 -0
  77. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/mistral.py +0 -0
  78. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/openai.py +0 -0
  79. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/openrouter.py +0 -0
  80. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/models/together.py +0 -0
  81. {lm_deluge-0.0.80/src/lm_deluge/llm_tools → lm_deluge-0.0.81/src/lm_deluge/pipelines}/classify.py +0 -0
  82. {lm_deluge-0.0.80/src/lm_deluge/llm_tools → lm_deluge-0.0.81/src/lm_deluge/pipelines}/extract.py +0 -0
  83. {lm_deluge-0.0.80/src/lm_deluge/llm_tools → lm_deluge-0.0.81/src/lm_deluge/pipelines}/locate.py +0 -0
  84. {lm_deluge-0.0.80/src/lm_deluge/llm_tools → lm_deluge-0.0.81/src/lm_deluge/pipelines}/ocr.py +0 -0
  85. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/rerank.py +0 -0
  86. {lm_deluge-0.0.80/src/lm_deluge/llm_tools → lm_deluge-0.0.81/src/lm_deluge/tool/prefab}/subagents.py +0 -0
  87. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/tracker.py +0 -0
  88. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/usage.py +0 -0
  89. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/util/harmony.py +0 -0
  90. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/util/json.py +0 -0
  91. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/util/logprobs.py +0 -0
  92. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/util/schema.py +0 -0
  93. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/util/spatial.py +0 -0
  94. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/util/validation.py +0 -0
  95. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/util/xml.py +0 -0
  96. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge/warnings.py +0 -0
  97. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge.egg-info/dependency_links.txt +0 -0
  98. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge.egg-info/requires.txt +0 -0
  99. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/src/lm_deluge.egg-info/top_level.txt +0 -0
  100. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/tests/test_builtin_tools.py +0 -0
  101. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/tests/test_file_upload.py +0 -0
  102. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/tests/test_mock_openai.py +0 -0
  103. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/tests/test_native_mcp_server.py +0 -0
  104. {lm_deluge-0.0.80 → lm_deluge-0.0.81}/tests/test_openrouter_generic.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lm_deluge
3
- Version: 0.0.80
3
+ Version: 0.0.81
4
4
  Summary: Python utility for using LLM API models.
5
5
  Author-email: Benjamin Anderson <ben@trytaylor.ai>
6
6
  Requires-Python: >=3.10
@@ -296,11 +296,10 @@ We support structured outputs via `json_mode` parameter provided to `SamplingPar
296
296
 
297
297
  ## Built‑in tools
298
298
 
299
- The `lm_deluge.llm_tools` package exposes a few helper functions:
299
+ The `lm_deluge.pipelines` module exposes a few helper functions that combine LLMClient with prompt and output parsing to accomplish tasks:
300
300
 
301
301
  - `extract` – structure text or images into a Pydantic model based on a schema.
302
302
  - `translate` – translate a list of strings to English.
303
303
  - `score_llm` – simple yes/no style scoring with optional log probability output.
304
- - `FilesystemManager` – expose a sandboxed read/write filesystem tool (with optional regex search and `apply_patch` support) that agents can call without touching the host machine.
305
304
 
306
305
  Experimental embeddings (`embed.embed_parallel_async`) and document reranking (`rerank.rerank_parallel_async`) clients are also provided.
@@ -267,11 +267,10 @@ We support structured outputs via `json_mode` parameter provided to `SamplingPar
267
267
 
268
268
  ## Built‑in tools
269
269
 
270
- The `lm_deluge.llm_tools` package exposes a few helper functions:
270
+ The `lm_deluge.pipelines` module exposes a few helper functions that combine LLMClient with prompt and output parsing to accomplish tasks:
271
271
 
272
272
  - `extract` – structure text or images into a Pydantic model based on a schema.
273
273
  - `translate` – translate a list of strings to English.
274
274
  - `score_llm` – simple yes/no style scoring with optional log probability output.
275
- - `FilesystemManager` – expose a sandboxed read/write filesystem tool (with optional regex search and `apply_patch` support) that agents can call without touching the host machine.
276
275
 
277
276
  Experimental embeddings (`embed.embed_parallel_async`) and document reranking (`rerank.rerank_parallel_async`) clients are also provided.
@@ -3,7 +3,7 @@ requires = ["setuptools", "wheel"]
3
3
 
4
4
  [project]
5
5
  name = "lm_deluge"
6
- version = "0.0.80"
6
+ version = "0.0.81"
7
7
  authors = [{ name = "Benjamin Anderson", email = "ben@trytaylor.ai" }]
8
8
  description = "Python utility for using LLM API models."
9
9
  readme = "README.md"
@@ -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, ToolParams
4
+ from .tool import Tool
5
5
 
6
6
  try:
7
7
  from .mock_openai import ( # noqa
@@ -25,7 +25,6 @@ __all__ = [
25
25
  "Conversation",
26
26
  "Message",
27
27
  "Tool",
28
- "ToolParams",
29
28
  "File",
30
29
  ]
31
30
 
@@ -64,7 +64,7 @@ def _build_anthropic_request(
64
64
  }
65
65
 
66
66
  if model.id == "claude-4.5-opus" and sampling_params.global_effort:
67
- request_json["effort"] = sampling_params.global_effort
67
+ request_json["output_config"] = {"effort": sampling_params.global_effort}
68
68
  _add_beta(base_headers, "effort-2025-11-24")
69
69
 
70
70
  # handle thinking
@@ -115,6 +115,7 @@ def _build_anthropic_request(
115
115
  if "4-1" in model.name or "4-5" in model.name:
116
116
  request_json.pop("top_p")
117
117
 
118
+ # print(request_json)
118
119
  # Handle structured outputs (output_format)
119
120
  if context.output_schema:
120
121
  if model.supports_json:
@@ -222,6 +222,19 @@ class APIRequestBase(ABC):
222
222
  usage=None,
223
223
  )
224
224
 
225
+ except aiohttp.ServerDisconnectedError:
226
+ return APIResponse(
227
+ id=self.context.task_id,
228
+ model_internal=self.context.model_name,
229
+ prompt=self.context.prompt,
230
+ sampling_params=self.context.sampling_params,
231
+ status_code=None,
232
+ is_error=True,
233
+ error_message="Server disconnected.",
234
+ content=None,
235
+ usage=None,
236
+ )
237
+
225
238
  except Exception as e:
226
239
  raise_if_modal_exception(e)
227
240
  tb = traceback.format_exc()
@@ -173,7 +173,7 @@ class GeminiRequest(APIRequestBase):
173
173
  self.request_json = await _build_gemini_request(
174
174
  self.model,
175
175
  self.context.prompt,
176
- self.context.tools,
176
+ self.context.tools, # type: ignore
177
177
  self.context.sampling_params,
178
178
  )
179
179
 
@@ -2,17 +2,18 @@ import json
2
2
  import os
3
3
  import traceback as tb
4
4
  from types import SimpleNamespace
5
+ from typing import Sequence
5
6
 
6
7
  import aiohttp
7
8
  from aiohttp import ClientResponse
8
9
 
9
10
  from lm_deluge.request_context import RequestContext
10
11
  from lm_deluge.tool import MCPServer, Tool
11
- from lm_deluge.warnings import maybe_warn
12
12
  from lm_deluge.util.schema import (
13
13
  prepare_output_schema,
14
14
  transform_schema_for_openai,
15
15
  )
16
+ from lm_deluge.warnings import maybe_warn
16
17
 
17
18
  from ..config import SamplingParams
18
19
  from ..models import APIModel
@@ -610,7 +611,7 @@ async def stream_chat(
610
611
  model_name: str, # must correspond to registry
611
612
  prompt: Conversation,
612
613
  sampling_params: SamplingParams = SamplingParams(),
613
- tools: list | None = None,
614
+ tools: Sequence[Tool | dict | MCPServer] | None = None,
614
615
  cache: CachePattern | None = None,
615
616
  extra_headers: dict[str, str] | None = None,
616
617
  ):
@@ -572,7 +572,7 @@ class _LLMClient(BaseModel):
572
572
  *,
573
573
  return_completions_only: Literal[True],
574
574
  show_progress: bool = ...,
575
- tools: list[Tool | dict | MCPServer] | None = ...,
575
+ tools: Sequence[Tool | dict | MCPServer] | None = ...,
576
576
  output_schema: type[BaseModel] | dict | None = ...,
577
577
  cache: CachePattern | None = ...,
578
578
  service_tier: Literal["auto", "default", "flex", "priority"] | None = ...,
@@ -585,7 +585,7 @@ class _LLMClient(BaseModel):
585
585
  *,
586
586
  return_completions_only: Literal[False] = ...,
587
587
  show_progress: bool = ...,
588
- tools: list[Tool | dict | MCPServer] | None = ...,
588
+ tools: Sequence[Tool | dict | MCPServer] | None = ...,
589
589
  output_schema: type[BaseModel] | dict | None = ...,
590
590
  cache: CachePattern | None = ...,
591
591
  service_tier: Literal["auto", "default", "flex", "priority"] | None = ...,
@@ -597,7 +597,7 @@ class _LLMClient(BaseModel):
597
597
  *,
598
598
  return_completions_only: bool = False,
599
599
  show_progress: bool = True,
600
- tools: list[Tool | dict | MCPServer] | None = None,
600
+ tools: Sequence[Tool | dict | MCPServer] | None = None,
601
601
  output_schema: type[BaseModel] | dict | None = None,
602
602
  cache: CachePattern | None = None,
603
603
  service_tier: Literal["auto", "default", "flex", "priority"] | None = None,
@@ -672,7 +672,7 @@ class _LLMClient(BaseModel):
672
672
  *,
673
673
  return_completions_only: bool = False,
674
674
  show_progress=True,
675
- tools: list[Tool | dict | MCPServer] | None = None,
675
+ tools: Sequence[Tool | dict | MCPServer] | None = None,
676
676
  output_schema: type[BaseModel] | dict | None = None,
677
677
  cache: CachePattern | None = None,
678
678
  ):
@@ -705,7 +705,7 @@ class _LLMClient(BaseModel):
705
705
  self,
706
706
  prompt: Prompt,
707
707
  *,
708
- tools: list[Tool | dict | MCPServer] | None = None,
708
+ tools: Sequence[Tool | dict | MCPServer] | None = None,
709
709
  output_schema: type[BaseModel] | dict | None = None,
710
710
  cache: CachePattern | None = None,
711
711
  service_tier: Literal["auto", "default", "flex", "priority"] | None = None,
@@ -742,7 +742,7 @@ class _LLMClient(BaseModel):
742
742
  self,
743
743
  prompt: Prompt,
744
744
  *,
745
- tools: list[Tool | dict | MCPServer] | None = None,
745
+ tools: Sequence[Tool | dict | MCPServer] | None = None,
746
746
  output_schema: type[BaseModel] | dict | None = None,
747
747
  cache: CachePattern | None = None,
748
748
  service_tier: Literal["auto", "default", "flex", "priority"] | None = None,
@@ -835,7 +835,7 @@ class _LLMClient(BaseModel):
835
835
  async def stream(
836
836
  self,
837
837
  prompt: Prompt,
838
- tools: list[Tool | dict | MCPServer] | None = None,
838
+ tools: Sequence[Tool | dict | MCPServer] | None = None,
839
839
  ):
840
840
  model, sampling_params = self._select_model()
841
841
  prompt = prompts_to_conversations([prompt])[0]
@@ -856,7 +856,7 @@ class _LLMClient(BaseModel):
856
856
  task_id: int,
857
857
  conversation: Conversation,
858
858
  *,
859
- tools: list[Tool | dict | MCPServer] | None = None,
859
+ tools: Sequence[Tool | dict | MCPServer] | None = None,
860
860
  max_rounds: int = 5,
861
861
  ) -> AgentLoopResponse:
862
862
  """Internal method to run agent loop and return wrapped result."""
@@ -920,7 +920,7 @@ class _LLMClient(BaseModel):
920
920
  self,
921
921
  conversation: Prompt,
922
922
  *,
923
- tools: list[Tool | dict | MCPServer] | None = None,
923
+ tools: Sequence[Tool | dict | MCPServer] | None = None,
924
924
  max_rounds: int = 5,
925
925
  ) -> int:
926
926
  """Start an agent loop without waiting for it to complete.
@@ -967,7 +967,7 @@ class _LLMClient(BaseModel):
967
967
  self,
968
968
  conversation: Prompt,
969
969
  *,
970
- tools: list[Tool | dict | MCPServer] | None = None,
970
+ tools: Sequence[Tool | dict | MCPServer] | None = None,
971
971
  max_rounds: int = 5,
972
972
  show_progress: bool = False,
973
973
  ) -> tuple[Conversation, APIResponse]:
@@ -986,7 +986,7 @@ class _LLMClient(BaseModel):
986
986
  self,
987
987
  conversation: Prompt,
988
988
  *,
989
- tools: list[Tool | dict | MCPServer] | None = None,
989
+ tools: Sequence[Tool | dict | MCPServer] | None = None,
990
990
  max_rounds: int = 5,
991
991
  show_progress: bool = False,
992
992
  ) -> tuple[Conversation, APIResponse]:
@@ -0,0 +1,25 @@
1
+ # Backward compatibility - re-export from new locations
2
+ # Pipelines (workflow functions)
3
+ from ..pipelines import extract, extract_async, score_llm, translate, translate_async
4
+
5
+ # Prefab tools (Tool managers)
6
+ from ..tool.prefab import (
7
+ SubAgentManager,
8
+ TodoItem,
9
+ TodoManager,
10
+ TodoPriority,
11
+ TodoStatus,
12
+ )
13
+
14
+ __all__ = [
15
+ "extract",
16
+ "extract_async",
17
+ "TodoItem",
18
+ "TodoManager",
19
+ "TodoPriority",
20
+ "TodoStatus",
21
+ "translate",
22
+ "translate_async",
23
+ "score_llm",
24
+ "SubAgentManager",
25
+ ]
@@ -1,18 +1,11 @@
1
1
  from .extract import extract, extract_async
2
2
  from .score import score_llm
3
- from .subagents import SubAgentManager
4
- from .todos import TodoItem, TodoManager, TodoPriority, TodoStatus
5
3
  from .translate import translate, translate_async
6
4
 
7
5
  __all__ = [
8
6
  "extract",
9
7
  "extract_async",
10
- "TodoItem",
11
- "TodoManager",
12
- "TodoPriority",
13
- "TodoStatus",
14
8
  "translate",
15
9
  "translate_async",
16
10
  "score_llm",
17
- "SubAgentManager",
18
11
  ]
@@ -1,4 +1,4 @@
1
- from ..client import LLMClient, APIResponse
1
+ from ..client import _LLMClient, APIResponse
2
2
  from ..util.logprobs import extract_prob
3
3
 
4
4
  # def extract_prob_yes(logprobs: list[dict]):
@@ -24,7 +24,7 @@ from ..util.logprobs import extract_prob
24
24
  def score_llm(
25
25
  scoring_prompt_template: str,
26
26
  inputs: list[tuple | list | dict], # to format the template
27
- scoring_model: LLMClient,
27
+ scoring_model: _LLMClient,
28
28
  return_probabilities: bool,
29
29
  yes_token: str = "yes",
30
30
  ) -> list[bool | None] | list[float | None]:
@@ -1,5 +1,5 @@
1
1
  import asyncio
2
- from ..client import LLMClient
2
+ from ..client import _LLMClient
3
3
 
4
4
  translation_prompt = (
5
5
  "Translate the following text (enclosed in ```) into English. "
@@ -20,7 +20,9 @@ def is_english(text: str, low_memory: bool = True):
20
20
  return True
21
21
 
22
22
 
23
- async def translate_async(texts: list[str], client: LLMClient, low_memory: bool = True):
23
+ async def translate_async(
24
+ texts: list[str], client: _LLMClient, low_memory: bool = True
25
+ ):
24
26
  to_translate_idxs = [
25
27
  i for i, text in enumerate(texts) if not is_english(text, low_memory=low_memory)
26
28
  ]
@@ -40,5 +42,5 @@ async def translate_async(texts: list[str], client: LLMClient, low_memory: bool
40
42
  return texts
41
43
 
42
44
 
43
- def translate(texts: list[str], client: LLMClient, low_memory: bool = True):
45
+ def translate(texts: list[str], client: _LLMClient, low_memory: bool = True):
44
46
  return asyncio.run(translate_async(texts, client, low_memory))
@@ -1598,6 +1598,111 @@ class Conversation:
1598
1598
 
1599
1599
  return {"messages": serialized}
1600
1600
 
1601
+ def print(self, max_text_length: int = 500, indent: int = 2) -> None:
1602
+ """Pretty-print the conversation to stdout.
1603
+
1604
+ Args:
1605
+ max_text_length: Truncate text content longer than this (default 500 chars)
1606
+ indent: JSON indentation for tool calls/results (default 2)
1607
+ """
1608
+ ROLE_COLORS = {
1609
+ "system": "\033[95m", # magenta
1610
+ "user": "\033[94m", # blue
1611
+ "assistant": "\033[92m", # green
1612
+ "tool": "\033[93m", # yellow
1613
+ }
1614
+ RESET = "\033[0m"
1615
+ DIM = "\033[2m"
1616
+ BOLD = "\033[1m"
1617
+
1618
+ def truncate(text: str, max_len: int) -> str:
1619
+ if len(text) <= max_len:
1620
+ return text
1621
+ return (
1622
+ text[:max_len] + f"{DIM}... [{len(text) - max_len} more chars]{RESET}"
1623
+ )
1624
+
1625
+ def format_json(obj: dict | list, ind: int) -> str:
1626
+ return json.dumps(obj, indent=ind, ensure_ascii=False)
1627
+
1628
+ print(f"\n{BOLD}{'=' * 60}{RESET}")
1629
+ print(f"{BOLD}Conversation ({len(self.messages)} messages){RESET}")
1630
+ print(f"{BOLD}{'=' * 60}{RESET}\n")
1631
+
1632
+ for i, msg in enumerate(self.messages):
1633
+ role_color = ROLE_COLORS.get(msg.role, "")
1634
+ print(f"{role_color}{BOLD}[{msg.role.upper()}]{RESET}")
1635
+
1636
+ for part in msg.parts:
1637
+ if isinstance(part, Text):
1638
+ text = truncate(part.text, max_text_length)
1639
+ # Indent multiline text
1640
+ lines = text.split("\n")
1641
+ if len(lines) > 1:
1642
+ print(" " + "\n ".join(lines))
1643
+ else:
1644
+ print(f" {text}")
1645
+
1646
+ elif isinstance(part, Image):
1647
+ w, h = part.size
1648
+ print(f" {DIM}<Image ({w}x{h})>{RESET}")
1649
+
1650
+ elif isinstance(part, File):
1651
+ size = part.size
1652
+ filename = getattr(part, "filename", None)
1653
+ if filename:
1654
+ print(f" {DIM}<File: {filename} ({size} bytes)>{RESET}")
1655
+ else:
1656
+ print(f" {DIM}<File ({size} bytes)>{RESET}")
1657
+
1658
+ elif isinstance(part, ToolCall):
1659
+ print(
1660
+ f" {DIM}Tool Call:{RESET} {BOLD}{part.name}{RESET} (id: {part.id})"
1661
+ )
1662
+ if part.arguments:
1663
+ args_json = format_json(part.arguments, indent)
1664
+ # Indent the JSON
1665
+ indented = "\n".join(
1666
+ " " + line for line in args_json.split("\n")
1667
+ )
1668
+ print(indented)
1669
+
1670
+ elif isinstance(part, ToolResult):
1671
+ print(f" {DIM}Tool Result:{RESET} (call_id: {part.tool_call_id})")
1672
+ if isinstance(part.result, str):
1673
+ result_text = truncate(part.result, max_text_length)
1674
+ lines = result_text.split("\n")
1675
+ for line in lines:
1676
+ print(f" {line}")
1677
+ elif isinstance(part.result, dict):
1678
+ result_json = format_json(part.result, indent)
1679
+ indented = "\n".join(
1680
+ " " + line for line in result_json.split("\n")
1681
+ )
1682
+ print(indented)
1683
+ elif isinstance(part.result, list):
1684
+ print(f" {DIM}<{len(part.result)} content blocks>{RESET}")
1685
+ for block in part.result:
1686
+ if isinstance(block, Text):
1687
+ block_text = truncate(block.text, max_text_length // 2)
1688
+ print(f" [text] {block_text}")
1689
+ elif isinstance(block, Image):
1690
+ bw, bh = block.size
1691
+ print(f" {DIM}<Image ({bw}x{bh})>{RESET}")
1692
+
1693
+ elif isinstance(part, Thinking):
1694
+ print(f" {DIM}Thinking:{RESET}")
1695
+ thought = truncate(part.content, max_text_length)
1696
+ lines = thought.split("\n")
1697
+ for line in lines:
1698
+ print(f" {DIM}{line}{RESET}")
1699
+
1700
+ # Separator between messages
1701
+ if i < len(self.messages) - 1:
1702
+ print(f"\n{'-' * 40}\n")
1703
+
1704
+ print(f"\n{BOLD}{'=' * 60}{RESET}\n")
1705
+
1601
1706
  @classmethod
1602
1707
  def from_log(cls, payload: dict) -> "Conversation":
1603
1708
  """Re-hydrate a Conversation previously produced by `to_log()`."""
@@ -1,6 +1,6 @@
1
1
  from dataclasses import dataclass, field
2
2
  from functools import cached_property
3
- from typing import Any, Callable, TYPE_CHECKING
3
+ from typing import Any, Callable, Sequence, TYPE_CHECKING
4
4
 
5
5
  from .config import SamplingParams
6
6
  from .prompt import CachePattern, Conversation
@@ -34,7 +34,7 @@ class RequestContext:
34
34
  callback: Callable | None = None
35
35
 
36
36
  # Optional features
37
- tools: list | None = None
37
+ tools: Sequence[Any] | None = None
38
38
  output_schema: "type[BaseModel] | dict | None" = None
39
39
  cache: CachePattern | None = None
40
40
  use_responses_api: bool = False