agentnova 0.3.8__tar.gz → 0.3.9__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.8 → agentnova-0.3.9}/PKG-INFO +5 -7
- {agentnova-0.3.8 → agentnova-0.3.9}/README.md +4 -6
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/__init__.py +1 -1
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/agent.py +31 -18
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/backends/ollama.py +31 -4
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/__init__.py +7 -7
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/helpers.py +38 -3
- agentnova-0.3.9/agentnova/core/model_config.py +36 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/model_family_config.py +51 -2
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/models.py +6 -1
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/prompts.py +14 -1
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/tool_parse.py +46 -43
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/tools/builtins.py +1 -9
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/tools/registry.py +1 -1
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova.egg-info/PKG-INFO +5 -7
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova.egg-info/SOURCES.txt +2 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/pyproject.toml +1 -1
- agentnova-0.3.9/tests/test_builtins.py +349 -0
- agentnova-0.3.9/tests/test_security.py +406 -0
- agentnova-0.3.8/agentnova/core/model_config.py +0 -239
- {agentnova-0.3.8 → agentnova-0.3.9}/LICENSE +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/__main__.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/acp_plugin.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/agent_mode.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/backends/__init__.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/backends/base.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/backends/bitnet.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/cli.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/colors.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/config.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/args_normal.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/error_recovery.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/math_prompts.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/memory.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/openresponses.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/tool_cache.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/core/types.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/00_basic_agent.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/01_quick_diagnostic.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/02_tool_test.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/03_reasoning_test.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/04_gsm8k_benchmark.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/05_common_sense.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/06_causal_reasoning.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/07_logical_deduction.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/08_reading_comprehension.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/09_general_knowledge.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/10_implicit_reasoning.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/11_analogical_reasoning.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/examples/__init__.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/model_discovery.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/orchestrator.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/py.typed +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/shared_args.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/__init__.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/acp/SKILL.md +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/datetime/SKILL.md +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/loader.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/SKILL.md +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/__init__.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/aggregate_benchmark.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/generate_report.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/improve_description.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/init_skill.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/package_skill.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/quick_validate.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/run_eval.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/run_loop.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/security_scan.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/test_package_skill.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/test_quick_validate.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/utils.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/skill-creator/scripts/validate.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/skills/web-search/SKILL.md +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/soul/__init__.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/soul/loader.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/soul/types.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/souls/nova-helper/IDENTITY.md +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/souls/nova-helper/SOUL.md +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/souls/nova-helper/STYLE.md +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/souls/nova-helper/soul.json +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/tools/__init__.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova/tools/sandboxed_repl.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova.egg-info/dependency_links.txt +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova.egg-info/entry_points.txt +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova.egg-info/requires.txt +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/agentnova.egg-info/top_level.txt +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/localclaw/__init__.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/localclaw-redirect/localclaw/__init__.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/setup.cfg +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/tests/test_agent.py +0 -0
- {agentnova-0.3.8 → agentnova-0.3.9}/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.9
|
|
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.9
|
|
36
36
|
|
|
37
37
|
**Status: Alpha**
|
|
38
38
|
|
|
@@ -77,12 +77,10 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
|
|
|
77
77
|
## Installation
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
cd AgentNova
|
|
83
|
-
pip install -e .
|
|
80
|
+
# Latest Development Release
|
|
81
|
+
pip install git+https://github.com/VTSTech/AgentNova.git --force-reinstall
|
|
84
82
|
|
|
85
|
-
#
|
|
83
|
+
# Last Stable (as stable as Alpha can be) Release
|
|
86
84
|
pip install agentnova
|
|
87
85
|
```
|
|
88
86
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ⚛️ AgentNova R03.
|
|
1
|
+
# ⚛️ AgentNova R03.9
|
|
2
2
|
|
|
3
3
|
**Status: Alpha**
|
|
4
4
|
|
|
@@ -43,12 +43,10 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
|
|
|
43
43
|
## Installation
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
cd AgentNova
|
|
49
|
-
pip install -e .
|
|
46
|
+
# Latest Development Release
|
|
47
|
+
pip install git+https://github.com/VTSTech/AgentNova.git --force-reinstall
|
|
50
48
|
|
|
51
|
-
#
|
|
49
|
+
# Last Stable (as stable as Alpha can be) Release
|
|
52
50
|
pip install agentnova
|
|
53
51
|
```
|
|
54
52
|
|
|
@@ -226,7 +226,7 @@ class Agent:
|
|
|
226
226
|
self.memory = Memory(memory_config or MemoryConfig())
|
|
227
227
|
|
|
228
228
|
# Get model configuration (for temperature, max_tokens defaults)
|
|
229
|
-
from .core.
|
|
229
|
+
from .core.model_family_config import get_model_config
|
|
230
230
|
self.model_config = get_model_config(model)
|
|
231
231
|
|
|
232
232
|
# Detect model family (for backend-specific settings like think=False)
|
|
@@ -414,8 +414,11 @@ Final Answer: <the answer>
|
|
|
414
414
|
# No synthesis or fallback mechanisms are used.
|
|
415
415
|
|
|
416
416
|
# Track when we're expecting a Final Answer (after successful tool use)
|
|
417
|
+
# Only enforced for terminal tools (calculator, get_time, etc.) to avoid
|
|
418
|
+
# breaking multi-step workflows that need multiple tool calls.
|
|
417
419
|
_expecting_final_answer = False
|
|
418
420
|
_last_successful_result = None
|
|
421
|
+
_last_tool_name = None
|
|
419
422
|
|
|
420
423
|
# Reset error tracker for new run
|
|
421
424
|
self._error_tracker.reset()
|
|
@@ -584,9 +587,6 @@ Final Answer: <the answer>
|
|
|
584
587
|
self.memory.add("user", f"Observation: Error: {error_msg}")
|
|
585
588
|
continue
|
|
586
589
|
|
|
587
|
-
# Fuzzy match tool name
|
|
588
|
-
tool_name = self._parser._fuzzy_match_tool(tool_name)
|
|
589
|
-
|
|
590
590
|
# Create FunctionCallItem
|
|
591
591
|
fc_item = create_function_call_item(tool_name, tool_args, tool_call_id)
|
|
592
592
|
fc_item.status = ItemStatus.IN_PROGRESS
|
|
@@ -616,6 +616,9 @@ Final Answer: <the answer>
|
|
|
616
616
|
fc_item.status = ItemStatus.FAILED
|
|
617
617
|
response.mark_failed({"message": "Too many tool failures", "type": "error_recovery"})
|
|
618
618
|
break
|
|
619
|
+
else:
|
|
620
|
+
# Record success to reset consecutive failure counter
|
|
621
|
+
self._error_tracker.record_success(tool_name)
|
|
619
622
|
|
|
620
623
|
# Update FunctionCallItem status
|
|
621
624
|
fc_item.status = ItemStatus.COMPLETED
|
|
@@ -648,15 +651,25 @@ Final Answer: <the answer>
|
|
|
648
651
|
)
|
|
649
652
|
|
|
650
653
|
# Update expecting_final_answer flag
|
|
654
|
+
# Only enforce for terminal tools (simple, direct-answer tools)
|
|
655
|
+
# to avoid breaking multi-step workflows
|
|
651
656
|
if is_error:
|
|
652
657
|
_expecting_final_answer = False
|
|
658
|
+
_last_tool_name = None
|
|
653
659
|
else:
|
|
654
|
-
|
|
655
|
-
|
|
660
|
+
from .core.error_recovery import _is_simple_result
|
|
661
|
+
if _is_simple_result(str(result), tool_name):
|
|
662
|
+
_expecting_final_answer = True
|
|
663
|
+
_last_successful_result = str(result)
|
|
664
|
+
_last_tool_name = tool_name
|
|
665
|
+
else:
|
|
666
|
+
# Complex/intermediate result — allow more tool calls
|
|
667
|
+
_expecting_final_answer = False
|
|
668
|
+
_last_tool_name = None
|
|
656
669
|
|
|
657
670
|
self.memory.add("user", observation_msg)
|
|
658
671
|
|
|
659
|
-
if not
|
|
672
|
+
if not is_error:
|
|
660
673
|
successful_results.append(f"{tool_name}: {result}")
|
|
661
674
|
|
|
662
675
|
steps.append(StepResult(
|
|
@@ -956,6 +969,7 @@ Final Answer: <the answer>
|
|
|
956
969
|
# Stream model output, check for tool calls, execute them, repeat.
|
|
957
970
|
_expecting_final_answer = False
|
|
958
971
|
_last_successful_result = None
|
|
972
|
+
_last_tool_name = None
|
|
959
973
|
tool_call_count = 0
|
|
960
974
|
|
|
961
975
|
for step_num in range(self.max_steps):
|
|
@@ -1030,9 +1044,6 @@ Final Answer: <the answer>
|
|
|
1030
1044
|
self.memory.add("user", f"Observation: Error: {error_msg}")
|
|
1031
1045
|
continue
|
|
1032
1046
|
|
|
1033
|
-
# Fuzzy match
|
|
1034
|
-
tool_name = self._parser._fuzzy_match_tool(tool_name)
|
|
1035
|
-
|
|
1036
1047
|
# Create FunctionCallItem and emit SSE events
|
|
1037
1048
|
fc_item = create_function_call_item(tool_name, tool_args)
|
|
1038
1049
|
fc_item.status = ItemStatus.IN_PROGRESS
|
|
@@ -1075,9 +1086,16 @@ Final Answer: <the answer>
|
|
|
1075
1086
|
|
|
1076
1087
|
if is_error:
|
|
1077
1088
|
_expecting_final_answer = False
|
|
1089
|
+
_last_tool_name = None
|
|
1078
1090
|
else:
|
|
1079
|
-
|
|
1080
|
-
|
|
1091
|
+
from .core.error_recovery import _is_simple_result
|
|
1092
|
+
if _is_simple_result(str(result), tool_name):
|
|
1093
|
+
_expecting_final_answer = True
|
|
1094
|
+
_last_successful_result = str(result)
|
|
1095
|
+
_last_tool_name = tool_name
|
|
1096
|
+
else:
|
|
1097
|
+
_expecting_final_answer = False
|
|
1098
|
+
_last_tool_name = None
|
|
1081
1099
|
|
|
1082
1100
|
self.memory.add("user", observation_msg)
|
|
1083
1101
|
|
|
@@ -1330,12 +1348,7 @@ Final Answer: <the answer>
|
|
|
1330
1348
|
OpenResponses: Tool execution is straightforward - no synthesis.
|
|
1331
1349
|
Arguments must come from the model.
|
|
1332
1350
|
"""
|
|
1333
|
-
|
|
1334
|
-
matched_name = _fuzzy_match_tool_name(name, self.tools.names())
|
|
1335
|
-
if matched_name:
|
|
1336
|
-
name = matched_name
|
|
1337
|
-
|
|
1338
|
-
tool = self.tools.get_fuzzy(name)
|
|
1351
|
+
tool = self.tools.get(name)
|
|
1339
1352
|
|
|
1340
1353
|
if tool is None:
|
|
1341
1354
|
return f"Error: Unknown tool '{name}'. Available tools: {self.tools.names()}"
|
|
@@ -978,6 +978,9 @@ class OllamaBackend(BaseBackend):
|
|
|
978
978
|
Key insight: "native" requires ACTUAL native tool_calls structure in API response.
|
|
979
979
|
Models that output JSON as text are "react", not "native".
|
|
980
980
|
|
|
981
|
+
Results are automatically cached after each live test. If force_test is False,
|
|
982
|
+
the cache is checked first before returning UNTESTED.
|
|
983
|
+
|
|
981
984
|
Args:
|
|
982
985
|
model: Model name
|
|
983
986
|
family: Optional family hint (unused, kept for API compatibility)
|
|
@@ -986,9 +989,16 @@ class OllamaBackend(BaseBackend):
|
|
|
986
989
|
Returns:
|
|
987
990
|
ToolSupportLevel (NATIVE, REACT, NONE, or UNTESTED if force_test=False)
|
|
988
991
|
"""
|
|
992
|
+
from ..core.tool_cache import get_cached_tool_support, cache_tool_support
|
|
993
|
+
|
|
994
|
+
# Determine API mode for cache namespacing
|
|
995
|
+
api_mode = self._api_mode.value if hasattr(self, "_api_mode") else "openre"
|
|
996
|
+
|
|
989
997
|
if not force_test:
|
|
990
|
-
#
|
|
991
|
-
|
|
998
|
+
# Check cache first, even without force_test
|
|
999
|
+
cached = get_cached_tool_support(model, api_mode=api_mode)
|
|
1000
|
+
if cached is not None:
|
|
1001
|
+
return cached
|
|
992
1002
|
return ToolSupportLevel.UNTESTED
|
|
993
1003
|
|
|
994
1004
|
# Test tool: Weather (simple, commonly supported)
|
|
@@ -1032,20 +1042,33 @@ class OllamaBackend(BaseBackend):
|
|
|
1032
1042
|
if func_name == "get_weather":
|
|
1033
1043
|
# Check for reasonable arguments
|
|
1034
1044
|
if isinstance(args, dict) and ("location" in args or "city" in args or len(args) > 0):
|
|
1035
|
-
|
|
1045
|
+
result = ToolSupportLevel.NATIVE
|
|
1046
|
+
break
|
|
1036
1047
|
# Native tool_calls exist but wrong function - still native capability
|
|
1037
1048
|
# (model might be confused but HAS native support)
|
|
1038
1049
|
elif func_name: # Any function name = native structure
|
|
1039
|
-
|
|
1050
|
+
result = ToolSupportLevel.NATIVE
|
|
1051
|
+
break
|
|
1052
|
+
else:
|
|
1053
|
+
# tool_calls existed but no valid function found
|
|
1054
|
+
result = ToolSupportLevel.REACT
|
|
1055
|
+
else:
|
|
1056
|
+
result = None # No native tool calls
|
|
1057
|
+
|
|
1058
|
+
if result is not None:
|
|
1059
|
+
cache_tool_support(model, result, family=family or "", api_mode=api_mode)
|
|
1060
|
+
return result
|
|
1040
1061
|
|
|
1041
1062
|
# 3. Check for text-based tool calls in content
|
|
1042
1063
|
# Models that output JSON like {"name": "get_weather", ...} as TEXT
|
|
1043
1064
|
if self._contains_text_tool_call(content):
|
|
1065
|
+
cache_tool_support(model, ToolSupportLevel.REACT, family=family or "", api_mode=api_mode)
|
|
1044
1066
|
return ToolSupportLevel.REACT
|
|
1045
1067
|
|
|
1046
1068
|
# 4. API succeeded, no explicit rejection, no native tool_calls
|
|
1047
1069
|
# Model accepted tools parameter but didn't use native calling
|
|
1048
1070
|
# This is the "react" case - can still parse text-based tool calls
|
|
1071
|
+
cache_tool_support(model, ToolSupportLevel.REACT, family=family or "", api_mode=api_mode)
|
|
1049
1072
|
return ToolSupportLevel.REACT
|
|
1050
1073
|
|
|
1051
1074
|
except Exception as e:
|
|
@@ -1053,9 +1076,13 @@ class OllamaBackend(BaseBackend):
|
|
|
1053
1076
|
|
|
1054
1077
|
# 1. Check for explicit "does not support tools" rejection
|
|
1055
1078
|
if "does not support tools" in error_str.lower():
|
|
1079
|
+
cache_tool_support(model, ToolSupportLevel.NONE, family=family or "",
|
|
1080
|
+
error=error_str[:100], api_mode=api_mode)
|
|
1056
1081
|
return ToolSupportLevel.NONE
|
|
1057
1082
|
|
|
1058
1083
|
# Other HTTP errors or connection issues
|
|
1084
|
+
cache_tool_support(model, ToolSupportLevel.REACT, family=family or "",
|
|
1085
|
+
error=error_str[:100], api_mode=api_mode)
|
|
1059
1086
|
return ToolSupportLevel.REACT
|
|
1060
1087
|
|
|
1061
1088
|
finally:
|
|
@@ -6,9 +6,9 @@ that form the foundation of the AgentNova framework.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from .types import StepResultType, ToolSupportLevel, BackendType, ApiMode
|
|
9
|
-
from .models import StepResult, AgentRun, Tool, ToolParam
|
|
9
|
+
from .models import StepResult, AgentRun, Tool, ToolParam, ToolCall
|
|
10
10
|
from .memory import Memory, MemoryConfig
|
|
11
|
-
from .tool_parse import ToolParser
|
|
11
|
+
from .tool_parse import ToolParser
|
|
12
12
|
from .helpers import (
|
|
13
13
|
fuzzy_match, normalize_args, validate_path, is_safe_url,
|
|
14
14
|
strip_tool_prefix, is_simple_answered_query, is_greeting_or_simple,
|
|
@@ -19,18 +19,18 @@ from .prompts import (
|
|
|
19
19
|
TOOL_ARG_ALIASES, FEW_SHOT_SUFFIX, FEW_SHOT_COMPACT,
|
|
20
20
|
PLATFORM_DIR_CMD,
|
|
21
21
|
)
|
|
22
|
-
from .model_config import ModelFamilyConfig, get_model_config
|
|
23
|
-
from .args_normal import (
|
|
24
|
-
normalize_args as normalize_args_full,
|
|
25
|
-
fix_calculator_args, synthesize_missing_args, generate_helpful_error_message,
|
|
26
|
-
)
|
|
27
22
|
from .model_family_config import (
|
|
23
|
+
ModelFamilyConfig, get_model_config,
|
|
28
24
|
get_family_config, get_stop_tokens, supports_tools,
|
|
29
25
|
get_tool_format, get_no_tools_system_prompt, get_preferred_temperature,
|
|
30
26
|
should_use_few_shot, get_few_shot_style, has_known_issues,
|
|
31
27
|
get_react_system_suffix, get_native_tool_hints,
|
|
32
28
|
FAMILY_CONFIGS,
|
|
33
29
|
)
|
|
30
|
+
from .args_normal import (
|
|
31
|
+
normalize_args as normalize_args_full,
|
|
32
|
+
fix_calculator_args, synthesize_missing_args, generate_helpful_error_message,
|
|
33
|
+
)
|
|
34
34
|
# OpenResponses types
|
|
35
35
|
from .openresponses import (
|
|
36
36
|
Response, ResponseStatus, ItemStatus,
|
|
@@ -131,10 +131,40 @@ def normalize_args(args: dict[str, Any], expected_params: list[str], tool_name:
|
|
|
131
131
|
power_parts = {}
|
|
132
132
|
|
|
133
133
|
# Import tool-specific aliases
|
|
134
|
-
from .prompts import TOOL_ARG_ALIASES
|
|
134
|
+
from .prompts import TOOL_ARG_ALIASES, CONTEXTUAL_ALIASES
|
|
135
135
|
|
|
136
136
|
# Get tool-specific aliases
|
|
137
137
|
tool_aliases = TOOL_ARG_ALIASES.get(tool_name, {}) if tool_name else {}
|
|
138
|
+
contextual_aliases = CONTEXTUAL_ALIASES.get(tool_name, set()) if tool_name else set()
|
|
139
|
+
|
|
140
|
+
# First pass: identify which keys map to expected params via non-ambiguous means
|
|
141
|
+
# (direct match, case-insensitive match, or non-contextual alias)
|
|
142
|
+
matched_keys = set()
|
|
143
|
+
|
|
144
|
+
for key, value in args.items():
|
|
145
|
+
key_lower = key.lower().replace("-", "_")
|
|
146
|
+
|
|
147
|
+
# Direct match
|
|
148
|
+
if key in expected_set:
|
|
149
|
+
matched_keys.add(key_lower)
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
# Case-insensitive match
|
|
153
|
+
for param in expected_params:
|
|
154
|
+
if param.lower() == key_lower:
|
|
155
|
+
matched_keys.add(key_lower)
|
|
156
|
+
break
|
|
157
|
+
|
|
158
|
+
# Non-contextual alias (safe to apply always)
|
|
159
|
+
if key_lower not in matched_keys and key_lower in tool_aliases:
|
|
160
|
+
alias_target = tool_aliases[key_lower]
|
|
161
|
+
if alias_target != "_combine_power" and alias_target in expected_set:
|
|
162
|
+
if key_lower not in contextual_aliases:
|
|
163
|
+
matched_keys.add(key_lower)
|
|
164
|
+
|
|
165
|
+
# Second pass: build normalized dict with full resolution
|
|
166
|
+
normalized = {}
|
|
167
|
+
power_parts = {}
|
|
138
168
|
|
|
139
169
|
for key, value in args.items():
|
|
140
170
|
key_lower = key.lower().replace("-", "_")
|
|
@@ -148,8 +178,13 @@ def normalize_args(args: dict[str, Any], expected_params: list[str], tool_name:
|
|
|
148
178
|
power_parts[key_lower] = value
|
|
149
179
|
continue
|
|
150
180
|
elif alias_target in expected_set:
|
|
151
|
-
|
|
152
|
-
|
|
181
|
+
# Contextual validation: only apply ambiguous aliases when
|
|
182
|
+
# no other key has already matched an expected param
|
|
183
|
+
if key_lower in contextual_aliases and matched_keys:
|
|
184
|
+
pass # Skip this ambiguous alias
|
|
185
|
+
else:
|
|
186
|
+
target_param = alias_target
|
|
187
|
+
target_pname = alias_target
|
|
153
188
|
|
|
154
189
|
# Strategy 2: Direct match
|
|
155
190
|
if target_param is None and key in expected_set:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AgentNova — Model Configuration (DEPRECATED)
|
|
3
|
+
|
|
4
|
+
This module is deprecated. All model family configuration has been merged into
|
|
5
|
+
model_family_config.py. This module re-exports ModelFamilyConfig and
|
|
6
|
+
get_model_config from there for backward compatibility.
|
|
7
|
+
|
|
8
|
+
Please import from model_family_config instead:
|
|
9
|
+
from agentnova.core.model_family_config import ModelFamilyConfig, get_model_config
|
|
10
|
+
|
|
11
|
+
Written by VTSTech — https://www.vts-tech.org
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import warnings
|
|
17
|
+
|
|
18
|
+
# Re-export from the unified module for backward compatibility
|
|
19
|
+
from .model_family_config import ModelFamilyConfig, get_model_config
|
|
20
|
+
|
|
21
|
+
# Legacy MODEL_CONFIGS dict — maps to FAMILY_CONFIGS entries
|
|
22
|
+
from .model_family_config import FAMILY_CONFIGS as MODEL_CONFIGS
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def list_supported_families() -> list[str]:
|
|
26
|
+
"""List all supported model families."""
|
|
27
|
+
return [k for k in MODEL_CONFIGS.keys()]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Emit deprecation warning on import
|
|
31
|
+
warnings.warn(
|
|
32
|
+
"model_config.py is deprecated. Use model_family_config.py instead. "
|
|
33
|
+
"Import ModelFamilyConfig and get_model_config from agentnova.core.model_family_config.",
|
|
34
|
+
DeprecationWarning,
|
|
35
|
+
stacklevel=2,
|
|
36
|
+
)
|
|
@@ -21,26 +21,50 @@ _PLATFORM_DIR_CMD = "cd" if _IS_WINDOWS else "pwd"
|
|
|
21
21
|
|
|
22
22
|
@dataclass
|
|
23
23
|
class ModelFamilyConfig:
|
|
24
|
-
"""Configuration for a specific model family.
|
|
24
|
+
"""Configuration for a specific model family.
|
|
25
|
+
|
|
26
|
+
Unified configuration combining prompting, formatting, tool handling,
|
|
27
|
+
and generation defaults. Single source of truth for all model-family-
|
|
28
|
+
specific settings.
|
|
29
|
+
"""
|
|
25
30
|
family: str
|
|
31
|
+
# Prompting & formatting
|
|
26
32
|
start_tokens: dict = field(default_factory=dict)
|
|
27
33
|
stop_tokens: list[str] = field(default_factory=list)
|
|
28
34
|
tool_format: Literal["native", "xml", "json", "none"] = "native"
|
|
29
35
|
tool_call_start: str = ""
|
|
30
36
|
tool_call_end: str = ""
|
|
31
37
|
supports_native_tools: bool = True
|
|
32
|
-
system_prompt_style: Literal["separate", "first_user", "template"] = "separate"
|
|
38
|
+
system_prompt_style: Literal["separate", "first_user", "template", "default", "react", "minimal"] = "separate"
|
|
39
|
+
# Generation defaults
|
|
33
40
|
preferred_temperature: float = 0.7
|
|
41
|
+
default_temperature: float = 0.7 # Used by agent.py for generation
|
|
42
|
+
default_top_p: float = 0.9
|
|
43
|
+
default_max_tokens: int = 8192
|
|
44
|
+
# Model behavior
|
|
34
45
|
needs_think_directive: bool = False
|
|
35
46
|
prefers_few_shot: bool = True
|
|
36
47
|
few_shot_style: Literal["react", "native", "compact"] = "react"
|
|
37
48
|
reasoning_hints: list[str] = field(default_factory=list)
|
|
49
|
+
# Streaming / capabilities
|
|
50
|
+
supports_streaming: bool = True
|
|
51
|
+
supports_vision: bool = False
|
|
52
|
+
# Think tag handling (e.g., <think/> for DeepSeek)
|
|
53
|
+
think_tag: str | None = None
|
|
54
|
+
strip_think_tags: bool = False
|
|
38
55
|
# Special behaviors
|
|
39
56
|
has_schema_dump_issue: bool = False # Some models dump tool schema as text
|
|
40
57
|
truncate_json_args: bool = False # Some models truncate JSON in ReAct
|
|
58
|
+
needs_empty_system: bool = False # Some models break with empty system
|
|
59
|
+
prefers_user_system: bool = False # Put system prompt in first user message
|
|
41
60
|
# Override system prompt for models without tool support (pure reasoning)
|
|
42
61
|
no_tools_system_prompt: str | None = None
|
|
43
62
|
|
|
63
|
+
def get_stop_sequences(self) -> list[str]:
|
|
64
|
+
"""Get stop sequences for this model family."""
|
|
65
|
+
stops = list(self.stop_tokens)
|
|
66
|
+
return stops
|
|
67
|
+
|
|
44
68
|
|
|
45
69
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
46
70
|
# FAMILY CONFIGURATIONS
|
|
@@ -57,6 +81,7 @@ FAMILY_CONFIGS: dict[str, ModelFamilyConfig] = {
|
|
|
57
81
|
supports_native_tools=False,
|
|
58
82
|
system_prompt_style="first_user",
|
|
59
83
|
preferred_temperature=0.7,
|
|
84
|
+
needs_empty_system=True,
|
|
60
85
|
prefers_few_shot=False,
|
|
61
86
|
few_shot_style="compact",
|
|
62
87
|
reasoning_hints=["Think step by step", "Show your work"],
|
|
@@ -262,6 +287,8 @@ Keep answers brief. One word when possible.""",
|
|
|
262
287
|
preferred_temperature=0.6,
|
|
263
288
|
needs_think_directive=True, # DeepSeek-R1 has thinking mode
|
|
264
289
|
prefers_few_shot=True,
|
|
290
|
+
think_tag="think",
|
|
291
|
+
strip_think_tags=True,
|
|
265
292
|
few_shot_style="react",
|
|
266
293
|
reasoning_hints=["Think step by step", "Show your reasoning"],
|
|
267
294
|
),
|
|
@@ -284,6 +311,8 @@ Keep answers brief. One word when possible.""",
|
|
|
284
311
|
needs_think_directive=False, # Standard DeepSeek models don't have thinking mode
|
|
285
312
|
prefers_few_shot=True,
|
|
286
313
|
few_shot_style="react",
|
|
314
|
+
think_tag="think",
|
|
315
|
+
strip_think_tags=True,
|
|
287
316
|
),
|
|
288
317
|
}
|
|
289
318
|
|
|
@@ -441,6 +470,25 @@ def detect_family(model_name: str) -> str | None:
|
|
|
441
470
|
return None
|
|
442
471
|
|
|
443
472
|
|
|
473
|
+
def get_model_config(model_name: str) -> ModelFamilyConfig:
|
|
474
|
+
"""
|
|
475
|
+
Get unified configuration for a model.
|
|
476
|
+
|
|
477
|
+
This replaces the separate get_model_config() from model_config.py.
|
|
478
|
+
Uses detect_family() for consistent family resolution.
|
|
479
|
+
|
|
480
|
+
Args:
|
|
481
|
+
model_name: Name of the model (e.g., "qwen2.5:7b")
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
ModelFamilyConfig for the model
|
|
485
|
+
"""
|
|
486
|
+
family = detect_family(model_name)
|
|
487
|
+
if family and family in FAMILY_CONFIGS:
|
|
488
|
+
return FAMILY_CONFIGS[family]
|
|
489
|
+
return ModelFamilyConfig(family=family or "unknown")
|
|
490
|
+
|
|
491
|
+
|
|
444
492
|
__all__ = [
|
|
445
493
|
"ModelFamilyConfig",
|
|
446
494
|
"FAMILY_CONFIGS",
|
|
@@ -456,5 +504,6 @@ __all__ = [
|
|
|
456
504
|
"get_native_tool_hints",
|
|
457
505
|
"get_no_tools_system_prompt",
|
|
458
506
|
"detect_family",
|
|
507
|
+
"get_model_config",
|
|
459
508
|
"needs_no_think_directive",
|
|
460
509
|
]
|
|
@@ -87,11 +87,16 @@ class Tool:
|
|
|
87
87
|
|
|
88
88
|
@dataclass
|
|
89
89
|
class ToolCall:
|
|
90
|
-
"""Represents a parsed tool call from model output.
|
|
90
|
+
"""Represents a parsed tool call from model output.
|
|
91
|
+
|
|
92
|
+
OpenResponses Enhancement: Includes thought capture for ReasoningItem.
|
|
93
|
+
"""
|
|
91
94
|
name: str
|
|
92
95
|
arguments: dict[str, Any]
|
|
93
96
|
raw: str = "" # Original text that was parsed
|
|
94
97
|
confidence: float = 1.0 # Confidence of parsing (for fuzzy matches)
|
|
98
|
+
final_answer: str | None = None # OpenResponses: Final answer if present in same content
|
|
99
|
+
thought: str | None = None # OpenResponses: Captured reasoning for ReasoningItem
|
|
95
100
|
|
|
96
101
|
|
|
97
102
|
@dataclass
|
|
@@ -25,7 +25,7 @@ TOOL_ARG_ALIASES = {
|
|
|
25
25
|
"calculator": {
|
|
26
26
|
# Common hallucinations for expression
|
|
27
27
|
"a": "expression", "b": "expression", "x": "expression", "y": "expression",
|
|
28
|
-
"num": "expression", "number": "expression",
|
|
28
|
+
"num": "expression", "number": "expression",
|
|
29
29
|
"input": "expression", "formula": "expression", "math": "expression",
|
|
30
30
|
"expr": "expression", "calc": "expression", "result": "expression",
|
|
31
31
|
# Power operations - combine into expression
|
|
@@ -89,6 +89,19 @@ TOOL_ARG_ALIASES = {
|
|
|
89
89
|
},
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
# Aliases that are too generic and should only be applied when the args dict
|
|
93
|
+
# has no other parameters that already matched a real expected param.
|
|
94
|
+
# These are the aliases most likely to misinterpret legitimate model output.
|
|
95
|
+
CONTEXTUAL_ALIASES = {
|
|
96
|
+
"calculator": {"value", "input", "result", "n", "p", "exp"},
|
|
97
|
+
"shell": {"text", "input", "arg", "args", "str", "value"},
|
|
98
|
+
"write_file": {"value", "string"},
|
|
99
|
+
"read_file": {"input"},
|
|
100
|
+
"web-search": {"text", "input"},
|
|
101
|
+
"python_repl": {"input", "expression", "expr"},
|
|
102
|
+
"convert_currency": {"value"},
|
|
103
|
+
}
|
|
104
|
+
|
|
92
105
|
# ------------------------------------------------------------------ #
|
|
93
106
|
# Few-shot prompting suffix for small models #
|
|
94
107
|
# ------------------------------------------------------------------ #
|