deepanalysts 0.2.3__tar.gz → 0.2.5__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.
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/PKG-INFO +7 -7
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/README.md +2 -2
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/backends/sandbox.py +4 -4
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/clients/basement.py +3 -3
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/middleware/memory.py +4 -4
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/middleware/tool_errors.py +15 -2
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts.egg-info/PKG-INFO +7 -7
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts.egg-info/SOURCES.txt +1 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/pyproject.toml +12 -5
- deepanalysts-0.2.5/tests/test_tool_errors.py +106 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/__init__.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/backends/__init__.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/backends/basement.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/backends/composite.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/backends/filesystem.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/backends/protocol.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/backends/state.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/backends/store.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/backends/supabase_storage.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/backends/utils.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/clients/__init__.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/middleware/__init__.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/middleware/_utils.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/middleware/filesystem.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/middleware/patch_tool_calls.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/middleware/skills.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/middleware/subagents.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/middleware/summarization.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/utils/__init__.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts/utils/retry.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts.egg-info/dependency_links.txt +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts.egg-info/requires.txt +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/deepanalysts.egg-info/top_level.txt +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/setup.cfg +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/tests/test_basement.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/tests/test_composite_backend.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/tests/test_filesystem_middleware.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/tests/test_prompt_sections.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/tests/test_sandbox_backend.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/tests/test_skills_middleware.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/tests/test_store_backend.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/tests/test_summarization_middleware.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/tests/test_supabase_storage_backend.py +0 -0
- {deepanalysts-0.2.3 → deepanalysts-0.2.5}/tests/test_utils.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepanalysts
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: LangChain/LangGraph middleware for building AI agents with memory, skills, and filesystem support
|
|
5
5
|
Author-email: Ganchuluun Narantsatsralt <tsatsralt@swifttech.cloud>
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/SKE-Labs/
|
|
8
|
-
Project-URL: Documentation, https://github.com/SKE-Labs/
|
|
9
|
-
Project-URL: Repository, https://github.com/SKE-Labs/
|
|
10
|
-
Project-URL: Issues, https://github.com/SKE-Labs/
|
|
7
|
+
Project-URL: Homepage, https://github.com/SKE-Labs/deepalpha-cli
|
|
8
|
+
Project-URL: Documentation, https://github.com/SKE-Labs/deepalpha-cli/tree/main/libs/deepanalysts
|
|
9
|
+
Project-URL: Repository, https://github.com/SKE-Labs/deepalpha-cli.git
|
|
10
|
+
Project-URL: Issues, https://github.com/SKE-Labs/deepalpha-cli/issues
|
|
11
11
|
Keywords: langchain,langgraph,agents,middleware,ai,trading
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -57,7 +57,7 @@ Deep Analysts provides a complete middleware stack for LangChain agents:
|
|
|
57
57
|
- **Backends**: Store (LangGraph BaseStore), Sandbox (subprocess execution), Composite (path-based routing)
|
|
58
58
|
- **API Integration**: Basement client for syncing skills/memories to cloud
|
|
59
59
|
|
|
60
|
-
Uses only langchain, langgraph, and standard libraries (no external
|
|
60
|
+
Uses only langchain, langgraph, and standard libraries (no external deepalpha dependencies).
|
|
61
61
|
|
|
62
62
|
## Usage
|
|
63
63
|
|
|
@@ -196,7 +196,7 @@ middleware_stack = [
|
|
|
196
196
|
from deepanalysts.clients import BasementClient
|
|
197
197
|
|
|
198
198
|
client = BasementClient(
|
|
199
|
-
base_url="https://basement.
|
|
199
|
+
base_url="https://basement.deepalpha.mn",
|
|
200
200
|
token="jwt-token",
|
|
201
201
|
)
|
|
202
202
|
|
|
@@ -22,7 +22,7 @@ Deep Analysts provides a complete middleware stack for LangChain agents:
|
|
|
22
22
|
- **Backends**: Store (LangGraph BaseStore), Sandbox (subprocess execution), Composite (path-based routing)
|
|
23
23
|
- **API Integration**: Basement client for syncing skills/memories to cloud
|
|
24
24
|
|
|
25
|
-
Uses only langchain, langgraph, and standard libraries (no external
|
|
25
|
+
Uses only langchain, langgraph, and standard libraries (no external deepalpha dependencies).
|
|
26
26
|
|
|
27
27
|
## Usage
|
|
28
28
|
|
|
@@ -161,7 +161,7 @@ middleware_stack = [
|
|
|
161
161
|
from deepanalysts.clients import BasementClient
|
|
162
162
|
|
|
163
163
|
client = BasementClient(
|
|
164
|
-
base_url="https://basement.
|
|
164
|
+
base_url="https://basement.deepalpha.mn",
|
|
165
165
|
token="jwt-token",
|
|
166
166
|
)
|
|
167
167
|
|
|
@@ -88,9 +88,9 @@ os.makedirs(parent_dir, exist_ok=True)
|
|
|
88
88
|
|
|
89
89
|
with open(file_path, 'w') as f:
|
|
90
90
|
f.write(content)
|
|
91
|
-
" <<'
|
|
91
|
+
" <<'__DEEPALPHA_EOF__'
|
|
92
92
|
{payload_b64}
|
|
93
|
-
|
|
93
|
+
__DEEPALPHA_EOF__"""
|
|
94
94
|
|
|
95
95
|
# Use heredoc to pass edit parameters via stdin to avoid ARG_MAX limits.
|
|
96
96
|
# Stdin format: base64-encoded JSON with {{"path": str, "old": str, "new": str}}.
|
|
@@ -146,9 +146,9 @@ with open(file_path, 'w') as f:
|
|
|
146
146
|
f.write(result)
|
|
147
147
|
|
|
148
148
|
print(count)
|
|
149
|
-
" <<'
|
|
149
|
+
" <<'__DEEPALPHA_EOF__'
|
|
150
150
|
{payload_b64}
|
|
151
|
-
|
|
151
|
+
__DEEPALPHA_EOF__"""
|
|
152
152
|
|
|
153
153
|
_READ_COMMAND_TEMPLATE = """python3 -c "
|
|
154
154
|
import os
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Basement API client for skills and memories.
|
|
2
2
|
|
|
3
3
|
This provides a generic client interface that can be configured for either
|
|
4
|
-
cloud (park) or local (
|
|
4
|
+
cloud (park) or local (deepalpha-cli) usage.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
@@ -15,7 +15,7 @@ import httpx
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
17
|
# Default API endpoint (can be overridden via BASEMENT_API env var)
|
|
18
|
-
DEFAULT_BASEMENT_API = os.environ.get("BASEMENT_API", "https://basement.
|
|
18
|
+
DEFAULT_BASEMENT_API = os.environ.get("BASEMENT_API", "https://basement.deepalpha.mn")
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
@runtime_checkable
|
|
@@ -52,7 +52,7 @@ class BasementClient:
|
|
|
52
52
|
"""Initialize the client.
|
|
53
53
|
|
|
54
54
|
Args:
|
|
55
|
-
base_url: API base URL (defaults to basement.
|
|
55
|
+
base_url: API base URL (defaults to basement.deepalpha.mn)
|
|
56
56
|
token: Static JWT token to use
|
|
57
57
|
token_provider: Callable that returns JWT token dynamically
|
|
58
58
|
timeout: Request timeout in seconds
|
|
@@ -18,8 +18,8 @@ from deepanalysts.backends import CompositeBackend
|
|
|
18
18
|
middleware = MemoryMiddleware(
|
|
19
19
|
backend=backend,
|
|
20
20
|
sources=[
|
|
21
|
-
"~/.
|
|
22
|
-
"./.
|
|
21
|
+
"~/.deepalpha/AGENTS.md",
|
|
22
|
+
"./.deepalpha/AGENTS.md",
|
|
23
23
|
],
|
|
24
24
|
)
|
|
25
25
|
|
|
@@ -142,8 +142,8 @@ class MemoryMiddleware(AgentMiddleware):
|
|
|
142
142
|
backend: Backend instance or factory function that takes runtime
|
|
143
143
|
and returns a backend. Use a factory for StateBackend.
|
|
144
144
|
Optional if using loader mode.
|
|
145
|
-
sources: List of memory file paths to load (e.g., ["~/.
|
|
146
|
-
"./.
|
|
145
|
+
sources: List of memory file paths to load (e.g., ["~/.deepalpha/AGENTS.md",
|
|
146
|
+
"./.deepalpha/AGENTS.md"]). Display names are automatically derived
|
|
147
147
|
from the paths. Sources are loaded in order. Optional if using loader.
|
|
148
148
|
loader: Optional loader for API-based memory loading.
|
|
149
149
|
When provided, takes precedence over backend/sources.
|
|
@@ -57,7 +57,13 @@ class ToolErrorHandlingMiddleware(AgentMiddleware):
|
|
|
57
57
|
self._failure_counts.clear()
|
|
58
58
|
|
|
59
59
|
def _on_failure(self, tool_name: str, error: str, tool_call_id: str) -> ToolMessage:
|
|
60
|
-
"""Track failure and return an appropriate (possibly escalated) error message.
|
|
60
|
+
"""Track failure and return an appropriate (possibly escalated) error message.
|
|
61
|
+
|
|
62
|
+
The returned ToolMessage carries `status="error"` and `name=tool_name`
|
|
63
|
+
so downstream middleware (and any caller scanning history) can
|
|
64
|
+
distinguish failed invocations from successful ones without parsing
|
|
65
|
+
content prefixes.
|
|
66
|
+
"""
|
|
61
67
|
count = self._failure_counts.get(tool_name, 0) + 1
|
|
62
68
|
self._failure_counts[tool_name] = count
|
|
63
69
|
|
|
@@ -70,7 +76,12 @@ class ToolErrorHandlingMiddleware(AgentMiddleware):
|
|
|
70
76
|
else:
|
|
71
77
|
content = f"Tool error: {error}"
|
|
72
78
|
|
|
73
|
-
return ToolMessage(
|
|
79
|
+
return ToolMessage(
|
|
80
|
+
content=content,
|
|
81
|
+
tool_call_id=tool_call_id,
|
|
82
|
+
name=tool_name,
|
|
83
|
+
status="error",
|
|
84
|
+
)
|
|
74
85
|
|
|
75
86
|
def _blocked_message(self, tool_name: str, tool_call_id: str) -> ToolMessage:
|
|
76
87
|
"""Return a message for tools that have already been blocked."""
|
|
@@ -83,6 +94,8 @@ class ToolErrorHandlingMiddleware(AgentMiddleware):
|
|
|
83
94
|
f"again. Inform the user and proceed with available information."
|
|
84
95
|
),
|
|
85
96
|
tool_call_id=tool_call_id,
|
|
97
|
+
name=tool_name,
|
|
98
|
+
status="error",
|
|
86
99
|
)
|
|
87
100
|
|
|
88
101
|
def wrap_tool_call(
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepanalysts
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: LangChain/LangGraph middleware for building AI agents with memory, skills, and filesystem support
|
|
5
5
|
Author-email: Ganchuluun Narantsatsralt <tsatsralt@swifttech.cloud>
|
|
6
6
|
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/SKE-Labs/
|
|
8
|
-
Project-URL: Documentation, https://github.com/SKE-Labs/
|
|
9
|
-
Project-URL: Repository, https://github.com/SKE-Labs/
|
|
10
|
-
Project-URL: Issues, https://github.com/SKE-Labs/
|
|
7
|
+
Project-URL: Homepage, https://github.com/SKE-Labs/deepalpha-cli
|
|
8
|
+
Project-URL: Documentation, https://github.com/SKE-Labs/deepalpha-cli/tree/main/libs/deepanalysts
|
|
9
|
+
Project-URL: Repository, https://github.com/SKE-Labs/deepalpha-cli.git
|
|
10
|
+
Project-URL: Issues, https://github.com/SKE-Labs/deepalpha-cli/issues
|
|
11
11
|
Keywords: langchain,langgraph,agents,middleware,ai,trading
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
@@ -57,7 +57,7 @@ Deep Analysts provides a complete middleware stack for LangChain agents:
|
|
|
57
57
|
- **Backends**: Store (LangGraph BaseStore), Sandbox (subprocess execution), Composite (path-based routing)
|
|
58
58
|
- **API Integration**: Basement client for syncing skills/memories to cloud
|
|
59
59
|
|
|
60
|
-
Uses only langchain, langgraph, and standard libraries (no external
|
|
60
|
+
Uses only langchain, langgraph, and standard libraries (no external deepalpha dependencies).
|
|
61
61
|
|
|
62
62
|
## Usage
|
|
63
63
|
|
|
@@ -196,7 +196,7 @@ middleware_stack = [
|
|
|
196
196
|
from deepanalysts.clients import BasementClient
|
|
197
197
|
|
|
198
198
|
client = BasementClient(
|
|
199
|
-
base_url="https://basement.
|
|
199
|
+
base_url="https://basement.deepalpha.mn",
|
|
200
200
|
token="jwt-token",
|
|
201
201
|
)
|
|
202
202
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "deepanalysts"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.5"
|
|
4
4
|
description = "LangChain/LangGraph middleware for building AI agents with memory, skills, and filesystem support"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -26,10 +26,10 @@ dependencies = [
|
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
[project.urls]
|
|
29
|
-
Homepage = "https://github.com/SKE-Labs/
|
|
30
|
-
Documentation = "https://github.com/SKE-Labs/
|
|
31
|
-
Repository = "https://github.com/SKE-Labs/
|
|
32
|
-
Issues = "https://github.com/SKE-Labs/
|
|
29
|
+
Homepage = "https://github.com/SKE-Labs/deepalpha-cli"
|
|
30
|
+
Documentation = "https://github.com/SKE-Labs/deepalpha-cli/tree/main/libs/deepanalysts"
|
|
31
|
+
Repository = "https://github.com/SKE-Labs/deepalpha-cli.git"
|
|
32
|
+
Issues = "https://github.com/SKE-Labs/deepalpha-cli/issues"
|
|
33
33
|
|
|
34
34
|
[project.optional-dependencies]
|
|
35
35
|
postgres = [
|
|
@@ -52,3 +52,10 @@ include = ["deepanalysts*"]
|
|
|
52
52
|
|
|
53
53
|
[tool.setuptools.package-data]
|
|
54
54
|
deepanalysts = ["py.typed"]
|
|
55
|
+
|
|
56
|
+
[dependency-groups]
|
|
57
|
+
dev = [
|
|
58
|
+
"anyio>=4.12.1",
|
|
59
|
+
"pytest>=9.0.2",
|
|
60
|
+
"pytest-anyio>=0.0.0",
|
|
61
|
+
]
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Tests for ToolErrorHandlingMiddleware error-message contract.
|
|
2
|
+
|
|
3
|
+
The middleware must mark failure ToolMessages with `status="error"` and
|
|
4
|
+
`name=tool_name` so downstream middleware can distinguish failed tool
|
|
5
|
+
invocations from successful ones without parsing content prefixes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from langchain.agents.middleware.types import ToolCallRequest
|
|
12
|
+
from langchain_core.messages import ToolMessage
|
|
13
|
+
from langchain_core.tools import ToolException
|
|
14
|
+
|
|
15
|
+
from deepanalysts.middleware import ToolErrorHandlingMiddleware
|
|
16
|
+
|
|
17
|
+
pytest_plugins = ("anyio",)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _request(tool_name: str, call_id: str = "c1") -> ToolCallRequest:
|
|
21
|
+
return ToolCallRequest(
|
|
22
|
+
tool_call={"name": tool_name, "args": {}, "id": call_id},
|
|
23
|
+
tool=None,
|
|
24
|
+
state={"messages": []},
|
|
25
|
+
runtime=None,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async def _raise_tool_exc(req: ToolCallRequest) -> ToolMessage:
|
|
30
|
+
raise ToolException("boom")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def _raise_unexpected(req: ToolCallRequest) -> ToolMessage:
|
|
34
|
+
raise RuntimeError("unexpected")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def _success(req: ToolCallRequest) -> ToolMessage:
|
|
38
|
+
return ToolMessage(
|
|
39
|
+
content="ok",
|
|
40
|
+
name=req.tool_call["name"],
|
|
41
|
+
tool_call_id=req.tool_call["id"],
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TestErrorMessageStatus:
|
|
46
|
+
@pytest.mark.anyio
|
|
47
|
+
async def test_tool_exception_marks_status_error(self):
|
|
48
|
+
mw = ToolErrorHandlingMiddleware()
|
|
49
|
+
result = await mw.awrap_tool_call(_request("my_tool"), _raise_tool_exc)
|
|
50
|
+
assert isinstance(result, ToolMessage)
|
|
51
|
+
assert result.status == "error"
|
|
52
|
+
assert result.name == "my_tool"
|
|
53
|
+
assert result.content.startswith("Tool error:")
|
|
54
|
+
|
|
55
|
+
@pytest.mark.anyio
|
|
56
|
+
async def test_unexpected_exception_marks_status_error(self):
|
|
57
|
+
mw = ToolErrorHandlingMiddleware()
|
|
58
|
+
result = await mw.awrap_tool_call(_request("my_tool"), _raise_unexpected)
|
|
59
|
+
assert result.status == "error"
|
|
60
|
+
assert result.name == "my_tool"
|
|
61
|
+
|
|
62
|
+
@pytest.mark.anyio
|
|
63
|
+
async def test_circuit_breaker_stop_marks_status_error(self):
|
|
64
|
+
"""After max_retries failures, the next failure escalates to STOP:
|
|
65
|
+
— that message must also be tagged status=error."""
|
|
66
|
+
mw = ToolErrorHandlingMiddleware(max_retries=2)
|
|
67
|
+
# Two failures to trip the breaker on the next failure-handling path.
|
|
68
|
+
await mw.awrap_tool_call(_request("my_tool", "c1"), _raise_tool_exc)
|
|
69
|
+
result = await mw.awrap_tool_call(_request("my_tool", "c2"), _raise_tool_exc)
|
|
70
|
+
assert result.content.startswith("STOP:")
|
|
71
|
+
assert result.status == "error"
|
|
72
|
+
assert result.name == "my_tool"
|
|
73
|
+
|
|
74
|
+
@pytest.mark.anyio
|
|
75
|
+
async def test_blocked_message_marks_status_error(self):
|
|
76
|
+
"""Once the circuit is open, subsequent calls short-circuit to a
|
|
77
|
+
BLOCKED message — that message must also be status=error."""
|
|
78
|
+
mw = ToolErrorHandlingMiddleware(max_retries=1)
|
|
79
|
+
# Trip the breaker.
|
|
80
|
+
await mw.awrap_tool_call(_request("my_tool", "c1"), _raise_tool_exc)
|
|
81
|
+
# This call is short-circuited via _blocked_message.
|
|
82
|
+
result = await mw.awrap_tool_call(_request("my_tool", "c2"), _success)
|
|
83
|
+
assert result.content.startswith("BLOCKED:")
|
|
84
|
+
assert result.status == "error"
|
|
85
|
+
assert result.name == "my_tool"
|
|
86
|
+
|
|
87
|
+
@pytest.mark.anyio
|
|
88
|
+
async def test_success_passes_through_unchanged(self):
|
|
89
|
+
mw = ToolErrorHandlingMiddleware()
|
|
90
|
+
result = await mw.awrap_tool_call(_request("my_tool"), _success)
|
|
91
|
+
# Successful results are returned as-is — status defaults to success.
|
|
92
|
+
assert result.status == "success"
|
|
93
|
+
assert result.content == "ok"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TestSyncPath:
|
|
97
|
+
def test_sync_tool_exception_marks_status_error(self):
|
|
98
|
+
mw = ToolErrorHandlingMiddleware()
|
|
99
|
+
|
|
100
|
+
def raise_exc(req: ToolCallRequest) -> ToolMessage:
|
|
101
|
+
raise ToolException("boom")
|
|
102
|
+
|
|
103
|
+
result = mw.wrap_tool_call(_request("my_tool"), raise_exc)
|
|
104
|
+
assert result.status == "error"
|
|
105
|
+
assert result.name == "my_tool"
|
|
106
|
+
assert result.content.startswith("Tool error:")
|
|
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
|