AbstractAgent 0.3.18__tar.gz → 0.3.19__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.
- {abstractagent-0.3.18/src/abstractagent.egg-info → abstractagent-0.3.19}/PKG-INFO +1 -1
- {abstractagent-0.3.18 → abstractagent-0.3.19}/pyproject.toml +1 -1
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/emit_inventory.py +6 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/react_runtime.py +34 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19/src/abstractagent.egg-info}/PKG-INFO +1 -1
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent.egg-info/SOURCES.txt +1 -0
- abstractagent-0.3.19/tests/test_react_tool_format_retry.py +106 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/LICENSE +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/README.md +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/setup.cfg +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/codeact_runtime.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/generation_params.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/loop_hooks.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/media.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/memact_runtime.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/native_loop_registry.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/progress.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/read_orchestration.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/tool_allowlist.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/tool_failure_hints.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/transcripts.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/agents/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/agents/base.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/agents/codeact.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/agents/defaults.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/agents/memact.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/agents/react.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/agents/task_reset.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/agents/unattended.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/logic/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/logic/builtins.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/logic/codeact.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/logic/memact.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/logic/react.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/logic/types.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/metrics/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/metrics/prefix_reuse.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/repl.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/sandbox/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/sandbox/interface.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/sandbox/local.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/scripts/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/scripts/lmstudio_tool_eval.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/tools/__init__.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/tools/code_execution.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/tools/self_improve.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent.egg-info/dependency_links.txt +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent.egg-info/entry_points.txt +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent.egg-info/requires.txt +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent.egg-info/top_level.txt +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_active_context_policy_adoption.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_adversary_hardening_2026_07_13.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_agent_plan_review_modes.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_agent_speculation_inheritance.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_agent_stream_inheritance.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_agents_integration_llm.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_base_agent_trace_accessors.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_batching_prompt_rule.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_budget_triage_tail.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_builtin_tool_specs_optional_defaults.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_c2447_visit_lane_honesty.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_claim_0029_residue_2026_07_14.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_codeact_logic.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_codeact_runtime_verifier_policy.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_delegate_session_route_composition.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_emit_inventory_drift.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_execute_python_sandbox.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_gated_remainder_wave_2026_07_14.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_generation_params_media_policies.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_history_monster_clamp.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_improvement_wave_2026_07_13.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_llm_call_payload_requires_prompt_or_messages.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_lmstudio_tool_eval_metrics.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_loop_hooks.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_loop_hooks_0026_follow_ups.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_memact_codeact_guidance_durable_transcript.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_memact_session_memory.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_memact_workflow.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_memory_recall_tool.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_native_loop_registry.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_open_attachment_tool_available.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_operator_conclude.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_optimal_wave_batch2_2026_07_13.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_packaging_profiles.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_parse_prompt_cache_capture.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_prefix_reuse_metric.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_progress_detection.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_act_only_observation.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_active_attachments_disable_tools_first_iteration.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_adapter_forwards_context_attachments_media.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_delegate_agent_tool.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_election_fence_conformance.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_entity_dress_conformance.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_guidance_durable_transcript.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_history_window.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_logic.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_loop_aborted_generation_retry.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_loop_context_transcript_levels.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_loop_followthrough_retry.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_loop_side_effect_dedupe_comms.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_loop_truncated_output_retry.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_max_iterations_concludes.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_phase_grant_boundary.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_planish_heuristic.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_prompt_prefix_stability.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_sight_lane_media_fold.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_turn_byte_stability.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_verifier_and_plan.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_visit_composition.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_visit_merge.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_workflow_system_prompt_and_tools_override.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_workflow_toolset_refresh.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_read_orchestration_guidance.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_reasoning_first_citizen_agent.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_review_failure_containment.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_run_start_seeds_runtime_provider_model.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_session_memory.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_sibling_loop_defects_batch.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_sibling_strict_transcripts_0011.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_stuck_streak_termination.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_tool_failure_hints.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_tool_loop_prefix_extension_a3.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_tool_observe_emits_result_for_tui.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_tools_editing.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_unseeded_render_limits.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_verifier_execution_preference.py +0 -0
- {abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_verifier_schema_strict.py +0 -0
|
@@ -85,6 +85,12 @@ REACT_STEPS = COMMON_STEPS | frozenset(
|
|
|
85
85
|
"parse_retry_empty",
|
|
86
86
|
"parse_retry_plan_only",
|
|
87
87
|
"parse_retry_truncated",
|
|
88
|
+
# Tool-call format repair (2026-10-03, shipped round 14): AbstractCore
|
|
89
|
+
# reported `metadata.tool_call_error` (code invalid_tool_syntax |
|
|
90
|
+
# unavailable_tool) and accepted ZERO calls; the model is asked once
|
|
91
|
+
# more with the tool roster — payload {cycle, attempt, code}. At most
|
|
92
|
+
# two per turn; the third raises (the run fails with the sentence).
|
|
93
|
+
"parse_retry_tool_format",
|
|
88
94
|
# 0017 work half (2026-07-21), nudge-then-stop (2026-08-21): fires on
|
|
89
95
|
# every detection of a consecutive-identical or A-B-A-B tool-batch
|
|
90
96
|
# streak; payload {kind: repeat|oscillation, span, key, answered,
|
|
@@ -1446,6 +1446,10 @@ def reset_react_turn(run_vars: Dict[str, Any]) -> None:
|
|
|
1446
1446
|
# retry stash is the same lifecycle class.
|
|
1447
1447
|
"pending_media",
|
|
1448
1448
|
"last_call_media",
|
|
1449
|
+
# Tool-call format repair budget is per TURN (two retries each):
|
|
1450
|
+
# without this pop, two format failures anywhere in a conversation
|
|
1451
|
+
# made every later failing turn raise at once.
|
|
1452
|
+
"tool_format_retries",
|
|
1449
1453
|
):
|
|
1450
1454
|
temp.pop(key, None)
|
|
1451
1455
|
|
|
@@ -2496,6 +2500,36 @@ def create_react_workflow(
|
|
|
2496
2500
|
emit("parse_tool_calls", {"count": len(tool_calls)})
|
|
2497
2501
|
return StepPlan(node_id="parse", next_node="act")
|
|
2498
2502
|
|
|
2503
|
+
# Core owns syntax recognition; the agent owns bounded recovery. Retry
|
|
2504
|
+
# only when zero calls were accepted, so a partial batch is never replayed.
|
|
2505
|
+
metadata = response.get("metadata") if isinstance(response, dict) else None
|
|
2506
|
+
tool_error = metadata.get("tool_call_error") if isinstance(metadata, dict) else None
|
|
2507
|
+
if isinstance(tool_error, dict) and tool_error.get("code") in {"invalid_tool_syntax", "unavailable_tool"}:
|
|
2508
|
+
retries = int(temp.get("tool_format_retries", 0) or 0)
|
|
2509
|
+
if retries >= 2 or cycle_i >= max_iterations:
|
|
2510
|
+
raise RuntimeError("Tool-call format repair failed: no executable tool calls after bounded retries.")
|
|
2511
|
+
specs = runtime_ns.get("tool_specs") or []
|
|
2512
|
+
names = [str(spec["name"]) for spec in specs if isinstance(spec, dict) and spec.get("name")]
|
|
2513
|
+
if not names:
|
|
2514
|
+
raise RuntimeError("The model attempted a tool call, but this run has no tools configured.")
|
|
2515
|
+
temp["tool_format_retries"] = retries + 1
|
|
2516
|
+
_push_inbox(
|
|
2517
|
+
runtime_ns,
|
|
2518
|
+
"Your previous tool call was rejected; nothing was executed. "
|
|
2519
|
+
"Use only the available tools: " + ", ".join(names) + ". "
|
|
2520
|
+
# No format of our own: the tool-calling format is the one the
|
|
2521
|
+
# request's instructions carry (native structured calls, or the
|
|
2522
|
+
# provider's prompted text format) — a hard-coded text syntax
|
|
2523
|
+
# here would teach a native tool-calling model the wrong one.
|
|
2524
|
+
"Follow the tool-calling format and argument schemas supplied in the instructions, "
|
|
2525
|
+
"with an actual available tool name and valid arguments. "
|
|
2526
|
+
"Emit the corrected call now, or explain why you cannot complete the task without it.",
|
|
2527
|
+
)
|
|
2528
|
+
emit("parse_retry_tool_format", {"cycle": cycle_i, "attempt": retries + 1, "code": tool_error["code"]})
|
|
2529
|
+
if last_call_media:
|
|
2530
|
+
temp["pending_media"] = last_call_media
|
|
2531
|
+
return StepPlan(node_id="parse", next_node="reason")
|
|
2532
|
+
|
|
2499
2533
|
# If the model hit an output limit, treat the step as incomplete and continue.
|
|
2500
2534
|
# Retry-nudge lane honesty (iteration-3 adversary P0, 2026-07-19 — the
|
|
2501
2535
|
# c2447 chrome class at the PARSE boundary): these nudges ride the
|
|
@@ -103,6 +103,7 @@ tests/test_react_phase_grant_boundary.py
|
|
|
103
103
|
tests/test_react_planish_heuristic.py
|
|
104
104
|
tests/test_react_prompt_prefix_stability.py
|
|
105
105
|
tests/test_react_sight_lane_media_fold.py
|
|
106
|
+
tests/test_react_tool_format_retry.py
|
|
106
107
|
tests/test_react_turn_byte_stability.py
|
|
107
108
|
tests/test_react_verifier_and_plan.py
|
|
108
109
|
tests/test_react_visit_composition.py
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from abstractagent.adapters.react_runtime import create_react_workflow
|
|
6
|
+
from abstractagent.logic.react import ReActLogic
|
|
7
|
+
from abstractcore.tools import ToolDefinition
|
|
8
|
+
from abstractruntime.core.models import EffectType, RunStatus
|
|
9
|
+
from abstractruntime.core.runtime import EffectOutcome, Runtime
|
|
10
|
+
from abstractruntime.storage.in_memory import InMemoryLedgerStore, InMemoryRunStore
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.parametrize("recover", [False, True])
|
|
14
|
+
def test_format_repair_is_bounded_and_never_executes_rejected_calls(recover):
|
|
15
|
+
requests: list[dict[str, Any]] = []
|
|
16
|
+
executed = []
|
|
17
|
+
|
|
18
|
+
def llm(run, effect, next_node):
|
|
19
|
+
requests.append(effect.payload)
|
|
20
|
+
if len(requests) > 1:
|
|
21
|
+
assert "nothing was executed" in str(effect.payload["messages"])
|
|
22
|
+
# The repair names no text format of its own (a native tool-calling
|
|
23
|
+
# provider must never be taught a text syntax): it points at the
|
|
24
|
+
# request's own instructions and lists the roster.
|
|
25
|
+
assert "<tool_call>" not in str(effect.payload["messages"])
|
|
26
|
+
assert "format and argument schemas supplied in the instructions" in str(effect.payload["messages"])
|
|
27
|
+
assert "Use only the available tools: web_search." in str(effect.payload["messages"])
|
|
28
|
+
if recover and len(requests) == 2:
|
|
29
|
+
return EffectOutcome.completed({"content": "", "tool_calls": [{"name": "web_search", "arguments": {"query": "test"}, "call_id": "one"}]})
|
|
30
|
+
if recover and len(requests) == 3:
|
|
31
|
+
return EffectOutcome.completed({"content": "Found it."})
|
|
32
|
+
return EffectOutcome.completed({"content": "Broken tool block", "metadata": {
|
|
33
|
+
"tool_call_error": {"code": "invalid_tool_syntax", "available_tools": ["web_search"]}}})
|
|
34
|
+
|
|
35
|
+
def tool(run, effect, next_node):
|
|
36
|
+
executed.extend(effect.payload["tool_calls"])
|
|
37
|
+
return EffectOutcome.completed({"mode": "executed", "results": [{"name": "web_search", "call_id": "one", "success": True, "output": "found"}]})
|
|
38
|
+
|
|
39
|
+
runtime = Runtime(run_store=InMemoryRunStore(), ledger_store=InMemoryLedgerStore(),
|
|
40
|
+
effect_handlers={EffectType.LLM_CALL: llm, EffectType.TOOL_CALLS: tool})
|
|
41
|
+
workflow = create_react_workflow(logic=ReActLogic(tools=[ToolDefinition(name="web_search", description="Search", parameters={"query": {"type": "string"}})]),
|
|
42
|
+
workflow_id="format-repair", provider="stub", model="stub", allowed_tools=["web_search"])
|
|
43
|
+
rid = runtime.start(workflow=workflow, vars={"context": {"task": "Search", "messages": []}, "_runtime": {"inbox": []}})
|
|
44
|
+
def drive():
|
|
45
|
+
for _ in range(80):
|
|
46
|
+
state = runtime.tick(workflow=workflow, run_id=rid, max_steps=1)
|
|
47
|
+
if state.status in (RunStatus.COMPLETED, RunStatus.FAILED):
|
|
48
|
+
return state
|
|
49
|
+
if recover:
|
|
50
|
+
assert drive().status == RunStatus.COMPLETED
|
|
51
|
+
else:
|
|
52
|
+
# GatewayRunner persists planning exceptions as FAILED (tick_exception).
|
|
53
|
+
with pytest.raises(RuntimeError, match="format repair failed"):
|
|
54
|
+
drive()
|
|
55
|
+
assert len(requests) == 3
|
|
56
|
+
assert len(executed) == (1 if recover else 0)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_format_repair_budget_is_per_turn_in_a_composed_conversation():
|
|
60
|
+
"""Two turns of one run (visit composition): each turn has two rejected formats and
|
|
61
|
+
still recovers. Without `reset_react_turn` clearing `tool_format_retries`, turn 2's
|
|
62
|
+
first rejection would raise at once (the budget was per conversation)."""
|
|
63
|
+
from abstractagent.adapters.react_runtime import reset_react_turn
|
|
64
|
+
from abstractruntime.core.models import StepPlan
|
|
65
|
+
from abstractruntime.core.spec import WorkflowSpec
|
|
66
|
+
|
|
67
|
+
bad = {"content": "Broken tool block", "metadata": {"tool_call_error": {"code": "invalid_tool_syntax", "available_tools": ["web_search"]}}}
|
|
68
|
+
script = [bad, bad, {"content": "Answer one."}, bad, bad, {"content": "Answer two."}]
|
|
69
|
+
calls = {"n": 0}
|
|
70
|
+
events: list[str] = []
|
|
71
|
+
answers: list[str] = []
|
|
72
|
+
|
|
73
|
+
def llm(run, effect, next_node):
|
|
74
|
+
i = calls["n"]
|
|
75
|
+
calls["n"] += 1
|
|
76
|
+
return EffectOutcome.completed(dict(script[min(i, len(script) - 1)]))
|
|
77
|
+
|
|
78
|
+
react = create_react_workflow(
|
|
79
|
+
logic=ReActLogic(tools=[ToolDefinition(name="web_search", description="Search", parameters={"query": {"type": "string"}})]),
|
|
80
|
+
workflow_id="format-repair-turns", provider="stub", model="stub", allowed_tools=["web_search"],
|
|
81
|
+
final_next_node="park", on_step=lambda step, data: events.append(step),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def park(run, ctx):
|
|
85
|
+
temp = run.vars.get("_temp") or {}
|
|
86
|
+
answers.append(str(temp.get("final_answer") or ""))
|
|
87
|
+
if len(answers) >= 2:
|
|
88
|
+
return StepPlan(node_id="park", complete_output={"turns": len(answers)})
|
|
89
|
+
reset_react_turn(run.vars)
|
|
90
|
+
run.vars["context"]["messages"].append({"role": "user", "content": "Second message"})
|
|
91
|
+
return StepPlan(node_id="park", next_node="reason")
|
|
92
|
+
|
|
93
|
+
nodes = dict(react.nodes)
|
|
94
|
+
nodes["park"] = park
|
|
95
|
+
workflow = WorkflowSpec(workflow_id=react.workflow_id, entry_node=react.entry_node, nodes=nodes)
|
|
96
|
+
runtime = Runtime(run_store=InMemoryRunStore(), ledger_store=InMemoryLedgerStore(), effect_handlers={EffectType.LLM_CALL: llm})
|
|
97
|
+
rid = runtime.start(workflow=workflow, vars={"context": {"task": "First message", "messages": []}, "_runtime": {"inbox": []}})
|
|
98
|
+
state = None
|
|
99
|
+
for _ in range(120):
|
|
100
|
+
state = runtime.tick(workflow=workflow, run_id=rid, max_steps=1)
|
|
101
|
+
if state.status in (RunStatus.COMPLETED, RunStatus.FAILED):
|
|
102
|
+
break
|
|
103
|
+
assert state is not None and state.status == RunStatus.COMPLETED, state
|
|
104
|
+
assert answers == ["Answer one.", "Answer two."]
|
|
105
|
+
assert events.count("parse_retry_tool_format") == 4
|
|
106
|
+
assert calls["n"] == 6
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/generation_params.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/native_loop_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/read_orchestration.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/adapters/tool_failure_hints.py
RENAMED
|
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
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent/scripts/lmstudio_tool_eval.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/src/abstractagent.egg-info/dependency_links.txt
RENAMED
|
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
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_builtin_tool_specs_optional_defaults.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_delegate_session_route_composition.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_generation_params_media_policies.py
RENAMED
|
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
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_election_fence_conformance.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_guidance_durable_transcript.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_loop_aborted_generation_retry.py
RENAMED
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_loop_context_transcript_levels.py
RENAMED
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_loop_side_effect_dedupe_comms.py
RENAMED
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_react_loop_truncated_output_retry.py
RENAMED
|
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
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_run_start_seeds_runtime_provider_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{abstractagent-0.3.18 → abstractagent-0.3.19}/tests/test_tool_observe_emits_result_for_tui.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|