nighthawk-python 0.11.0__tar.gz → 0.12.0__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.
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.claude/rules/docs.md +2 -2
- nighthawk_python-0.12.0/.devcontainer/devcontainer-lock.json +14 -0
- nighthawk_python-0.12.0/.devcontainer/devcontainer.json +16 -0
- nighthawk_python-0.12.0/.devcontainer/postCreateCommand.sh +16 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.github/dependabot.yml +6 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.github/workflows/docs.yml +1 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/CHANGELOG.md +11 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/CONTRIBUTING.md +10 -7
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/PKG-INFO +9 -9
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/README.md +3 -3
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/coding-agent-backends.md +2 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/index.md +4 -4
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/natural-blocks.md +26 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/patterns.md +21 -9
- nighthawk_python-0.12.0/docs/philosophy.md +196 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/quickstart.md +1 -3
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/roadmap.md +8 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/runtime-configuration.md +7 -7
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/specification.md +4 -3
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/verification.md +21 -12
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/research-result.md +1 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/pyproject.toml +8 -7
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/backends/base.py +29 -3
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/backends/claude_code_cli.py +1 -2
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/backends/claude_code_sdk.py +1 -2
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/backends/codex.py +1 -2
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/backends/mcp_boundary.py +2 -2
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/backends/mcp_server.py +28 -16
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/runtime/step_executor.py +1 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/backends/test_codex.py +1 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/docs/test_docs_architecture.py +10 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/execution/test_runtime.py +28 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/governance/test_governance.py +1 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/public/test_public_api.py +1 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/resilience/test_budget.py +1 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/resilience/test_circuit_breaker.py +1 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/resilience/test_retry.py +1 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/resilience/test_timeout.py +1 -1
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/tools/test_tool_boundary.py +8 -8
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/uv.lock +263 -275
- nighthawk_python-0.11.0/.devcontainer/Dockerfile.devcontainer +0 -3
- nighthawk_python-0.11.0/.devcontainer/Dockerfile.litellm +0 -11
- nighthawk_python-0.11.0/.devcontainer/devcontainer.json +0 -24
- nighthawk_python-0.11.0/.devcontainer/docker-compose.yaml +0 -19
- nighthawk_python-0.11.0/.devcontainer/litellm-config.yaml +0 -37
- nighthawk_python-0.11.0/docs/philosophy.md +0 -178
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.claude/rules/promptfoo.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.claude/rules/src.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.claude/rules/tests.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.claude/settings.json +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.claude/unset_envs.sh +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.github/workflows/ci.yml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.github/workflows/publish.yml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.gitignore +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/.python-version +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/AGENTS.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/CLAUDE.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/LICENSE +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/AGENTS.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/api.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/assets/nighthawk_logo-128x128.png +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/executors.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/for-coding-agents.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/docs/pydantic-ai-providers.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/assertions/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/assertions/binding_value.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/assertions/outcome_kind.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/assertions/raise_message.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/evidence/2026-03-26-baseline-prompt-ab.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/evidence/2026-03-26-baseline-regression.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/evidence/2026-03-26-baseline-suffix-ab.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/evidence/2026-04-27-runtime-prompt-optimization.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/promptfooconfig-agents.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/promptfooconfig-nano-sanity.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/promptfooconfig-prompt-ab.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/promptfooconfig-suffix-ab.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/promptfooconfig.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/prompts/eval_coding_agent.txt +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/prompts/eval_default.txt +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/prompts/eval_legacy_default.txt +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/prompts/eval_mutation_aware.txt +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/prompts/eval_sequenced.txt +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/provider.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/test_cases/binding_operations.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/test_cases/edge_cases.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/test_cases/loop_outcomes.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/test_cases/loop_outcomes_ja.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/test_cases/multi_step.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/test_cases/null_handling.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/test_cases/outcome_kinds.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/evals/promptfoo/test_cases/tool_selection.yaml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/mkdocs.yml +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/pyrightconfig.json +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/AGENTS.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/backends/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/backends/_text_fallback.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/backends/claude_code_settings.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/backends/text_projection.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/backends/tool_bridge.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/configuration.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/errors.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/identifier_path.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/json_renderer.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/natural/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/natural/blocks.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/natural/decorator.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/natural/transform.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/oversight.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/resilience/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/resilience/_budget.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/resilience/_circuit_breaker.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/resilience/_fallback.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/resilience/_retry.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/resilience/_timeout.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/resilience/_vote.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/runtime/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/runtime/_user_content.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/runtime/async_bridge.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/runtime/prompt.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/runtime/runner.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/runtime/scoping.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/runtime/step_context.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/runtime/step_contract.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/runtime/tool_calls.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/testing.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/tools/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/tools/_tool_return_content.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/tools/assignment.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/tools/contracts.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/tools/execution.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/tools/provided.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/tools/registry.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/src/nighthawk/ulid.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/AGENTS.md +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/backends/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/backends/test_claude_code_cli.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/backends/test_claude_code_sdk.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/conftest.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/docs/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/docs/test_coding_agent_examples.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/docs/test_prompt_examples.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/execution/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/execution/prompt_test_helpers.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/execution/stub_executor.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/execution/test_execution_outcome_prompt_fragment.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/execution/test_globals_prompt.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/execution/test_infer_binding_types.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/execution/test_natural_block_ordering.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/execution/test_natural_traceback.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/execution/test_variables_prompt.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/integration/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/integration/skip_helpers.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/integration/test_carry_pattern.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/integration/test_claude_code_cli_integration.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/integration/test_claude_code_sdk_integration.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/integration/test_codex_integration.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/integration/test_llm_integration.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/natural/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/natural/test_blocks.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/public/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/public/test_readme_example.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/public/test_usage_meter.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/resilience/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/resilience/test_composition.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/resilience/test_fallback.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/resilience/test_vote.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/test_renderer.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/test_testing.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/tools/__init__.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/tools/test_assignment_async.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/tools/test_contracts.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/tools/test_multimodal_helpers.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/tools/test_provided_async.py +0 -0
- {nighthawk_python-0.11.0 → nighthawk_python-0.12.0}/tests/tools/test_registry.py +0 -0
|
@@ -39,7 +39,7 @@ Public API documentation layering:
|
|
|
39
39
|
| `coding-agent-backends.md` | Backend users | Backend reference | Backend-specific settings, shared capabilities, skills, MCP tool exposure, working directory, troubleshooting. |
|
|
40
40
|
| `for-coding-agents.md` | Coding agents (LLMs) | Operational guide | Condensed, decision-oriented rules derived from human-oriented docs. Self-contained with absolute URLs. |
|
|
41
41
|
| `specification.md` | Implementors | Canonical spec | Syntax, state layers, tools, outcomes, frontmatter, runtime semantics, observability contract, and custom backend capability/protocol semantics. Numbered section headings. |
|
|
42
|
-
| `philosophy.md` | Evaluators | Design rationale | Execution model,
|
|
42
|
+
| `philosophy.md` | Evaluators | Design rationale | Execution model, design landscape, boundary-first consequences, trust and safety boundaries, tradeoffs, and runtime evaluation rationale. |
|
|
43
43
|
| `api.md` | Developers | API reference | Auto-generated from docstrings, including protocol and extension-hook symbols. |
|
|
44
44
|
| `roadmap.md` | Contributors | Future directions | Ideas only. Remove when implemented. |
|
|
45
45
|
| `docs/AGENTS.md` | Coding agents editing docs | Documentation governance | Canonical ownership, page roles, routing rules, and docs test invariants. Symlink to `.claude/rules/docs.md`. |
|
|
@@ -171,7 +171,7 @@ Pages in the Getting started, Patterns & verification, and Configuration nav gro
|
|
|
171
171
|
|
|
172
172
|
**`philosophy.md`**
|
|
173
173
|
|
|
174
|
-
- Owns the cumulative argument: execution model,
|
|
174
|
+
- Owns the cumulative argument: execution model, design landscape, boundary-first consequences (resilience, scoped execution contexts, tool exposure, multi-agent coordination), trust and safety boundaries, tradeoffs, and runtime evaluation rationale.
|
|
175
175
|
- External references acceptable. Prefer stable URLs with enough inline context to survive link rot.
|
|
176
176
|
- No how-to code examples for patterns in `natural-blocks.md` or `patterns.md`. Exception: positioning examples may reuse function names from those pages.
|
|
177
177
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"features": {
|
|
3
|
+
"ghcr.io/devcontainers/features/docker-in-docker:4.1.0": {
|
|
4
|
+
"version": "4.1.0",
|
|
5
|
+
"resolved": "ghcr.io/devcontainers/features/docker-in-docker@sha256:7d979c4a36d595eb4ab79909caa3f396daf3b6152dcf38e8328ea4eff42206c7",
|
|
6
|
+
"integrity": "sha256:7d979c4a36d595eb4ab79909caa3f396daf3b6152dcf38e8328ea4eff42206c7"
|
|
7
|
+
},
|
|
8
|
+
"ghcr.io/devcontainers/features/node:2.1.0": {
|
|
9
|
+
"version": "2.1.0",
|
|
10
|
+
"resolved": "ghcr.io/devcontainers/features/node@sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857",
|
|
11
|
+
"integrity": "sha256:586c9a6f7dd40bd3ba2cd41e7f2f88dcc31fbe5d1442afcbf07ffbc66b686857"
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Python 3.13",
|
|
3
|
+
"image": "mcr.microsoft.com/devcontainers/python:3-3.13-bookworm",
|
|
4
|
+
"features": {
|
|
5
|
+
"ghcr.io/devcontainers/features/docker-in-docker:4.1.0": {},
|
|
6
|
+
"ghcr.io/devcontainers/features/node:2.1.0": {}
|
|
7
|
+
},
|
|
8
|
+
"postCreateCommand": "/bin/sh .devcontainer/postCreateCommand.sh",
|
|
9
|
+
"customizations": {
|
|
10
|
+
"vscode": {
|
|
11
|
+
"extensions": [
|
|
12
|
+
"charliermarsh.ruff"
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
sudo apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
|
5
|
+
&& sudo apt-get -y install --no-install-recommends ripgrep fzf bubblewrap \
|
|
6
|
+
&& sudo apt-get clean \
|
|
7
|
+
&& sudo rm -rf /var/lib/apt/lists/*
|
|
8
|
+
|
|
9
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
10
|
+
|
|
11
|
+
uv sync
|
|
12
|
+
|
|
13
|
+
npm install -g pyright
|
|
14
|
+
|
|
15
|
+
curl -LsSf https://chatgpt.com/codex/install.sh | CODEX_NON_INTERACTIVE=1 sh
|
|
16
|
+
curl -LsSf https://claude.ai/install.sh | bash
|
|
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.12.0]
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Clarified the boundary-first philosophy page.
|
|
14
|
+
- Clarified multimodal documentation and specification.
|
|
15
|
+
- Raised the minimum Pydantic AI version to 2.37.0 and migrated coding-agent backends to its native-tool and structured-instruction APIs.
|
|
16
|
+
- Raised the MCP minimum version to 2.1.1 and migrated the embedded tool server and content types to the MCP 2 API.
|
|
17
|
+
- Updated the Claude Agent SDK integration to 0.2.151.
|
|
18
|
+
|
|
10
19
|
## [0.11.0]
|
|
11
20
|
|
|
12
21
|
### Added
|
|
@@ -179,7 +188,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
179
188
|
- Step executor abstraction and provider integration foundation.
|
|
180
189
|
- Core documentation and project scaffolding.
|
|
181
190
|
|
|
182
|
-
[Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.
|
|
191
|
+
[Unreleased]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.12.0...HEAD
|
|
192
|
+
[0.12.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.11.0...v0.12.0
|
|
183
193
|
[0.11.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.10.0...v0.11.0
|
|
184
194
|
[0.10.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.9.0...v0.10.0
|
|
185
195
|
[0.9.0]: https://github.com/kurusugawa-computer/nighthawk-python/compare/v0.8.0...v0.9.0
|
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
uv sync --all-extras --all-groups
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
+
`uv` manages the project virtual environment in `.venv`. Do not create or
|
|
16
|
+
activate a separate virtual environment; run project commands through `uv run`.
|
|
17
|
+
The `.venv` directory is generated and ignored by Git. It can be deleted to
|
|
18
|
+
rebuild the local environment, and the next `uv sync` or `uv run` recreates it.
|
|
19
|
+
|
|
20
|
+
Declare project dependencies in `pyproject.toml` through `uv add`, rather than
|
|
21
|
+
installing them directly into `.venv` with `uv pip install`.
|
|
22
|
+
|
|
15
23
|
If you see a `uv` warning about hardlinking (common in containers or cross-filesystem workspaces), suppress it with:
|
|
16
24
|
|
|
17
25
|
```bash
|
|
@@ -39,13 +47,8 @@ uv run pytest -q # quiet output
|
|
|
39
47
|
|
|
40
48
|
### Integration tests
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
uv pip install pydantic-ai-slim[openai]
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
Then run with your `.env` loaded:
|
|
50
|
+
The full setup command installs the OpenAI provider development dependency.
|
|
51
|
+
Run integration tests with your `.env` loaded:
|
|
49
52
|
|
|
50
53
|
```bash
|
|
51
54
|
set -a; source .env; set +a; uv run pytest -q
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: nighthawk-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.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/
|
|
@@ -20,17 +20,17 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
21
|
Requires-Python: >=3.13
|
|
22
22
|
Requires-Dist: headson>=0.16.1
|
|
23
|
-
Requires-Dist: pydantic-ai-slim>=
|
|
23
|
+
Requires-Dist: pydantic-ai-slim>=2.37.0
|
|
24
24
|
Requires-Dist: pydantic>=2.13.3
|
|
25
25
|
Requires-Dist: pyyaml>=6.0.3
|
|
26
26
|
Requires-Dist: tenacity>=9.1.4
|
|
27
27
|
Requires-Dist: tiktoken>=0.12.0
|
|
28
28
|
Provides-Extra: claude-code-cli
|
|
29
|
-
Requires-Dist: mcp>=1.
|
|
29
|
+
Requires-Dist: mcp>=2.1.1; extra == 'claude-code-cli'
|
|
30
30
|
Provides-Extra: claude-code-sdk
|
|
31
|
-
Requires-Dist: claude-agent-sdk==0.
|
|
31
|
+
Requires-Dist: claude-agent-sdk==0.2.151; extra == 'claude-code-sdk'
|
|
32
32
|
Provides-Extra: codex
|
|
33
|
-
Requires-Dist: mcp>=1.
|
|
33
|
+
Requires-Dist: mcp>=2.1.1; extra == 'codex'
|
|
34
34
|
Description-Content-Type: text/markdown
|
|
35
35
|
|
|
36
36
|
[](https://pypi.org/project/nighthawk-python)
|
|
@@ -68,12 +68,12 @@ For other providers, see [Pydantic AI providers](https://kurusugawa-computer.git
|
|
|
68
68
|
```py
|
|
69
69
|
import nighthawk as nh
|
|
70
70
|
|
|
71
|
+
|
|
71
72
|
def python_average(numbers):
|
|
72
73
|
return sum(numbers) / len(numbers)
|
|
73
74
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
)
|
|
75
|
+
|
|
76
|
+
step_executor = nh.AgentStepExecutor.from_configuration(configuration=nh.StepExecutorConfiguration(model="openai-responses:gpt-5.4-nano"))
|
|
77
77
|
|
|
78
78
|
with nh.run(step_executor):
|
|
79
79
|
|
|
@@ -33,12 +33,12 @@ For other providers, see [Pydantic AI providers](https://kurusugawa-computer.git
|
|
|
33
33
|
```py
|
|
34
34
|
import nighthawk as nh
|
|
35
35
|
|
|
36
|
+
|
|
36
37
|
def python_average(numbers):
|
|
37
38
|
return sum(numbers) / len(numbers)
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
)
|
|
40
|
+
|
|
41
|
+
step_executor = nh.AgentStepExecutor.from_configuration(configuration=nh.StepExecutorConfiguration(model="openai-responses:gpt-5.4-nano"))
|
|
42
42
|
|
|
43
43
|
with nh.run(step_executor):
|
|
44
44
|
|
|
@@ -45,7 +45,7 @@ Coding agent backends do not receive native VLM-style user-prompt multimodal inp
|
|
|
45
45
|
- `BinaryContent` is staged to a local file and referenced in the prompt with `<image>` / `<file>` plus a local file path.
|
|
46
46
|
- URL-based content (`ImageUrl`, `AudioUrl`, `DocumentUrl`, `VideoUrl`) is referenced by URL in the projected prompt text.
|
|
47
47
|
- `UploadedFile` user-prompt content is rejected because these backends cannot resolve provider-owned file identifiers. Tool results still preserve the rest of the payload and replace only the `UploadedFile` item with explanatory fallback text.
|
|
48
|
-
- Tool results cross the backend boundary as MCP content. Image (`BinaryContent.is_image`) and audio (`BinaryContent.is_audio`) items travel natively as `ImageContent` / `AudioContent`; all other `BinaryContent` and `FileUrl` items project to `TextContent` so the MCP path stays symmetric with the text-projected prompt path. When mixed top-level text/multimodal tool-result sequences preserve their original order they satisfy the same `UserContent` rule used for prompt hoisting. The user prompt itself remains text-projected for these backends.
|
|
48
|
+
- Tool results cross the backend boundary as MCP content. Image (`BinaryContent.is_image`) and audio (`BinaryContent.is_audio`) items travel natively as `ImageContent` / `AudioContent`; all other `BinaryContent` and `FileUrl` items project to `TextContent` so the MCP path stays symmetric with the text-projected prompt path. Native MCP video carriage is intentionally unsupported for now because video-modal requirements are unstable; document carriage also remains text-projected unless a future backend advertises an explicit capability. When mixed top-level text/multimodal tool-result sequences preserve their original order they satisfy the same `UserContent` rule used for prompt hoisting. The user prompt itself remains text-projected for these backends.
|
|
49
49
|
- When a successful tool result splits to no text and no user content, the projected prompt stays header-only rather than inserting the preview JSON envelope.
|
|
50
50
|
|
|
51
51
|
This differs from provider-backed executors that can pass a `tuple[UserContent, ...]` to a multimodal-capable provider API directly.
|
|
@@ -281,6 +281,7 @@ Example Natural function that invokes the skill:
|
|
|
281
281
|
```py
|
|
282
282
|
import nighthawk as nh
|
|
283
283
|
|
|
284
|
+
|
|
284
285
|
@nh.natural_function
|
|
285
286
|
def summarize_feedback(feedback_items: list[str]) -> str:
|
|
286
287
|
def group_feedback_by_topic(feedback_items: list[str]) -> dict[str, list[str]]:
|
|
@@ -14,12 +14,12 @@ The same mechanism handles lightweight LLM judgments ("classify this sentiment")
|
|
|
14
14
|
```py
|
|
15
15
|
import nighthawk as nh
|
|
16
16
|
|
|
17
|
+
|
|
17
18
|
def python_average(numbers):
|
|
18
19
|
return sum(numbers) / len(numbers)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
)
|
|
21
|
+
|
|
22
|
+
step_executor = nh.AgentStepExecutor.from_configuration(configuration=nh.StepExecutorConfiguration(model="openai-responses:gpt-5.4-nano"))
|
|
23
23
|
|
|
24
24
|
with nh.run(step_executor):
|
|
25
25
|
|
|
@@ -61,7 +61,7 @@ The LLM interprets the mixed-format input list via `<numbers>`, calls the Python
|
|
|
61
61
|
|
|
62
62
|
## Background and project
|
|
63
63
|
|
|
64
|
-
- **[Philosophy](philosophy.md)** -- Design rationale: execution model,
|
|
64
|
+
- **[Philosophy](philosophy.md)** -- Design rationale: execution model, design landscape, boundary-first consequences, trust and safety boundaries, tradeoffs, and runtime evaluation rationale.
|
|
65
65
|
- **[Roadmap](roadmap.md)** -- Future directions and open questions.
|
|
66
66
|
|
|
67
67
|
## Origins
|
|
@@ -139,11 +139,13 @@ Write bindings can use Pydantic models for structured output with automatic vali
|
|
|
139
139
|
```py
|
|
140
140
|
from pydantic import BaseModel
|
|
141
141
|
|
|
142
|
+
|
|
142
143
|
class ReviewVerdict(BaseModel):
|
|
143
144
|
approved: bool
|
|
144
145
|
reason: str
|
|
145
146
|
risk_level: str
|
|
146
147
|
|
|
148
|
+
|
|
147
149
|
@nh.natural_function
|
|
148
150
|
def judge_review(review_data: str) -> ReviewVerdict:
|
|
149
151
|
verdict: ReviewVerdict
|
|
@@ -171,7 +173,21 @@ Transport depends on the executor:
|
|
|
171
173
|
- Provider-backed executors that accept Pydantic AI `UserContent` send the value natively to the VLM API.
|
|
172
174
|
- Coding-agent backends text-project the value (staged local files or URL references). See [Coding agent backends: multimodal inputs](coding-agent-backends.md#multimodal-inputs).
|
|
173
175
|
|
|
174
|
-
|
|
176
|
+
Nested multimodal values inside dicts, models, and objects are not recursively hoisted. Keep the prompt boundary explicit by lifting the media-bearing leaf into a helper variable before the Natural block:
|
|
177
|
+
|
|
178
|
+
```py
|
|
179
|
+
def summarize_report(report: Report) -> str:
|
|
180
|
+
report_scan = report.attachments.scan_image
|
|
181
|
+
summary = ""
|
|
182
|
+
"""natural
|
|
183
|
+
Read <report_scan> and summarize the visual evidence in <:summary>.
|
|
184
|
+
"""
|
|
185
|
+
return summary
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
This keeps the media selection visible in ordinary Python, avoids accidental traversal of large object graphs, and lets normal LOCALS budgeting decide whether the helper variable is rendered.
|
|
189
|
+
|
|
190
|
+
For the normative rendering and transport rules, including dotted-reference hoisting and nested multimodal behavior, see [Specification Section 8.2](specification.md#82-locals-summary).
|
|
175
191
|
|
|
176
192
|
### f-string injection
|
|
177
193
|
|
|
@@ -180,6 +196,7 @@ Inline f-string blocks embed Python expressions directly into the Natural progra
|
|
|
180
196
|
```py
|
|
181
197
|
PROJECT_POLICY = ["safety-first", "concise-output", "cite-assumptions"]
|
|
182
198
|
|
|
199
|
+
|
|
183
200
|
@nh.natural_function
|
|
184
201
|
def choose_policy(post: str) -> str:
|
|
185
202
|
selected_policy = ""
|
|
@@ -219,10 +236,12 @@ Member accesses and function results work too:
|
|
|
219
236
|
```py
|
|
220
237
|
from pydantic import BaseModel
|
|
221
238
|
|
|
239
|
+
|
|
222
240
|
class Config(BaseModel):
|
|
223
241
|
max_length: int = 100
|
|
224
242
|
style: str = "formal"
|
|
225
243
|
|
|
244
|
+
|
|
226
245
|
@nh.natural_function
|
|
227
246
|
def generate(config: Config, topic: str) -> str:
|
|
228
247
|
output = ""
|
|
@@ -296,6 +315,7 @@ When a Natural block references a module-level name via `<name>`, it appears in
|
|
|
296
315
|
def python_average(numbers):
|
|
297
316
|
return sum(numbers) / len(numbers)
|
|
298
317
|
|
|
318
|
+
|
|
299
319
|
@nh.natural_function
|
|
300
320
|
def calculate_average(numbers):
|
|
301
321
|
"""natural
|
|
@@ -375,11 +395,13 @@ For deterministic functions (no Natural blocks), the boundary is at the function
|
|
|
375
395
|
```py
|
|
376
396
|
from pydantic import BaseModel
|
|
377
397
|
|
|
398
|
+
|
|
378
399
|
class ScoreInput(BaseModel):
|
|
379
400
|
base: int
|
|
380
401
|
bonus: int
|
|
381
402
|
multiplier: float = 1.0
|
|
382
403
|
|
|
404
|
+
|
|
383
405
|
def compute_score(score_input: ScoreInput) -> int:
|
|
384
406
|
return int((score_input.base + score_input.bonus) * score_input.multiplier)
|
|
385
407
|
```
|
|
@@ -392,11 +414,13 @@ For judgment-heavy functions (containing Natural blocks), the boundary moves *in
|
|
|
392
414
|
from pydantic import BaseModel
|
|
393
415
|
from nighthawk import JsonableValue
|
|
394
416
|
|
|
417
|
+
|
|
395
418
|
class ReviewVerdict(BaseModel):
|
|
396
419
|
approved: bool
|
|
397
420
|
reason: str
|
|
398
421
|
risk_level: str
|
|
399
422
|
|
|
423
|
+
|
|
400
424
|
@nh.natural_function
|
|
401
425
|
def judge_review(review_data: str | JsonableValue) -> ReviewVerdict:
|
|
402
426
|
verdict: ReviewVerdict
|
|
@@ -470,6 +494,7 @@ def get_feedback_summary(topic: str, max_items: int = 10) -> str:
|
|
|
470
494
|
items = fetch_feedback(topic=topic, limit=max_items)
|
|
471
495
|
return format_summary(items)
|
|
472
496
|
|
|
497
|
+
|
|
473
498
|
# Natural block sees only what it needs
|
|
474
499
|
@nh.natural_function
|
|
475
500
|
def analyze_feedback(topic: str) -> str:
|
|
@@ -117,6 +117,7 @@ def validate(data: str) -> str:
|
|
|
117
117
|
"""
|
|
118
118
|
return result
|
|
119
119
|
|
|
120
|
+
|
|
120
121
|
try:
|
|
121
122
|
validate("corrupted-input")
|
|
122
123
|
except nh.ExecutionError as e:
|
|
@@ -131,6 +132,7 @@ When a Natural block references exception types visible in step locals or global
|
|
|
131
132
|
class InputError(Exception):
|
|
132
133
|
pass
|
|
133
134
|
|
|
135
|
+
|
|
134
136
|
@nh.natural_function
|
|
135
137
|
def strict_validate(data: str) -> str:
|
|
136
138
|
result = ""
|
|
@@ -140,6 +142,7 @@ def strict_validate(data: str) -> str:
|
|
|
140
142
|
"""
|
|
141
143
|
return result
|
|
142
144
|
|
|
145
|
+
|
|
143
146
|
try:
|
|
144
147
|
strict_validate("bad")
|
|
145
148
|
except InputError as e:
|
|
@@ -165,6 +168,7 @@ async def summarize_async(text: str) -> str:
|
|
|
165
168
|
"""
|
|
166
169
|
return result
|
|
167
170
|
|
|
171
|
+
|
|
168
172
|
summary = await summarize_async("A long document about climate change...")
|
|
169
173
|
```
|
|
170
174
|
|
|
@@ -181,6 +185,7 @@ async def fetch_data(query: str) -> list[str]:
|
|
|
181
185
|
"""Fetch data matching the query from an external API."""
|
|
182
186
|
...
|
|
183
187
|
|
|
188
|
+
|
|
184
189
|
@nh.natural_function
|
|
185
190
|
async def analyze(query: str) -> str:
|
|
186
191
|
result = ""
|
|
@@ -199,6 +204,7 @@ Async natural functions are ordinary coroutines, so you can run multiple Natural
|
|
|
199
204
|
```py
|
|
200
205
|
import asyncio
|
|
201
206
|
|
|
207
|
+
|
|
202
208
|
@nh.natural_function
|
|
203
209
|
async def classify(text: str) -> str:
|
|
204
210
|
label: str = ""
|
|
@@ -207,6 +213,7 @@ async def classify(text: str) -> str:
|
|
|
207
213
|
"""
|
|
208
214
|
return label
|
|
209
215
|
|
|
216
|
+
|
|
210
217
|
async def classify_batch(texts: list[str]) -> list[str]:
|
|
211
218
|
return list(await asyncio.gather(*(classify(t) for t in texts)))
|
|
212
219
|
```
|
|
@@ -242,6 +249,7 @@ def step_1(carry: list[str]) -> int:
|
|
|
242
249
|
"""
|
|
243
250
|
return result
|
|
244
251
|
|
|
252
|
+
|
|
245
253
|
@nh.natural_function
|
|
246
254
|
def step_2(carry: list[str]) -> int:
|
|
247
255
|
result = 0
|
|
@@ -253,9 +261,10 @@ def step_2(carry: list[str]) -> int:
|
|
|
253
261
|
"""
|
|
254
262
|
return result
|
|
255
263
|
|
|
264
|
+
|
|
256
265
|
carry: list[str] = []
|
|
257
|
-
r1 = step_1(carry)
|
|
258
|
-
r2 = step_2(carry)
|
|
266
|
+
r1 = step_1(carry) # carry now has 1 entry
|
|
267
|
+
r2 = step_2(carry) # carry now has 2 entries
|
|
259
268
|
```
|
|
260
269
|
|
|
261
270
|
Any mutable object works -- `list`, `dict`, Pydantic models, custom classes.
|
|
@@ -293,7 +302,7 @@ seed_step(carry)
|
|
|
293
302
|
carry_a = carry.copy()
|
|
294
303
|
carry_b = carry.copy()
|
|
295
304
|
|
|
296
|
-
result_a = branch_add(carry_a)
|
|
305
|
+
result_a = branch_add(carry_a) # diverges from here
|
|
297
306
|
result_b = branch_multiply(carry_b) # independent path
|
|
298
307
|
```
|
|
299
308
|
|
|
@@ -443,9 +452,11 @@ For monetary budgets, supply a `cost_function` that converts `RunUsage` to a flo
|
|
|
443
452
|
```py
|
|
444
453
|
from pydantic_ai.usage import RunUsage
|
|
445
454
|
|
|
455
|
+
|
|
446
456
|
def dollar_cost(usage: RunUsage) -> float:
|
|
447
457
|
return usage.input_tokens * 3e-6 + usage.output_tokens * 15e-6
|
|
448
458
|
|
|
459
|
+
|
|
449
460
|
budgeted = budget(cost=1.00, cost_function=dollar_cost)(classify)
|
|
450
461
|
```
|
|
451
462
|
|
|
@@ -459,7 +470,7 @@ Prevent repeated calls to a failing service. After `fail_threshold` consecutive
|
|
|
459
470
|
from nighthawk.resilience import circuit_breaker, CircuitState
|
|
460
471
|
|
|
461
472
|
protected_api = circuit_breaker(fail_threshold=5, reset_timeout=60)(call_api)
|
|
462
|
-
protected_api.state
|
|
473
|
+
protected_api.state # CircuitState.CLOSED
|
|
463
474
|
protected_api.reset() # manual reset
|
|
464
475
|
```
|
|
465
476
|
|
|
@@ -471,11 +482,11 @@ All transformers produce callables with the original signature, so they compose
|
|
|
471
482
|
|
|
472
483
|
```py
|
|
473
484
|
robust_classify = fallback(
|
|
474
|
-
retrying(attempts=2)(
|
|
475
|
-
vote(count=3)(classify_gpt4)
|
|
476
|
-
),
|
|
477
|
-
retrying(attempts=2)(classify_mini),
|
|
478
|
-
default="unknown",
|
|
485
|
+
retrying(attempts=2)( # 3. Retry the voted call
|
|
486
|
+
vote(count=3)(classify_gpt4) # 2. Vote 3x with GPT-4
|
|
487
|
+
), # 1. Try GPT-4 first
|
|
488
|
+
retrying(attempts=2)(classify_mini), # 4. Fall back to mini
|
|
489
|
+
default="unknown", # 5. Last resort
|
|
479
490
|
)
|
|
480
491
|
|
|
481
492
|
result = robust_classify(text)
|
|
@@ -499,6 +510,7 @@ Recommended composition order (innermost to outermost):
|
|
|
499
510
|
```py
|
|
500
511
|
from functools import lru_cache
|
|
501
512
|
|
|
513
|
+
|
|
502
514
|
@lru_cache(maxsize=256)
|
|
503
515
|
@nh.natural_function
|
|
504
516
|
def classify(text: str) -> str:
|