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.
Files changed (93) hide show
  1. {agentnova-0.3.4 → agentnova-0.3.6}/PKG-INFO +4 -3
  2. {agentnova-0.3.4 → agentnova-0.3.6}/README.md +3 -2
  3. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/__init__.py +1 -1
  4. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/acp_plugin.py +86 -0
  5. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/agent.py +166 -4
  6. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/agent_mode.py +2 -21
  7. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/backends/ollama.py +149 -37
  8. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/cli.py +64 -194
  9. agentnova-0.3.6/agentnova/colors.py +204 -0
  10. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/config.py +1 -1
  11. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/model_config.py +23 -25
  12. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/model_family_config.py +43 -1
  13. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/openresponses.py +248 -0
  14. agentnova-0.3.6/agentnova/core/tool_cache.py +189 -0
  15. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/tool_parse.py +16 -13
  16. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/types.py +31 -19
  17. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/01_quick_diagnostic.py +62 -6
  18. agentnova-0.3.4/agentnova/orchestrator_enhanced.py → agentnova-0.3.6/agentnova/orchestrator.py +172 -77
  19. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/shared_args.py +35 -0
  20. agentnova-0.3.6/agentnova/skills/loader.py +734 -0
  21. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/soul/loader.py +38 -3
  22. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/soul/types.py +26 -3
  23. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/souls/nova-helper/SOUL.md +43 -6
  24. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/PKG-INFO +4 -3
  25. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/SOURCES.txt +4 -4
  26. {agentnova-0.3.4 → agentnova-0.3.6}/pyproject.toml +1 -1
  27. {agentnova-0.3.4 → agentnova-0.3.6}/tests/test_agent.py +19 -9
  28. agentnova-0.3.6/tests/test_spec_compliance.py +383 -0
  29. agentnova-0.3.4/agentnova/orchestrator.py +0 -205
  30. agentnova-0.3.4/agentnova/skills/loader.py +0 -1893
  31. agentnova-0.3.4/tests/test_acp_integration.py +0 -223
  32. agentnova-0.3.4/tests/test_acp_subagents.py +0 -296
  33. {agentnova-0.3.4 → agentnova-0.3.6}/LICENSE +0 -0
  34. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/__main__.py +0 -0
  35. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/backends/__init__.py +0 -0
  36. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/backends/base.py +0 -0
  37. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/backends/bitnet.py +0 -0
  38. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/__init__.py +0 -0
  39. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/args_normal.py +0 -0
  40. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/error_recovery.py +0 -0
  41. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/helpers.py +0 -0
  42. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/math_prompts.py +0 -0
  43. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/memory.py +0 -0
  44. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/models.py +0 -0
  45. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/core/prompts.py +0 -0
  46. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/00_basic_agent.py +0 -0
  47. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/02_tool_test.py +0 -0
  48. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/03_reasoning_test.py +0 -0
  49. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/04_gsm8k_benchmark.py +0 -0
  50. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/05_common_sense.py +0 -0
  51. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/06_causal_reasoning.py +0 -0
  52. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/07_logical_deduction.py +0 -0
  53. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/08_reading_comprehension.py +0 -0
  54. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/09_general_knowledge.py +0 -0
  55. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/10_implicit_reasoning.py +0 -0
  56. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/11_analogical_reasoning.py +0 -0
  57. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/examples/__init__.py +0 -0
  58. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/model_discovery.py +0 -0
  59. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/py.typed +0 -0
  60. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/__init__.py +0 -0
  61. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/acp/SKILL.md +0 -0
  62. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/datetime/SKILL.md +0 -0
  63. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/SKILL.md +0 -0
  64. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/__init__.py +0 -0
  65. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/aggregate_benchmark.py +0 -0
  66. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/generate_report.py +0 -0
  67. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/improve_description.py +0 -0
  68. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/init_skill.py +0 -0
  69. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/package_skill.py +0 -0
  70. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/quick_validate.py +0 -0
  71. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/run_eval.py +0 -0
  72. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/run_loop.py +0 -0
  73. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/security_scan.py +0 -0
  74. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/test_package_skill.py +0 -0
  75. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/test_quick_validate.py +0 -0
  76. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/utils.py +0 -0
  77. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/skill-creator/scripts/validate.py +0 -0
  78. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/skills/web-search/SKILL.md +0 -0
  79. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/soul/__init__.py +0 -0
  80. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/souls/nova-helper/IDENTITY.md +0 -0
  81. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/souls/nova-helper/STYLE.md +0 -0
  82. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/souls/nova-helper/soul.json +0 -0
  83. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/tools/__init__.py +0 -0
  84. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/tools/builtins.py +0 -0
  85. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/tools/registry.py +0 -0
  86. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova/tools/sandboxed_repl.py +0 -0
  87. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/dependency_links.txt +0 -0
  88. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/entry_points.txt +0 -0
  89. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/requires.txt +0 -0
  90. {agentnova-0.3.4 → agentnova-0.3.6}/agentnova.egg-info/top_level.txt +0 -0
  91. {agentnova-0.3.4 → agentnova-0.3.6}/localclaw/__init__.py +0 -0
  92. {agentnova-0.3.4 → agentnova-0.3.6}/localclaw-redirect/localclaw/__init__.py +0 -0
  93. {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.4
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.3
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://img.shields.io/badge/License-MIT-blue)](#license) [![Go to Python website](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FVTSTech%2FAgentNova%2Frefs%2Fheads%2Fmain%2Fpyproject.toml&query=project.requires-python&label=python&logo=python&logoColor=white)](https://python.org)
51
51
 
52
- <img width="1400" height="1001" alt="image" src="https://github.com/user-attachments/assets/1bc8fb7a-415b-4750-9ce2-1e721bbf9149" />
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.3
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://img.shields.io/badge/License-MIT-blue)](#license) [![Go to Python website](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2FVTSTech%2FAgentNova%2Frefs%2Fheads%2Fmain%2Fpyproject.toml&query=project.requires-python&label=python&logo=python&logoColor=white)](https://python.org)
17
17
 
18
- <img width="1400" height="1001" alt="image" src="https://github.com/user-attachments/assets/1bc8fb7a-415b-4750-9ce2-1e721bbf9149" />
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
 
@@ -28,7 +28,7 @@ Example Usage:
28
28
  agent = Agent(model="qwen2.5:0.5b", soul="/path/to/soul/package")
29
29
  """
30
30
 
31
- __version__ = "0.3.4"
31
+ __version__ = "0.3.6"
32
32
  __author__ = "VTSTech"
33
33
  __status__ = "Alpha"
34
34
 
@@ -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
- if self.debug:
927
- print(f" [DEBUG] num_ctx: {self.num_ctx}")
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=self.model_config.default_temperature,
938
- max_tokens=self.model_config.default_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
+ ]