fix: preserve tool failures under noisy output
This commit is contained in:
@@ -120,6 +120,59 @@ class ParallelRegistry:
|
||||
return {"ok": True, "path": arguments["path"]}
|
||||
|
||||
|
||||
class CountingRegistry:
|
||||
def __init__(self) -> None:
|
||||
self.calls = 0
|
||||
|
||||
def specs(self, *, read_only=False, allow_delegate=True):
|
||||
return [TOOL_METADATA["read_file"].openai_spec()]
|
||||
|
||||
async def execute(self, name, arguments, context):
|
||||
self.calls += 1
|
||||
return {"ok": True, "path": arguments["path"]}
|
||||
|
||||
|
||||
async def test_tool_call_batch_is_bounded(settings) -> None:
|
||||
crowded = {
|
||||
"choices": [
|
||||
{
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": None,
|
||||
"tool_calls": [tool_call(str(index), "read_file", {"path": f"{index}.txt"}) for index in range(20)],
|
||||
},
|
||||
"finish_reason": "tool_calls",
|
||||
}
|
||||
]
|
||||
}
|
||||
final = {"choices": [{"message": {"role": "assistant", "content": "done"}, "finish_reason": "stop"}]}
|
||||
store = RuntimeStore(settings.database_url)
|
||||
await store.initialize()
|
||||
registry = CountingRegistry()
|
||||
loop = AgentLoop(ScriptedProvider([crowded, final]), registry, store, max_tool_output_chars=10_000)
|
||||
events = []
|
||||
|
||||
async def callback(event_type, payload):
|
||||
events.append((event_type, payload))
|
||||
|
||||
try:
|
||||
answer = await loop.run(
|
||||
spec=get_model_spec("work-medium"),
|
||||
messages=[{"role": "user", "content": "检查当前状态"}],
|
||||
identity=UserIdentity("u1", "u1@example.test", "U1", "user"),
|
||||
raw_user_jwt="jwt",
|
||||
chat_id="bounded-batch",
|
||||
callback=callback,
|
||||
)
|
||||
finally:
|
||||
await store.close()
|
||||
|
||||
assert answer == "done"
|
||||
assert registry.calls == 8
|
||||
limited = next(payload for event_type, payload in events if event_type == "tool.batch_limited")
|
||||
assert limited == {"requested": 20, "accepted": 8, "iteration": 1, "depth": 0}
|
||||
|
||||
|
||||
async def test_read_only_tool_calls_run_in_parallel(settings) -> None:
|
||||
first = {
|
||||
"choices": [
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from agent_platform.text import truncate_middle
|
||||
|
||||
|
||||
def test_truncate_middle_preserves_error_at_end() -> None:
|
||||
text = "stdout:" + ("x" * 1000) + "\nNameError: start is not defined"
|
||||
|
||||
truncated = truncate_middle(text, 120)
|
||||
|
||||
assert len(truncated) == 120
|
||||
assert truncated.startswith("stdout:")
|
||||
assert "middle omitted" in truncated
|
||||
assert truncated.endswith("NameError: start is not defined")
|
||||
|
||||
|
||||
def test_truncate_middle_leaves_short_text_unchanged() -> None:
|
||||
assert truncate_middle("short", 20) == "short"
|
||||
|
||||
|
||||
def test_truncate_middle_honors_tiny_limits() -> None:
|
||||
marker = "<omitted>"
|
||||
|
||||
assert truncate_middle("abcdef", 0, marker) == ""
|
||||
assert truncate_middle("abcdef", 3, marker) == "abc"
|
||||
assert truncate_middle("abcdefghijklmnopqrstuvwxyz", len(marker) + 1, marker) == "a<omitted>"
|
||||
Reference in New Issue
Block a user