agentx-dev 3.0.2__tar.gz → 3.0.3__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.
- {agentx_dev-3.0.2/agentx_dev.egg-info → agentx_dev-3.0.3}/PKG-INFO +1 -1
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Agents/Agent.py +139 -14
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Runner/AgentRun.py +57 -19
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Runner/AsyncAgentRun.py +31 -2
- {agentx_dev-3.0.2 → agentx_dev-3.0.3/agentx_dev.egg-info}/PKG-INFO +1 -1
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev.egg-info/top_level.txt +0 -1
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/pyproject.toml +1 -1
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/AGENTX.md +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/LICENSE +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/MANIFEST.in +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/README.md +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Agents/__init__.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/AsyncTools.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/AutoSetup.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Cache.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/ChatModel.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Config.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/DefaultTools.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Loader.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/MCP.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Memory.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Observability.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Planner.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Runner/__init__.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Runner/promptTemplate.yaml +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Session.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Streaming.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Supervisor.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/Tools.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/WebTools.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/__init__.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/resources/__init__.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev/resources/promptTemplate.yaml +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev.egg-info/SOURCES.txt +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev.egg-info/dependency_links.txt +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/agentx_dev.egg-info/requires.txt +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/README.md +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/async_example.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/async_quickstart.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/auto_features_example.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/caching_example.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/chatbot_example.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/complete_example.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/concurrent_example.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/concurrent_tool_example.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/file_agent_demo.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/function_calling_demo.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/mcp_demo.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/observability_example.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/orchestration_demo.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/planner_example.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/supervisor_codebase_analysis_demo.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/supervisor_example.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/examples/sync_quickstart.py +0 -0
- {agentx_dev-3.0.2 → agentx_dev-3.0.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-dev
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.3
|
|
4
4
|
Summary: A lightweight LLM agent framework with multi-agent orchestration (Supervisor + dynamic specialist spawning), permission-gated default tools, MCP integration, and prompt-template presets.
|
|
5
5
|
Author-email: Bruce-Arhin Shadrach <brucearhin098@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -99,8 +99,28 @@ class StandardParser(_ParserMixin, BaseModel):
|
|
|
99
99
|
- from_json: Creates an instance from a JSON string.
|
|
100
100
|
- from_function_call: Creates an instance from a tool-call args dict.
|
|
101
101
|
"""
|
|
102
|
-
action: str = Field(
|
|
103
|
-
|
|
102
|
+
action: str = Field(
|
|
103
|
+
default="",
|
|
104
|
+
description=(
|
|
105
|
+
"REQUIRED. Must be one of the registered tool names shown "
|
|
106
|
+
"in the system prompt, OR the exact literal string "
|
|
107
|
+
"'Final_Answer' (with underscore) when you are ready to "
|
|
108
|
+
"return the completed answer. Do NOT put natural-language "
|
|
109
|
+
"response text here — that goes in action_input."
|
|
110
|
+
),
|
|
111
|
+
alias='action',
|
|
112
|
+
)
|
|
113
|
+
action_input: str | Dict | List = Field(
|
|
114
|
+
default="",
|
|
115
|
+
description=(
|
|
116
|
+
"REQUIRED. When action is a tool name, this is the JSON "
|
|
117
|
+
"object matching that tool's argument schema (or the "
|
|
118
|
+
"single string the tool expects). When action is "
|
|
119
|
+
"'Final_Answer', this is the actual answer TEXT you want "
|
|
120
|
+
"the user to see."
|
|
121
|
+
),
|
|
122
|
+
alias='action_input',
|
|
123
|
+
)
|
|
104
124
|
|
|
105
125
|
model_config = ConfigDict(populate_by_name=True)
|
|
106
126
|
|
|
@@ -150,8 +170,27 @@ class Instruction_Tuned_(_ParserMixin, BaseModel):
|
|
|
150
170
|
- action: str, the action to take or final answer.
|
|
151
171
|
- action_input: str, Dict, or List, the input to the action.
|
|
152
172
|
"""
|
|
153
|
-
action: str = Field(
|
|
154
|
-
|
|
173
|
+
action: str = Field(
|
|
174
|
+
default="",
|
|
175
|
+
description=(
|
|
176
|
+
"REQUIRED. Must be one of the registered tool names shown "
|
|
177
|
+
"in the system prompt, OR the exact literal string "
|
|
178
|
+
"'Final_Answer' (with underscore) when you are ready to "
|
|
179
|
+
"return the completed answer. Never put natural-language "
|
|
180
|
+
"response text here — that goes in action_input."
|
|
181
|
+
),
|
|
182
|
+
alias='action',
|
|
183
|
+
)
|
|
184
|
+
action_input: str | Dict | List = Field(
|
|
185
|
+
default="",
|
|
186
|
+
description=(
|
|
187
|
+
"REQUIRED. When action is a tool name, this is the JSON "
|
|
188
|
+
"object matching that tool's schema (or the single string "
|
|
189
|
+
"it expects). When action is 'Final_Answer', this is the "
|
|
190
|
+
"actual answer TEXT you want the user to see."
|
|
191
|
+
),
|
|
192
|
+
alias='action_input',
|
|
193
|
+
)
|
|
155
194
|
|
|
156
195
|
model_config = ConfigDict(populate_by_name=True)
|
|
157
196
|
|
|
@@ -172,9 +211,36 @@ class React_(_ParserMixin, BaseModel):
|
|
|
172
211
|
- action: str, the action to take or final answer.
|
|
173
212
|
- action_input: str, Dict, or List, the input to the action.
|
|
174
213
|
"""
|
|
175
|
-
Thought: str = Field(
|
|
176
|
-
|
|
177
|
-
|
|
214
|
+
Thought: str = Field(
|
|
215
|
+
default="",
|
|
216
|
+
description=(
|
|
217
|
+
"REQUIRED. Brief reasoning for THIS turn: what the last "
|
|
218
|
+
"tool returned + what you'll do differently now. Keep it "
|
|
219
|
+
"concrete, not generic."
|
|
220
|
+
),
|
|
221
|
+
alias='Thought',
|
|
222
|
+
)
|
|
223
|
+
action: str = Field(
|
|
224
|
+
default="",
|
|
225
|
+
description=(
|
|
226
|
+
"REQUIRED. Must be one of the registered tool names shown "
|
|
227
|
+
"in the system prompt, OR the exact literal string "
|
|
228
|
+
"'Final_Answer' (with underscore) when you are ready to "
|
|
229
|
+
"return the completed answer. Never put natural-language "
|
|
230
|
+
"response text here — that goes in action_input."
|
|
231
|
+
),
|
|
232
|
+
alias='action',
|
|
233
|
+
)
|
|
234
|
+
action_input: str | Dict | List = Field(
|
|
235
|
+
default="",
|
|
236
|
+
description=(
|
|
237
|
+
"REQUIRED. When action is a tool name, this is the JSON "
|
|
238
|
+
"object matching that tool's schema (or the single string "
|
|
239
|
+
"it expects). When action is 'Final_Answer', this is the "
|
|
240
|
+
"actual answer TEXT you want the user to see."
|
|
241
|
+
),
|
|
242
|
+
alias='action_input',
|
|
243
|
+
)
|
|
178
244
|
|
|
179
245
|
model_config = ConfigDict(populate_by_name=True)
|
|
180
246
|
|
|
@@ -198,14 +264,35 @@ class ChainOfThought(_ParserMixin, BaseModel):
|
|
|
198
264
|
``instance.action`` / ``instance.action_input`` uniformly across every
|
|
199
265
|
AgentType — no more parser-specific field lookups.
|
|
200
266
|
"""
|
|
201
|
-
Thought: str = Field(
|
|
267
|
+
Thought: str = Field(
|
|
268
|
+
default="",
|
|
269
|
+
description=(
|
|
270
|
+
"REQUIRED. Step-by-step reasoning covering: (a) what the "
|
|
271
|
+
"user asked; (b) what info is needed; (c) which tool fits; "
|
|
272
|
+
"(d) the exact argument shape; (e) whether one call finishes."
|
|
273
|
+
),
|
|
274
|
+
alias='Thought',
|
|
275
|
+
)
|
|
202
276
|
action: str = Field(
|
|
203
|
-
"
|
|
277
|
+
default="",
|
|
278
|
+
description=(
|
|
279
|
+
"REQUIRED. Must be one of the registered tool names shown "
|
|
280
|
+
"in the system prompt, OR the exact literal string "
|
|
281
|
+
"'Final_Answer' (with underscore) when you are ready to "
|
|
282
|
+
"return the completed answer. Never put natural-language "
|
|
283
|
+
"response text here — that goes in action_input."
|
|
284
|
+
),
|
|
204
285
|
validation_alias=AliasChoices("action", "Action"),
|
|
205
286
|
serialization_alias="action",
|
|
206
287
|
)
|
|
207
288
|
action_input: str | Dict | List = Field(
|
|
208
|
-
"
|
|
289
|
+
default="",
|
|
290
|
+
description=(
|
|
291
|
+
"REQUIRED. When action is a tool name, this is the JSON "
|
|
292
|
+
"object matching that tool's schema (or the single string "
|
|
293
|
+
"it expects). When action is 'Final_Answer', this is the "
|
|
294
|
+
"actual answer TEXT you want the user to see."
|
|
295
|
+
),
|
|
209
296
|
validation_alias=AliasChoices("action_input", "Action_Input"),
|
|
210
297
|
serialization_alias="action_input",
|
|
211
298
|
)
|
|
@@ -224,8 +311,27 @@ class ZeroShot(_ParserMixin, BaseModel):
|
|
|
224
311
|
"""
|
|
225
312
|
A Pydantic model representing the Zero-Shot prompt template.
|
|
226
313
|
"""
|
|
227
|
-
action: str = Field(
|
|
228
|
-
|
|
314
|
+
action: str = Field(
|
|
315
|
+
default="",
|
|
316
|
+
description=(
|
|
317
|
+
"REQUIRED. Must be one of the registered tool names shown "
|
|
318
|
+
"in the system prompt, OR the exact literal string "
|
|
319
|
+
"'Final_Answer' (with underscore) when you are ready to "
|
|
320
|
+
"return the completed answer. Never put natural-language "
|
|
321
|
+
"response text here — that goes in action_input."
|
|
322
|
+
),
|
|
323
|
+
alias='action',
|
|
324
|
+
)
|
|
325
|
+
action_input: str | Dict | List = Field(
|
|
326
|
+
default="",
|
|
327
|
+
description=(
|
|
328
|
+
"REQUIRED. When action is a tool name, this is the JSON "
|
|
329
|
+
"object matching that tool's schema (or the single string "
|
|
330
|
+
"it expects). When action is 'Final_Answer', this is the "
|
|
331
|
+
"actual answer TEXT you want the user to see."
|
|
332
|
+
),
|
|
333
|
+
alias='action_input',
|
|
334
|
+
)
|
|
229
335
|
|
|
230
336
|
model_config = ConfigDict(populate_by_name=True)
|
|
231
337
|
|
|
@@ -241,8 +347,27 @@ class FewShot(_ParserMixin, BaseModel):
|
|
|
241
347
|
"""
|
|
242
348
|
A Pydantic model representing the Few-Shot prompt template.
|
|
243
349
|
"""
|
|
244
|
-
action: str = Field(
|
|
245
|
-
|
|
350
|
+
action: str = Field(
|
|
351
|
+
default="",
|
|
352
|
+
description=(
|
|
353
|
+
"REQUIRED. Must be one of the registered tool names shown "
|
|
354
|
+
"in the system prompt, OR the exact literal string "
|
|
355
|
+
"'Final_Answer' (with underscore) when you are ready to "
|
|
356
|
+
"return the completed answer. Never put natural-language "
|
|
357
|
+
"response text here — that goes in action_input."
|
|
358
|
+
),
|
|
359
|
+
alias='action',
|
|
360
|
+
)
|
|
361
|
+
action_input: str | Dict | List = Field(
|
|
362
|
+
default="",
|
|
363
|
+
description=(
|
|
364
|
+
"REQUIRED. When action is a tool name, this is the JSON "
|
|
365
|
+
"object matching that tool's schema (or the single string "
|
|
366
|
+
"it expects). When action is 'Final_Answer', this is the "
|
|
367
|
+
"actual answer TEXT you want the user to see."
|
|
368
|
+
),
|
|
369
|
+
alias='action_input',
|
|
370
|
+
)
|
|
246
371
|
|
|
247
372
|
model_config = ConfigDict(populate_by_name=True)
|
|
248
373
|
|
|
@@ -114,6 +114,34 @@ class CircuitBreaker:
|
|
|
114
114
|
_NO_ARG = object()
|
|
115
115
|
|
|
116
116
|
|
|
117
|
+
# Set of case-insensitive strings that should be treated as the model's
|
|
118
|
+
# "I'm done, return this as the final answer" signal. Exact string
|
|
119
|
+
# "Final_Answer" is the canonical form the prompt template teaches;
|
|
120
|
+
# the tolerant variants exist because under use_function_calling=True
|
|
121
|
+
# the schema doesn't constrain `action` to any specific value, so
|
|
122
|
+
# LLMs (especially gpt-4o-mini) emit "Final Answer" (with space) or
|
|
123
|
+
# "final_answer" (lowercase) or "finalanswer" (no separator). Any of
|
|
124
|
+
# those should end the loop, not get routed to Tool_Runner.
|
|
125
|
+
#
|
|
126
|
+
# Deliberately excludes bare "answer" or "final" — those are too
|
|
127
|
+
# likely to collide with a user-defined tool of the same name.
|
|
128
|
+
_TERMINAL_ACTION_VARIANTS = frozenset({
|
|
129
|
+
"final_answer", "finalanswer",
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _is_terminal_action(action: str) -> bool:
|
|
134
|
+
"""True if ``action`` should end the loop with action_input as the
|
|
135
|
+
final answer. Accepts Final_Answer, Final Answer, final_answer,
|
|
136
|
+
FINAL_ANSWER, finalanswer — any capitalization + underscore/space
|
|
137
|
+
variant. Empty string is not terminal (caller handles that as
|
|
138
|
+
'unrecognized action' via the implicit-final guardrail)."""
|
|
139
|
+
if not action or not isinstance(action, str):
|
|
140
|
+
return False
|
|
141
|
+
normalized = action.strip().lower().replace(" ", "_").replace("-", "_")
|
|
142
|
+
return normalized in _TERMINAL_ACTION_VARIANTS
|
|
143
|
+
|
|
144
|
+
|
|
117
145
|
def _build_sandbox_hint(perms: Any) -> Optional[str]:
|
|
118
146
|
"""Generate a short block describing where the runner is allowed to
|
|
119
147
|
operate on disk. Injected into the system prompt when Permissions
|
|
@@ -1323,42 +1351,52 @@ class AgentRunner:
|
|
|
1323
1351
|
step_description = f"Step {count}: {action} with {action_input}"
|
|
1324
1352
|
steps.append(step_description)
|
|
1325
1353
|
|
|
1326
|
-
|
|
1354
|
+
# Terminal action check — tolerant matching so the loop ends
|
|
1355
|
+
# for the canonical "Final_Answer" AND for the LLM's common
|
|
1356
|
+
# variants ("Final Answer", "final_answer", "finalanswer").
|
|
1357
|
+
# Under use_function_calling=True the schema doesn't
|
|
1358
|
+
# constrain `action` to any spelling, so we accept them all.
|
|
1359
|
+
if _is_terminal_action(action):
|
|
1327
1360
|
final_answer = action_input
|
|
1328
1361
|
yield {"type": "final", "content": final_answer}
|
|
1329
1362
|
break
|
|
1330
1363
|
|
|
1331
|
-
# Guardrail
|
|
1332
|
-
#
|
|
1333
|
-
#
|
|
1334
|
-
#
|
|
1335
|
-
#
|
|
1336
|
-
#
|
|
1337
|
-
#
|
|
1338
|
-
#
|
|
1339
|
-
#
|
|
1340
|
-
#
|
|
1341
|
-
#
|
|
1342
|
-
#
|
|
1364
|
+
# Guardrail: the model emitted an `action` value that is
|
|
1365
|
+
# neither a terminal-answer variant NOR a registered tool.
|
|
1366
|
+
# Two scenarios trigger this:
|
|
1367
|
+
# 1. use_function_calling=True: the parser tool schema
|
|
1368
|
+
# accepts any string, so the model stuffs its natural-
|
|
1369
|
+
# language closing text into `action` (e.g. "Provide a
|
|
1370
|
+
# response to the athlete."). Without this guard, the
|
|
1371
|
+
# runner would dispatch it as a tool, get "not found",
|
|
1372
|
+
# and the turn dies.
|
|
1373
|
+
# 2. action="" (empty). Under FC, the model sometimes
|
|
1374
|
+
# omits `action` entirely; Pydantic falls back to the
|
|
1375
|
+
# field default. Same failure — empty tool name.
|
|
1376
|
+
# Route both to Final_Answer, built from whatever text the
|
|
1377
|
+
# model actually provided (thought + action + action_input).
|
|
1343
1378
|
known_tools = set(self.func) | set(self.args)
|
|
1344
|
-
if
|
|
1379
|
+
if not action or action not in known_tools:
|
|
1345
1380
|
parts: List[str] = []
|
|
1346
1381
|
if thought:
|
|
1347
1382
|
parts.append(str(thought))
|
|
1348
|
-
#
|
|
1349
|
-
|
|
1383
|
+
# Only include `action` in the reply if it looks like
|
|
1384
|
+
# real response text (not empty, not a suspicious short
|
|
1385
|
+
# sentinel like "answer"). Skip empty.
|
|
1386
|
+
if action and action.strip():
|
|
1350
1387
|
parts.append(str(action))
|
|
1351
|
-
# action_input often carries the actual body
|
|
1352
1388
|
if action_input and str(action_input).strip():
|
|
1353
1389
|
parts.append(str(action_input))
|
|
1354
1390
|
final_answer = "\n\n".join(parts) or (
|
|
1355
1391
|
"(agent emitted an unrecognized action and no text)"
|
|
1356
1392
|
)
|
|
1357
1393
|
if self.verbose:
|
|
1394
|
+
preview = (action or "<empty>")[:60]
|
|
1358
1395
|
print(
|
|
1359
1396
|
f"\x1B[1;33m[loop] implicit-final: action "
|
|
1360
|
-
f"'{
|
|
1361
|
-
f"treating turn as
|
|
1397
|
+
f"'{preview}' is not a registered tool and not "
|
|
1398
|
+
f"a Final_Answer variant; treating turn as "
|
|
1399
|
+
f"Final_Answer\x1B[0m"
|
|
1362
1400
|
)
|
|
1363
1401
|
yield {"type": "final", "content": final_answer}
|
|
1364
1402
|
break
|
|
@@ -13,7 +13,7 @@ from agentx_dev.ChatModel import BaseChatModel
|
|
|
13
13
|
from agentx_dev.Agents.Agent import StandardParser, ToolCall, ToolError
|
|
14
14
|
from agentx_dev.Tools import StandardTool, StructuredTool, logger
|
|
15
15
|
from agentx_dev.AsyncTools import AsyncStandardTool, AsyncStructuredTool
|
|
16
|
-
from agentx_dev.Runner.AgentRun import ToolRegistry
|
|
16
|
+
from agentx_dev.Runner.AgentRun import ToolRegistry, _is_terminal_action
|
|
17
17
|
from typing import Dict, Callable, List, Type, Optional, Any, AsyncIterator
|
|
18
18
|
from pydantic import BaseModel, Field
|
|
19
19
|
import asyncio
|
|
@@ -493,7 +493,10 @@ class AsyncAgentRunner:
|
|
|
493
493
|
step_description = f"Step {count}: {action} with {action_input}"
|
|
494
494
|
steps.append(step_description)
|
|
495
495
|
|
|
496
|
-
|
|
496
|
+
# Terminal-action check — tolerant of format variants that
|
|
497
|
+
# LLMs emit under use_function_calling=True where the schema
|
|
498
|
+
# doesn't constrain `action` to a specific spelling.
|
|
499
|
+
if _is_terminal_action(action):
|
|
497
500
|
final_answer = action_input
|
|
498
501
|
break
|
|
499
502
|
|
|
@@ -503,6 +506,32 @@ class AsyncAgentRunner:
|
|
|
503
506
|
elif thought:
|
|
504
507
|
logger.info(f"thought: {thought}")
|
|
505
508
|
|
|
509
|
+
# Implicit-final guardrail: `action` is empty OR not a
|
|
510
|
+
# registered tool AND not a Final_Answer variant. Route to
|
|
511
|
+
# Final_Answer built from the text the model provided.
|
|
512
|
+
known_tools = set(self.registry.sync_std) | set(self.registry.sync_struct) \
|
|
513
|
+
| set(self.registry.async_std) | set(self.registry.async_struct)
|
|
514
|
+
if not action or action not in known_tools:
|
|
515
|
+
parts: List[str] = []
|
|
516
|
+
if thought:
|
|
517
|
+
parts.append(str(thought))
|
|
518
|
+
if action and action.strip():
|
|
519
|
+
parts.append(str(action))
|
|
520
|
+
if action_input and str(action_input).strip():
|
|
521
|
+
parts.append(str(action_input))
|
|
522
|
+
final_answer = "\n\n".join(parts) or (
|
|
523
|
+
"(agent emitted an unrecognized action and no text)"
|
|
524
|
+
)
|
|
525
|
+
if self.verbose:
|
|
526
|
+
preview = (action or "<empty>")[:60]
|
|
527
|
+
print(
|
|
528
|
+
f"\x1B[1;33m[loop] implicit-final: action "
|
|
529
|
+
f"'{preview}' is not a registered tool and not "
|
|
530
|
+
f"a Final_Answer variant; treating turn as "
|
|
531
|
+
f"Final_Answer\x1B[0m"
|
|
532
|
+
)
|
|
533
|
+
break
|
|
534
|
+
|
|
506
535
|
if self.verbose:
|
|
507
536
|
print(f"\x1B[3;33m[tool] Invoking '{action}' with args: {action_input}\x1B[0m")
|
|
508
537
|
else:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentx-dev
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.3
|
|
4
4
|
Summary: A lightweight LLM agent framework with multi-agent orchestration (Supervisor + dynamic specialist spawning), permission-gated default tools, MCP integration, and prompt-template presets.
|
|
5
5
|
Author-email: Bruce-Arhin Shadrach <brucearhin098@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
|
|
3
3
|
name = "agentx-dev"
|
|
4
|
-
version = "3.0.
|
|
4
|
+
version = "3.0.3"
|
|
5
5
|
description = "A lightweight LLM agent framework with multi-agent orchestration (Supervisor + dynamic specialist spawning), permission-gated default tools, MCP integration, and prompt-template presets."
|
|
6
6
|
authors = [{ name = "Bruce-Arhin Shadrach", email = "brucearhin098@gmail.com" }]
|
|
7
7
|
# Best practice: link to the license file
|
|
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
|
|
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
|