agentnova 0.3.6__tar.gz → 0.3.8__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.
- {agentnova-0.3.6 → agentnova-0.3.8}/PKG-INFO +44 -13
- {agentnova-0.3.6 → agentnova-0.3.8}/README.md +43 -12
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/__init__.py +1 -1
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/acp_plugin.py +7 -3
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/agent.py +235 -20
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/backends/__init__.py +5 -4
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/backends/ollama.py +49 -39
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/cli.py +132 -101
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/colors.py +203 -203
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/helpers.py +38 -20
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/tool_cache.py +223 -188
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/types.py +5 -5
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/00_basic_agent.py +29 -5
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/01_quick_diagnostic.py +9 -8
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/02_tool_test.py +221 -52
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/03_reasoning_test.py +39 -3
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/04_gsm8k_benchmark.py +39 -3
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/05_common_sense.py +39 -3
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/06_causal_reasoning.py +39 -3
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/07_logical_deduction.py +39 -3
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/08_reading_comprehension.py +39 -3
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/09_general_knowledge.py +39 -3
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/10_implicit_reasoning.py +39 -3
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/11_analogical_reasoning.py +39 -3
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/loader.py +5 -2
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/SKILL.md +1 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/soul/loader.py +12 -4
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/souls/nova-helper/SOUL.md +29 -1
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/tools/sandboxed_repl.py +4 -5
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/PKG-INFO +44 -13
- {agentnova-0.3.6 → agentnova-0.3.8}/pyproject.toml +1 -1
- {agentnova-0.3.6 → agentnova-0.3.8}/LICENSE +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/__main__.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/agent_mode.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/backends/base.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/backends/bitnet.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/config.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/__init__.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/args_normal.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/error_recovery.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/math_prompts.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/memory.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/model_config.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/model_family_config.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/models.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/openresponses.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/prompts.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/core/tool_parse.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/examples/__init__.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/model_discovery.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/orchestrator.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/py.typed +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/shared_args.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/__init__.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/acp/SKILL.md +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/datetime/SKILL.md +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/__init__.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/aggregate_benchmark.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/generate_report.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/improve_description.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/init_skill.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/package_skill.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/quick_validate.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/run_eval.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/run_loop.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/security_scan.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/test_package_skill.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/test_quick_validate.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/utils.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/skill-creator/scripts/validate.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/skills/web-search/SKILL.md +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/soul/__init__.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/soul/types.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/souls/nova-helper/IDENTITY.md +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/souls/nova-helper/STYLE.md +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/souls/nova-helper/soul.json +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/tools/__init__.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/tools/builtins.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova/tools/registry.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/SOURCES.txt +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/dependency_links.txt +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/entry_points.txt +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/requires.txt +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/agentnova.egg-info/top_level.txt +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/localclaw/__init__.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/localclaw-redirect/localclaw/__init__.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/setup.cfg +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/tests/test_agent.py +0 -0
- {agentnova-0.3.6 → agentnova-0.3.8}/tests/test_spec_compliance.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentnova
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
4
4
|
Summary: ⚛️ AgentNova - A minimal, hackable agentic framework for local LLM inference
|
|
5
5
|
Author-email: VTSTech <veritas@vts-tech.org>
|
|
6
6
|
Maintainer-email: VTSTech <veritas@vts-tech.org>
|
|
@@ -32,7 +32,7 @@ Requires-Dist: black>=23.0; extra == "dev"
|
|
|
32
32
|
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
33
33
|
Dynamic: license-file
|
|
34
34
|
|
|
35
|
-
# ⚛️ AgentNova R03.
|
|
35
|
+
# ⚛️ AgentNova R03.8
|
|
36
36
|
|
|
37
37
|
**Status: Alpha**
|
|
38
38
|
|
|
@@ -49,7 +49,8 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
|
|
|
49
49
|
|
|
50
50
|
[](#license) [](https://python.org)
|
|
51
51
|
|
|
52
|
-
<img width="
|
|
52
|
+
<img width="1314" height="993" alt="image" src="https://github.com/user-attachments/assets/586b1a44-441e-41ae-85c2-b65b6a0816b9" />
|
|
53
|
+
<img width="1063" height="574" alt="image" src="https://github.com/user-attachments/assets/eab6f4ad-810c-4741-b637-e120f9ccb974" />
|
|
53
54
|
|
|
54
55
|
## 📚 Documentation
|
|
55
56
|
|
|
@@ -63,7 +64,7 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
|
|
|
63
64
|
|
|
64
65
|
- **Zero dependencies** — Uses Python stdlib only (urllib for HTTP)
|
|
65
66
|
- **Ollama + BitNet backends** — Switch with `--backend` flag
|
|
66
|
-
- **Dual API support** — OpenResponses (`--api
|
|
67
|
+
- **Dual API support** — OpenResponses (`--api openre`) and OpenAI Chat-Completions (`--api openai`)
|
|
67
68
|
- **Three-tier tool support** — Native, ReAct, or none (auto-detected)
|
|
68
69
|
- **Small model optimized** — Fuzzy matching, argument normalization
|
|
69
70
|
- **Built-in security** — Path validation, command blocklist, SSRF protection
|
|
@@ -99,8 +100,8 @@ agentnova chat -m qwen2.5:0.5b --tools calculator,shell
|
|
|
99
100
|
# Autonomous agent mode
|
|
100
101
|
agentnova agent -m qwen2.5:7b --tools calculator,shell,write_file
|
|
101
102
|
|
|
102
|
-
# Use Chat-Completions API
|
|
103
|
-
agentnova chat -m qwen2.5:0.5b --api
|
|
103
|
+
# Use OpenAI Chat-Completions API
|
|
104
|
+
agentnova chat -m qwen2.5:0.5b --api openai
|
|
104
105
|
|
|
105
106
|
# List available models
|
|
106
107
|
agentnova models
|
|
@@ -138,7 +139,7 @@ from agentnova.backends import get_backend
|
|
|
138
139
|
from agentnova.core.types import ApiMode
|
|
139
140
|
|
|
140
141
|
# Use Chat-Completions mode with streaming
|
|
141
|
-
backend = get_backend("ollama", api_mode=ApiMode.
|
|
142
|
+
backend = get_backend("ollama", api_mode=ApiMode.OPENAI)
|
|
142
143
|
|
|
143
144
|
for chunk in backend.generate_completions_stream(
|
|
144
145
|
model="qwen2.5:0.5b",
|
|
@@ -261,7 +262,7 @@ agentnova config --urls # Show only URLs
|
|
|
261
262
|
|
|
262
263
|
| Option | Description |
|
|
263
264
|
|--------|-------------|
|
|
264
|
-
| `--api
|
|
265
|
+
| `--api openre\|openai` | API mode: OpenResponses (default) or OpenAI Chat-Completions |
|
|
265
266
|
| `--response-format text\|json` | Response format (Chat-Completions mode) |
|
|
266
267
|
| `--truncation auto\|disabled` | Truncation behavior for long responses |
|
|
267
268
|
| `--soul <path>` | Load Soul Spec persona package |
|
|
@@ -283,7 +284,7 @@ agentnova run "What is 2+2?"
|
|
|
283
284
|
|
|
284
285
|
## Tests & Examples
|
|
285
286
|
|
|
286
|
-
AgentNova includes a suite of tests for validating agent capabilities:
|
|
287
|
+
AgentNova includes a comprehensive suite of tests for validating agent capabilities across reasoning, knowledge, and tool usage:
|
|
287
288
|
|
|
288
289
|
```bash
|
|
289
290
|
# Basic agent test (no tools)
|
|
@@ -292,7 +293,7 @@ python -m agentnova.examples.00_basic_agent
|
|
|
292
293
|
# Quick 5-question diagnostic
|
|
293
294
|
python -m agentnova.examples.01_quick_diagnostic
|
|
294
295
|
|
|
295
|
-
# Tool usage tests (calculator, shell, datetime)
|
|
296
|
+
# Tool usage tests (calculator, shell, datetime, file, python_repl)
|
|
296
297
|
python -m agentnova.examples.02_tool_test
|
|
297
298
|
|
|
298
299
|
# Logic and reasoning tests (BBH-style)
|
|
@@ -300,16 +301,45 @@ python -m agentnova.examples.03_reasoning_test
|
|
|
300
301
|
|
|
301
302
|
# GSM8K math benchmark (50 questions)
|
|
302
303
|
python -m agentnova.examples.04_gsm8k_benchmark
|
|
304
|
+
|
|
305
|
+
# Common sense reasoning (BIG-bench)
|
|
306
|
+
python -m agentnova.examples.05_common_sense
|
|
307
|
+
|
|
308
|
+
# Causal reasoning (BIG-bench)
|
|
309
|
+
python -m agentnova.examples.06_causal_reasoning
|
|
310
|
+
|
|
311
|
+
# Logical deduction (BIG-bench)
|
|
312
|
+
python -m agentnova.examples.07_logical_deduction
|
|
313
|
+
|
|
314
|
+
# Reading comprehension
|
|
315
|
+
python -m agentnova.examples.08_reading_comprehension
|
|
316
|
+
|
|
317
|
+
# General knowledge (BIG-bench)
|
|
318
|
+
python -m agentnova.examples.09_general_knowledge
|
|
319
|
+
|
|
320
|
+
# Implicit reasoning
|
|
321
|
+
python -m agentnova.examples.10_implicit_reasoning
|
|
322
|
+
|
|
323
|
+
# Analogical reasoning
|
|
324
|
+
python -m agentnova.examples.11_analogical_reasoning
|
|
303
325
|
```
|
|
304
326
|
|
|
305
327
|
### Test Categories
|
|
306
328
|
|
|
307
329
|
| Test | Questions | Focus |
|
|
308
330
|
|------|-----------|-------|
|
|
331
|
+
| Basic Agent | 1 | Single prompt, no tools |
|
|
309
332
|
| Quick Diagnostic | 5 | Calculator tool, multi-step reasoning |
|
|
310
|
-
| Tool Test | 10 | Calculator, shell, datetime tools |
|
|
311
|
-
| Reasoning Test |
|
|
333
|
+
| Tool Test | 10 | Calculator, shell, datetime, file, python_repl tools |
|
|
334
|
+
| Reasoning Test | 14 | Logic, deduction, patterns, spatial |
|
|
312
335
|
| GSM8K Benchmark | 50 | Math word problems |
|
|
336
|
+
| Common Sense | 25 | Physical properties, everyday reasoning |
|
|
337
|
+
| Causal Reasoning | 25 | Cause and effect relationships |
|
|
338
|
+
| Logical Deduction | 25 | Formal logic puzzles |
|
|
339
|
+
| Reading Comprehension | 25 | Passage-based Q&A |
|
|
340
|
+
| General Knowledge | 25 | Science, history, geography |
|
|
341
|
+
| Implicit Reasoning | 25 | Unstated assumptions and inference |
|
|
342
|
+
| Analogical Reasoning | 25 | Pattern matching and analogies |
|
|
313
343
|
|
|
314
344
|
### Benchmark Results (Quick Diagnostic)
|
|
315
345
|
|
|
@@ -318,8 +348,9 @@ python -m agentnova.examples.04_gsm8k_benchmark
|
|
|
318
348
|
| functiongemma:270m | 5/5 (100%) | ~20s | native |
|
|
319
349
|
| granite4:350m | 5/5 (100%) | ~50s | native |
|
|
320
350
|
| qwen2.5:0.5b | 5/5 (100%) | 38s | native |
|
|
321
|
-
| qwen2.5-coder:0.5b | 5/5 (100%) | 93s |
|
|
351
|
+
| qwen2.5-coder:0.5b | 5/5 (100%) | 93s | native |
|
|
322
352
|
| qwen3:0.6b | 5/5 (100%) | 70s | react |
|
|
353
|
+
| deepseek-r1:1.5b | 5/5 (100%) | ~305s | native |
|
|
323
354
|
|
|
324
355
|
All tested models achieve 100% on the Quick Diagnostic. Native models are ~2x faster than ReAct models due to direct API tool calling.
|
|
325
356
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ⚛️ AgentNova R03.
|
|
1
|
+
# ⚛️ AgentNova R03.8
|
|
2
2
|
|
|
3
3
|
**Status: Alpha**
|
|
4
4
|
|
|
@@ -15,7 +15,8 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
|
|
|
15
15
|
|
|
16
16
|
[](#license) [](https://python.org)
|
|
17
17
|
|
|
18
|
-
<img width="
|
|
18
|
+
<img width="1314" height="993" alt="image" src="https://github.com/user-attachments/assets/586b1a44-441e-41ae-85c2-b65b6a0816b9" />
|
|
19
|
+
<img width="1063" height="574" alt="image" src="https://github.com/user-attachments/assets/eab6f4ad-810c-4741-b637-e120f9ccb974" />
|
|
19
20
|
|
|
20
21
|
## 📚 Documentation
|
|
21
22
|
|
|
@@ -29,7 +30,7 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
|
|
|
29
30
|
|
|
30
31
|
- **Zero dependencies** — Uses Python stdlib only (urllib for HTTP)
|
|
31
32
|
- **Ollama + BitNet backends** — Switch with `--backend` flag
|
|
32
|
-
- **Dual API support** — OpenResponses (`--api
|
|
33
|
+
- **Dual API support** — OpenResponses (`--api openre`) and OpenAI Chat-Completions (`--api openai`)
|
|
33
34
|
- **Three-tier tool support** — Native, ReAct, or none (auto-detected)
|
|
34
35
|
- **Small model optimized** — Fuzzy matching, argument normalization
|
|
35
36
|
- **Built-in security** — Path validation, command blocklist, SSRF protection
|
|
@@ -65,8 +66,8 @@ agentnova chat -m qwen2.5:0.5b --tools calculator,shell
|
|
|
65
66
|
# Autonomous agent mode
|
|
66
67
|
agentnova agent -m qwen2.5:7b --tools calculator,shell,write_file
|
|
67
68
|
|
|
68
|
-
# Use Chat-Completions API
|
|
69
|
-
agentnova chat -m qwen2.5:0.5b --api
|
|
69
|
+
# Use OpenAI Chat-Completions API
|
|
70
|
+
agentnova chat -m qwen2.5:0.5b --api openai
|
|
70
71
|
|
|
71
72
|
# List available models
|
|
72
73
|
agentnova models
|
|
@@ -104,7 +105,7 @@ from agentnova.backends import get_backend
|
|
|
104
105
|
from agentnova.core.types import ApiMode
|
|
105
106
|
|
|
106
107
|
# Use Chat-Completions mode with streaming
|
|
107
|
-
backend = get_backend("ollama", api_mode=ApiMode.
|
|
108
|
+
backend = get_backend("ollama", api_mode=ApiMode.OPENAI)
|
|
108
109
|
|
|
109
110
|
for chunk in backend.generate_completions_stream(
|
|
110
111
|
model="qwen2.5:0.5b",
|
|
@@ -227,7 +228,7 @@ agentnova config --urls # Show only URLs
|
|
|
227
228
|
|
|
228
229
|
| Option | Description |
|
|
229
230
|
|--------|-------------|
|
|
230
|
-
| `--api
|
|
231
|
+
| `--api openre\|openai` | API mode: OpenResponses (default) or OpenAI Chat-Completions |
|
|
231
232
|
| `--response-format text\|json` | Response format (Chat-Completions mode) |
|
|
232
233
|
| `--truncation auto\|disabled` | Truncation behavior for long responses |
|
|
233
234
|
| `--soul <path>` | Load Soul Spec persona package |
|
|
@@ -249,7 +250,7 @@ agentnova run "What is 2+2?"
|
|
|
249
250
|
|
|
250
251
|
## Tests & Examples
|
|
251
252
|
|
|
252
|
-
AgentNova includes a suite of tests for validating agent capabilities:
|
|
253
|
+
AgentNova includes a comprehensive suite of tests for validating agent capabilities across reasoning, knowledge, and tool usage:
|
|
253
254
|
|
|
254
255
|
```bash
|
|
255
256
|
# Basic agent test (no tools)
|
|
@@ -258,7 +259,7 @@ python -m agentnova.examples.00_basic_agent
|
|
|
258
259
|
# Quick 5-question diagnostic
|
|
259
260
|
python -m agentnova.examples.01_quick_diagnostic
|
|
260
261
|
|
|
261
|
-
# Tool usage tests (calculator, shell, datetime)
|
|
262
|
+
# Tool usage tests (calculator, shell, datetime, file, python_repl)
|
|
262
263
|
python -m agentnova.examples.02_tool_test
|
|
263
264
|
|
|
264
265
|
# Logic and reasoning tests (BBH-style)
|
|
@@ -266,16 +267,45 @@ python -m agentnova.examples.03_reasoning_test
|
|
|
266
267
|
|
|
267
268
|
# GSM8K math benchmark (50 questions)
|
|
268
269
|
python -m agentnova.examples.04_gsm8k_benchmark
|
|
270
|
+
|
|
271
|
+
# Common sense reasoning (BIG-bench)
|
|
272
|
+
python -m agentnova.examples.05_common_sense
|
|
273
|
+
|
|
274
|
+
# Causal reasoning (BIG-bench)
|
|
275
|
+
python -m agentnova.examples.06_causal_reasoning
|
|
276
|
+
|
|
277
|
+
# Logical deduction (BIG-bench)
|
|
278
|
+
python -m agentnova.examples.07_logical_deduction
|
|
279
|
+
|
|
280
|
+
# Reading comprehension
|
|
281
|
+
python -m agentnova.examples.08_reading_comprehension
|
|
282
|
+
|
|
283
|
+
# General knowledge (BIG-bench)
|
|
284
|
+
python -m agentnova.examples.09_general_knowledge
|
|
285
|
+
|
|
286
|
+
# Implicit reasoning
|
|
287
|
+
python -m agentnova.examples.10_implicit_reasoning
|
|
288
|
+
|
|
289
|
+
# Analogical reasoning
|
|
290
|
+
python -m agentnova.examples.11_analogical_reasoning
|
|
269
291
|
```
|
|
270
292
|
|
|
271
293
|
### Test Categories
|
|
272
294
|
|
|
273
295
|
| Test | Questions | Focus |
|
|
274
296
|
|------|-----------|-------|
|
|
297
|
+
| Basic Agent | 1 | Single prompt, no tools |
|
|
275
298
|
| Quick Diagnostic | 5 | Calculator tool, multi-step reasoning |
|
|
276
|
-
| Tool Test | 10 | Calculator, shell, datetime tools |
|
|
277
|
-
| Reasoning Test |
|
|
299
|
+
| Tool Test | 10 | Calculator, shell, datetime, file, python_repl tools |
|
|
300
|
+
| Reasoning Test | 14 | Logic, deduction, patterns, spatial |
|
|
278
301
|
| GSM8K Benchmark | 50 | Math word problems |
|
|
302
|
+
| Common Sense | 25 | Physical properties, everyday reasoning |
|
|
303
|
+
| Causal Reasoning | 25 | Cause and effect relationships |
|
|
304
|
+
| Logical Deduction | 25 | Formal logic puzzles |
|
|
305
|
+
| Reading Comprehension | 25 | Passage-based Q&A |
|
|
306
|
+
| General Knowledge | 25 | Science, history, geography |
|
|
307
|
+
| Implicit Reasoning | 25 | Unstated assumptions and inference |
|
|
308
|
+
| Analogical Reasoning | 25 | Pattern matching and analogies |
|
|
279
309
|
|
|
280
310
|
### Benchmark Results (Quick Diagnostic)
|
|
281
311
|
|
|
@@ -284,8 +314,9 @@ python -m agentnova.examples.04_gsm8k_benchmark
|
|
|
284
314
|
| functiongemma:270m | 5/5 (100%) | ~20s | native |
|
|
285
315
|
| granite4:350m | 5/5 (100%) | ~50s | native |
|
|
286
316
|
| qwen2.5:0.5b | 5/5 (100%) | 38s | native |
|
|
287
|
-
| qwen2.5-coder:0.5b | 5/5 (100%) | 93s |
|
|
317
|
+
| qwen2.5-coder:0.5b | 5/5 (100%) | 93s | native |
|
|
288
318
|
| qwen3:0.6b | 5/5 (100%) | 70s | react |
|
|
319
|
+
| deepseek-r1:1.5b | 5/5 (100%) | ~305s | native |
|
|
289
320
|
|
|
290
321
|
All tested models achieve 100% on the Quick Diagnostic. Native models are ~2x faster than ReAct models due to direct API tool calling.
|
|
291
322
|
|
|
@@ -281,6 +281,10 @@ class ACPPlugin:
|
|
|
281
281
|
"get_note": "READ",
|
|
282
282
|
"list_notes": "READ",
|
|
283
283
|
"list_directory": "READ",
|
|
284
|
+
# ACP v1.0.4: A2A agent-to-agent communication
|
|
285
|
+
"a2a_send": "A2A",
|
|
286
|
+
"a2a_request": "A2A",
|
|
287
|
+
"a2a_response": "A2A",
|
|
284
288
|
}
|
|
285
289
|
|
|
286
290
|
# v1.0.4: Auto-derive capabilities from tool action map if not explicitly provided
|
|
@@ -1089,11 +1093,11 @@ class ACPPlugin:
|
|
|
1089
1093
|
"""Format tool arguments as a target string for ACP."""
|
|
1090
1094
|
# Tool-specific formatting
|
|
1091
1095
|
if tool_name == "read_file":
|
|
1092
|
-
return args.get("path", "unknown")
|
|
1096
|
+
return args.get("file_path", args.get("path", "unknown"))
|
|
1093
1097
|
elif tool_name == "write_file":
|
|
1094
|
-
return args.get("path", "unknown")
|
|
1098
|
+
return args.get("file_path", args.get("path", "unknown"))
|
|
1095
1099
|
elif tool_name == "edit_file":
|
|
1096
|
-
return args.get("path", "unknown")
|
|
1100
|
+
return args.get("file_path", args.get("path", "unknown"))
|
|
1097
1101
|
elif tool_name == "shell":
|
|
1098
1102
|
return args.get("command", "")[:100]
|
|
1099
1103
|
elif tool_name == "calculator":
|
|
@@ -40,6 +40,7 @@ from .core.openresponses import (
|
|
|
40
40
|
MessageItem, FunctionCallItem, FunctionCallOutputItem, ReasoningItem,
|
|
41
41
|
OutputText, InputText,
|
|
42
42
|
RequestConfig, Error,
|
|
43
|
+
EventType, ResponseEvent, OutputItemEvent,
|
|
43
44
|
create_message_item, create_function_call_item, create_function_call_output,
|
|
44
45
|
create_function_call_output_item,
|
|
45
46
|
stream_response_events,
|
|
@@ -132,7 +133,7 @@ class Agent:
|
|
|
132
133
|
system_prompt: Custom system prompt (overrides soul)
|
|
133
134
|
soul: Path to Soul Spec package (default: "nova-helper")
|
|
134
135
|
soul_level: Progressive disclosure level for soul (1-3)
|
|
135
|
-
num_ctx: Context window size in tokens (default:
|
|
136
|
+
num_ctx: Context window size in tokens (default: 8192)
|
|
136
137
|
temperature: Sampling temperature (default: model-specific)
|
|
137
138
|
top_p: Nucleus sampling probability (default: model-specific)
|
|
138
139
|
num_predict: Maximum tokens to generate (default: model-specific)
|
|
@@ -143,12 +144,12 @@ class Agent:
|
|
|
143
144
|
self.model = model
|
|
144
145
|
self.max_steps = max_steps
|
|
145
146
|
self.debug = debug
|
|
146
|
-
# Get num_ctx from: explicit param > config/env > default
|
|
147
|
+
# Get num_ctx from: explicit param > config/env > default 8192
|
|
147
148
|
if num_ctx is not None:
|
|
148
149
|
self.num_ctx = num_ctx
|
|
149
150
|
else:
|
|
150
151
|
config = get_config()
|
|
151
|
-
self.num_ctx = config.num_ctx if config.num_ctx else
|
|
152
|
+
self.num_ctx = config.num_ctx if config.num_ctx else 8192
|
|
152
153
|
|
|
153
154
|
# Generation parameters (use model defaults if not specified)
|
|
154
155
|
self._temperature = temperature
|
|
@@ -314,7 +315,7 @@ class Agent:
|
|
|
314
315
|
@property
|
|
315
316
|
def _is_comp_mode(self) -> bool:
|
|
316
317
|
"""Check if backend is using Chat-Completions (comp) API mode."""
|
|
317
|
-
return hasattr(self.backend, 'api_mode') and self.backend.api_mode == ApiMode.
|
|
318
|
+
return hasattr(self.backend, 'api_mode') and self.backend.api_mode == ApiMode.OPENAI
|
|
318
319
|
|
|
319
320
|
def _log_openresponses(self, msg: str) -> None:
|
|
320
321
|
"""Log OpenResponses debug message only when not in comp mode."""
|
|
@@ -441,6 +442,31 @@ Final Answer: <the answer>
|
|
|
441
442
|
tokens = gen_response.get("usage", {}).get("total_tokens", 0)
|
|
442
443
|
total_tokens += tokens
|
|
443
444
|
|
|
445
|
+
# OpenResponses: Handle finish_reason from backend
|
|
446
|
+
finish_reason = gen_response.get("_finish_reason", "stop")
|
|
447
|
+
if finish_reason == "length":
|
|
448
|
+
# Token budget exhausted — response is incomplete
|
|
449
|
+
if self.debug:
|
|
450
|
+
print(f" [OpenResponses] finish_reason='length' — marking incomplete")
|
|
451
|
+
steps.append(StepResult(
|
|
452
|
+
type=StepResultType.MAX_STEPS,
|
|
453
|
+
content="Response truncated: token limit reached",
|
|
454
|
+
tokens_used=tokens,
|
|
455
|
+
))
|
|
456
|
+
response.mark_incomplete()
|
|
457
|
+
break
|
|
458
|
+
elif finish_reason == "content_filter":
|
|
459
|
+
# Content was filtered — response failed
|
|
460
|
+
if self.debug:
|
|
461
|
+
print(f" [OpenResponses] finish_reason='content_filter' — marking failed")
|
|
462
|
+
steps.append(StepResult(
|
|
463
|
+
type=StepResultType.ERROR,
|
|
464
|
+
error="Response blocked by content filter",
|
|
465
|
+
tokens_used=tokens,
|
|
466
|
+
))
|
|
467
|
+
response.mark_failed({"message": "Content filtered by provider", "type": "content_filter"})
|
|
468
|
+
break
|
|
469
|
+
|
|
444
470
|
if self.debug:
|
|
445
471
|
print(f" Content: {content[:200] if content else '(empty)'}...")
|
|
446
472
|
print(f" Native tool calls: {native_tool_calls}")
|
|
@@ -873,6 +899,10 @@ Final Answer: <the answer>
|
|
|
873
899
|
OpenResponses specification. It yields Server-Sent Events (SSE) that
|
|
874
900
|
describe the response lifecycle and content deltas.
|
|
875
901
|
|
|
902
|
+
IMPORTANT: The agentic loop is fully supported during streaming.
|
|
903
|
+
When the model produces a tool call, it is executed and the loop
|
|
904
|
+
continues, streaming the next model response.
|
|
905
|
+
|
|
876
906
|
SSE Event Sequence (per OpenResponses spec):
|
|
877
907
|
1. response.queued - Response is queued
|
|
878
908
|
2. response.in_progress - Response started
|
|
@@ -895,6 +925,8 @@ Final Answer: <the answer>
|
|
|
895
925
|
for sse_event in agent.run_stream("Hello!"):
|
|
896
926
|
print(sse_event) # SSE formatted event
|
|
897
927
|
"""
|
|
928
|
+
start_time = time.time()
|
|
929
|
+
|
|
898
930
|
# Create OpenResponses Response object
|
|
899
931
|
response = Response(
|
|
900
932
|
model=self.model,
|
|
@@ -903,9 +935,8 @@ Final Answer: <the answer>
|
|
|
903
935
|
allowed_tools=self._allowed_tools or [],
|
|
904
936
|
)
|
|
905
937
|
|
|
906
|
-
if self.debug
|
|
907
|
-
print(f"\n[OpenResponses] Response created: id={response.id}")
|
|
908
|
-
print(f"[OpenResponses] Response status: {response.status.value}")
|
|
938
|
+
if self.debug:
|
|
939
|
+
print(f"\n[OpenResponses stream] Response created: id={response.id}")
|
|
909
940
|
|
|
910
941
|
# Add user prompt to memory
|
|
911
942
|
self.memory.add("user", prompt)
|
|
@@ -914,22 +945,190 @@ Final Answer: <the answer>
|
|
|
914
945
|
user_item = create_message_item("user", prompt)
|
|
915
946
|
response.input.append(user_item)
|
|
916
947
|
|
|
917
|
-
if self.debug and not self._is_comp_mode:
|
|
918
|
-
print(f"[OpenResponses] Input item added: id={user_item.id}, type={user_item.type}, role={user_item.role}")
|
|
919
|
-
|
|
920
948
|
if self.debug:
|
|
921
|
-
print(f"\n[AgentNova] Model: {self.model}")
|
|
922
|
-
print(f"[AgentNova] Backend: {self.backend.base_url}")
|
|
923
|
-
print(f"[AgentNova] tool_choice: {self.tool_choice.type.value}")
|
|
924
|
-
print(f"[AgentNova] Tools: {self.tools.names()}")
|
|
925
|
-
print(f"[AgentNova] Prompt
|
|
949
|
+
print(f"\n[AgentNova stream] Model: {self.model}")
|
|
950
|
+
print(f"[AgentNova stream] Backend: {self.backend.base_url}")
|
|
951
|
+
print(f"[AgentNova stream] tool_choice: {self.tool_choice.type.value}")
|
|
952
|
+
print(f"[AgentNova stream] Tools: {self.tools.names()}")
|
|
953
|
+
print(f"[AgentNova stream] Prompt: {prompt}\n")
|
|
954
|
+
|
|
955
|
+
# OpenResponses: Agentic Loop (streaming variant)
|
|
956
|
+
# Stream model output, check for tool calls, execute them, repeat.
|
|
957
|
+
_expecting_final_answer = False
|
|
958
|
+
_last_successful_result = None
|
|
959
|
+
tool_call_count = 0
|
|
960
|
+
|
|
961
|
+
for step_num in range(self.max_steps):
|
|
962
|
+
if self.debug:
|
|
963
|
+
print(f"[Stream Step {step_num + 1}]")
|
|
964
|
+
|
|
965
|
+
# Collect the full streamed response
|
|
966
|
+
full_content = ""
|
|
967
|
+
|
|
968
|
+
# Stream model response, collecting content for tool-call detection
|
|
969
|
+
try:
|
|
970
|
+
for chunk in self._generate_stream_chunks(prompt):
|
|
971
|
+
full_content += chunk
|
|
972
|
+
except Exception as e:
|
|
973
|
+
if self.debug:
|
|
974
|
+
print(f" [Stream] ERROR: {e}")
|
|
975
|
+
# Emit failure event
|
|
976
|
+
response.mark_failed({"message": str(e), "type": "stream_error"})
|
|
977
|
+
fail_event = ResponseEvent(
|
|
978
|
+
type=EventType.RESPONSE_FAILED,
|
|
979
|
+
response=response,
|
|
980
|
+
)
|
|
981
|
+
yield fail_event.to_sse()
|
|
982
|
+
return
|
|
983
|
+
|
|
984
|
+
# Parse for tool calls (ReAct format)
|
|
985
|
+
tool_calls_found = []
|
|
986
|
+
|
|
987
|
+
if full_content:
|
|
988
|
+
parsed_calls = self._parser.parse(full_content)
|
|
989
|
+
for call in parsed_calls:
|
|
990
|
+
if hasattr(call, 'thought') and call.thought:
|
|
991
|
+
reasoning_item = ReasoningItem(
|
|
992
|
+
content=[OutputText(text=call.thought)]
|
|
993
|
+
)
|
|
994
|
+
reasoning_item.status = ItemStatus.COMPLETED
|
|
995
|
+
response.add_output_item(reasoning_item)
|
|
926
996
|
|
|
927
|
-
|
|
928
|
-
|
|
997
|
+
tool_calls_found.append({
|
|
998
|
+
"name": call.name,
|
|
999
|
+
"arguments": call.arguments,
|
|
1000
|
+
"id": "",
|
|
1001
|
+
"final_answer": getattr(call, 'final_answer', None),
|
|
1002
|
+
})
|
|
929
1003
|
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1004
|
+
# ---- Execute tool calls if found ----
|
|
1005
|
+
if tool_calls_found:
|
|
1006
|
+
# Final Answer enforcement (same logic as run())
|
|
1007
|
+
if _expecting_final_answer and _last_successful_result is not None:
|
|
1008
|
+
text_chunks_gen = iter([_last_successful_result])
|
|
1009
|
+
for sse_event in stream_response_events(
|
|
1010
|
+
Response(model=self.model, status=ResponseStatus.IN_PROGRESS,
|
|
1011
|
+
tool_choice=self.tool_choice, allowed_tools=self._allowed_tools or []),
|
|
1012
|
+
text_chunks_gen, debug=self.debug,
|
|
1013
|
+
):
|
|
1014
|
+
yield sse_event
|
|
1015
|
+
return
|
|
1016
|
+
|
|
1017
|
+
pending_final_answer = None
|
|
1018
|
+
self.memory.add("assistant", full_content)
|
|
1019
|
+
|
|
1020
|
+
for tc in tool_calls_found:
|
|
1021
|
+
tool_name = tc["name"]
|
|
1022
|
+
tool_args = tc["arguments"]
|
|
1023
|
+
|
|
1024
|
+
if tc.get("final_answer"):
|
|
1025
|
+
pending_final_answer = tc["final_answer"]
|
|
1026
|
+
|
|
1027
|
+
# Check allowed_tools
|
|
1028
|
+
if self._allowed_tools and tool_name not in self._allowed_tools:
|
|
1029
|
+
error_msg = f"Tool '{tool_name}' not in allowed_tools: {self._allowed_tools}"
|
|
1030
|
+
self.memory.add("user", f"Observation: Error: {error_msg}")
|
|
1031
|
+
continue
|
|
1032
|
+
|
|
1033
|
+
# Fuzzy match
|
|
1034
|
+
tool_name = self._parser._fuzzy_match_tool(tool_name)
|
|
1035
|
+
|
|
1036
|
+
# Create FunctionCallItem and emit SSE events
|
|
1037
|
+
fc_item = create_function_call_item(tool_name, tool_args)
|
|
1038
|
+
fc_item.status = ItemStatus.IN_PROGRESS
|
|
1039
|
+
response.add_output_item(fc_item)
|
|
1040
|
+
output_index = len(response.output) - 1
|
|
1041
|
+
|
|
1042
|
+
fc_added = OutputItemEvent(
|
|
1043
|
+
type=EventType.OUTPUT_ITEM_ADDED,
|
|
1044
|
+
item=fc_item,
|
|
1045
|
+
output_index=output_index,
|
|
1046
|
+
)
|
|
1047
|
+
yield fc_added.to_sse()
|
|
1048
|
+
|
|
1049
|
+
# Execute the tool
|
|
1050
|
+
result = self._execute_tool(tool_name, tool_args, prompt)
|
|
1051
|
+
tool_call_count += 1
|
|
1052
|
+
|
|
1053
|
+
fc_item.status = ItemStatus.COMPLETED
|
|
1054
|
+
|
|
1055
|
+
fc_done = OutputItemEvent(
|
|
1056
|
+
type=EventType.OUTPUT_ITEM_DONE,
|
|
1057
|
+
item=fc_item,
|
|
1058
|
+
output_index=output_index,
|
|
1059
|
+
)
|
|
1060
|
+
yield fc_done.to_sse()
|
|
1061
|
+
|
|
1062
|
+
# Create function_call_output
|
|
1063
|
+
fco_item = create_function_call_output(fc_item.call_id, str(result))
|
|
1064
|
+
response.add_output_item(fco_item)
|
|
1065
|
+
|
|
1066
|
+
# Build observation and add to memory
|
|
1067
|
+
is_error = is_error_result(str(result))
|
|
1068
|
+
observation_msg = build_enhanced_observation(
|
|
1069
|
+
tool_name=tool_name,
|
|
1070
|
+
result=str(result),
|
|
1071
|
+
tracker=self._error_tracker,
|
|
1072
|
+
available_tools=self.tools.names(),
|
|
1073
|
+
is_error=is_error,
|
|
1074
|
+
)
|
|
1075
|
+
|
|
1076
|
+
if is_error:
|
|
1077
|
+
_expecting_final_answer = False
|
|
1078
|
+
else:
|
|
1079
|
+
_expecting_final_answer = True
|
|
1080
|
+
_last_successful_result = str(result)
|
|
1081
|
+
|
|
1082
|
+
self.memory.add("user", observation_msg)
|
|
1083
|
+
|
|
1084
|
+
# Check for pending final answer
|
|
1085
|
+
if pending_final_answer:
|
|
1086
|
+
text_chunks_gen = iter([pending_final_answer])
|
|
1087
|
+
for sse_event in stream_response_events(
|
|
1088
|
+
Response(model=self.model, status=ResponseStatus.IN_PROGRESS,
|
|
1089
|
+
tool_choice=self.tool_choice, allowed_tools=self._allowed_tools or []),
|
|
1090
|
+
text_chunks_gen, debug=self.debug,
|
|
1091
|
+
):
|
|
1092
|
+
yield sse_event
|
|
1093
|
+
return
|
|
1094
|
+
|
|
1095
|
+
# Continue the agentic loop (next streaming iteration)
|
|
1096
|
+
continue
|
|
1097
|
+
|
|
1098
|
+
# ---- No tool calls — stream final response ----
|
|
1099
|
+
# Check for Final Answer format
|
|
1100
|
+
if self._parser.is_final_answer(full_content):
|
|
1101
|
+
answer = self._parser.extract_final_answer(full_content)
|
|
1102
|
+
text_chunks_gen = iter([answer])
|
|
1103
|
+
else:
|
|
1104
|
+
text_chunks_gen = iter([full_content])
|
|
1105
|
+
|
|
1106
|
+
# Stream the final response with proper OpenResponses events
|
|
1107
|
+
final_response = Response(
|
|
1108
|
+
model=self.model,
|
|
1109
|
+
status=ResponseStatus.IN_PROGRESS,
|
|
1110
|
+
tool_choice=self.tool_choice,
|
|
1111
|
+
allowed_tools=self._allowed_tools or [],
|
|
1112
|
+
)
|
|
1113
|
+
# Carry over any items from previous loop iterations
|
|
1114
|
+
final_response.output = response.output
|
|
1115
|
+
final_response.input = response.input
|
|
1116
|
+
final_response.usage = response.usage
|
|
1117
|
+
|
|
1118
|
+
for sse_event in stream_response_events(final_response, text_chunks_gen, debug=self.debug):
|
|
1119
|
+
yield sse_event
|
|
1120
|
+
|
|
1121
|
+
# Only one pass needed when there are no tool calls
|
|
1122
|
+
return
|
|
1123
|
+
|
|
1124
|
+
else:
|
|
1125
|
+
# Max steps reached
|
|
1126
|
+
response.mark_incomplete()
|
|
1127
|
+
incomplete_event = ResponseEvent(
|
|
1128
|
+
type=EventType.RESPONSE_INCOMPLETE,
|
|
1129
|
+
response=response,
|
|
1130
|
+
)
|
|
1131
|
+
yield incomplete_event.to_sse()
|
|
933
1132
|
|
|
934
1133
|
def _generate_stream_chunks(self, prompt: str) -> Generator[str, None, None]:
|
|
935
1134
|
"""
|
|
@@ -1076,6 +1275,11 @@ Final Answer: <the answer>
|
|
|
1076
1275
|
if self._num_predict is not None:
|
|
1077
1276
|
backend_kwargs["num_predict"] = self._num_predict
|
|
1078
1277
|
|
|
1278
|
+
# OpenResponses: Forward tool_choice to backend API
|
|
1279
|
+
# This allows the backend to enforce tool invocation constraints natively
|
|
1280
|
+
if self.tool_choice and self.tool_choice.type != ToolChoiceType.AUTO:
|
|
1281
|
+
backend_kwargs["tool_choice"] = self.tool_choice.to_dict()
|
|
1282
|
+
|
|
1079
1283
|
# Pass tools for native tool calling (OpenResponses/ChatCompletions compliant)
|
|
1080
1284
|
# ReAct parsing remains as fallback for models without native support
|
|
1081
1285
|
tools_for_backend = self.tools.all() if self.tools and len(self.tools) > 0 else None
|
|
@@ -1101,6 +1305,17 @@ Final Answer: <the answer>
|
|
|
1101
1305
|
**backend_kwargs,
|
|
1102
1306
|
)
|
|
1103
1307
|
|
|
1308
|
+
# OpenResponses / Chat Completions: Handle finish_reason
|
|
1309
|
+
# Per spec, finish_reason affects response status:
|
|
1310
|
+
# "stop" → normal completion (default)
|
|
1311
|
+
# "length" → incomplete — token budget exhausted
|
|
1312
|
+
# "content_filter" → failed — content was filtered
|
|
1313
|
+
finish_reason = response.get("finish_reason", "stop")
|
|
1314
|
+
if self.debug:
|
|
1315
|
+
print(f" [DEBUG] finish_reason: {finish_reason}")
|
|
1316
|
+
# Store for caller to consume
|
|
1317
|
+
response["_finish_reason"] = finish_reason
|
|
1318
|
+
|
|
1104
1319
|
if self.debug:
|
|
1105
1320
|
print(f" [DEBUG] Response keys: {list(response.keys())}")
|
|
1106
1321
|
print(f" [DEBUG] Content: {response.get('content', '')[:100]}...")
|
|
@@ -26,7 +26,7 @@ def get_backend(name: str, timeout: int | None = None, api_mode: ApiMode | str |
|
|
|
26
26
|
Args:
|
|
27
27
|
name: Backend name ("ollama", "bitnet")
|
|
28
28
|
timeout: Request timeout in seconds (default: 120)
|
|
29
|
-
api_mode: API mode ("
|
|
29
|
+
api_mode: API mode ("openre" for OpenResponses, "openai" for Chat-Completions)
|
|
30
30
|
**kwargs: Backend-specific configuration
|
|
31
31
|
|
|
32
32
|
Returns:
|
|
@@ -59,7 +59,7 @@ def get_backend(name: str, timeout: int | None = None, api_mode: ApiMode | str |
|
|
|
59
59
|
return backend_class(**kwargs)
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
def get_default_backend(name: str | None = None, api_mode: ApiMode | str | None = None) -> BaseBackend:
|
|
62
|
+
def get_default_backend(name: str | None = None, api_mode: ApiMode | str | None = None, timeout: int | None = None) -> BaseBackend:
|
|
63
63
|
"""
|
|
64
64
|
Get a default backend instance.
|
|
65
65
|
|
|
@@ -67,13 +67,14 @@ def get_default_backend(name: str | None = None, api_mode: ApiMode | str | None
|
|
|
67
67
|
|
|
68
68
|
Args:
|
|
69
69
|
name: Backend name (optional, uses env var if not provided)
|
|
70
|
-
api_mode: API mode ("
|
|
70
|
+
api_mode: API mode ("openre" for OpenResponses/native, "openai" for Chat-Completions)
|
|
71
|
+
timeout: Request timeout in seconds (default: 120)
|
|
71
72
|
|
|
72
73
|
Returns:
|
|
73
74
|
Backend instance with default configuration
|
|
74
75
|
"""
|
|
75
76
|
backend_name = name or AGENTNOVA_BACKEND
|
|
76
|
-
return get_backend(backend_name, api_mode=api_mode)
|
|
77
|
+
return get_backend(backend_name, api_mode=api_mode, timeout=timeout)
|
|
77
78
|
|
|
78
79
|
|
|
79
80
|
def register_backend(name: str, backend_class: type[BaseBackend]) -> None:
|