autobyteus 1.1.0__py3-none-any.whl → 1.1.2__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.
- autobyteus/agent/bootstrap_steps/agent_bootstrapper.py +1 -1
- autobyteus/agent/bootstrap_steps/agent_runtime_queue_initialization_step.py +1 -1
- autobyteus/agent/bootstrap_steps/base_bootstrap_step.py +1 -1
- autobyteus/agent/bootstrap_steps/system_prompt_processing_step.py +1 -1
- autobyteus/agent/bootstrap_steps/workspace_context_initialization_step.py +1 -1
- autobyteus/agent/context/__init__.py +0 -5
- autobyteus/agent/context/agent_config.py +6 -2
- autobyteus/agent/context/agent_context.py +2 -5
- autobyteus/agent/context/agent_phase_manager.py +105 -5
- autobyteus/agent/context/agent_runtime_state.py +2 -2
- autobyteus/agent/context/phases.py +2 -0
- autobyteus/agent/events/__init__.py +0 -11
- autobyteus/agent/events/agent_events.py +0 -37
- autobyteus/agent/events/notifiers.py +25 -7
- autobyteus/agent/events/worker_event_dispatcher.py +1 -1
- autobyteus/agent/factory/agent_factory.py +6 -2
- autobyteus/agent/group/agent_group.py +16 -7
- autobyteus/agent/handlers/approved_tool_invocation_event_handler.py +28 -14
- autobyteus/agent/handlers/lifecycle_event_logger.py +1 -1
- autobyteus/agent/handlers/llm_complete_response_received_event_handler.py +4 -2
- autobyteus/agent/handlers/tool_invocation_request_event_handler.py +40 -15
- autobyteus/agent/handlers/tool_result_event_handler.py +12 -7
- autobyteus/agent/hooks/__init__.py +7 -0
- autobyteus/agent/hooks/base_phase_hook.py +11 -2
- autobyteus/agent/hooks/hook_definition.py +36 -0
- autobyteus/agent/hooks/hook_meta.py +37 -0
- autobyteus/agent/hooks/hook_registry.py +118 -0
- autobyteus/agent/input_processor/base_user_input_processor.py +6 -3
- autobyteus/agent/input_processor/passthrough_input_processor.py +2 -1
- autobyteus/agent/input_processor/processor_meta.py +1 -1
- autobyteus/agent/input_processor/processor_registry.py +19 -0
- autobyteus/agent/llm_response_processor/base_processor.py +6 -3
- autobyteus/agent/llm_response_processor/processor_meta.py +1 -1
- autobyteus/agent/llm_response_processor/processor_registry.py +19 -0
- autobyteus/agent/llm_response_processor/provider_aware_tool_usage_processor.py +2 -1
- autobyteus/agent/message/context_file_type.py +2 -3
- autobyteus/agent/phases/__init__.py +18 -0
- autobyteus/agent/phases/discover.py +52 -0
- autobyteus/agent/phases/manager.py +265 -0
- autobyteus/agent/phases/phase_enum.py +49 -0
- autobyteus/agent/phases/transition_decorator.py +40 -0
- autobyteus/agent/phases/transition_info.py +33 -0
- autobyteus/agent/remote_agent.py +1 -1
- autobyteus/agent/runtime/agent_runtime.py +5 -10
- autobyteus/agent/runtime/agent_worker.py +62 -19
- autobyteus/agent/streaming/agent_event_stream.py +58 -5
- autobyteus/agent/streaming/stream_event_payloads.py +24 -13
- autobyteus/agent/streaming/stream_events.py +14 -11
- autobyteus/agent/system_prompt_processor/base_processor.py +6 -3
- autobyteus/agent/system_prompt_processor/processor_meta.py +1 -1
- autobyteus/agent/system_prompt_processor/tool_manifest_injector_processor.py +45 -31
- autobyteus/agent/tool_invocation.py +29 -3
- autobyteus/agent/utils/wait_for_idle.py +1 -1
- autobyteus/agent/workspace/__init__.py +2 -0
- autobyteus/agent/workspace/base_workspace.py +33 -11
- autobyteus/agent/workspace/workspace_config.py +160 -0
- autobyteus/agent/workspace/workspace_definition.py +36 -0
- autobyteus/agent/workspace/workspace_meta.py +37 -0
- autobyteus/agent/workspace/workspace_registry.py +72 -0
- autobyteus/cli/__init__.py +4 -3
- autobyteus/cli/agent_cli.py +25 -207
- autobyteus/cli/cli_display.py +205 -0
- autobyteus/events/event_manager.py +2 -1
- autobyteus/events/event_types.py +3 -1
- autobyteus/llm/api/autobyteus_llm.py +2 -12
- autobyteus/llm/api/deepseek_llm.py +11 -173
- autobyteus/llm/api/grok_llm.py +11 -172
- autobyteus/llm/api/kimi_llm.py +24 -0
- autobyteus/llm/api/mistral_llm.py +4 -4
- autobyteus/llm/api/ollama_llm.py +2 -2
- autobyteus/llm/api/openai_compatible_llm.py +193 -0
- autobyteus/llm/api/openai_llm.py +11 -139
- autobyteus/llm/extensions/token_usage_tracking_extension.py +11 -1
- autobyteus/llm/llm_factory.py +168 -42
- autobyteus/llm/models.py +25 -29
- autobyteus/llm/ollama_provider.py +6 -2
- autobyteus/llm/ollama_provider_resolver.py +44 -0
- autobyteus/llm/providers.py +1 -0
- autobyteus/llm/token_counter/kimi_token_counter.py +24 -0
- autobyteus/llm/token_counter/token_counter_factory.py +3 -0
- autobyteus/llm/utils/messages.py +3 -3
- autobyteus/tools/__init__.py +2 -0
- autobyteus/tools/base_tool.py +7 -1
- autobyteus/tools/functional_tool.py +20 -5
- autobyteus/tools/mcp/call_handlers/stdio_handler.py +15 -1
- autobyteus/tools/mcp/config_service.py +106 -127
- autobyteus/tools/mcp/registrar.py +247 -59
- autobyteus/tools/mcp/types.py +5 -3
- autobyteus/tools/registry/tool_definition.py +8 -1
- autobyteus/tools/registry/tool_registry.py +18 -0
- autobyteus/tools/tool_category.py +11 -0
- autobyteus/tools/tool_meta.py +3 -1
- autobyteus/tools/tool_state.py +20 -0
- autobyteus/tools/usage/parsers/_json_extractor.py +99 -0
- autobyteus/tools/usage/parsers/default_json_tool_usage_parser.py +46 -77
- autobyteus/tools/usage/parsers/default_xml_tool_usage_parser.py +87 -96
- autobyteus/tools/usage/parsers/gemini_json_tool_usage_parser.py +37 -47
- autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py +112 -113
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/METADATA +13 -12
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/RECORD +103 -82
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/WHEEL +0 -0
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/licenses/LICENSE +0 -0
- {autobyteus-1.1.0.dist-info → autobyteus-1.1.2.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# file: autobyteus/autobyteus/tools/usage/parsers/openai_json_tool_usage_parser.py
|
|
2
2
|
import json
|
|
3
3
|
import logging
|
|
4
|
-
import re
|
|
5
|
-
import uuid
|
|
6
4
|
from typing import TYPE_CHECKING, List, Optional, Any, Dict
|
|
7
5
|
|
|
8
6
|
from autobyteus.agent.tool_invocation import ToolInvocation
|
|
9
7
|
from .base_parser import BaseToolUsageParser
|
|
8
|
+
from .exceptions import ToolUsageParseException
|
|
9
|
+
from ._json_extractor import _find_json_blobs
|
|
10
10
|
|
|
11
11
|
if TYPE_CHECKING:
|
|
12
12
|
from autobyteus.llm.utils.response_types import CompleteResponse
|
|
@@ -15,133 +15,132 @@ logger = logging.getLogger(__name__)
|
|
|
15
15
|
|
|
16
16
|
class OpenAiJsonToolUsageParser(BaseToolUsageParser):
|
|
17
17
|
"""
|
|
18
|
-
Parses LLM responses for tool usage commands formatted in
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
Parses LLM responses for tool usage commands formatted in various JSON
|
|
19
|
+
styles commonly produced by OpenAI and similar models.
|
|
20
|
+
|
|
21
|
+
This parser is highly flexible and robustly handles multiple formats:
|
|
22
|
+
- A root object with a "tool_calls" or "tools" key containing a list of tool calls.
|
|
23
|
+
- A root object with a single "tool" key.
|
|
24
|
+
- A root object that is itself a single tool call.
|
|
25
|
+
- A raw list of tool calls.
|
|
26
|
+
- Tool call arguments can be either a JSON object or a stringified JSON.
|
|
25
27
|
"""
|
|
26
28
|
def get_name(self) -> str:
|
|
27
29
|
return "openai_json_tool_usage_parser"
|
|
28
30
|
|
|
29
|
-
def _extract_json_from_response(self, text: str) -> Optional[str]:
|
|
30
|
-
match = re.search(r"```(?:json)?\s*([\s\S]+?)\s*```", text)
|
|
31
|
-
if match:
|
|
32
|
-
return match.group(1).strip()
|
|
33
|
-
|
|
34
|
-
# Try to find a JSON object or array
|
|
35
|
-
first_bracket = text.find('[')
|
|
36
|
-
first_brace = text.find('{')
|
|
37
|
-
|
|
38
|
-
if first_brace == -1 and first_bracket == -1:
|
|
39
|
-
return None
|
|
40
|
-
|
|
41
|
-
start_index = -1
|
|
42
|
-
if first_bracket != -1 and first_brace != -1:
|
|
43
|
-
start_index = min(first_bracket, first_brace)
|
|
44
|
-
elif first_bracket != -1:
|
|
45
|
-
start_index = first_bracket
|
|
46
|
-
else: # first_brace != -1
|
|
47
|
-
start_index = first_brace
|
|
48
|
-
|
|
49
|
-
json_substring = text[start_index:]
|
|
50
|
-
try:
|
|
51
|
-
# Check if the substring is valid JSON
|
|
52
|
-
json.loads(json_substring)
|
|
53
|
-
return json_substring
|
|
54
|
-
except json.JSONDecodeError:
|
|
55
|
-
logger.debug(f"Found potential start of JSON, but substring was not valid: {json_substring[:100]}")
|
|
56
|
-
return None
|
|
57
|
-
|
|
58
31
|
def parse(self, response: 'CompleteResponse') -> List[ToolInvocation]:
|
|
32
|
+
response_text = response.content
|
|
59
33
|
invocations: List[ToolInvocation] = []
|
|
60
|
-
|
|
61
|
-
|
|
34
|
+
|
|
35
|
+
# Use a robust method to find all potential JSON blobs in the text
|
|
36
|
+
json_blobs = _find_json_blobs(response_text)
|
|
37
|
+
if not json_blobs:
|
|
62
38
|
logger.debug("No valid JSON object could be extracted from the response content.")
|
|
63
39
|
return invocations
|
|
64
40
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
41
|
+
for blob in json_blobs:
|
|
42
|
+
try:
|
|
43
|
+
data = json.loads(blob)
|
|
44
|
+
|
|
45
|
+
# Determine the structure of the JSON data
|
|
46
|
+
tool_calls_data: List[Dict[str, Any]] = []
|
|
47
|
+
if isinstance(data, list):
|
|
48
|
+
# Format: [{"function":...}, {"function":...}]
|
|
49
|
+
tool_calls_data = data
|
|
50
|
+
elif isinstance(data, dict):
|
|
51
|
+
# Format: {"tool_calls": [...]} or {"tools": [...]}
|
|
52
|
+
if "tool_calls" in data and isinstance(data["tool_calls"], list):
|
|
53
|
+
tool_calls_data = data["tool_calls"]
|
|
54
|
+
elif "tools" in data and isinstance(data["tools"], list):
|
|
55
|
+
tool_calls_data = data["tools"]
|
|
56
|
+
# Format: {"tool": {...}}
|
|
57
|
+
elif "tool" in data and isinstance(data["tool"], dict):
|
|
58
|
+
tool_calls_data = [data["tool"]]
|
|
59
|
+
# Format: {"function": ...}
|
|
60
|
+
else:
|
|
61
|
+
tool_calls_data = [data]
|
|
62
|
+
|
|
63
|
+
if not tool_calls_data:
|
|
64
|
+
logger.debug(f"JSON response does not match any expected tool call format. Content: {blob[:200]}")
|
|
65
|
+
continue
|
|
70
66
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if not isinstance(tool_calls, list):
|
|
76
|
-
tool_calls = data.get("tools")
|
|
77
|
-
elif isinstance(data, list):
|
|
78
|
-
# The entire response is a list of tool calls
|
|
79
|
-
tool_calls = data
|
|
80
|
-
|
|
81
|
-
if not isinstance(tool_calls, list):
|
|
82
|
-
# Handle the case where a single tool call is returned as a dictionary, not in a list.
|
|
83
|
-
if isinstance(data, dict):
|
|
84
|
-
# Check for a 'tool' wrapper. If present, the content is the call.
|
|
85
|
-
if "tool" in data and isinstance(data.get("tool"), dict):
|
|
86
|
-
tool_calls = [data] # The list contains the wrapped object
|
|
87
|
-
# Otherwise, check for standard function/simplified formats.
|
|
88
|
-
elif ('name' in data and 'arguments' in data) or 'function' in data:
|
|
89
|
-
tool_calls = [data]
|
|
90
|
-
|
|
91
|
-
if not isinstance(tool_calls, list):
|
|
92
|
-
logger.warning(f"Expected a list of tool calls, but couldn't find one in the response. Data type: {type(data)}. Skipping.")
|
|
93
|
-
return invocations
|
|
67
|
+
for call_data in tool_calls_data:
|
|
68
|
+
invocation = self._parse_tool_call_object(call_data)
|
|
69
|
+
if invocation:
|
|
70
|
+
invocations.append(invocation)
|
|
94
71
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
72
|
+
except json.JSONDecodeError:
|
|
73
|
+
# This can happen if a blob is not a tool call (e.g., just example JSON).
|
|
74
|
+
# We can safely ignore these.
|
|
75
|
+
logger.debug(f"Could not parse extracted text as JSON in {self.get_name()}. Blob: {blob[:200]}")
|
|
98
76
|
continue
|
|
77
|
+
except Exception as e:
|
|
78
|
+
# If we're here, it's likely a valid JSON but with unexpected structure.
|
|
79
|
+
# It's safer to raise this for upstream handling.
|
|
80
|
+
error_msg = f"Unexpected error while parsing JSON blob: {e}. Blob: {blob[:200]}"
|
|
81
|
+
logger.error(error_msg, exc_info=True)
|
|
82
|
+
raise ToolUsageParseException(error_msg, original_exception=e)
|
|
83
|
+
|
|
84
|
+
return invocations
|
|
99
85
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
86
|
+
def _parse_tool_call_object(self, call_data: Dict[str, Any]) -> Optional[ToolInvocation]:
|
|
87
|
+
"""
|
|
88
|
+
Parses a single tool call object, which can have various structures.
|
|
89
|
+
- {"function": {"name": str, "arguments": str_json_or_dict}}
|
|
90
|
+
- {"name": str, "arguments": dict}
|
|
91
|
+
"""
|
|
92
|
+
if not isinstance(call_data, dict):
|
|
93
|
+
logger.debug(f"Skipping non-dictionary item in tool call list: {call_data}")
|
|
94
|
+
return None
|
|
104
95
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
# The tool call can be in the full format `{"function": ...}` or a simplified `{"name": ...}`.
|
|
110
|
-
function_data: Optional[Dict] = call_data.get("function")
|
|
111
|
-
if not isinstance(function_data, dict):
|
|
112
|
-
# If 'function' key is missing, assume simplified format where call_data is the function data.
|
|
113
|
-
function_data = call_data
|
|
114
|
-
|
|
96
|
+
function_data: Optional[Dict] = call_data.get("function")
|
|
97
|
+
if isinstance(function_data, dict):
|
|
98
|
+
# Standard OpenAI format: {"function": {"name": ..., "arguments": ...}}
|
|
115
99
|
tool_name = function_data.get("name")
|
|
116
100
|
arguments_raw = function_data.get("arguments")
|
|
101
|
+
else:
|
|
102
|
+
# Handle flattened format: {"name": ..., "arguments": ...}
|
|
103
|
+
tool_name = call_data.get("name")
|
|
104
|
+
arguments_raw = call_data.get("arguments")
|
|
117
105
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
106
|
+
if not tool_name or not isinstance(tool_name, str):
|
|
107
|
+
logger.debug(f"Skipping malformed tool call (missing or invalid 'name'): {call_data}")
|
|
108
|
+
return None
|
|
121
109
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
arguments = {}
|
|
110
|
+
arguments: Dict[str, Any]
|
|
111
|
+
if arguments_raw is None:
|
|
112
|
+
arguments = {}
|
|
113
|
+
elif isinstance(arguments_raw, dict):
|
|
114
|
+
# Arguments are already a dictionary
|
|
115
|
+
arguments = arguments_raw
|
|
116
|
+
elif isinstance(arguments_raw, str):
|
|
117
|
+
# Arguments are a stringified JSON
|
|
118
|
+
arg_string = arguments_raw.strip()
|
|
119
|
+
if not arg_string:
|
|
120
|
+
arguments = {}
|
|
133
121
|
else:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
122
|
+
try:
|
|
123
|
+
parsed_args = json.loads(arg_string)
|
|
124
|
+
if not isinstance(parsed_args, dict):
|
|
125
|
+
logger.error(f"Parsed 'arguments' string for tool '{tool_name}' must be a dictionary, but got {type(parsed_args)}.")
|
|
126
|
+
return None
|
|
127
|
+
arguments = parsed_args
|
|
128
|
+
except json.JSONDecodeError as e:
|
|
129
|
+
# If it's a string but not valid JSON, it's a hard error.
|
|
130
|
+
raise ToolUsageParseException(
|
|
131
|
+
f"Failed to parse 'arguments' string for tool '{tool_name}': {arguments_raw}",
|
|
132
|
+
original_exception=e
|
|
133
|
+
)
|
|
134
|
+
else:
|
|
135
|
+
# Any other type for arguments is invalid
|
|
136
|
+
logger.error(f"Skipping tool call with invalid 'arguments' type. Expected dict or string, got {type(arguments_raw)}: {call_data}")
|
|
137
|
+
return None
|
|
138
|
+
|
|
139
|
+
try:
|
|
140
|
+
# The ToolInvocation constructor will generate a deterministic ID if 'id' is None.
|
|
141
|
+
tool_invocation = ToolInvocation(name=tool_name, arguments=arguments, id=None)
|
|
142
|
+
logger.info(f"Successfully parsed OpenAI-style JSON tool invocation for '{tool_name}'.")
|
|
143
|
+
return tool_invocation
|
|
144
|
+
except Exception as e:
|
|
145
|
+
logger.error(f"Unexpected error creating ToolInvocation for tool '{tool_name}': {e}", exc_info=True)
|
|
146
|
+
return None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: autobyteus
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: Multi-Agent framework
|
|
5
5
|
Home-page: https://github.com/AutoByteus/autobyteus
|
|
6
6
|
Author: Ryan Zheng
|
|
@@ -25,12 +25,13 @@ Requires-Dist: boto3
|
|
|
25
25
|
Requires-Dist: botocore
|
|
26
26
|
Requires-Dist: anthropic==0.37.1
|
|
27
27
|
Requires-Dist: Jinja2
|
|
28
|
-
Requires-Dist: ollama
|
|
29
|
-
Requires-Dist: mistral_common
|
|
28
|
+
Requires-Dist: ollama
|
|
29
|
+
Requires-Dist: mistral_common==1.6.3
|
|
30
|
+
Requires-Dist: mistralai==1.5.2
|
|
30
31
|
Requires-Dist: certifi==2025.4.26
|
|
31
32
|
Requires-Dist: numpy==2.2.5
|
|
32
33
|
Requires-Dist: aiohttp
|
|
33
|
-
Requires-Dist: autobyteus-llm-client==1.1.
|
|
34
|
+
Requires-Dist: autobyteus-llm-client==1.1.1
|
|
34
35
|
Requires-Dist: brui-core==1.0.8
|
|
35
36
|
Provides-Extra: dev
|
|
36
37
|
Requires-Dist: coverage; extra == "dev"
|
|
@@ -66,19 +67,19 @@ Dynamic: summary
|
|
|
66
67
|
|
|
67
68
|
# Autobyteus
|
|
68
69
|
|
|
69
|
-
Autobyteus is an open-source
|
|
70
|
+
Autobyteus is an open-source, application-first agentic framework for Python. It is designed to help developers build, test, and deploy complex, stateful, and extensible AI agents by providing a robust architecture and a powerful set of tools.
|
|
70
71
|
|
|
71
72
|
## Architecture
|
|
72
73
|
|
|
73
74
|
Autobyteus is built with a modular, event-driven architecture designed for extensibility and clear separation of concerns. The key components are:
|
|
74
75
|
|
|
75
|
-
- **Agent Core**: The heart of the system
|
|
76
|
-
- **Context & Configuration**: Agent behavior is defined through a
|
|
77
|
-
- **Event-Driven System**: Agents operate on an internal event loop.
|
|
78
|
-
- **Pluggable Processors**: The framework
|
|
79
|
-
- **Tooling**:
|
|
80
|
-
- **
|
|
81
|
-
- **
|
|
76
|
+
- **Agent Core**: The heart of the system. Each agent is a stateful, autonomous entity that runs as a background process in its own thread, managed by a dedicated `AgentWorker`. This design makes every agent a truly independent entity capable of handling long-running tasks.
|
|
77
|
+
- **Context & Configuration**: Agent behavior is defined through a static configuration (`AgentConfig`) and its dynamic state is managed in `AgentRuntimeState`. These are bundled into a comprehensive `AgentContext` that is passed to all components, providing a single source of truth.
|
|
78
|
+
- **Event-Driven System**: Agents operate on an internal `asyncio` event loop. User messages, tool results, and internal signals are handled as events, which are processed by dedicated `EventHandlers`. This decouples logic and makes the system highly extensible.
|
|
79
|
+
- **Pluggable Processors & Hooks**: The framework provides extension points to inject custom logic. `InputProcessors` and `LLMResponseProcessors` modify data in the main processing pipeline, while `PhaseHooks` allow custom code to run on specific agent lifecycle transitions (e.g., from `BOOTSTRAPPING` to `IDLE`).
|
|
80
|
+
- **Context-Aware Tooling**: Tools are first-class citizens that receive the agent's full `AgentContext` during execution. This allows tools to be deeply integrated with the agent's state, configuration, and workspace, enabling more intelligent and powerful actions.
|
|
81
|
+
- **Tool Approval Flow**: The framework has native support for human-in-the-loop workflows. By setting `auto_execute_tools=False` in the agent's configuration, the agent will pause before executing a tool, emit an event requesting permission, and wait for external approval before proceeding.
|
|
82
|
+
- **MCP Integration**: The framework has native support for the Model Context Protocol (MCP). This allows agents to discover and use tools from external, language-agnostic tool servers, making the ecosystem extremely flexible and ready for enterprise integration.
|
|
82
83
|
|
|
83
84
|
## Features
|
|
84
85
|
|