@openlucaskaka/kagent 0.1.7
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.
- package/README.md +353 -0
- package/npm/bin/kagent-serve.js +6 -0
- package/npm/bin/kagent.js +6 -0
- package/npm/lib/App.js +524 -0
- package/npm/lib/app-state.js +224 -0
- package/npm/lib/approval-choice.js +25 -0
- package/npm/lib/commands.js +59 -0
- package/npm/lib/editor.js +188 -0
- package/npm/lib/ink-runner.js +41 -0
- package/npm/lib/kagent-home.js +39 -0
- package/npm/lib/launcher.js +221 -0
- package/npm/lib/protocol.js +33 -0
- package/npm/lib/provider-setup.js +139 -0
- package/npm/lib/python-runner.js +892 -0
- package/npm/lib/runtime-client.js +390 -0
- package/npm/lib/terminal-input.js +127 -0
- package/npm/lib/terminal-text.js +19 -0
- package/npm/lib/terminal-width.js +14 -0
- package/npm/lib/transcript.js +227 -0
- package/npm/lib/ui-components.js +247 -0
- package/npm/lib/update-manager.js +334 -0
- package/package.json +39 -0
- package/pyproject.toml +55 -0
- package/src/kagent/__init__.py +90 -0
- package/src/kagent/cli/__init__.py +5 -0
- package/src/kagent/cli/__main__.py +6 -0
- package/src/kagent/cli/commands.py +112 -0
- package/src/kagent/cli/conversation.py +127 -0
- package/src/kagent/cli/interactive.py +841 -0
- package/src/kagent/cli/main.py +685 -0
- package/src/kagent/cli/memory.py +460 -0
- package/src/kagent/cli/pending_approval.py +169 -0
- package/src/kagent/cli/provider.py +27 -0
- package/src/kagent/cli/session_commands.py +401 -0
- package/src/kagent/cli/stdio_runtime.py +784 -0
- package/src/kagent/cli/trace.py +67 -0
- package/src/kagent/cli/ui.py +931 -0
- package/src/kagent/core/__init__.py +0 -0
- package/src/kagent/core/agent.py +296 -0
- package/src/kagent/core/faults.py +11 -0
- package/src/kagent/core/invariants.py +47 -0
- package/src/kagent/core/normalization.py +81 -0
- package/src/kagent/core/planning.py +48 -0
- package/src/kagent/core/state.py +73 -0
- package/src/kagent/core/summary.py +64 -0
- package/src/kagent/core/tools.py +196 -0
- package/src/kagent/core/trace.py +57 -0
- package/src/kagent/eval/__init__.py +11 -0
- package/src/kagent/eval/cases.py +229 -0
- package/src/kagent/eval/evaluator.py +216 -0
- package/src/kagent/integrations/__init__.py +3 -0
- package/src/kagent/integrations/audit.py +95 -0
- package/src/kagent/integrations/backends.py +131 -0
- package/src/kagent/integrations/memory.py +301 -0
- package/src/kagent/ops/__init__.py +0 -0
- package/src/kagent/ops/batch.py +156 -0
- package/src/kagent/ops/doctor.py +255 -0
- package/src/kagent/ops/metrics.py +214 -0
- package/src/kagent/ops/release_evidence.py +877 -0
- package/src/kagent/ops/release_manifest.py +142 -0
- package/src/kagent/ops/trace_replay.py +285 -0
- package/src/kagent/providers/__init__.py +7 -0
- package/src/kagent/providers/embeddings.py +187 -0
- package/src/kagent/providers/llm.py +770 -0
- package/src/kagent/py.typed +1 -0
- package/src/kagent/runtime/__init__.py +28 -0
- package/src/kagent/runtime/action_graph.py +543 -0
- package/src/kagent/runtime/agent.py +2089 -0
- package/src/kagent/runtime/approval.py +64 -0
- package/src/kagent/runtime/cancellation.py +64 -0
- package/src/kagent/runtime/checkpoint_state.py +146 -0
- package/src/kagent/runtime/checkpoint_storage.py +270 -0
- package/src/kagent/runtime/context.py +65 -0
- package/src/kagent/runtime/file_transaction.py +195 -0
- package/src/kagent/runtime/hooks.py +74 -0
- package/src/kagent/runtime/metadata.py +116 -0
- package/src/kagent/runtime/patch_checkpoints.py +385 -0
- package/src/kagent/runtime/policy.py +50 -0
- package/src/kagent/runtime/presentation.py +205 -0
- package/src/kagent/runtime/redaction.py +130 -0
- package/src/kagent/runtime/sandbox.py +331 -0
- package/src/kagent/runtime/skills.py +66 -0
- package/src/kagent/runtime/steering.py +56 -0
- package/src/kagent/runtime/steps.py +255 -0
- package/src/kagent/runtime/task_state.py +40 -0
- package/src/kagent/runtime/tools.py +3532 -0
- package/src/kagent/runtime/types.py +240 -0
- package/src/kagent/runtime/workspace.py +597 -0
- package/src/kagent/service/__init__.py +26 -0
- package/src/kagent/service/__main__.py +6 -0
- package/src/kagent/service/active_runs.py +178 -0
- package/src/kagent/service/cli.py +737 -0
- package/src/kagent/service/contract.py +2571 -0
- package/src/kagent/service/errors.py +71 -0
- package/src/kagent/service/idempotency.py +584 -0
- package/src/kagent/service/router.py +884 -0
- package/src/kagent/service/run.py +150 -0
- package/src/kagent/service/runtime.py +1915 -0
- package/src/kagent/service/runtime_approval.py +20 -0
- package/src/kagent/service/runtime_cancel.py +192 -0
- package/src/kagent/service/runtime_lifecycle.py +229 -0
- package/src/kagent/service/runtime_metadata.py +21 -0
- package/src/kagent/service/runtime_policy.py +115 -0
- package/src/kagent/service/runtime_recovery.py +573 -0
- package/src/kagent/service/runtime_resume.py +382 -0
- package/src/kagent/service/runtime_resume_claim.py +116 -0
- package/src/kagent/service/runtime_run.py +350 -0
- package/src/kagent/service/runtime_status.py +2007 -0
- package/src/kagent/service/safety.py +139 -0
- package/src/kagent/service/server.py +114 -0
- package/src/kagent/service/status.py +233 -0
- package/src/kagent/service/trace_store.py +322 -0
- package/src/kagent/service/transport.py +24 -0
- package/src/kagent/utils/__init__.py +0 -0
- package/src/kagent/utils/config_validation.py +21 -0
- package/src/kagent/utils/json_output.py +23 -0
- package/src/kagent/utils/paths.py +473 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Callable, Dict, List, Match, Optional, Pattern
|
|
6
|
+
|
|
7
|
+
ToolResult = Dict[str, str]
|
|
8
|
+
ToolHandler = Callable[[Match[str]], ToolResult]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class ToolSpec:
|
|
13
|
+
name: str
|
|
14
|
+
command: str
|
|
15
|
+
description: str
|
|
16
|
+
example: str
|
|
17
|
+
pattern: Pattern[str]
|
|
18
|
+
handler: ToolHandler
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def execute_step(step: str) -> Optional[ToolResult]:
|
|
22
|
+
for tool in TOOLS:
|
|
23
|
+
match = tool.pattern.fullmatch(step)
|
|
24
|
+
if match:
|
|
25
|
+
return tool.handler(match)
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def registered_tool_names() -> List[str]:
|
|
30
|
+
return [tool.name for tool in TOOLS]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def registered_tool_metadata() -> List[Dict[str, str]]:
|
|
34
|
+
return [
|
|
35
|
+
{
|
|
36
|
+
"name": tool.name,
|
|
37
|
+
"command": tool.command,
|
|
38
|
+
"description": tool.description,
|
|
39
|
+
"example": tool.example,
|
|
40
|
+
}
|
|
41
|
+
for tool in TOOLS
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def matching_tool_name(step: str) -> Optional[str]:
|
|
46
|
+
for tool in TOOLS:
|
|
47
|
+
if tool.pattern.fullmatch(step):
|
|
48
|
+
return tool.name
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def expected_answer(step: Optional[str]) -> Optional[str]:
|
|
53
|
+
if step is None:
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
execution = execute_step(step)
|
|
57
|
+
if execution is None:
|
|
58
|
+
return None
|
|
59
|
+
return execution["output"]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _calculate(match: Match[str]) -> ToolResult:
|
|
63
|
+
left, right = match.groups()
|
|
64
|
+
return _numeric_result("calculate_sum", match, int(left) + int(right))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _multiply(match: Match[str]) -> ToolResult:
|
|
68
|
+
left, right = match.groups()
|
|
69
|
+
return _numeric_result("multiply_numbers", match, int(left) * int(right))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _subtract(match: Match[str]) -> ToolResult:
|
|
73
|
+
left, right = match.groups()
|
|
74
|
+
return _numeric_result("subtract_numbers", match, int(left) - int(right))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _numeric_result(tool: str, match: Match[str], output: int) -> ToolResult:
|
|
78
|
+
return {
|
|
79
|
+
"tool": tool,
|
|
80
|
+
"input": match.group(0),
|
|
81
|
+
"output": str(output),
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _count_words(match: Match[str]) -> ToolResult:
|
|
86
|
+
text = match.group(1)
|
|
87
|
+
return _text_result("count_words", text, str(len([word for word in text.split() if word])))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _lowercase_text(match: Match[str]) -> ToolResult:
|
|
91
|
+
text = match.group(1)
|
|
92
|
+
return _text_result("lowercase_text", text, text.lower())
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _uppercase_text(match: Match[str]) -> ToolResult:
|
|
96
|
+
text = match.group(1)
|
|
97
|
+
return _text_result("uppercase_text", text, text.upper())
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _reverse_text(match: Match[str]) -> ToolResult:
|
|
101
|
+
text = match.group(1)
|
|
102
|
+
return _text_result("reverse_text", text, text[::-1])
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _trim_text(match: Match[str]) -> ToolResult:
|
|
106
|
+
text = match.group(1)
|
|
107
|
+
return _text_result("trim_text", text, text.strip())
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _text_result(tool: str, text: str, output: str) -> ToolResult:
|
|
111
|
+
return {
|
|
112
|
+
"tool": tool,
|
|
113
|
+
"input": text,
|
|
114
|
+
"output": output,
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _text_tool(
|
|
119
|
+
name: str,
|
|
120
|
+
command: str,
|
|
121
|
+
description: str,
|
|
122
|
+
example: str,
|
|
123
|
+
handler: ToolHandler,
|
|
124
|
+
) -> ToolSpec:
|
|
125
|
+
return ToolSpec(
|
|
126
|
+
name=name,
|
|
127
|
+
command=f"{command} text in 'text'",
|
|
128
|
+
description=description,
|
|
129
|
+
example=example,
|
|
130
|
+
pattern=re.compile(rf"{command}\s+text\s+in\s+['\"](.*)['\"]"),
|
|
131
|
+
handler=handler,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
TOOLS = [
|
|
136
|
+
ToolSpec(
|
|
137
|
+
name="calculate_sum",
|
|
138
|
+
command="calculate N + M",
|
|
139
|
+
description="Add two integers.",
|
|
140
|
+
example="calculate 2 + 3",
|
|
141
|
+
pattern=re.compile(r"calculate\s+(-?\d+)\s*\+\s*(-?\d+)"),
|
|
142
|
+
handler=_calculate,
|
|
143
|
+
),
|
|
144
|
+
ToolSpec(
|
|
145
|
+
name="count_words",
|
|
146
|
+
command="count words in 'text'",
|
|
147
|
+
description="Count whitespace-separated words in quoted text.",
|
|
148
|
+
example="count words in 'ship small reliable agents'",
|
|
149
|
+
pattern=re.compile(r"count\s+words\s+in\s+['\"](.*)['\"]"),
|
|
150
|
+
handler=_count_words,
|
|
151
|
+
),
|
|
152
|
+
_text_tool(
|
|
153
|
+
"lowercase_text",
|
|
154
|
+
"lowercase",
|
|
155
|
+
"Convert quoted text to lowercase.",
|
|
156
|
+
"lowercase text in 'Agent Loop'",
|
|
157
|
+
_lowercase_text,
|
|
158
|
+
),
|
|
159
|
+
ToolSpec(
|
|
160
|
+
name="multiply_numbers",
|
|
161
|
+
command="multiply N * M",
|
|
162
|
+
description="Multiply two integers.",
|
|
163
|
+
example="multiply 6 * 7",
|
|
164
|
+
pattern=re.compile(r"multiply\s+(-?\d+)\s*\*\s*(-?\d+)"),
|
|
165
|
+
handler=_multiply,
|
|
166
|
+
),
|
|
167
|
+
_text_tool(
|
|
168
|
+
"reverse_text",
|
|
169
|
+
"reverse",
|
|
170
|
+
"Reverse quoted text while preserving character case.",
|
|
171
|
+
"reverse text in 'Agent Loop'",
|
|
172
|
+
_reverse_text,
|
|
173
|
+
),
|
|
174
|
+
ToolSpec(
|
|
175
|
+
name="subtract_numbers",
|
|
176
|
+
command="subtract N - M",
|
|
177
|
+
description="Subtract the right integer from the left integer.",
|
|
178
|
+
example="subtract 10 - 4",
|
|
179
|
+
pattern=re.compile(r"subtract\s+(-?\d+)\s*-\s*(-?\d+)"),
|
|
180
|
+
handler=_subtract,
|
|
181
|
+
),
|
|
182
|
+
_text_tool(
|
|
183
|
+
"trim_text",
|
|
184
|
+
"trim",
|
|
185
|
+
"Remove surrounding whitespace from quoted text.",
|
|
186
|
+
"trim text in ' agent loop '",
|
|
187
|
+
_trim_text,
|
|
188
|
+
),
|
|
189
|
+
_text_tool(
|
|
190
|
+
"uppercase_text",
|
|
191
|
+
"uppercase",
|
|
192
|
+
"Convert quoted text to uppercase.",
|
|
193
|
+
"uppercase text in 'agent loop'",
|
|
194
|
+
_uppercase_text,
|
|
195
|
+
),
|
|
196
|
+
]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from kagent.core.state import AgentState
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def record_node_event(state: AgentState, node: str) -> None:
|
|
7
|
+
state["events"].append(
|
|
8
|
+
{
|
|
9
|
+
"node": node,
|
|
10
|
+
"status": state["status"].value,
|
|
11
|
+
"retry_count": state["retry_count"],
|
|
12
|
+
"step_retry_count": state.get("step_retry_count", 0),
|
|
13
|
+
}
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def record_execution_attempt(
|
|
18
|
+
state: AgentState,
|
|
19
|
+
step: str,
|
|
20
|
+
tool: str,
|
|
21
|
+
output: str,
|
|
22
|
+
fault: str,
|
|
23
|
+
) -> None:
|
|
24
|
+
state["execution_attempts"].append(
|
|
25
|
+
{
|
|
26
|
+
"step": step,
|
|
27
|
+
"tool": tool,
|
|
28
|
+
"output": output,
|
|
29
|
+
"fault": fault,
|
|
30
|
+
"retry": str(state["step_retry_count"]),
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def copy_agent_state(state: AgentState) -> AgentState:
|
|
36
|
+
copied = dict(state)
|
|
37
|
+
copied["plan"] = list(state.get("plan", []))
|
|
38
|
+
copied["plan_validations"] = [
|
|
39
|
+
dict(item) for item in state.get("plan_validations", [])
|
|
40
|
+
]
|
|
41
|
+
copied["step_results"] = list(state.get("step_results", []))
|
|
42
|
+
copied["tool_calls"] = [dict(item) for item in state.get("tool_calls", [])]
|
|
43
|
+
copied["execution_attempts"] = [
|
|
44
|
+
dict(item) for item in state.get("execution_attempts", [])
|
|
45
|
+
]
|
|
46
|
+
copied["errors"] = list(state.get("errors", []))
|
|
47
|
+
copied["events"] = [dict(item) for item in state.get("events", [])]
|
|
48
|
+
copied["reflection_notes"] = list(state.get("reflection_notes", []))
|
|
49
|
+
copied["reflections"] = [dict(item) for item in state.get("reflections", [])]
|
|
50
|
+
copied["verification_results"] = [
|
|
51
|
+
dict(item) for item in state.get("verification_results", [])
|
|
52
|
+
]
|
|
53
|
+
copied["fault_plan"] = {
|
|
54
|
+
key: list(value) for key, value in state.get("fault_plan", {}).items()
|
|
55
|
+
}
|
|
56
|
+
copied["fault_counters"] = dict(state.get("fault_counters", {}))
|
|
57
|
+
return copied
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Any, Callable, Dict, List
|
|
5
|
+
|
|
6
|
+
CaseCheck = Callable[[Dict[str, Any]], bool]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class EvaluationCase:
|
|
11
|
+
name: str
|
|
12
|
+
category: str
|
|
13
|
+
run: Callable[[], Dict[str, Any]]
|
|
14
|
+
check: CaseCheck
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def build_evaluation_cases(AgentConfig, AgentStatus, run_agent) -> List[EvaluationCase]:
|
|
18
|
+
return [
|
|
19
|
+
EvaluationCase(
|
|
20
|
+
"multi_step_success",
|
|
21
|
+
"workflow",
|
|
22
|
+
lambda: run_agent(
|
|
23
|
+
"calculate 2 + 3 then count words in 'ship small reliable agents'",
|
|
24
|
+
config=AgentConfig(max_steps=4, max_retries=1),
|
|
25
|
+
),
|
|
26
|
+
lambda result: (
|
|
27
|
+
result["status"] == AgentStatus.DONE
|
|
28
|
+
and result["step_results"] == ["5", "4"]
|
|
29
|
+
and result["retry_count"] == 0
|
|
30
|
+
and [item["passed"] for item in result["verification_results"]]
|
|
31
|
+
== ["true", "true"]
|
|
32
|
+
and [item["tool"] for item in result["plan_validations"]]
|
|
33
|
+
== ["calculate_sum", "count_words"]
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
EvaluationCase(
|
|
37
|
+
"self_correction_success",
|
|
38
|
+
"recovery",
|
|
39
|
+
lambda: run_agent(
|
|
40
|
+
"calculate 2 + 3",
|
|
41
|
+
config=AgentConfig(max_steps=3, max_retries=2),
|
|
42
|
+
fault_plan={"calculate 2 + 3": ["wrong-answer"]},
|
|
43
|
+
),
|
|
44
|
+
lambda result: (
|
|
45
|
+
result["status"] == AgentStatus.DONE
|
|
46
|
+
and result["answer"] == "5"
|
|
47
|
+
and result["retry_count"] == 1
|
|
48
|
+
and result["reflections"][-1]["actual"] == "6"
|
|
49
|
+
and result["reflections"][-1]["expected"] == "5"
|
|
50
|
+
and [item["passed"] for item in result["verification_results"]]
|
|
51
|
+
== ["false", "true"]
|
|
52
|
+
),
|
|
53
|
+
),
|
|
54
|
+
EvaluationCase(
|
|
55
|
+
"retry_budget_failure",
|
|
56
|
+
"failure",
|
|
57
|
+
lambda: run_agent(
|
|
58
|
+
"calculate 2 + 3",
|
|
59
|
+
config=AgentConfig(max_steps=3, max_retries=1),
|
|
60
|
+
fault_plan={"calculate 2 + 3": ["wrong-answer", "wrong-answer"]},
|
|
61
|
+
),
|
|
62
|
+
lambda result: (
|
|
63
|
+
result["status"] == AgentStatus.FAILED
|
|
64
|
+
and result["errors"][-1] == "retry budget exhausted"
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
EvaluationCase(
|
|
68
|
+
"uppercase_tool_success",
|
|
69
|
+
"tool",
|
|
70
|
+
lambda: run_agent(
|
|
71
|
+
"uppercase text in 'agent loop'",
|
|
72
|
+
config=AgentConfig(max_steps=3, max_retries=1),
|
|
73
|
+
),
|
|
74
|
+
lambda result: (
|
|
75
|
+
result["status"] == AgentStatus.DONE
|
|
76
|
+
and result["answer"] == "AGENT LOOP"
|
|
77
|
+
and result["tool_calls"][-1]["tool"] == "uppercase_text"
|
|
78
|
+
),
|
|
79
|
+
),
|
|
80
|
+
EvaluationCase(
|
|
81
|
+
"unsupported_plan_failure",
|
|
82
|
+
"failure",
|
|
83
|
+
lambda: run_agent(
|
|
84
|
+
"calculate 1 + 1 then search the web",
|
|
85
|
+
config=AgentConfig(max_steps=4, max_retries=2),
|
|
86
|
+
),
|
|
87
|
+
lambda result: (
|
|
88
|
+
result["status"] == AgentStatus.FAILED
|
|
89
|
+
and result["errors"] == ["unsupported planned step: search the web"]
|
|
90
|
+
and result["retry_count"] == 0
|
|
91
|
+
and [event["node"] for event in result["events"]] == ["planner"]
|
|
92
|
+
and result["plan_validations"][-1]["supported"] == "false"
|
|
93
|
+
),
|
|
94
|
+
),
|
|
95
|
+
EvaluationCase(
|
|
96
|
+
"empty_answer_recovery",
|
|
97
|
+
"recovery",
|
|
98
|
+
lambda: run_agent(
|
|
99
|
+
"uppercase text in 'agent loop'",
|
|
100
|
+
config=AgentConfig(max_steps=4, max_retries=2),
|
|
101
|
+
fault_plan={"uppercase text in 'agent loop'": ["empty-answer"]},
|
|
102
|
+
),
|
|
103
|
+
lambda result: (
|
|
104
|
+
result["status"] == AgentStatus.DONE
|
|
105
|
+
and result["answer"] == "AGENT LOOP"
|
|
106
|
+
and result["reflections"][-1]["actual"] == ""
|
|
107
|
+
and result["reflections"][-1]["reason"] == "answer was empty"
|
|
108
|
+
and [item["passed"] for item in result["verification_results"]]
|
|
109
|
+
== ["false", "true"]
|
|
110
|
+
and [item["fault"] for item in result["execution_attempts"]]
|
|
111
|
+
== ["empty-answer", ""]
|
|
112
|
+
and result["execution_attempts"][-1]["tool"] == "uppercase_text"
|
|
113
|
+
),
|
|
114
|
+
),
|
|
115
|
+
EvaluationCase(
|
|
116
|
+
"per_step_retry_budget_success",
|
|
117
|
+
"recovery",
|
|
118
|
+
lambda: run_agent(
|
|
119
|
+
"calculate 2 + 3 then uppercase text in 'agent loop'",
|
|
120
|
+
config=AgentConfig(max_steps=4, max_retries=1),
|
|
121
|
+
fault_plan={
|
|
122
|
+
"calculate 2 + 3": ["wrong-answer"],
|
|
123
|
+
"uppercase text in 'agent loop'": ["empty-answer"],
|
|
124
|
+
},
|
|
125
|
+
),
|
|
126
|
+
lambda result: (
|
|
127
|
+
result["status"] == AgentStatus.DONE
|
|
128
|
+
and result["retry_count"] == 2
|
|
129
|
+
and result["step_retry_count"] == 0
|
|
130
|
+
and result["step_results"] == ["5", "AGENT LOOP"]
|
|
131
|
+
and [item["retry"] for item in result["verification_results"]]
|
|
132
|
+
== ["0", "1", "0", "1"]
|
|
133
|
+
),
|
|
134
|
+
),
|
|
135
|
+
EvaluationCase(
|
|
136
|
+
"multiplication_tool_success",
|
|
137
|
+
"tool",
|
|
138
|
+
lambda: run_agent(
|
|
139
|
+
"multiply 6 * 7",
|
|
140
|
+
config=AgentConfig(max_steps=3, max_retries=1),
|
|
141
|
+
),
|
|
142
|
+
lambda result: (
|
|
143
|
+
result["status"] == AgentStatus.DONE
|
|
144
|
+
and result["answer"] == "42"
|
|
145
|
+
and result["tool_calls"][-1]["tool"] == "multiply_numbers"
|
|
146
|
+
),
|
|
147
|
+
),
|
|
148
|
+
EvaluationCase(
|
|
149
|
+
"reverse_text_tool_success",
|
|
150
|
+
"tool",
|
|
151
|
+
lambda: run_agent(
|
|
152
|
+
"Reverse Text in 'Agent Loop'",
|
|
153
|
+
config=AgentConfig(max_steps=3, max_retries=1),
|
|
154
|
+
),
|
|
155
|
+
lambda result: (
|
|
156
|
+
result["status"] == AgentStatus.DONE
|
|
157
|
+
and result["answer"] == "pooL tnegA"
|
|
158
|
+
and result["tool_calls"][-1]["tool"] == "reverse_text"
|
|
159
|
+
),
|
|
160
|
+
),
|
|
161
|
+
EvaluationCase(
|
|
162
|
+
"lowercase_text_tool_success",
|
|
163
|
+
"tool",
|
|
164
|
+
lambda: run_agent(
|
|
165
|
+
"Lowercase Text in 'Agent Loop'",
|
|
166
|
+
config=AgentConfig(max_steps=3, max_retries=1),
|
|
167
|
+
),
|
|
168
|
+
lambda result: (
|
|
169
|
+
result["status"] == AgentStatus.DONE
|
|
170
|
+
and result["answer"] == "agent loop"
|
|
171
|
+
and result["tool_calls"][-1]["tool"] == "lowercase_text"
|
|
172
|
+
),
|
|
173
|
+
),
|
|
174
|
+
EvaluationCase(
|
|
175
|
+
"trim_text_tool_success",
|
|
176
|
+
"tool",
|
|
177
|
+
lambda: run_agent(
|
|
178
|
+
"Trim Text in ' Agent Loop '",
|
|
179
|
+
config=AgentConfig(max_steps=3, max_retries=1),
|
|
180
|
+
),
|
|
181
|
+
lambda result: (
|
|
182
|
+
result["status"] == AgentStatus.DONE
|
|
183
|
+
and result["answer"] == "Agent Loop"
|
|
184
|
+
and result["tool_calls"][-1]["tool"] == "trim_text"
|
|
185
|
+
),
|
|
186
|
+
),
|
|
187
|
+
EvaluationCase(
|
|
188
|
+
"subtraction_tool_success",
|
|
189
|
+
"tool",
|
|
190
|
+
lambda: run_agent(
|
|
191
|
+
"subtract 10 - 4",
|
|
192
|
+
config=AgentConfig(max_steps=3, max_retries=1),
|
|
193
|
+
),
|
|
194
|
+
lambda result: (
|
|
195
|
+
result["status"] == AgentStatus.DONE
|
|
196
|
+
and result["answer"] == "6"
|
|
197
|
+
and result["tool_calls"][-1]["tool"] == "subtract_numbers"
|
|
198
|
+
),
|
|
199
|
+
),
|
|
200
|
+
EvaluationCase(
|
|
201
|
+
"tool_error_recovery",
|
|
202
|
+
"recovery",
|
|
203
|
+
lambda: run_agent(
|
|
204
|
+
"reverse text in 'Agent Loop'",
|
|
205
|
+
config=AgentConfig(max_steps=4, max_retries=2),
|
|
206
|
+
fault_plan={"reverse text in 'Agent Loop'": ["tool-error"]},
|
|
207
|
+
),
|
|
208
|
+
lambda result: (
|
|
209
|
+
result["status"] == AgentStatus.DONE
|
|
210
|
+
and result["answer"] == "pooL tnegA"
|
|
211
|
+
and result["reflections"][-1]["reason"] == "tool execution failed"
|
|
212
|
+
and [item["fault"] for item in result["execution_attempts"]]
|
|
213
|
+
== ["tool-error", ""]
|
|
214
|
+
),
|
|
215
|
+
),
|
|
216
|
+
EvaluationCase(
|
|
217
|
+
"empty_plan_failure",
|
|
218
|
+
"failure",
|
|
219
|
+
lambda: run_agent(
|
|
220
|
+
" ",
|
|
221
|
+
config=AgentConfig(max_steps=3, max_retries=1),
|
|
222
|
+
),
|
|
223
|
+
lambda result: (
|
|
224
|
+
result["status"] == AgentStatus.FAILED
|
|
225
|
+
and result["errors"] == ["empty plan"]
|
|
226
|
+
and [event["node"] for event in result["events"]] == ["planner"]
|
|
227
|
+
),
|
|
228
|
+
),
|
|
229
|
+
]
|