agentnova 0.3.4__tar.gz → 0.3.6__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.4 → agentnova-0.3.6}/PKG-INFO +4 -3
- {agentnova-0.3.4 → agentnova-0.3.6}/README.md +3 -2
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/__init__.py +1 -1
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/acp_plugin.py +86 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/agent.py +166 -4
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/agent_mode.py +2 -21
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/backends/ollama.py +149 -37
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/cli.py +64 -194
- agentnova-0.3.6/agentnova/colors.py +204 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/config.py +1 -1
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/model_config.py +23 -25
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/model_family_config.py +43 -1
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/openresponses.py +248 -0
- agentnova-0.3.6/agentnova/core/tool_cache.py +189 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/tool_parse.py +16 -13
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/types.py +31 -19
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/01_quick_diagnostic.py +62 -6
- agentnova-0.3.4/agentnova/orchestrator_enhanced.py → agentnova-0.3.6/agentnova/orchestrator.py +172 -77
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/shared_args.py +35 -0
- agentnova-0.3.6/agentnova/skills/loader.py +734 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/soul/loader.py +38 -3
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/soul/types.py +26 -3
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/souls/nova-helper/SOUL.md +43 -6
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/PKG-INFO +4 -3
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/SOURCES.txt +4 -4
- {agentnova-0.3.4 → agentnova-0.3.6}/pyproject.toml +1 -1
- {agentnova-0.3.4 → agentnova-0.3.6}/tests/test_agent.py +19 -9
- agentnova-0.3.6/tests/test_spec_compliance.py +383 -0
- agentnova-0.3.4/agentnova/orchestrator.py +0 -205
- agentnova-0.3.4/agentnova/skills/loader.py +0 -1893
- agentnova-0.3.4/tests/test_acp_integration.py +0 -223
- agentnova-0.3.4/tests/test_acp_subagents.py +0 -296
- {agentnova-0.3.4 → agentnova-0.3.6}/LICENSE +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/__main__.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/backends/__init__.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/backends/base.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/backends/bitnet.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/__init__.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/args_normal.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/error_recovery.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/helpers.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/math_prompts.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/memory.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/models.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/prompts.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/00_basic_agent.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/02_tool_test.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/03_reasoning_test.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/04_gsm8k_benchmark.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/05_common_sense.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/06_causal_reasoning.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/07_logical_deduction.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/08_reading_comprehension.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/09_general_knowledge.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/10_implicit_reasoning.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/11_analogical_reasoning.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/__init__.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/model_discovery.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/py.typed +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/__init__.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/acp/SKILL.md +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/datetime/SKILL.md +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/SKILL.md +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/__init__.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/aggregate_benchmark.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/generate_report.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/improve_description.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/init_skill.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/package_skill.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/quick_validate.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/run_eval.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/run_loop.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/security_scan.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/test_package_skill.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/test_quick_validate.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/utils.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/validate.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/web-search/SKILL.md +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/soul/__init__.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/souls/nova-helper/IDENTITY.md +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/souls/nova-helper/STYLE.md +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/souls/nova-helper/soul.json +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/tools/__init__.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/tools/builtins.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/tools/registry.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/tools/sandboxed_repl.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/dependency_links.txt +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/entry_points.txt +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/requires.txt +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/top_level.txt +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/localclaw/__init__.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/localclaw-redirect/localclaw/__init__.py +0 -0
- {agentnova-0.3.4 → agentnova-0.3.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agentnova
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
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.6
|
|
36
36
|
|
|
37
37
|
**Status: Alpha**
|
|
38
38
|
|
|
@@ -49,7 +49,7 @@ 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="1378" height="996" alt="image" src="https://github.com/user-attachments/assets/0fe69695-73d9-4ce1-999f-08443f879971" />
|
|
53
53
|
|
|
54
54
|
## 📚 Documentation
|
|
55
55
|
|
|
@@ -71,6 +71,7 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
|
|
|
71
71
|
- **Soul Spec v0.5** — Persona packages with progressive disclosure
|
|
72
72
|
- **ACP v1.0.5 integration** — Agent Control Panel for monitoring and control
|
|
73
73
|
- **AgentSkills spec** — Skill loading with SPDX license validation
|
|
74
|
+
- **Thinking models support** — Automatic handling of qwen3, deepseek-r1 thinking mode
|
|
74
75
|
|
|
75
76
|
## Installation
|
|
76
77
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# ⚛️ AgentNova R03.
|
|
1
|
+
# ⚛️ AgentNova R03.6
|
|
2
2
|
|
|
3
3
|
**Status: Alpha**
|
|
4
4
|
|
|
@@ -15,7 +15,7 @@ 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="1378" height="996" alt="image" src="https://github.com/user-attachments/assets/0fe69695-73d9-4ce1-999f-08443f879971" />
|
|
19
19
|
|
|
20
20
|
## 📚 Documentation
|
|
21
21
|
|
|
@@ -37,6 +37,7 @@ Inspired by the architecture of OpenClaw, rebuilt from scratch for local-first o
|
|
|
37
37
|
- **Soul Spec v0.5** — Persona packages with progressive disclosure
|
|
38
38
|
- **ACP v1.0.5 integration** — Agent Control Panel for monitoring and control
|
|
39
39
|
- **AgentSkills spec** — Skill loading with SPDX license validation
|
|
40
|
+
- **Thinking models support** — Automatic handling of qwen3, deepseek-r1 thinking mode
|
|
40
41
|
|
|
41
42
|
## Installation
|
|
42
43
|
|
|
@@ -67,6 +67,7 @@ from __future__ import annotations
|
|
|
67
67
|
|
|
68
68
|
import base64
|
|
69
69
|
import json
|
|
70
|
+
import random
|
|
70
71
|
import time
|
|
71
72
|
import urllib.request
|
|
72
73
|
import urllib.error
|
|
@@ -74,6 +75,14 @@ from contextlib import contextmanager
|
|
|
74
75
|
from dataclasses import dataclass, field
|
|
75
76
|
from typing import Any, Callable, Generator
|
|
76
77
|
|
|
78
|
+
# Default retry configuration
|
|
79
|
+
ACP_RETRY_CONFIG = {
|
|
80
|
+
"max_retries": 3,
|
|
81
|
+
"base_delay": 0.5,
|
|
82
|
+
"max_delay": 8.0,
|
|
83
|
+
"jitter": True, # Add randomness to avoid thundering herd
|
|
84
|
+
}
|
|
85
|
+
|
|
77
86
|
# Import StepResult for type hints (optional - works without it)
|
|
78
87
|
try:
|
|
79
88
|
from .core.models import StepResult
|
|
@@ -369,6 +378,83 @@ class ACPPlugin:
|
|
|
369
378
|
self._csrf_token = token if token else ""
|
|
370
379
|
self._csrf_expiry = now
|
|
371
380
|
|
|
381
|
+
def _request_with_retry(
|
|
382
|
+
self,
|
|
383
|
+
endpoint: str,
|
|
384
|
+
method: str = "GET",
|
|
385
|
+
data: dict | None = None,
|
|
386
|
+
timeout: float = 5.0,
|
|
387
|
+
max_retries: int = None,
|
|
388
|
+
base_delay: float = None,
|
|
389
|
+
max_delay: float = None,
|
|
390
|
+
jitter: bool = None,
|
|
391
|
+
retryable_errors: tuple = None,
|
|
392
|
+
) -> dict:
|
|
393
|
+
"""
|
|
394
|
+
Make HTTP request with exponential backoff retry logic.
|
|
395
|
+
|
|
396
|
+
This wraps _request() with automatic retry on transient failures,
|
|
397
|
+
implementing exponential backoff to avoid overwhelming the server.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
endpoint: API endpoint path
|
|
401
|
+
method: HTTP method
|
|
402
|
+
data: Request body data
|
|
403
|
+
timeout: Request timeout
|
|
404
|
+
max_retries: Maximum number of retry attempts (default: from ACP_RETRY_CONFIG)
|
|
405
|
+
base_delay: Initial delay in seconds (default: from ACP_RETRY_CONFIG)
|
|
406
|
+
max_delay: Maximum delay cap in seconds (default: from ACP_RETRY_CONFIG)
|
|
407
|
+
jitter: Add randomness to avoid thundering herd (default: from ACP_RETRY_CONFIG)
|
|
408
|
+
retryable_errors: Tuple of error types that should trigger retry
|
|
409
|
+
Default: (502, 503, 504, connection errors)
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
API response dict
|
|
413
|
+
"""
|
|
414
|
+
# Use config defaults if not specified
|
|
415
|
+
if max_retries is None:
|
|
416
|
+
max_retries = ACP_RETRY_CONFIG["max_retries"]
|
|
417
|
+
if base_delay is None:
|
|
418
|
+
base_delay = ACP_RETRY_CONFIG["base_delay"]
|
|
419
|
+
if max_delay is None:
|
|
420
|
+
max_delay = ACP_RETRY_CONFIG["max_delay"]
|
|
421
|
+
if jitter is None:
|
|
422
|
+
jitter = ACP_RETRY_CONFIG["jitter"]
|
|
423
|
+
|
|
424
|
+
if retryable_errors is None:
|
|
425
|
+
# Default retryable: server errors and connection issues
|
|
426
|
+
retryable_errors = (502, 503, 504, "connection", "timeout")
|
|
427
|
+
|
|
428
|
+
last_error = None
|
|
429
|
+
|
|
430
|
+
for attempt in range(max_retries + 1):
|
|
431
|
+
result = self._request(endpoint, method, data, timeout)
|
|
432
|
+
|
|
433
|
+
# Check for success
|
|
434
|
+
if result.get("success") != False or "error" not in result:
|
|
435
|
+
return result
|
|
436
|
+
|
|
437
|
+
# Check if error is retryable
|
|
438
|
+
error_str = str(result.get("error", "")).lower()
|
|
439
|
+
is_retryable = any(
|
|
440
|
+
str(code) in error_str or str(code) in error_str.lower()
|
|
441
|
+
for code in retryable_errors
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
if not is_retryable or attempt == max_retries:
|
|
445
|
+
return result
|
|
446
|
+
|
|
447
|
+
# Calculate exponential backoff delay with optional jitter
|
|
448
|
+
delay = min(base_delay * (2 ** attempt), max_delay)
|
|
449
|
+
if jitter:
|
|
450
|
+
# Add up to 25% random jitter
|
|
451
|
+
delay = delay * (0.75 + random.random() * 0.5)
|
|
452
|
+
self._log(f"Retry {attempt + 1}/{max_retries} after {delay:.2f}s: {error_str}")
|
|
453
|
+
time.sleep(delay)
|
|
454
|
+
last_error = error_str
|
|
455
|
+
|
|
456
|
+
return result
|
|
457
|
+
|
|
372
458
|
# ------------------------------------------------------------------ #
|
|
373
459
|
# JSON-RPC 2.0 Support (1.0.4 - A2A Compliance) #
|
|
374
460
|
# ------------------------------------------------------------------ #
|
|
@@ -42,6 +42,7 @@ from .core.openresponses import (
|
|
|
42
42
|
RequestConfig, Error,
|
|
43
43
|
create_message_item, create_function_call_item, create_function_call_output,
|
|
44
44
|
create_function_call_output_item,
|
|
45
|
+
stream_response_events,
|
|
45
46
|
)
|
|
46
47
|
from .tools import ToolRegistry, make_builtin_registry
|
|
47
48
|
from .backends import BaseBackend, get_default_backend
|
|
@@ -109,6 +110,10 @@ class Agent:
|
|
|
109
110
|
soul: str = "nova-helper",
|
|
110
111
|
soul_level: int = 3,
|
|
111
112
|
num_ctx: int | None = None,
|
|
113
|
+
# Generation parameters
|
|
114
|
+
temperature: float | None = None,
|
|
115
|
+
top_p: float | None = None,
|
|
116
|
+
num_predict: int | None = None,
|
|
112
117
|
# OpenResponses parameters
|
|
113
118
|
tool_choice: str | ToolChoice = "auto", # Default per OpenResponses spec
|
|
114
119
|
allowed_tools: list[str] | None = None,
|
|
@@ -128,6 +133,9 @@ class Agent:
|
|
|
128
133
|
soul: Path to Soul Spec package (default: "nova-helper")
|
|
129
134
|
soul_level: Progressive disclosure level for soul (1-3)
|
|
130
135
|
num_ctx: Context window size in tokens (default: 4096)
|
|
136
|
+
temperature: Sampling temperature (default: model-specific)
|
|
137
|
+
top_p: Nucleus sampling probability (default: model-specific)
|
|
138
|
+
num_predict: Maximum tokens to generate (default: model-specific)
|
|
131
139
|
tool_choice: Control tool invocation ("auto", "required", "none", or specific tool name)
|
|
132
140
|
allowed_tools: List of tools the model is allowed to invoke (subset of tools)
|
|
133
141
|
**kwargs: Additional configuration
|
|
@@ -142,6 +150,11 @@ class Agent:
|
|
|
142
150
|
config = get_config()
|
|
143
151
|
self.num_ctx = config.num_ctx if config.num_ctx else 4096
|
|
144
152
|
|
|
153
|
+
# Generation parameters (use model defaults if not specified)
|
|
154
|
+
self._temperature = temperature
|
|
155
|
+
self._top_p = top_p
|
|
156
|
+
self._num_predict = num_predict
|
|
157
|
+
|
|
145
158
|
# Initialize backend
|
|
146
159
|
if backend is None:
|
|
147
160
|
self.backend = get_default_backend()
|
|
@@ -852,6 +865,143 @@ Final Answer: <the answer>
|
|
|
852
865
|
success=bool(final_answer),
|
|
853
866
|
)
|
|
854
867
|
|
|
868
|
+
def run_stream(self, prompt: str) -> Generator[str, None, None]:
|
|
869
|
+
"""
|
|
870
|
+
Run the agent on a prompt with streaming OpenResponses SSE events.
|
|
871
|
+
|
|
872
|
+
This method implements the agentic loop with streaming output following
|
|
873
|
+
OpenResponses specification. It yields Server-Sent Events (SSE) that
|
|
874
|
+
describe the response lifecycle and content deltas.
|
|
875
|
+
|
|
876
|
+
SSE Event Sequence (per OpenResponses spec):
|
|
877
|
+
1. response.queued - Response is queued
|
|
878
|
+
2. response.in_progress - Response started
|
|
879
|
+
3. response.output_item.added - New output item added
|
|
880
|
+
4. response.content_part.added - New content part added
|
|
881
|
+
5. response.output_text.delta - Text deltas (multiple)
|
|
882
|
+
6. response.output_text.done - Text completed
|
|
883
|
+
7. response.content_part.done - Content part completed
|
|
884
|
+
8. response.output_item.done - Output item completed
|
|
885
|
+
9. response.completed - Response finished
|
|
886
|
+
|
|
887
|
+
Args:
|
|
888
|
+
prompt: User prompt
|
|
889
|
+
|
|
890
|
+
Yields:
|
|
891
|
+
SSE-formatted strings (event: ...\\ndata: ...\\n\\n)
|
|
892
|
+
|
|
893
|
+
Example:
|
|
894
|
+
agent = Agent(model="qwen2.5:0.5b")
|
|
895
|
+
for sse_event in agent.run_stream("Hello!"):
|
|
896
|
+
print(sse_event) # SSE formatted event
|
|
897
|
+
"""
|
|
898
|
+
# Create OpenResponses Response object
|
|
899
|
+
response = Response(
|
|
900
|
+
model=self.model,
|
|
901
|
+
status=ResponseStatus.QUEUED,
|
|
902
|
+
tool_choice=self.tool_choice,
|
|
903
|
+
allowed_tools=self._allowed_tools or [],
|
|
904
|
+
)
|
|
905
|
+
|
|
906
|
+
if self.debug and not self._is_comp_mode:
|
|
907
|
+
print(f"\n[OpenResponses] Response created: id={response.id}")
|
|
908
|
+
print(f"[OpenResponses] Response status: {response.status.value}")
|
|
909
|
+
|
|
910
|
+
# Add user prompt to memory
|
|
911
|
+
self.memory.add("user", prompt)
|
|
912
|
+
|
|
913
|
+
# Add input item
|
|
914
|
+
user_item = create_message_item("user", prompt)
|
|
915
|
+
response.input.append(user_item)
|
|
916
|
+
|
|
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
|
+
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 (streaming): {prompt}\n")
|
|
926
|
+
|
|
927
|
+
# Get text chunks from backend streaming
|
|
928
|
+
text_chunks = self._generate_stream_chunks(prompt)
|
|
929
|
+
|
|
930
|
+
# Wrap with OpenResponses SSE events using stream_response_events()
|
|
931
|
+
for sse_event in stream_response_events(response, text_chunks, debug=self.debug):
|
|
932
|
+
yield sse_event
|
|
933
|
+
|
|
934
|
+
def _generate_stream_chunks(self, prompt: str) -> Generator[str, None, None]:
|
|
935
|
+
"""
|
|
936
|
+
Generate streaming text chunks from the backend.
|
|
937
|
+
|
|
938
|
+
This is a helper method that wraps the backend's streaming functionality
|
|
939
|
+
and yields raw text chunks for the OpenResponses event generator.
|
|
940
|
+
|
|
941
|
+
Args:
|
|
942
|
+
prompt: User prompt (unused, memory already has the prompt)
|
|
943
|
+
|
|
944
|
+
Yields:
|
|
945
|
+
Text chunks from the model
|
|
946
|
+
"""
|
|
947
|
+
messages = self.memory.get_messages()
|
|
948
|
+
|
|
949
|
+
if self.debug:
|
|
950
|
+
print(f" [DEBUG] Streaming {len(messages)} messages")
|
|
951
|
+
|
|
952
|
+
# Check if model needs thinking disabled (qwen3, deepseek-r1, etc.)
|
|
953
|
+
think = None
|
|
954
|
+
if self.model_family:
|
|
955
|
+
from .core.model_family_config import needs_no_think_directive
|
|
956
|
+
if needs_no_think_directive(self.model_family):
|
|
957
|
+
think = False
|
|
958
|
+
|
|
959
|
+
# Build kwargs for backend
|
|
960
|
+
backend_kwargs = {"think": think}
|
|
961
|
+
if self.num_ctx is not None:
|
|
962
|
+
backend_kwargs["num_ctx"] = self.num_ctx
|
|
963
|
+
|
|
964
|
+
# Check if backend has streaming support
|
|
965
|
+
if hasattr(self.backend, 'generate_stream'):
|
|
966
|
+
# Use native Ollama streaming
|
|
967
|
+
for chunk in self.backend.generate_stream(
|
|
968
|
+
model=self.model,
|
|
969
|
+
messages=messages,
|
|
970
|
+
tools=self.tools.all() if self.tools and len(self.tools) > 0 else None,
|
|
971
|
+
temperature=self.model_config.default_temperature,
|
|
972
|
+
max_tokens=self.model_config.default_max_tokens,
|
|
973
|
+
**backend_kwargs,
|
|
974
|
+
):
|
|
975
|
+
yield chunk
|
|
976
|
+
elif hasattr(self.backend, 'generate_completions_stream'):
|
|
977
|
+
# Use OpenAI-compatible streaming
|
|
978
|
+
for chunk_dict in self.backend.generate_completions_stream(
|
|
979
|
+
model=self.model,
|
|
980
|
+
messages=messages,
|
|
981
|
+
tools=self.tools.all() if self.tools and len(self.tools) > 0 else None,
|
|
982
|
+
temperature=self.model_config.default_temperature,
|
|
983
|
+
max_tokens=self.model_config.default_max_tokens,
|
|
984
|
+
**backend_kwargs,
|
|
985
|
+
):
|
|
986
|
+
delta = chunk_dict.get("delta", "")
|
|
987
|
+
if delta:
|
|
988
|
+
yield delta
|
|
989
|
+
else:
|
|
990
|
+
# Fallback: non-streaming with simulated streaming
|
|
991
|
+
result = self.backend.generate(
|
|
992
|
+
model=self.model,
|
|
993
|
+
messages=messages,
|
|
994
|
+
tools=self.tools.all() if self.tools and len(self.tools) > 0 else None,
|
|
995
|
+
temperature=self.model_config.default_temperature,
|
|
996
|
+
max_tokens=self.model_config.default_max_tokens,
|
|
997
|
+
**backend_kwargs,
|
|
998
|
+
)
|
|
999
|
+
content = result.get("content", "")
|
|
1000
|
+
# Yield content in chunks for consistent behavior
|
|
1001
|
+
chunk_size = 20
|
|
1002
|
+
for i in range(0, len(content), chunk_size):
|
|
1003
|
+
yield content[i:i + chunk_size]
|
|
1004
|
+
|
|
855
1005
|
def create_response(
|
|
856
1006
|
self,
|
|
857
1007
|
input_items: list = None,
|
|
@@ -923,19 +1073,31 @@ Final Answer: <the answer>
|
|
|
923
1073
|
backend_kwargs = {"think": think}
|
|
924
1074
|
if self.num_ctx is not None:
|
|
925
1075
|
backend_kwargs["num_ctx"] = self.num_ctx
|
|
926
|
-
|
|
927
|
-
|
|
1076
|
+
if self._num_predict is not None:
|
|
1077
|
+
backend_kwargs["num_predict"] = self._num_predict
|
|
928
1078
|
|
|
929
1079
|
# Pass tools for native tool calling (OpenResponses/ChatCompletions compliant)
|
|
930
1080
|
# ReAct parsing remains as fallback for models without native support
|
|
931
1081
|
tools_for_backend = self.tools.all() if self.tools and len(self.tools) > 0 else None
|
|
932
1082
|
|
|
1083
|
+
# Get generation parameters (use overrides or model defaults)
|
|
1084
|
+
gen_temperature = self._temperature if self._temperature is not None else self.model_config.default_temperature
|
|
1085
|
+
gen_max_tokens = self._num_predict if self._num_predict is not None else self.model_config.default_max_tokens
|
|
1086
|
+
gen_top_p = self._top_p if self._top_p is not None else self.model_config.default_top_p
|
|
1087
|
+
|
|
1088
|
+
if self.debug:
|
|
1089
|
+
params_str = f"temp={gen_temperature}, top_p={gen_top_p}, max_tokens={gen_max_tokens}, num_ctx={self.num_ctx}"
|
|
1090
|
+
if think is not None:
|
|
1091
|
+
params_str += f", think={think}"
|
|
1092
|
+
print(f" [DEBUG] Model params: {params_str}")
|
|
1093
|
+
|
|
933
1094
|
response = self.backend.generate(
|
|
934
1095
|
model=self.model,
|
|
935
1096
|
messages=messages,
|
|
936
1097
|
tools=tools_for_backend, # Native tool calling support
|
|
937
|
-
temperature=
|
|
938
|
-
max_tokens=
|
|
1098
|
+
temperature=gen_temperature,
|
|
1099
|
+
max_tokens=gen_max_tokens,
|
|
1100
|
+
top_p=gen_top_p,
|
|
939
1101
|
**backend_kwargs,
|
|
940
1102
|
)
|
|
941
1103
|
|
|
@@ -28,26 +28,7 @@ from enum import Enum
|
|
|
28
28
|
from pathlib import Path
|
|
29
29
|
from typing import Any, Callable, Optional
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
# ANSI color helpers
|
|
33
|
-
def dim(text: str) -> str:
|
|
34
|
-
"""Return dimmed text using ANSI escape codes."""
|
|
35
|
-
return f"\033[2m{text}\033[0m"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
def green(text: str) -> str:
|
|
39
|
-
"""Return green text using ANSI escape codes."""
|
|
40
|
-
return f"\033[32m{text}\033[0m"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def yellow(text: str) -> str:
|
|
44
|
-
"""Return yellow text using ANSI escape codes."""
|
|
45
|
-
return f"\033[33m{text}\033[0m"
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def cyan(text: str) -> str:
|
|
49
|
-
"""Return cyan text using ANSI escape codes."""
|
|
50
|
-
return f"\033[36m{text}\033[0m"
|
|
31
|
+
from .colors import dim, green, yellow, cyan
|
|
51
32
|
|
|
52
33
|
|
|
53
34
|
class AgentState(Enum):
|
|
@@ -843,4 +824,4 @@ __all__ = [
|
|
|
843
824
|
"create_shell_action",
|
|
844
825
|
"format_status",
|
|
845
826
|
"format_progress",
|
|
846
|
-
]
|
|
827
|
+
]
|