nighthawk-python 0.7.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
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.
- nighthawk/__init__.py +0 -2
- nighthawk/configuration.py +9 -44
- nighthawk/runtime/prompt.py +340 -44
- nighthawk/runtime/scoping.py +42 -47
- nighthawk/runtime/step_executor.py +4 -2
- nighthawk/tools/contracts.py +1 -1
- {nighthawk_python-0.7.0.dist-info → nighthawk_python-0.9.0.dist-info}/METADATA +1 -1
- {nighthawk_python-0.7.0.dist-info → nighthawk_python-0.9.0.dist-info}/RECORD +10 -10
- {nighthawk_python-0.7.0.dist-info → nighthawk_python-0.9.0.dist-info}/WHEEL +0 -0
- {nighthawk_python-0.7.0.dist-info → nighthawk_python-0.9.0.dist-info}/licenses/LICENSE +0 -0
nighthawk/__init__.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
from .configuration import (
|
|
4
4
|
StepContextLimits,
|
|
5
5
|
StepExecutorConfiguration,
|
|
6
|
-
StepExecutorConfigurationPatch,
|
|
7
6
|
StepPromptTemplates,
|
|
8
7
|
)
|
|
9
8
|
from .errors import (
|
|
@@ -32,7 +31,6 @@ __all__ = [
|
|
|
32
31
|
"StepContextLimits",
|
|
33
32
|
"StepExecutor",
|
|
34
33
|
"StepExecutorConfiguration",
|
|
35
|
-
"StepExecutorConfigurationPatch",
|
|
36
34
|
"StepPromptTemplates",
|
|
37
35
|
"ToolEvaluationError",
|
|
38
36
|
"ToolRegistrationError",
|
nighthawk/configuration.py
CHANGED
|
@@ -32,7 +32,7 @@ Trust boundaries:
|
|
|
32
32
|
|
|
33
33
|
Notes:
|
|
34
34
|
- In async Natural functions, expressions may use `await`.
|
|
35
|
-
- Tool calls return {"value": ..., "error": ...}
|
|
35
|
+
- Tool calls return {"value": ..., "error": ...}; "value" is a preview (max $tool_result_max_tokens tokens), so persist large/intermediate data via nh_assign and inspect with focused nh_eval reads.
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
38
|
|
|
@@ -81,8 +81,11 @@ class StepContextLimits(BaseModel):
|
|
|
81
81
|
locals_max_items: Maximum items rendered in the locals section.
|
|
82
82
|
globals_max_tokens: Maximum tokens for the globals section.
|
|
83
83
|
globals_max_items: Maximum items rendered in the globals section.
|
|
84
|
-
value_max_tokens: Maximum tokens for a single value
|
|
85
|
-
|
|
84
|
+
value_max_tokens: Maximum tokens for a single value preview.
|
|
85
|
+
object_max_methods: Maximum public methods rendered for one object capability view.
|
|
86
|
+
object_max_fields: Maximum public fields rendered for one object capability view.
|
|
87
|
+
object_field_value_max_tokens: Maximum tokens for one object field value preview.
|
|
88
|
+
tool_result_max_tokens: Maximum tokens for a tool result preview.
|
|
86
89
|
"""
|
|
87
90
|
|
|
88
91
|
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
@@ -94,6 +97,9 @@ class StepContextLimits(BaseModel):
|
|
|
94
97
|
globals_max_items: int = Field(default=40, ge=1)
|
|
95
98
|
|
|
96
99
|
value_max_tokens: int = Field(default=200, ge=1)
|
|
100
|
+
object_max_methods: int = Field(default=16, ge=0)
|
|
101
|
+
object_max_fields: int = Field(default=16, ge=0)
|
|
102
|
+
object_field_value_max_tokens: int = Field(default=120, ge=1)
|
|
97
103
|
tool_result_max_tokens: int = Field(default=1_200, ge=1)
|
|
98
104
|
|
|
99
105
|
|
|
@@ -154,44 +160,3 @@ class StepExecutorConfiguration(BaseModel):
|
|
|
154
160
|
return tiktoken.encoding_for_model(model_name)
|
|
155
161
|
except Exception:
|
|
156
162
|
return tiktoken.get_encoding("o200k_base")
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
class StepExecutorConfigurationPatch(BaseModel):
|
|
160
|
-
"""Partial override for StepExecutorConfiguration.
|
|
161
|
-
|
|
162
|
-
Non-None fields replace the corresponding fields in the target configuration.
|
|
163
|
-
|
|
164
|
-
Attributes:
|
|
165
|
-
model: Model identifier override.
|
|
166
|
-
model_settings: Model settings override. Accepts a dict or a
|
|
167
|
-
backend-specific BaseModel instance (auto-converted to dict).
|
|
168
|
-
prompts: Prompt templates override.
|
|
169
|
-
context_limits: Context limits override.
|
|
170
|
-
json_renderer_style: JSON renderer style override.
|
|
171
|
-
tokenizer_encoding: Tokenizer encoding override.
|
|
172
|
-
system_prompt_suffix_fragments: System prompt suffix fragments override.
|
|
173
|
-
user_prompt_suffix_fragments: User prompt suffix fragments override.
|
|
174
|
-
"""
|
|
175
|
-
|
|
176
|
-
model_config = ConfigDict(extra="forbid", frozen=True)
|
|
177
|
-
|
|
178
|
-
model: str | None = None
|
|
179
|
-
model_settings: dict[str, Any] | BaseModel | None = None
|
|
180
|
-
|
|
181
|
-
@field_validator("model_settings", mode="before")
|
|
182
|
-
@classmethod
|
|
183
|
-
def _normalize_model_settings(cls, value: Any) -> dict[str, Any] | None:
|
|
184
|
-
if isinstance(value, BaseModel):
|
|
185
|
-
return value.model_dump()
|
|
186
|
-
return value
|
|
187
|
-
|
|
188
|
-
prompts: StepPromptTemplates | None = None
|
|
189
|
-
context_limits: StepContextLimits | None = None
|
|
190
|
-
json_renderer_style: JsonRendererStyle | None = None
|
|
191
|
-
tokenizer_encoding: str | None = None
|
|
192
|
-
system_prompt_suffix_fragments: tuple[str, ...] | None = None
|
|
193
|
-
user_prompt_suffix_fragments: tuple[str, ...] | None = None
|
|
194
|
-
|
|
195
|
-
def apply_to(self, configuration: StepExecutorConfiguration) -> StepExecutorConfiguration:
|
|
196
|
-
"""Apply non-None fields to the given configuration and return a new copy."""
|
|
197
|
-
return configuration.model_copy(update=self.model_dump(exclude_none=True))
|
nighthawk/runtime/prompt.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import dataclasses
|
|
3
4
|
import functools
|
|
4
5
|
import inspect
|
|
5
6
|
import logging
|
|
6
7
|
import re
|
|
7
|
-
from collections.abc import Iterable
|
|
8
|
+
from collections.abc import Iterable, Mapping, Sequence
|
|
8
9
|
from string import Template
|
|
9
10
|
from typing import Any, TypeAliasType
|
|
10
11
|
|
|
11
12
|
import tiktoken
|
|
13
|
+
from pydantic import BaseModel
|
|
12
14
|
|
|
13
15
|
from ..configuration import StepContextLimits, StepExecutorConfiguration
|
|
14
16
|
from ..json_renderer import JsonRendererStyle, count_tokens, render_json_text
|
|
@@ -21,6 +23,8 @@ from .scoping import (
|
|
|
21
23
|
)
|
|
22
24
|
from .step_context import _MISSING, StepContext, resolve_name_in_step_context
|
|
23
25
|
|
|
26
|
+
type ReferenceAndValue = tuple[str, object]
|
|
27
|
+
|
|
24
28
|
|
|
25
29
|
def _resolve_partial_effective_signature(partial_callable: functools.partial[Any]) -> str | None:
|
|
26
30
|
try:
|
|
@@ -98,7 +102,7 @@ def _is_async_callable_value(value: object) -> bool:
|
|
|
98
102
|
|
|
99
103
|
def _find_ambiguous_callable_signatures(
|
|
100
104
|
*,
|
|
101
|
-
reference_and_value_list: list[
|
|
105
|
+
reference_and_value_list: list[ReferenceAndValue],
|
|
102
106
|
) -> dict[str, list[str]]:
|
|
103
107
|
callable_signature_text_to_reference_list: dict[str, list[str]] = {}
|
|
104
108
|
for reference, value in reference_and_value_list:
|
|
@@ -146,14 +150,275 @@ def _render_callable_line(
|
|
|
146
150
|
return rendered_text
|
|
147
151
|
|
|
148
152
|
|
|
153
|
+
def _is_object_capability_candidate(value: object) -> bool:
|
|
154
|
+
if callable(value):
|
|
155
|
+
return False
|
|
156
|
+
if isinstance(value, TypeAliasType):
|
|
157
|
+
return False
|
|
158
|
+
if value is None:
|
|
159
|
+
return False
|
|
160
|
+
if isinstance(value, (bool, int, float, str, bytes, bytearray)):
|
|
161
|
+
return False
|
|
162
|
+
return not isinstance(value, (Mapping, Sequence, set, frozenset))
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _resolve_public_method_name_to_value(*, value: object) -> dict[str, object]:
|
|
166
|
+
method_name_to_value: dict[str, object] = {}
|
|
167
|
+
for value_type in type(value).__mro__:
|
|
168
|
+
for method_name, class_attribute in value_type.__dict__.items():
|
|
169
|
+
if method_name.startswith("_"):
|
|
170
|
+
continue
|
|
171
|
+
if method_name in method_name_to_value:
|
|
172
|
+
continue
|
|
173
|
+
if isinstance(class_attribute, property):
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
method_value: object | None = None
|
|
177
|
+
if isinstance(class_attribute, staticmethod):
|
|
178
|
+
method_value = class_attribute.__func__
|
|
179
|
+
elif isinstance(class_attribute, classmethod) or inspect.isfunction(class_attribute):
|
|
180
|
+
method_value = class_attribute.__get__(value, type(value))
|
|
181
|
+
elif callable(class_attribute):
|
|
182
|
+
method_value = class_attribute
|
|
183
|
+
|
|
184
|
+
if method_value is None:
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
method_name_to_value[method_name] = method_value
|
|
188
|
+
|
|
189
|
+
return {method_name: method_name_to_value[method_name] for method_name in sorted(method_name_to_value)}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _resolve_public_slot_name_set(*, value: object) -> set[str]:
|
|
193
|
+
public_slot_name_set: set[str] = set()
|
|
194
|
+
for value_type in type(value).__mro__:
|
|
195
|
+
slot_definition = value_type.__dict__.get("__slots__")
|
|
196
|
+
if slot_definition is None:
|
|
197
|
+
continue
|
|
198
|
+
|
|
199
|
+
if isinstance(slot_definition, str):
|
|
200
|
+
slot_name_list = [slot_definition]
|
|
201
|
+
else:
|
|
202
|
+
slot_name_list = [slot_name for slot_name in slot_definition if isinstance(slot_name, str)]
|
|
203
|
+
|
|
204
|
+
for slot_name in slot_name_list:
|
|
205
|
+
if slot_name.startswith("_"):
|
|
206
|
+
continue
|
|
207
|
+
if slot_name in {"__dict__", "__weakref__"}:
|
|
208
|
+
continue
|
|
209
|
+
public_slot_name_set.add(slot_name)
|
|
210
|
+
|
|
211
|
+
return public_slot_name_set
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _resolve_public_field_name_to_value(*, value: object) -> dict[str, object]:
|
|
215
|
+
field_name_to_value: dict[str, object] = {}
|
|
216
|
+
|
|
217
|
+
def add_field(field_name: str, field_value: object) -> None:
|
|
218
|
+
if field_name.startswith("_"):
|
|
219
|
+
return
|
|
220
|
+
if field_name in field_name_to_value:
|
|
221
|
+
return
|
|
222
|
+
field_name_to_value[field_name] = field_value
|
|
223
|
+
|
|
224
|
+
try:
|
|
225
|
+
instance_dict = object.__getattribute__(value, "__dict__")
|
|
226
|
+
except Exception:
|
|
227
|
+
instance_dict = None
|
|
228
|
+
|
|
229
|
+
if isinstance(instance_dict, dict):
|
|
230
|
+
for field_name, field_value in instance_dict.items():
|
|
231
|
+
add_field(field_name, field_value)
|
|
232
|
+
|
|
233
|
+
if dataclasses.is_dataclass(value) and not isinstance(value, type):
|
|
234
|
+
for data_field in dataclasses.fields(value):
|
|
235
|
+
field_name = data_field.name
|
|
236
|
+
if field_name in field_name_to_value:
|
|
237
|
+
continue
|
|
238
|
+
try:
|
|
239
|
+
field_value = object.__getattribute__(value, field_name)
|
|
240
|
+
except Exception:
|
|
241
|
+
continue
|
|
242
|
+
add_field(field_name, field_value)
|
|
243
|
+
|
|
244
|
+
if isinstance(value, BaseModel):
|
|
245
|
+
for field_name, field_value in value.model_dump(mode="python").items():
|
|
246
|
+
add_field(field_name, field_value)
|
|
247
|
+
|
|
248
|
+
for slot_name in sorted(_resolve_public_slot_name_set(value=value)):
|
|
249
|
+
if slot_name in field_name_to_value:
|
|
250
|
+
continue
|
|
251
|
+
try:
|
|
252
|
+
slot_value = object.__getattribute__(value, slot_name)
|
|
253
|
+
except Exception:
|
|
254
|
+
continue
|
|
255
|
+
add_field(slot_name, slot_value)
|
|
256
|
+
|
|
257
|
+
for value_type in type(value).__mro__:
|
|
258
|
+
for field_name, class_attribute in value_type.__dict__.items():
|
|
259
|
+
if field_name in field_name_to_value:
|
|
260
|
+
continue
|
|
261
|
+
if field_name.startswith("_"):
|
|
262
|
+
continue
|
|
263
|
+
if isinstance(class_attribute, (property, staticmethod, classmethod)):
|
|
264
|
+
continue
|
|
265
|
+
if callable(class_attribute):
|
|
266
|
+
continue
|
|
267
|
+
add_field(field_name, class_attribute)
|
|
268
|
+
|
|
269
|
+
return {field_name: field_name_to_value[field_name] for field_name in sorted(field_name_to_value)}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _render_value_preview_line(
|
|
273
|
+
*,
|
|
274
|
+
reference: str,
|
|
275
|
+
value: object,
|
|
276
|
+
value_max_tokens: int,
|
|
277
|
+
token_encoding: tiktoken.Encoding,
|
|
278
|
+
json_renderer_style: JsonRendererStyle,
|
|
279
|
+
) -> str:
|
|
280
|
+
reference_type_name = type(value).__name__
|
|
281
|
+
rendered_name_and_type = f"{reference}: {reference_type_name} = "
|
|
282
|
+
|
|
283
|
+
rendered_value, _ = render_json_text(
|
|
284
|
+
value,
|
|
285
|
+
max_tokens=value_max_tokens,
|
|
286
|
+
encoding=token_encoding,
|
|
287
|
+
style=json_renderer_style,
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
return rendered_name_and_type + rendered_value
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def _collect_callable_reference_and_value_list(*, reference_and_value_list: list[ReferenceAndValue]) -> list[ReferenceAndValue]:
|
|
294
|
+
callable_reference_and_value_list: list[ReferenceAndValue] = []
|
|
295
|
+
for reference, value in reference_and_value_list:
|
|
296
|
+
if callable(value):
|
|
297
|
+
callable_reference_and_value_list.append((reference, value))
|
|
298
|
+
continue
|
|
299
|
+
|
|
300
|
+
if not _is_object_capability_candidate(value):
|
|
301
|
+
continue
|
|
302
|
+
|
|
303
|
+
method_name_to_value = _resolve_public_method_name_to_value(value=value)
|
|
304
|
+
for method_name, method_value in method_name_to_value.items():
|
|
305
|
+
callable_reference_and_value_list.append((f"{reference}.{method_name}", method_value))
|
|
306
|
+
|
|
307
|
+
return callable_reference_and_value_list
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def _render_object_capability_lines(
|
|
311
|
+
*,
|
|
312
|
+
reference: str,
|
|
313
|
+
value: object,
|
|
314
|
+
context_limits: StepContextLimits,
|
|
315
|
+
token_encoding: tiktoken.Encoding,
|
|
316
|
+
json_renderer_style: JsonRendererStyle,
|
|
317
|
+
callable_signature_text_to_reference_list: dict[str, list[str]],
|
|
318
|
+
) -> list[str]:
|
|
319
|
+
lines = [f"{reference}: object = {type(value).__name__}"]
|
|
320
|
+
|
|
321
|
+
method_name_to_value = _resolve_public_method_name_to_value(value=value)
|
|
322
|
+
field_name_to_value = _resolve_public_field_name_to_value(value=value)
|
|
323
|
+
|
|
324
|
+
shown_method_count = 0
|
|
325
|
+
for method_name, method_value in method_name_to_value.items():
|
|
326
|
+
if shown_method_count >= context_limits.object_max_methods:
|
|
327
|
+
break
|
|
328
|
+
method_reference = f"{reference}.{method_name}"
|
|
329
|
+
lines.append(
|
|
330
|
+
_render_callable_line(
|
|
331
|
+
reference=method_reference,
|
|
332
|
+
value=method_value,
|
|
333
|
+
callable_signature_text_to_reference_list=callable_signature_text_to_reference_list,
|
|
334
|
+
)
|
|
335
|
+
)
|
|
336
|
+
shown_method_count += 1
|
|
337
|
+
|
|
338
|
+
omitted_method_count = len(method_name_to_value) - shown_method_count
|
|
339
|
+
if omitted_method_count > 0:
|
|
340
|
+
lines.append(f"{reference}.<methods>: <snipped {omitted_method_count} public methods>")
|
|
341
|
+
|
|
342
|
+
shown_field_count = 0
|
|
343
|
+
for field_name, field_value in field_name_to_value.items():
|
|
344
|
+
if shown_field_count >= context_limits.object_max_fields:
|
|
345
|
+
break
|
|
346
|
+
field_reference = f"{reference}.{field_name}"
|
|
347
|
+
lines.append(
|
|
348
|
+
_render_value_preview_line(
|
|
349
|
+
reference=field_reference,
|
|
350
|
+
value=field_value,
|
|
351
|
+
value_max_tokens=context_limits.object_field_value_max_tokens,
|
|
352
|
+
token_encoding=token_encoding,
|
|
353
|
+
json_renderer_style=json_renderer_style,
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
shown_field_count += 1
|
|
357
|
+
|
|
358
|
+
omitted_field_count = len(field_name_to_value) - shown_field_count
|
|
359
|
+
if omitted_field_count > 0:
|
|
360
|
+
lines.append(f"{reference}.<fields>: <snipped {omitted_field_count} public fields>")
|
|
361
|
+
|
|
362
|
+
return lines
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def _render_reference_and_value_lines(
|
|
366
|
+
*,
|
|
367
|
+
reference: str,
|
|
368
|
+
value: object,
|
|
369
|
+
context_limits: StepContextLimits,
|
|
370
|
+
token_encoding: tiktoken.Encoding,
|
|
371
|
+
json_renderer_style: JsonRendererStyle,
|
|
372
|
+
callable_signature_text_to_reference_list: dict[str, list[str]],
|
|
373
|
+
) -> list[str]:
|
|
374
|
+
if isinstance(value, TypeAliasType):
|
|
375
|
+
return [f"{reference}: type = {value.__value__}"]
|
|
376
|
+
|
|
377
|
+
if callable(value):
|
|
378
|
+
return [
|
|
379
|
+
_render_callable_line(
|
|
380
|
+
reference=reference,
|
|
381
|
+
value=value,
|
|
382
|
+
callable_signature_text_to_reference_list=callable_signature_text_to_reference_list,
|
|
383
|
+
)
|
|
384
|
+
]
|
|
385
|
+
|
|
386
|
+
if _is_object_capability_candidate(value):
|
|
387
|
+
return _render_object_capability_lines(
|
|
388
|
+
reference=reference,
|
|
389
|
+
value=value,
|
|
390
|
+
context_limits=context_limits,
|
|
391
|
+
token_encoding=token_encoding,
|
|
392
|
+
json_renderer_style=json_renderer_style,
|
|
393
|
+
callable_signature_text_to_reference_list=callable_signature_text_to_reference_list,
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
return [
|
|
397
|
+
_render_value_preview_line(
|
|
398
|
+
reference=reference,
|
|
399
|
+
value=value,
|
|
400
|
+
value_max_tokens=context_limits.value_max_tokens,
|
|
401
|
+
token_encoding=token_encoding,
|
|
402
|
+
json_renderer_style=json_renderer_style,
|
|
403
|
+
)
|
|
404
|
+
]
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _count_rendered_line_list_tokens(*, lines: list[str], token_encoding: tiktoken.Encoding) -> int:
|
|
408
|
+
total_tokens = 0
|
|
409
|
+
for line in lines:
|
|
410
|
+
total_tokens += count_tokens(line, token_encoding) + 1
|
|
411
|
+
return total_tokens
|
|
412
|
+
|
|
413
|
+
|
|
149
414
|
def _render_reference_and_value_list_section(
|
|
150
415
|
*,
|
|
151
416
|
section_name: str,
|
|
152
417
|
step_context: StepContext,
|
|
153
|
-
reference_and_value_list: list[
|
|
418
|
+
reference_and_value_list: list[ReferenceAndValue],
|
|
154
419
|
max_items: int,
|
|
155
420
|
section_max_tokens: int,
|
|
156
|
-
|
|
421
|
+
context_limits: StepContextLimits,
|
|
157
422
|
token_encoding: tiktoken.Encoding,
|
|
158
423
|
json_renderer_style: JsonRendererStyle,
|
|
159
424
|
) -> str:
|
|
@@ -161,42 +426,34 @@ def _render_reference_and_value_list_section(
|
|
|
161
426
|
total_tokens = 0
|
|
162
427
|
shown_items = 0
|
|
163
428
|
token_limit_reached = False
|
|
164
|
-
|
|
429
|
+
|
|
430
|
+
callable_reference_and_value_list = _collect_callable_reference_and_value_list(reference_and_value_list=reference_and_value_list)
|
|
431
|
+
callable_signature_text_to_reference_list = _find_ambiguous_callable_signatures(reference_and_value_list=callable_reference_and_value_list)
|
|
165
432
|
|
|
166
433
|
for reference, value in reference_and_value_list:
|
|
167
434
|
if shown_items >= max_items:
|
|
168
435
|
break
|
|
169
436
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
value,
|
|
186
|
-
max_tokens=value_max_tokens,
|
|
187
|
-
encoding=token_encoding,
|
|
188
|
-
style=json_renderer_style,
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
rendered = rendered_name_and_type + rendered_value
|
|
192
|
-
rendered_tokens = count_tokens(rendered_name_and_type, token_encoding) + rendered_value_tokens
|
|
193
|
-
|
|
194
|
-
if total_tokens + rendered_tokens + 1 > section_max_tokens:
|
|
437
|
+
rendered_line_list = _render_reference_and_value_lines(
|
|
438
|
+
reference=reference,
|
|
439
|
+
value=value,
|
|
440
|
+
context_limits=context_limits,
|
|
441
|
+
token_encoding=token_encoding,
|
|
442
|
+
json_renderer_style=json_renderer_style,
|
|
443
|
+
callable_signature_text_to_reference_list=callable_signature_text_to_reference_list,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
rendered_tokens = _count_rendered_line_list_tokens(
|
|
447
|
+
lines=rendered_line_list,
|
|
448
|
+
token_encoding=token_encoding,
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
if total_tokens + rendered_tokens > section_max_tokens:
|
|
195
452
|
token_limit_reached = True
|
|
196
453
|
break
|
|
197
454
|
|
|
198
|
-
lines.
|
|
199
|
-
total_tokens += rendered_tokens
|
|
455
|
+
lines.extend(rendered_line_list)
|
|
456
|
+
total_tokens += rendered_tokens
|
|
200
457
|
shown_items += 1
|
|
201
458
|
|
|
202
459
|
truncated = shown_items < len(reference_and_value_list)
|
|
@@ -230,8 +487,7 @@ def _render_locals_section(
|
|
|
230
487
|
context_limits: StepContextLimits,
|
|
231
488
|
json_renderer_style: JsonRendererStyle,
|
|
232
489
|
) -> str:
|
|
233
|
-
|
|
234
|
-
eligible_reference_and_value_list: list[tuple[str, object]] = []
|
|
490
|
+
eligible_reference_and_value_list: list[ReferenceAndValue] = []
|
|
235
491
|
for reference in sorted(references):
|
|
236
492
|
if reference.startswith("__"):
|
|
237
493
|
continue
|
|
@@ -243,7 +499,7 @@ def _render_locals_section(
|
|
|
243
499
|
reference_and_value_list=eligible_reference_and_value_list,
|
|
244
500
|
max_items=context_limits.locals_max_items,
|
|
245
501
|
section_max_tokens=context_limits.locals_max_tokens,
|
|
246
|
-
|
|
502
|
+
context_limits=context_limits,
|
|
247
503
|
token_encoding=token_encoding,
|
|
248
504
|
json_renderer_style=json_renderer_style,
|
|
249
505
|
)
|
|
@@ -275,8 +531,7 @@ def _render_globals_section(
|
|
|
275
531
|
context_limits: StepContextLimits,
|
|
276
532
|
json_renderer_style: JsonRendererStyle,
|
|
277
533
|
) -> str:
|
|
278
|
-
|
|
279
|
-
eligible_reference_and_value_list: list[tuple[str, object]] = []
|
|
534
|
+
eligible_reference_and_value_list: list[ReferenceAndValue] = []
|
|
280
535
|
for reference in sorted(references):
|
|
281
536
|
if reference.startswith("__"):
|
|
282
537
|
continue
|
|
@@ -293,12 +548,53 @@ def _render_globals_section(
|
|
|
293
548
|
reference_and_value_list=eligible_reference_and_value_list,
|
|
294
549
|
max_items=context_limits.globals_max_items,
|
|
295
550
|
section_max_tokens=context_limits.globals_max_tokens,
|
|
296
|
-
|
|
551
|
+
context_limits=context_limits,
|
|
297
552
|
token_encoding=token_encoding,
|
|
298
553
|
json_renderer_style=json_renderer_style,
|
|
299
554
|
)
|
|
300
555
|
|
|
301
556
|
|
|
557
|
+
def _resolve_step_prompt_template_text(
|
|
558
|
+
*,
|
|
559
|
+
template_text: str,
|
|
560
|
+
name_to_value: dict[str, str],
|
|
561
|
+
) -> str:
|
|
562
|
+
template = Template(template_text)
|
|
563
|
+
return template.safe_substitute(name_to_value)
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _resolve_step_system_prompt_text(*, configuration: StepExecutorConfiguration) -> str:
|
|
567
|
+
return _resolve_step_prompt_template_text(
|
|
568
|
+
template_text=configuration.prompts.step_system_prompt_template,
|
|
569
|
+
name_to_value={
|
|
570
|
+
"tool_result_max_tokens": str(configuration.context_limits.tool_result_max_tokens),
|
|
571
|
+
},
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def _resolve_step_user_prompt_text(
|
|
576
|
+
*,
|
|
577
|
+
template_text: str,
|
|
578
|
+
program_text: str,
|
|
579
|
+
locals_text: str,
|
|
580
|
+
globals_text: str,
|
|
581
|
+
tool_result_max_tokens: int,
|
|
582
|
+
) -> str:
|
|
583
|
+
return _resolve_step_prompt_template_text(
|
|
584
|
+
template_text=template_text,
|
|
585
|
+
name_to_value={
|
|
586
|
+
"program": program_text,
|
|
587
|
+
"locals": locals_text,
|
|
588
|
+
"globals": globals_text,
|
|
589
|
+
"tool_result_max_tokens": str(tool_result_max_tokens),
|
|
590
|
+
},
|
|
591
|
+
)
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def build_system_prompt(*, configuration: StepExecutorConfiguration) -> str:
|
|
595
|
+
return _resolve_step_system_prompt_text(configuration=configuration)
|
|
596
|
+
|
|
597
|
+
|
|
302
598
|
def build_user_prompt(
|
|
303
599
|
*,
|
|
304
600
|
processed_natural_program: str,
|
|
@@ -327,12 +623,12 @@ def build_user_prompt(
|
|
|
327
623
|
json_renderer_style=configuration.json_renderer_style,
|
|
328
624
|
)
|
|
329
625
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
626
|
+
prompt_text = _resolve_step_user_prompt_text(
|
|
627
|
+
template_text=template_text,
|
|
628
|
+
program_text=program_text,
|
|
629
|
+
locals_text=locals_text,
|
|
630
|
+
globals_text=globals_text,
|
|
631
|
+
tool_result_max_tokens=context_limits.tool_result_max_tokens,
|
|
336
632
|
)
|
|
337
633
|
|
|
338
634
|
suffix_fragments = (
|
nighthawk/runtime/scoping.py
CHANGED
|
@@ -2,17 +2,17 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import importlib.metadata
|
|
4
4
|
import threading
|
|
5
|
-
from collections.abc import Iterator, Mapping
|
|
5
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
6
6
|
from contextlib import contextmanager
|
|
7
7
|
from contextvars import ContextVar
|
|
8
8
|
from copy import copy
|
|
9
9
|
from dataclasses import dataclass, replace
|
|
10
|
-
from typing import TYPE_CHECKING, Any
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
11
11
|
|
|
12
12
|
from opentelemetry.trace import Span, get_tracer_provider
|
|
13
13
|
from pydantic_ai.usage import RunUsage
|
|
14
14
|
|
|
15
|
-
from ..configuration import StepExecutorConfiguration
|
|
15
|
+
from ..configuration import StepExecutorConfiguration
|
|
16
16
|
from ..errors import NighthawkError
|
|
17
17
|
from ..tools.registry import tool_scope
|
|
18
18
|
from ..ulid import generate_ulid
|
|
@@ -295,24 +295,28 @@ def run(
|
|
|
295
295
|
@contextmanager
|
|
296
296
|
def scope(
|
|
297
297
|
*,
|
|
298
|
+
mode: Literal["inherit", "replace"] = "inherit",
|
|
298
299
|
step_executor_configuration: StepExecutorConfiguration | None = None,
|
|
299
|
-
step_executor_configuration_patch: StepExecutorConfigurationPatch | None = None,
|
|
300
300
|
step_executor: StepExecutor | None = None,
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
system_prompt_suffix_fragments: Sequence[str] | None = None,
|
|
302
|
+
user_prompt_suffix_fragments: Sequence[str] | None = None,
|
|
303
303
|
implicit_references: ImplicitReferenceNameToValue | None = None,
|
|
304
304
|
) -> Iterator[StepExecutor]:
|
|
305
|
-
"""Open a nested scope that can override the
|
|
305
|
+
"""Open a nested scope that can override the active execution context.
|
|
306
306
|
|
|
307
|
-
Must be called inside an active run context. Creates a new scope_id while inheriting the run_id from the parent context.
|
|
307
|
+
Must be called inside an active run context. Creates a new ``scope_id`` while inheriting the ``run_id`` from the parent context.
|
|
308
308
|
|
|
309
309
|
Args:
|
|
310
|
+
mode: Scope composition mode. ``"inherit"`` appends/merges values into the current scope. ``"replace"`` fully replaces provided list/dict values.
|
|
311
|
+
In both modes, ``None`` means no change.
|
|
310
312
|
step_executor_configuration: Full replacement configuration for the step executor.
|
|
311
|
-
step_executor_configuration_patch: Partial override applied on top of the current configuration.
|
|
312
313
|
step_executor: Replacement step executor for this scope.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
314
|
+
system_prompt_suffix_fragments: Additional system prompt suffix fragments.
|
|
315
|
+
In ``mode="inherit"``, fragments are appended. In ``mode="replace"``, provided fragments fully replace the current fragments.
|
|
316
|
+
user_prompt_suffix_fragments: Additional user prompt suffix fragments.
|
|
317
|
+
In ``mode="inherit"``, fragments are appended. In ``mode="replace"``, provided fragments fully replace the current fragments.
|
|
318
|
+
implicit_references: Implicit global references for this scope.
|
|
319
|
+
In ``mode="inherit"``, values are merged with conflict checks. In ``mode="replace"``, provided mappings fully replace the current mapping.
|
|
316
320
|
|
|
317
321
|
Yields:
|
|
318
322
|
The step executor active within this scope.
|
|
@@ -321,9 +325,9 @@ def scope(
|
|
|
321
325
|
```python
|
|
322
326
|
with nighthawk.run(executor):
|
|
323
327
|
with nighthawk.scope(
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
328
|
+
mode="replace",
|
|
329
|
+
implicit_references={},
|
|
330
|
+
system_prompt_suffix_fragments=["Use concise answers."],
|
|
327
331
|
) as scoped_executor:
|
|
328
332
|
result = my_natural_function()
|
|
329
333
|
```
|
|
@@ -332,31 +336,13 @@ def scope(
|
|
|
332
336
|
current_execution_context = get_execution_context()
|
|
333
337
|
|
|
334
338
|
next_step_executor = current_step_executor
|
|
335
|
-
|
|
336
339
|
if step_executor is not None:
|
|
337
340
|
next_step_executor = step_executor
|
|
338
341
|
|
|
339
|
-
|
|
340
|
-
value is not None
|
|
341
|
-
for value in (
|
|
342
|
-
step_executor_configuration,
|
|
343
|
-
step_executor_configuration_patch,
|
|
344
|
-
)
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
if has_configuration_update:
|
|
348
|
-
current_agent_step_executor = _resolve_agent_step_executor(next_step_executor)
|
|
349
|
-
next_configuration = current_agent_step_executor.configuration
|
|
350
|
-
|
|
351
|
-
if step_executor_configuration is not None:
|
|
352
|
-
next_configuration = step_executor_configuration
|
|
353
|
-
|
|
354
|
-
if step_executor_configuration_patch is not None:
|
|
355
|
-
next_configuration = step_executor_configuration_patch.apply_to(next_configuration)
|
|
356
|
-
|
|
342
|
+
if step_executor_configuration is not None:
|
|
357
343
|
next_step_executor = _replace_step_executor_with_configuration(
|
|
358
344
|
next_step_executor,
|
|
359
|
-
configuration=
|
|
345
|
+
configuration=step_executor_configuration,
|
|
360
346
|
)
|
|
361
347
|
|
|
362
348
|
next_execution_context = replace(
|
|
@@ -364,27 +350,36 @@ def scope(
|
|
|
364
350
|
scope_id=generate_ulid(),
|
|
365
351
|
)
|
|
366
352
|
|
|
367
|
-
|
|
368
|
-
|
|
353
|
+
next_system_prompt_suffix_fragments = _system_prompt_suffix_fragments_var.get()
|
|
354
|
+
next_user_prompt_suffix_fragments = _user_prompt_suffix_fragments_var.get()
|
|
369
355
|
next_implicit_reference_name_to_value = _implicit_reference_name_to_value_var.get()
|
|
370
356
|
|
|
371
|
-
if
|
|
372
|
-
|
|
357
|
+
if system_prompt_suffix_fragments is not None:
|
|
358
|
+
if mode == "inherit":
|
|
359
|
+
next_system_prompt_suffix_fragments = (*next_system_prompt_suffix_fragments, *system_prompt_suffix_fragments)
|
|
360
|
+
else:
|
|
361
|
+
next_system_prompt_suffix_fragments = tuple(system_prompt_suffix_fragments)
|
|
373
362
|
|
|
374
|
-
if
|
|
375
|
-
|
|
363
|
+
if user_prompt_suffix_fragments is not None:
|
|
364
|
+
if mode == "inherit":
|
|
365
|
+
next_user_prompt_suffix_fragments = (*next_user_prompt_suffix_fragments, *user_prompt_suffix_fragments)
|
|
366
|
+
else:
|
|
367
|
+
next_user_prompt_suffix_fragments = tuple(user_prompt_suffix_fragments)
|
|
376
368
|
|
|
377
369
|
if implicit_references is not None:
|
|
378
|
-
|
|
379
|
-
next_implicit_reference_name_to_value
|
|
380
|
-
|
|
381
|
-
|
|
370
|
+
if mode == "inherit":
|
|
371
|
+
next_implicit_reference_name_to_value = _merge_implicit_reference_name_to_value_with_conflict_check(
|
|
372
|
+
next_implicit_reference_name_to_value,
|
|
373
|
+
implicit_references,
|
|
374
|
+
)
|
|
375
|
+
else:
|
|
376
|
+
next_implicit_reference_name_to_value = dict(implicit_references)
|
|
382
377
|
|
|
383
378
|
with tool_scope():
|
|
384
379
|
step_executor_token = _step_executor_var.set(next_step_executor)
|
|
385
380
|
execution_context_token = _execution_context_var.set(next_execution_context)
|
|
386
|
-
system_fragments_token = _system_prompt_suffix_fragments_var.set(
|
|
387
|
-
user_fragments_token = _user_prompt_suffix_fragments_var.set(
|
|
381
|
+
system_fragments_token = _system_prompt_suffix_fragments_var.set(next_system_prompt_suffix_fragments)
|
|
382
|
+
user_fragments_token = _user_prompt_suffix_fragments_var.set(next_user_prompt_suffix_fragments)
|
|
388
383
|
implicit_reference_name_to_value_token = _implicit_reference_name_to_value_var.set(next_implicit_reference_name_to_value)
|
|
389
384
|
try:
|
|
390
385
|
with span(
|
|
@@ -11,7 +11,7 @@ from ..errors import ExecutionError
|
|
|
11
11
|
from ..tools.execution import ToolResultWrapperToolset
|
|
12
12
|
from ..tools.registry import get_visible_tools
|
|
13
13
|
from .async_bridge import run_coroutine_synchronously
|
|
14
|
-
from .prompt import build_user_prompt, extract_references_and_program
|
|
14
|
+
from .prompt import build_system_prompt, build_user_prompt, extract_references_and_program
|
|
15
15
|
from .scoping import (
|
|
16
16
|
get_current_usage_meter,
|
|
17
17
|
get_system_prompt_suffix_fragments,
|
|
@@ -108,11 +108,13 @@ def _new_agent_step_executor(
|
|
|
108
108
|
if configuration.model_settings is not None:
|
|
109
109
|
constructor_arguments["model_settings"] = configuration.model_settings
|
|
110
110
|
|
|
111
|
+
system_prompt_text = build_system_prompt(configuration=configuration)
|
|
112
|
+
|
|
111
113
|
agent = Agent(
|
|
112
114
|
model=model,
|
|
113
115
|
output_type=StepFinalResult,
|
|
114
116
|
deps_type=StepContext,
|
|
115
|
-
system_prompt=
|
|
117
|
+
system_prompt=system_prompt_text,
|
|
116
118
|
**constructor_arguments,
|
|
117
119
|
)
|
|
118
120
|
|
nighthawk/tools/contracts.py
CHANGED
|
@@ -38,7 +38,7 @@ def render_tool_result_json_text(
|
|
|
38
38
|
) -> str:
|
|
39
39
|
"""Render a tool result envelope as compact JSON text.
|
|
40
40
|
|
|
41
|
-
Both ``value`` and ``error`` are individually
|
|
41
|
+
Both ``value`` and ``error`` are individually previewed under their own
|
|
42
42
|
token budgets via ``render_json_text``, then assembled into a
|
|
43
43
|
``{"value": ..., "error": ...}`` envelope using f-string interpolation.
|
|
44
44
|
The sub-values are already valid JSON fragments produced by
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nighthawk-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: A Python library where Python controls flow and LLMs or coding agents reason within constrained Natural blocks.
|
|
5
5
|
Project-URL: Repository, https://github.com/kurusugawa-computer/nighthawk-python
|
|
6
6
|
Project-URL: Documentation, https://kurusugawa-computer.github.io/nighthawk-python/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
nighthawk/__init__.py,sha256=
|
|
2
|
-
nighthawk/configuration.py,sha256=
|
|
1
|
+
nighthawk/__init__.py,sha256=YqE9InkNzbW12v9Bh3qF4INLUHfJfmNT_hPqmE_kNb8,1308
|
|
2
|
+
nighthawk/configuration.py,sha256=1gtdLxcDMMnvz9t2fBZP-U-MHW54IHWmInSPiByTaGo,6286
|
|
3
3
|
nighthawk/errors.py,sha256=CSw_VfIoppCE2SdN2zMtVbMGu_wFwD628v8ZAVxeHIY,602
|
|
4
4
|
nighthawk/identifier_path.py,sha256=Io3VcT8BlxAASHQUSUclrRcCwnRKTtMDPSR1bpaDN6s,984
|
|
5
5
|
nighthawk/json_renderer.py,sha256=ixpUYTsVVKOp-iMdHldw7-6I3JxelXOQD8vK2KVXwbw,7640
|
|
@@ -27,20 +27,20 @@ nighthawk/resilience/_timeout.py,sha256=L5zWSXmhEYIyWlEjut0nA7ur3oRDOn_2JrhpBw_v
|
|
|
27
27
|
nighthawk/resilience/_vote.py,sha256=YV0MTYWwFsHBEbGJhfDByCYaFSuuE6rZYB2VnILx0Es,4616
|
|
28
28
|
nighthawk/runtime/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
29
|
nighthawk/runtime/async_bridge.py,sha256=k1s7e5dJEMNZJd0xHqkqAY7VkE7tOBzJf6nAEJH_Xh0,1478
|
|
30
|
-
nighthawk/runtime/prompt.py,sha256=
|
|
30
|
+
nighthawk/runtime/prompt.py,sha256=fod1f8wS2xKoc3RM44eB8wrKwPmap9K1gwLw4EzUSP4,22669
|
|
31
31
|
nighthawk/runtime/runner.py,sha256=saVkEeMkVfz1cm_AVaz8I23Y5OiWuWthqwv-z_4kwwY,23121
|
|
32
|
-
nighthawk/runtime/scoping.py,sha256=
|
|
32
|
+
nighthawk/runtime/scoping.py,sha256=oa3SqjxctFQtTTZNYFj-scvf5Q0_zaVtluh8V1h8pqE,14986
|
|
33
33
|
nighthawk/runtime/step_context.py,sha256=-MafRMUV9fo0IBTGnQfAF72IyQy90ZoUS9SK77OVgPc,5529
|
|
34
34
|
nighthawk/runtime/step_contract.py,sha256=2rbZpUBbmWGTogAksYerIAZfv2xgBgaNmidWxjJtfmY,6391
|
|
35
|
-
nighthawk/runtime/step_executor.py,sha256=
|
|
35
|
+
nighthawk/runtime/step_executor.py,sha256=Y4P7PuGzJT64FIwjhU2gABxoqHf6wPTXMiSFTSfkK6U,12847
|
|
36
36
|
nighthawk/runtime/tool_calls.py,sha256=rPLzp2iLdOIWDYvJFw7nMcbo2i786YHHE-gm2_clfB4,3076
|
|
37
37
|
nighthawk/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
38
|
nighthawk/tools/assignment.py,sha256=5_VrhuM2KUBz6RHE5kNOnVC-NZUO0x2N3QvNE17z0GY,9462
|
|
39
|
-
nighthawk/tools/contracts.py,sha256=
|
|
39
|
+
nighthawk/tools/contracts.py,sha256=9bFj88GiRqwyUSfjhZxY3jhtQafdBP3RvDSz6-QCYPs,2097
|
|
40
40
|
nighthawk/tools/execution.py,sha256=e1uv09MWOkCoZ5uQu9myUXyhQzM_UziR9yE0raFYtQI,2740
|
|
41
41
|
nighthawk/tools/provided.py,sha256=gsZ7xcrNmEmwcvdkQi4YndTAIcfpwY1jTxpKJsYU4QQ,2079
|
|
42
42
|
nighthawk/tools/registry.py,sha256=wsZznwI2xPTflN6tfVE8je5keNVqBHHtTPd9bq0Cu7g,6425
|
|
43
|
-
nighthawk_python-0.
|
|
44
|
-
nighthawk_python-0.
|
|
45
|
-
nighthawk_python-0.
|
|
46
|
-
nighthawk_python-0.
|
|
43
|
+
nighthawk_python-0.9.0.dist-info/METADATA,sha256=DIyU5rb7e0BvfBfC7hbiw8vc8SIeDQRapJ1XTM0gDg0,6192
|
|
44
|
+
nighthawk_python-0.9.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
45
|
+
nighthawk_python-0.9.0.dist-info/licenses/LICENSE,sha256=R8tjrX79o5Pbi_l_aFPPWDbTZkmz4is0CxmzMMs3f0A,1076
|
|
46
|
+
nighthawk_python-0.9.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|