ragbits-agents 1.4.0.dev202601261217__tar.gz → 1.4.0.dev202601310254__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.
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/CHANGELOG.md +3 -1
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/PKG-INFO +2 -2
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/pyproject.toml +2 -2
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/__init__.py +8 -2
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/_main.py +67 -62
- ragbits_agents-1.4.0.dev202601310254/src/ragbits/agents/hooks/__init__.py +77 -0
- ragbits_agents-1.4.0.dev202601310254/src/ragbits/agents/hooks/base.py +94 -0
- ragbits_agents-1.4.0.dev202601310254/src/ragbits/agents/hooks/confirmation.py +51 -0
- ragbits_agents-1.4.0.dev202601310254/src/ragbits/agents/hooks/manager.py +194 -0
- ragbits_agents-1.4.0.dev202601310254/src/ragbits/agents/hooks/types.py +141 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/tool.py +14 -49
- ragbits_agents-1.4.0.dev202601310254/tests/__init__.py +0 -0
- ragbits_agents-1.4.0.dev202601310254/tests/unit/__init__.py +0 -0
- ragbits_agents-1.4.0.dev202601310254/tests/unit/conftest.py +71 -0
- ragbits_agents-1.4.0.dev202601310254/tests/unit/hooks/__init__.py +0 -0
- ragbits_agents-1.4.0.dev202601310254/tests/unit/hooks/conftest.py +10 -0
- ragbits_agents-1.4.0.dev202601310254/tests/unit/hooks/test_base.py +35 -0
- ragbits_agents-1.4.0.dev202601310254/tests/unit/hooks/test_confirmation.py +34 -0
- ragbits_agents-1.4.0.dev202601310254/tests/unit/hooks/test_manager.py +182 -0
- ragbits_agents-1.4.0.dev202601310254/tests/unit/hooks/test_types.py +88 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/tests/unit/test_agent.py +122 -0
- ragbits_agents-1.4.0.dev202601261217/tests/unit/test_confirmation.py +0 -535
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/.gitignore +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/README.md +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/a2a/__init__.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/a2a/server.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/cli.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/confirmation.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/exceptions.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/mcp/__init__.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/mcp/server.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/mcp/utils.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/post_processors/__init__.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/post_processors/base.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/post_processors/exceptions.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/post_processors/supervisor.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/py.typed +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/tools/__init__.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/tools/memory.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/tools/openai.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/tools/todo.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/tools/types.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/src/ragbits/agents/types.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/tests/unit/mcp/helpers.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/tests/unit/mcp/test_caching.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/tests/unit/mcp/test_connect_disconnect.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/tests/unit/mcp/test_exceptions.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/tests/unit/mcp/test_mcp_utils.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/tests/unit/post_processors/test_base_post_processors.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/tests/unit/post_processors/test_supervisor.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/tests/unit/tools/test_memory.py +0 -0
- {ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/tests/unit/tools/test_openai.py +0 -0
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
- Add supervisor post-processor (#830)
|
|
18
18
|
- Add support for todo lists generated by agents with examples (#827)
|
|
19
19
|
- Add long-term semantic memory tools for agents (#839)
|
|
20
|
-
- Add
|
|
20
|
+
- Add control over what output from the tool is passed to the LLM (#920)
|
|
21
|
+
- Add support for confirmation requests in agents (#853) (#914)
|
|
22
|
+
- Add hooks system (pre- and post-tool) for lifecycle event interception (#914)
|
|
21
23
|
|
|
22
24
|
## 1.3.0 (2025-09-11)
|
|
23
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ragbits-agents
|
|
3
|
-
Version: 1.4.0.
|
|
3
|
+
Version: 1.4.0.dev202601310254
|
|
4
4
|
Summary: Building blocks for rapid development of GenAI applications
|
|
5
5
|
Project-URL: Homepage, https://github.com/deepsense-ai/ragbits
|
|
6
6
|
Project-URL: Bug Reports, https://github.com/deepsense-ai/ragbits/issues
|
|
@@ -22,7 +22,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
22
22
|
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
23
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
24
|
Requires-Python: >=3.10
|
|
25
|
-
Requires-Dist: ragbits-core==1.4.0.
|
|
25
|
+
Requires-Dist: ragbits-core==1.4.0.dev202601310254
|
|
26
26
|
Provides-Extra: a2a
|
|
27
27
|
Requires-Dist: a2a-sdk<1.0.0,>=0.2.9; extra == 'a2a'
|
|
28
28
|
Requires-Dist: fastapi<1.0.0,>=0.115.0; extra == 'a2a'
|
{ragbits_agents-1.4.0.dev202601261217 → ragbits_agents-1.4.0.dev202601310254}/pyproject.toml
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ragbits-agents"
|
|
3
|
-
version = "1.4.0.
|
|
3
|
+
version = "1.4.0.dev202601310254"
|
|
4
4
|
description = "Building blocks for rapid development of GenAI applications"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
@@ -31,7 +31,7 @@ classifiers = [
|
|
|
31
31
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
32
32
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
33
33
|
]
|
|
34
|
-
dependencies = ["ragbits-core==1.4.0.
|
|
34
|
+
dependencies = ["ragbits-core==1.4.0.dev202601310254"]
|
|
35
35
|
|
|
36
36
|
[project.optional-dependencies]
|
|
37
37
|
a2a = [
|
|
@@ -9,8 +9,12 @@ from ragbits.agents._main import (
|
|
|
9
9
|
ToolCall,
|
|
10
10
|
ToolCallResult,
|
|
11
11
|
)
|
|
12
|
+
from ragbits.agents.hooks import (
|
|
13
|
+
EventType,
|
|
14
|
+
Hook,
|
|
15
|
+
HookManager,
|
|
16
|
+
)
|
|
12
17
|
from ragbits.agents.post_processors.base import PostProcessor, StreamingPostProcessor
|
|
13
|
-
from ragbits.agents.tool import requires_confirmation
|
|
14
18
|
from ragbits.agents.tools import LongTermMemory, MemoryEntry, create_memory_tools
|
|
15
19
|
from ragbits.agents.types import QuestionAnswerAgent, QuestionAnswerPromptInput, QuestionAnswerPromptOutput
|
|
16
20
|
|
|
@@ -22,6 +26,9 @@ __all__ = [
|
|
|
22
26
|
"AgentResultStreaming",
|
|
23
27
|
"AgentRunContext",
|
|
24
28
|
"DownstreamAgentResult",
|
|
29
|
+
"EventType",
|
|
30
|
+
"Hook",
|
|
31
|
+
"HookManager",
|
|
25
32
|
"LongTermMemory",
|
|
26
33
|
"MemoryEntry",
|
|
27
34
|
"PostProcessor",
|
|
@@ -32,5 +39,4 @@ __all__ = [
|
|
|
32
39
|
"ToolCall",
|
|
33
40
|
"ToolCallResult",
|
|
34
41
|
"create_memory_tools",
|
|
35
|
-
"requires_confirmation",
|
|
36
42
|
]
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import hashlib
|
|
3
|
-
import json
|
|
4
2
|
import types
|
|
5
3
|
import uuid
|
|
6
4
|
from collections.abc import AsyncGenerator, AsyncIterator, Callable
|
|
@@ -32,6 +30,10 @@ from ragbits.agents.exceptions import (
|
|
|
32
30
|
AgentToolNotAvailableError,
|
|
33
31
|
AgentToolNotSupportedError,
|
|
34
32
|
)
|
|
33
|
+
from ragbits.agents.hooks import (
|
|
34
|
+
Hook,
|
|
35
|
+
HookManager,
|
|
36
|
+
)
|
|
35
37
|
from ragbits.agents.mcp.server import MCPServer, MCPServerStdio, MCPServerStreamableHttp
|
|
36
38
|
from ragbits.agents.mcp.utils import get_tools
|
|
37
39
|
from ragbits.agents.post_processors.base import (
|
|
@@ -40,7 +42,7 @@ from ragbits.agents.post_processors.base import (
|
|
|
40
42
|
StreamingPostProcessor,
|
|
41
43
|
stream_with_post_processing,
|
|
42
44
|
)
|
|
43
|
-
from ragbits.agents.tool import Tool, ToolCallResult, ToolChoice
|
|
45
|
+
from ragbits.agents.tool import Tool, ToolCallResult, ToolChoice, ToolReturn
|
|
44
46
|
from ragbits.core.audit.traces import trace
|
|
45
47
|
from ragbits.core.llms.base import (
|
|
46
48
|
LLM,
|
|
@@ -65,10 +67,6 @@ with suppress(ImportError):
|
|
|
65
67
|
|
|
66
68
|
from ragbits.core.llms import LiteLLM
|
|
67
69
|
|
|
68
|
-
# Confirmation ID length: 16 hex chars provides sufficient uniqueness
|
|
69
|
-
# while being compact for display and storage
|
|
70
|
-
CONFIRMATION_ID_LENGTH = 16
|
|
71
|
-
|
|
72
70
|
_Input = TypeVar("_Input", bound=BaseModel)
|
|
73
71
|
_Output = TypeVar("_Output")
|
|
74
72
|
|
|
@@ -212,9 +210,10 @@ class AgentRunContext(BaseModel, Generic[DepsT]):
|
|
|
212
210
|
"""Whether to stream events from downstream agents when tools execute other agents."""
|
|
213
211
|
downstream_agents: dict[str, "Agent"] = Field(default_factory=dict)
|
|
214
212
|
"""Registry of all agents that participated in this run"""
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
description="List of confirmed/declined
|
|
213
|
+
tool_confirmations: list[dict[str, Any]] = Field(
|
|
214
|
+
default_factory=list,
|
|
215
|
+
description="List of confirmed/declined tool executions. Each entry has 'confirmation_id' and 'confirmed' "
|
|
216
|
+
"(bool)",
|
|
218
217
|
)
|
|
219
218
|
|
|
220
219
|
def register_agent(self, agent: "Agent") -> None:
|
|
@@ -375,6 +374,7 @@ class Agent(
|
|
|
375
374
|
keep_history: bool = False,
|
|
376
375
|
tools: list[Callable] | None = None,
|
|
377
376
|
mcp_servers: list[MCPServer] | None = None,
|
|
377
|
+
hooks: list[Hook] | None = None,
|
|
378
378
|
default_options: AgentOptions[LLMClientOptionsT] | None = None,
|
|
379
379
|
) -> None:
|
|
380
380
|
"""
|
|
@@ -394,6 +394,7 @@ class Agent(
|
|
|
394
394
|
keep_history: Whether to keep the history of the agent.
|
|
395
395
|
tools: The tools available to the agent.
|
|
396
396
|
mcp_servers: The MCP servers available to the agent.
|
|
397
|
+
hooks: List of tool hooks to register for tool lifecycle events.
|
|
397
398
|
default_options: The default options for the agent run.
|
|
398
399
|
"""
|
|
399
400
|
super().__init__(default_options)
|
|
@@ -416,6 +417,7 @@ class Agent(
|
|
|
416
417
|
self.mcp_servers = mcp_servers or []
|
|
417
418
|
self.history = history or []
|
|
418
419
|
self.keep_history = keep_history
|
|
420
|
+
self.hook_manager = HookManager(hooks)
|
|
419
421
|
|
|
420
422
|
if getattr(self, "system_prompt", None) and not getattr(self, "input_type", None):
|
|
421
423
|
raise ValueError(
|
|
@@ -536,7 +538,9 @@ class Agent(
|
|
|
536
538
|
):
|
|
537
539
|
if isinstance(result, ToolCallResult):
|
|
538
540
|
tool_calls.append(result)
|
|
539
|
-
prompt_with_history = prompt_with_history.add_tool_use_message(
|
|
541
|
+
prompt_with_history = prompt_with_history.add_tool_use_message(
|
|
542
|
+
id=result.id, name=result.name, arguments=result.arguments, result=result.result
|
|
543
|
+
)
|
|
540
544
|
|
|
541
545
|
turn_count += 1
|
|
542
546
|
else:
|
|
@@ -762,7 +766,9 @@ class Agent(
|
|
|
762
766
|
elif isinstance(result, ToolCallResult):
|
|
763
767
|
# Add ALL tool results to history (including pending confirmations)
|
|
764
768
|
# This allows the agent to see them in the next turn
|
|
765
|
-
prompt_with_history = prompt_with_history.add_tool_use_message(
|
|
769
|
+
prompt_with_history = prompt_with_history.add_tool_use_message(
|
|
770
|
+
id=result.id, name=result.name, arguments=result.arguments, result=result.result
|
|
771
|
+
)
|
|
766
772
|
returned_tool_call = True
|
|
767
773
|
|
|
768
774
|
# If we have pending confirmations, prepare for text-only summary generation
|
|
@@ -958,54 +964,37 @@ class Agent(
|
|
|
958
964
|
|
|
959
965
|
tool = tools_mapping[tool_call.name]
|
|
960
966
|
|
|
961
|
-
#
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
# Generate a stable confirmation ID based on tool name and arguments
|
|
967
|
-
confirmation_id = hashlib.sha256(
|
|
968
|
-
f"{tool_call.name}:{json.dumps(tool_call.arguments, sort_keys=True)}".encode()
|
|
969
|
-
).hexdigest()[:CONFIRMATION_ID_LENGTH]
|
|
967
|
+
# Execute PRE_TOOL hooks with chaining
|
|
968
|
+
pre_tool_result = await self.hook_manager.execute_pre_tool(
|
|
969
|
+
tool_call=tool_call,
|
|
970
|
+
context=context,
|
|
971
|
+
)
|
|
970
972
|
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
973
|
+
# Check decision
|
|
974
|
+
if pre_tool_result.decision == "deny":
|
|
975
|
+
yield ToolCallResult(
|
|
976
|
+
id=tool_call.id,
|
|
977
|
+
name=tool_call.name,
|
|
978
|
+
arguments=tool_call.arguments,
|
|
979
|
+
result=pre_tool_result.reason or "Tool execution denied",
|
|
974
980
|
)
|
|
975
|
-
|
|
976
|
-
|
|
981
|
+
return
|
|
982
|
+
# Handle "ask" decision from hooks
|
|
983
|
+
elif pre_tool_result.decision == "ask" and pre_tool_result.confirmation_request is not None:
|
|
984
|
+
yield pre_tool_result.confirmation_request
|
|
985
|
+
|
|
986
|
+
yield ToolCallResult(
|
|
987
|
+
id=tool_call.id,
|
|
988
|
+
name=tool_call.name,
|
|
989
|
+
arguments=tool_call.arguments,
|
|
990
|
+
result=pre_tool_result.reason or "Hook requires user confirmation",
|
|
977
991
|
)
|
|
992
|
+
return
|
|
978
993
|
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
yield ToolCallResult(
|
|
982
|
-
id=tool_call.id,
|
|
983
|
-
name=tool_call.name,
|
|
984
|
-
arguments=tool_call.arguments,
|
|
985
|
-
result="❌ Action declined by user",
|
|
986
|
-
)
|
|
987
|
-
return
|
|
988
|
-
|
|
989
|
-
if not is_confirmed:
|
|
990
|
-
# Tool not confirmed yet - create and yield confirmation request
|
|
991
|
-
request = ConfirmationRequest(
|
|
992
|
-
confirmation_id=confirmation_id,
|
|
993
|
-
tool_name=tool_call.name,
|
|
994
|
-
tool_description=tool.description or "",
|
|
995
|
-
arguments=tool_call.arguments,
|
|
996
|
-
)
|
|
997
|
-
|
|
998
|
-
# Yield confirmation request (will be streamed to frontend)
|
|
999
|
-
yield request
|
|
994
|
+
# Always update arguments (chained from hooks)
|
|
995
|
+
tool_call.arguments = pre_tool_result.arguments
|
|
1000
996
|
|
|
1001
|
-
|
|
1002
|
-
yield ToolCallResult(
|
|
1003
|
-
id=tool_call.id,
|
|
1004
|
-
name=tool_call.name,
|
|
1005
|
-
arguments=tool_call.arguments,
|
|
1006
|
-
result="⏳ Awaiting user confirmation",
|
|
1007
|
-
)
|
|
1008
|
-
return
|
|
997
|
+
tool_error: Exception | None = None
|
|
1009
998
|
|
|
1010
999
|
with trace(agent_id=self.id, tool_name=tool_call.name, tool_arguments=tool_call.arguments) as outputs:
|
|
1011
1000
|
try:
|
|
@@ -1019,35 +1008,51 @@ class Agent(
|
|
|
1019
1008
|
else asyncio.to_thread(tool.on_tool_call, **call_args)
|
|
1020
1009
|
)
|
|
1021
1010
|
|
|
1022
|
-
if isinstance(tool_output,
|
|
1011
|
+
if isinstance(tool_output, ToolReturn):
|
|
1012
|
+
tool_return = tool_output
|
|
1013
|
+
elif isinstance(tool_output, AgentResultStreaming):
|
|
1023
1014
|
async for downstream_item in tool_output:
|
|
1024
1015
|
if context.stream_downstream_events:
|
|
1025
1016
|
yield DownstreamAgentResult(agent_id=tool.id, item=downstream_item)
|
|
1026
|
-
|
|
1027
|
-
tool_output = {
|
|
1028
|
-
"content": tool_output.content,
|
|
1017
|
+
metadata = {
|
|
1029
1018
|
"metadata": tool_output.metadata,
|
|
1030
1019
|
"tool_calls": tool_output.tool_calls,
|
|
1031
1020
|
"usage": tool_output.usage,
|
|
1032
1021
|
}
|
|
1022
|
+
tool_return = ToolReturn(value=tool_output.content, metadata=metadata)
|
|
1023
|
+
else:
|
|
1024
|
+
tool_return = ToolReturn(value=tool_output, metadata=None)
|
|
1033
1025
|
|
|
1034
1026
|
outputs.result = {
|
|
1035
|
-
"tool_output":
|
|
1027
|
+
"tool_output": tool_return.value,
|
|
1036
1028
|
"tool_call_id": tool_call.id,
|
|
1037
1029
|
}
|
|
1038
1030
|
|
|
1039
1031
|
except Exception as e:
|
|
1032
|
+
tool_error = e
|
|
1040
1033
|
outputs.result = {
|
|
1041
1034
|
"error": str(e),
|
|
1042
1035
|
"tool_call_id": tool_call.id,
|
|
1043
1036
|
}
|
|
1044
|
-
|
|
1037
|
+
|
|
1038
|
+
# Execute POST_TOOL hooks with chaining
|
|
1039
|
+
post_tool_result = await self.hook_manager.execute_post_tool(
|
|
1040
|
+
tool_call=tool_call,
|
|
1041
|
+
output=tool_output,
|
|
1042
|
+
error=tool_error,
|
|
1043
|
+
)
|
|
1044
|
+
tool_output = post_tool_result.output
|
|
1045
|
+
|
|
1046
|
+
# Raise error after hooks have been executed
|
|
1047
|
+
if tool_error:
|
|
1048
|
+
raise AgentToolExecutionError(tool_call.name, tool_error) from tool_error
|
|
1045
1049
|
|
|
1046
1050
|
yield ToolCallResult(
|
|
1047
1051
|
id=tool_call.id,
|
|
1048
1052
|
name=tool_call.name,
|
|
1049
1053
|
arguments=tool_call.arguments,
|
|
1050
|
-
result=
|
|
1054
|
+
result=tool_return.value,
|
|
1055
|
+
metadata=tool_return.metadata,
|
|
1051
1056
|
)
|
|
1052
1057
|
|
|
1053
1058
|
@requires_dependencies(["a2a.types"], "a2a")
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hooks system for lifecycle events.
|
|
3
|
+
|
|
4
|
+
This module provides a comprehensive hook system that allows users to register
|
|
5
|
+
custom logic at various points in the execution lifecycle.
|
|
6
|
+
|
|
7
|
+
Available event types:
|
|
8
|
+
- PRE_TOOL: Before a tool is invoked
|
|
9
|
+
- POST_TOOL: After a tool completes
|
|
10
|
+
|
|
11
|
+
Example usage:
|
|
12
|
+
|
|
13
|
+
from ragbits.agents.hooks import (
|
|
14
|
+
EventType,
|
|
15
|
+
Hook,
|
|
16
|
+
PreToolInput,
|
|
17
|
+
PreToolOutput,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Create a pre-tool hook callback
|
|
21
|
+
async def validate_input(input_data: PreToolInput) -> PreToolOutput:
|
|
22
|
+
if input_data.tool_call.name == "dangerous_tool":
|
|
23
|
+
return PreToolOutput(
|
|
24
|
+
arguments=input_data.tool_call.arguments,
|
|
25
|
+
decision="deny",
|
|
26
|
+
reason="This tool is not allowed"
|
|
27
|
+
)
|
|
28
|
+
return PreToolOutput(arguments=input_data.tool_call.arguments, decision="pass")
|
|
29
|
+
|
|
30
|
+
# Create hook instance with proper type annotation
|
|
31
|
+
hook: Hook[PreToolInput, PreToolOutput] = Hook(
|
|
32
|
+
event_type=EventType.PRE_TOOL,
|
|
33
|
+
callback=validate_input,
|
|
34
|
+
tool_names=["dangerous_tool"],
|
|
35
|
+
priority=10
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Register hooks with agent
|
|
39
|
+
agent = Agent(
|
|
40
|
+
...,
|
|
41
|
+
hooks=[hook]
|
|
42
|
+
)
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from ragbits.agents.hooks.base import Hook, HookInputT, HookOutputT
|
|
46
|
+
from ragbits.agents.hooks.confirmation import create_confirmation_hook
|
|
47
|
+
from ragbits.agents.hooks.manager import HookManager
|
|
48
|
+
from ragbits.agents.hooks.types import (
|
|
49
|
+
EventType,
|
|
50
|
+
PostToolHookCallback,
|
|
51
|
+
PostToolInput,
|
|
52
|
+
PostToolOutput,
|
|
53
|
+
PreToolHookCallback,
|
|
54
|
+
PreToolInput,
|
|
55
|
+
PreToolOutput,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
__all__ = [
|
|
59
|
+
# Event types
|
|
60
|
+
"EventType",
|
|
61
|
+
# Core classes
|
|
62
|
+
"Hook",
|
|
63
|
+
# Type variables
|
|
64
|
+
"HookInputT",
|
|
65
|
+
"HookManager",
|
|
66
|
+
"HookOutputT",
|
|
67
|
+
"PostToolHookCallback",
|
|
68
|
+
# Input/output types
|
|
69
|
+
"PostToolInput",
|
|
70
|
+
"PostToolOutput",
|
|
71
|
+
# Callback type aliases
|
|
72
|
+
"PreToolHookCallback",
|
|
73
|
+
"PreToolInput",
|
|
74
|
+
"PreToolOutput",
|
|
75
|
+
# Hook factories
|
|
76
|
+
"create_confirmation_hook",
|
|
77
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base classes for the hooks system.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from collections.abc import Awaitable, Callable
|
|
6
|
+
from typing import Generic, TypeVar
|
|
7
|
+
|
|
8
|
+
from ragbits.agents.hooks.types import EventType, HookEventIO
|
|
9
|
+
|
|
10
|
+
HookInputT = TypeVar("HookInputT", bound=HookEventIO)
|
|
11
|
+
HookOutputT = TypeVar("HookOutputT", bound=HookEventIO)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Hook(Generic[HookInputT, HookOutputT]):
|
|
15
|
+
"""
|
|
16
|
+
A hook that intercepts execution at various lifecycle points.
|
|
17
|
+
|
|
18
|
+
Hooks allow you to:
|
|
19
|
+
- Validate inputs before execution (pre hooks)
|
|
20
|
+
- Control access (pre hooks)
|
|
21
|
+
- Modify inputs (pre hooks)
|
|
22
|
+
- Deny execution (pre hooks)
|
|
23
|
+
- Modify outputs (post hooks)
|
|
24
|
+
- Handle errors (post hooks)
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
event_type: The type of event (e.g., PRE_TOOL, POST_TOOL)
|
|
28
|
+
callback: The async function to call when the event is triggered
|
|
29
|
+
tool_names: List of tool names this hook applies to. If None, applies to all tools.
|
|
30
|
+
priority: Execution priority (lower numbers execute first, default: 100)
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
```python
|
|
34
|
+
from ragbits.agents.hooks import Hook, EventType, PreToolInput, PreToolOutput
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def validate_input(input_data: PreToolInput) -> PreToolOutput:
|
|
38
|
+
if input_data.tool_call.name == "dangerous_tool":
|
|
39
|
+
return PreToolOutput(arguments=input_data.tool_call.arguments, decision="deny", reason="Not allowed")
|
|
40
|
+
return PreToolOutput(arguments=input_data.tool_call.arguments, decision="pass")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
hook: Hook[PreToolInput, PreToolOutput] = Hook(
|
|
44
|
+
event_type=EventType.PRE_TOOL, callback=validate_input, tool_names=["dangerous_tool"], priority=10
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
event_type: EventType,
|
|
52
|
+
callback: Callable[[HookInputT], Awaitable[HookOutputT]],
|
|
53
|
+
tool_names: list[str] | None = None,
|
|
54
|
+
priority: int = 100,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Initialize a hook.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
event_type: The type of event (e.g., PRE_TOOL, POST_TOOL)
|
|
61
|
+
callback: The async function to call when the event is triggered
|
|
62
|
+
tool_names: List of tool names this hook applies to. If None, applies to all tools.
|
|
63
|
+
priority: Execution priority (lower numbers execute first, default: 100)
|
|
64
|
+
"""
|
|
65
|
+
self.event_type = event_type
|
|
66
|
+
self.callback = callback
|
|
67
|
+
self.tool_names = tool_names
|
|
68
|
+
self.priority = priority
|
|
69
|
+
|
|
70
|
+
def matches_tool(self, tool_name: str) -> bool:
|
|
71
|
+
"""
|
|
72
|
+
Check if this hook applies to the given tool name.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
tool_name: The name of the tool to check
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
True if this hook should be executed for the given tool
|
|
79
|
+
"""
|
|
80
|
+
if self.tool_names is None:
|
|
81
|
+
return True
|
|
82
|
+
return tool_name in self.tool_names
|
|
83
|
+
|
|
84
|
+
async def execute(self, hook_input: HookInputT) -> HookOutputT:
|
|
85
|
+
"""
|
|
86
|
+
Execute the hook callback with the given input.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
hook_input: The input to pass to the callback
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
The output from the callback
|
|
93
|
+
"""
|
|
94
|
+
return await self.callback(hook_input)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Helper functions for creating common hooks.
|
|
3
|
+
|
|
4
|
+
This module provides factory functions for creating commonly used hooks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from ragbits.agents.hooks.base import Hook
|
|
8
|
+
from ragbits.agents.hooks.types import EventType, PreToolInput, PreToolOutput
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_confirmation_hook(
|
|
12
|
+
tool_names: list[str] | None = None, priority: int = 1
|
|
13
|
+
) -> Hook[PreToolInput, PreToolOutput]:
|
|
14
|
+
"""
|
|
15
|
+
Create a hook that requires user confirmation before tool execution.
|
|
16
|
+
|
|
17
|
+
The hook returns "ask" decision, which causes the agent to yield a ConfirmationRequest
|
|
18
|
+
and wait for user approval/decline.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
tool_names: List of tool names to require confirmation for. If None, applies to all tools.
|
|
22
|
+
priority: Hook priority (default: 1, runs first)
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Hook configured to require confirmation
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
```python
|
|
29
|
+
from ragbits.agents import Agent
|
|
30
|
+
from ragbits.agents.hooks.confirmation import create_confirmation_hook
|
|
31
|
+
|
|
32
|
+
agent = Agent(
|
|
33
|
+
tools=[delete_file, send_email], hooks=[create_confirmation_hook(tool_names=["delete_file", "send_email"])]
|
|
34
|
+
)
|
|
35
|
+
```
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
async def confirm_hook(input_data: PreToolInput) -> PreToolOutput:
|
|
39
|
+
"""Hook that always returns 'ask' to require confirmation."""
|
|
40
|
+
return PreToolOutput(
|
|
41
|
+
arguments=input_data.tool_call.arguments,
|
|
42
|
+
decision="ask",
|
|
43
|
+
reason=f"Tool '{input_data.tool_call.name}' requires user confirmation",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return Hook(
|
|
47
|
+
event_type=EventType.PRE_TOOL,
|
|
48
|
+
callback=confirm_hook,
|
|
49
|
+
tool_names=tool_names,
|
|
50
|
+
priority=priority,
|
|
51
|
+
)
|