handoffkit 0.6.0__tar.gz → 0.7.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.
- {handoffkit-0.6.0/handoffkit.egg-info → handoffkit-0.7.0}/PKG-INFO +124 -5
- {handoffkit-0.6.0 → handoffkit-0.7.0}/README.md +123 -4
- handoffkit-0.7.0/examples/contract_validation_demo.py +85 -0
- handoffkit-0.7.0/examples/handoff_quality_demo.py +46 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/provider_tool_adapter_demo.py +1 -0
- handoffkit-0.7.0/examples/provider_tool_formats_demo.py +98 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/__init__.py +23 -1
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/agent.py +21 -7
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/cli.py +113 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/handoff.py +9 -18
- handoffkit-0.7.0/handoffkit/provider_adapters.py +260 -0
- handoffkit-0.7.0/handoffkit/quality.py +277 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/structured.py +6 -0
- handoffkit-0.7.0/handoffkit/validation.py +322 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0/handoffkit.egg-info}/PKG-INFO +124 -5
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit.egg-info/SOURCES.txt +9 -1
- {handoffkit-0.6.0 → handoffkit-0.7.0}/pyproject.toml +1 -1
- handoffkit-0.7.0/tests/test_070_examples.py +49 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_cli.py +37 -1
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_imports.py +18 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_provider_adapters.py +98 -1
- handoffkit-0.7.0/tests/test_quality.py +63 -0
- handoffkit-0.7.0/tests/test_validation.py +97 -0
- handoffkit-0.6.0/handoffkit/provider_adapters.py +0 -134
- {handoffkit-0.6.0 → handoffkit-0.7.0}/LICENSE +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/MANIFEST.in +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/coding_review_recipe.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/coding_team.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/context_handoff_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/extension_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/fake_provider_tool_call_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/freemodel_best_model_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/freemodel_coding_team.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/freemodel_openai_compatible.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/handoff_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/memory_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/ollama_agent.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/project_context_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/real_task_calculator.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/recipe_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/repo_inspired_protocols.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/simple_agent.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/structured_output_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/structured_recipe_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/tool_agent.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/tool_execution_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/examples/tool_schema_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/builtins.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/context.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/errors.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/extensions.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/memory.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocol.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocols/__init__.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocols/compressed.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocols/hybrid_min.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocols/hybrid_state.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/protocols/natural.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/__init__.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/base.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/echo_provider.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/ollama_provider.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/openai_compatible.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/providers/openai_provider.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/py.typed +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/recipes.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/runner.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/safety.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/schemas.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tool.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tool_execution.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tools/__init__.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tools/filesystem.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tools/shell.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit/tools/text.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit.egg-info/dependency_links.txt +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit.egg-info/entry_points.txt +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit.egg-info/requires.txt +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/handoffkit.egg-info/top_level.txt +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/setup.cfg +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_agent.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_coding_team.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_contract_validation.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_handoff.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_memory_context.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_ollama_provider_mock.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_openai_model_selection.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_openai_provider_mock.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_protocols.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_real_task_demo.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_recipes_extensions.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_shell_safety.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_structured_outputs.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_team.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_tool.py +0 -0
- {handoffkit-0.6.0 → handoffkit-0.7.0}/tests/test_tool_execution.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: handoffkit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: A lightweight Python framework for building multi-agent workflows with structured state transfer protocols.
|
|
5
5
|
Author: DaosPath
|
|
6
6
|
License-Expression: MIT
|
|
@@ -80,6 +80,21 @@ Tester
|
|
|
80
80
|
|
|
81
81
|
That makes agent workflows easier to inspect, test, replay, and improve.
|
|
82
82
|
|
|
83
|
+
## What 0.7.0 Adds
|
|
84
|
+
|
|
85
|
+
HandoffKit 0.7.0 adds reusable validation and quality layers for mature
|
|
86
|
+
multi-agent workflows:
|
|
87
|
+
|
|
88
|
+
- `ValidationIssue` and `ValidationReport` for reusable contract reports,
|
|
89
|
+
- `HandoffStateValidator`, `StructuredOutputValidator`, and `ToolSchemaValidator`,
|
|
90
|
+
- `validate_report(...)` helpers while keeping existing `validate()` behavior,
|
|
91
|
+
- `HandoffQualityEvaluator` with deterministic handoff quality scoring,
|
|
92
|
+
- quality metrics for completeness, clarity, actionability, traceability, and error awareness,
|
|
93
|
+
- provider-native tool schemas for `handoffkit`, `openai`, and `anthropic`,
|
|
94
|
+
- provider-style tool call parsing with call ID preservation,
|
|
95
|
+
- `provider_adapter` support in `Agent.run_with_tools(...)`,
|
|
96
|
+
- offline quality, validator, and provider-format demos with JSON/Markdown reports.
|
|
97
|
+
|
|
83
98
|
## What 0.6.0 Adds
|
|
84
99
|
|
|
85
100
|
HandoffKit 0.6.0 adds structured outputs and provider-neutral tool adapters:
|
|
@@ -353,6 +368,105 @@ whether a provider returns HandoffKit JSON or function-style tool calls. This is
|
|
|
353
368
|
provider-neutral by design; HandoffKit does not require any external SDK for
|
|
354
369
|
normal tests.
|
|
355
370
|
|
|
371
|
+
## Handoff Quality Metrics
|
|
372
|
+
|
|
373
|
+
Quality checks are deterministic and offline. They help compare handoff states
|
|
374
|
+
without calling a provider or judging prose with another model.
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
from handoffkit import HandoffQualityEvaluator, HandoffState
|
|
378
|
+
|
|
379
|
+
state = HandoffState(
|
|
380
|
+
task="Create a Python CLI calculator.",
|
|
381
|
+
from_agent="Architect",
|
|
382
|
+
to_agent="Coder",
|
|
383
|
+
summary="Build an argparse calculator with pure operations and tests.",
|
|
384
|
+
decisions=["Use argparse.", "Keep operations pure."],
|
|
385
|
+
important_files=["calculator.py", "test_calculator.py"],
|
|
386
|
+
next_steps=["Implement calculator.py", "Run pytest -q"],
|
|
387
|
+
context_refs=["README.md#real-task-demo"],
|
|
388
|
+
metadata={"errors_checked": True},
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
report = HandoffQualityEvaluator().evaluate(state)
|
|
392
|
+
print(report.score, report.grade)
|
|
393
|
+
print(report.to_markdown())
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
Metrics:
|
|
397
|
+
|
|
398
|
+
| Metric | Checks |
|
|
399
|
+
|---|---|
|
|
400
|
+
| `completeness` | required fields, summary, decisions, files, context, next steps |
|
|
401
|
+
| `clarity` | useful summary length and non-empty decisions |
|
|
402
|
+
| `actionability` | next steps written as concrete actions |
|
|
403
|
+
| `traceability` | important files and context references |
|
|
404
|
+
| `error_awareness` | recorded errors or explicit error-check metadata |
|
|
405
|
+
|
|
406
|
+
## Contract Validators
|
|
407
|
+
|
|
408
|
+
Validators return `ValidationReport` objects that can be serialized, rendered as
|
|
409
|
+
Markdown, or raised as exceptions. Existing APIs stay compatible:
|
|
410
|
+
`HandoffState.validate()` still returns `self` or raises `HandoffValidationError`,
|
|
411
|
+
and `StructuredOutputSchema.validate()` still returns the input dictionary or
|
|
412
|
+
raises `OutputValidationError`.
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
from handoffkit import (
|
|
416
|
+
HandoffStateValidator,
|
|
417
|
+
StructuredOutputSchema,
|
|
418
|
+
StructuredOutputValidator,
|
|
419
|
+
ToolSchemaValidator,
|
|
420
|
+
tool,
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
@tool
|
|
424
|
+
def add(a: int, b: int) -> int:
|
|
425
|
+
"""Add two integers."""
|
|
426
|
+
return a + b
|
|
427
|
+
|
|
428
|
+
schema = StructuredOutputSchema(
|
|
429
|
+
name="Plan",
|
|
430
|
+
fields={"title": str, "ready": bool},
|
|
431
|
+
required=["title", "ready"],
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
print(StructuredOutputValidator().validate(schema, {"title": "Demo", "ready": True}).to_json())
|
|
435
|
+
print(ToolSchemaValidator().validate(add).to_markdown())
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Provider Tool Formats
|
|
439
|
+
|
|
440
|
+
`ProviderToolAdapter` can render tools as HandoffKit, OpenAI function-style, or
|
|
441
|
+
Anthropic `tool_use` schemas. It also parses provider-native tool call payloads
|
|
442
|
+
back into HandoffKit `ToolCall` objects.
|
|
443
|
+
|
|
444
|
+
```python
|
|
445
|
+
from handoffkit import ProviderToolAdapter, tool
|
|
446
|
+
|
|
447
|
+
@tool
|
|
448
|
+
def read_file(path: str) -> str:
|
|
449
|
+
"""Read a file."""
|
|
450
|
+
return f"read:{path}"
|
|
451
|
+
|
|
452
|
+
openai_adapter = ProviderToolAdapter(provider_format="openai")
|
|
453
|
+
anthropic_adapter = ProviderToolAdapter(provider_format="anthropic")
|
|
454
|
+
|
|
455
|
+
print(openai_adapter.tools_to_provider_format([read_file]))
|
|
456
|
+
print(anthropic_adapter.tools_to_provider_format([read_file]))
|
|
457
|
+
|
|
458
|
+
calls = openai_adapter.parse_tool_calls({
|
|
459
|
+
"tool_calls": [
|
|
460
|
+
{
|
|
461
|
+
"id": "call_123",
|
|
462
|
+
"type": "function",
|
|
463
|
+
"function": {"name": "read_file", "arguments": "{\"path\":\"README.md\"}"},
|
|
464
|
+
}
|
|
465
|
+
]
|
|
466
|
+
})
|
|
467
|
+
print(calls[0].tool_name, calls[0].arguments, calls[0].call_id)
|
|
468
|
+
```
|
|
469
|
+
|
|
356
470
|
## Memory + Project Context
|
|
357
471
|
|
|
358
472
|
HandoffKit can store durable project decisions, index local files, retrieve
|
|
@@ -559,6 +673,9 @@ handoffkit demo-recipe
|
|
|
559
673
|
handoffkit demo-extension
|
|
560
674
|
handoffkit demo-structured
|
|
561
675
|
handoffkit demo-provider-tools
|
|
676
|
+
handoffkit demo-quality
|
|
677
|
+
handoffkit demo-validators
|
|
678
|
+
handoffkit demo-provider-formats
|
|
562
679
|
```
|
|
563
680
|
|
|
564
681
|
## Examples
|
|
@@ -572,6 +689,9 @@ python examples/tool_execution_demo.py
|
|
|
572
689
|
python examples/fake_provider_tool_call_demo.py
|
|
573
690
|
python examples/structured_output_demo.py
|
|
574
691
|
python examples/provider_tool_adapter_demo.py
|
|
692
|
+
python examples/handoff_quality_demo.py
|
|
693
|
+
python examples/contract_validation_demo.py
|
|
694
|
+
python examples/provider_tool_formats_demo.py
|
|
575
695
|
python examples/structured_recipe_demo.py
|
|
576
696
|
python examples/recipe_demo.py
|
|
577
697
|
python examples/coding_review_recipe.py
|
|
@@ -631,11 +751,10 @@ HandoffKit is a developer library, not a copy of that repository.
|
|
|
631
751
|
|
|
632
752
|
## Roadmap
|
|
633
753
|
|
|
634
|
-
- richer
|
|
754
|
+
- richer memory persistence adapters,
|
|
635
755
|
- broader tool schema coverage,
|
|
636
|
-
- provider
|
|
637
|
-
-
|
|
638
|
-
- handoff quality metrics,
|
|
756
|
+
- provider-specific integration examples,
|
|
757
|
+
- benchmark-inspired quality comparisons,
|
|
639
758
|
- memory integrations,
|
|
640
759
|
- project context retrieval,
|
|
641
760
|
- benchmark-inspired examples,
|
|
@@ -51,6 +51,21 @@ Tester
|
|
|
51
51
|
|
|
52
52
|
That makes agent workflows easier to inspect, test, replay, and improve.
|
|
53
53
|
|
|
54
|
+
## What 0.7.0 Adds
|
|
55
|
+
|
|
56
|
+
HandoffKit 0.7.0 adds reusable validation and quality layers for mature
|
|
57
|
+
multi-agent workflows:
|
|
58
|
+
|
|
59
|
+
- `ValidationIssue` and `ValidationReport` for reusable contract reports,
|
|
60
|
+
- `HandoffStateValidator`, `StructuredOutputValidator`, and `ToolSchemaValidator`,
|
|
61
|
+
- `validate_report(...)` helpers while keeping existing `validate()` behavior,
|
|
62
|
+
- `HandoffQualityEvaluator` with deterministic handoff quality scoring,
|
|
63
|
+
- quality metrics for completeness, clarity, actionability, traceability, and error awareness,
|
|
64
|
+
- provider-native tool schemas for `handoffkit`, `openai`, and `anthropic`,
|
|
65
|
+
- provider-style tool call parsing with call ID preservation,
|
|
66
|
+
- `provider_adapter` support in `Agent.run_with_tools(...)`,
|
|
67
|
+
- offline quality, validator, and provider-format demos with JSON/Markdown reports.
|
|
68
|
+
|
|
54
69
|
## What 0.6.0 Adds
|
|
55
70
|
|
|
56
71
|
HandoffKit 0.6.0 adds structured outputs and provider-neutral tool adapters:
|
|
@@ -324,6 +339,105 @@ whether a provider returns HandoffKit JSON or function-style tool calls. This is
|
|
|
324
339
|
provider-neutral by design; HandoffKit does not require any external SDK for
|
|
325
340
|
normal tests.
|
|
326
341
|
|
|
342
|
+
## Handoff Quality Metrics
|
|
343
|
+
|
|
344
|
+
Quality checks are deterministic and offline. They help compare handoff states
|
|
345
|
+
without calling a provider or judging prose with another model.
|
|
346
|
+
|
|
347
|
+
```python
|
|
348
|
+
from handoffkit import HandoffQualityEvaluator, HandoffState
|
|
349
|
+
|
|
350
|
+
state = HandoffState(
|
|
351
|
+
task="Create a Python CLI calculator.",
|
|
352
|
+
from_agent="Architect",
|
|
353
|
+
to_agent="Coder",
|
|
354
|
+
summary="Build an argparse calculator with pure operations and tests.",
|
|
355
|
+
decisions=["Use argparse.", "Keep operations pure."],
|
|
356
|
+
important_files=["calculator.py", "test_calculator.py"],
|
|
357
|
+
next_steps=["Implement calculator.py", "Run pytest -q"],
|
|
358
|
+
context_refs=["README.md#real-task-demo"],
|
|
359
|
+
metadata={"errors_checked": True},
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
report = HandoffQualityEvaluator().evaluate(state)
|
|
363
|
+
print(report.score, report.grade)
|
|
364
|
+
print(report.to_markdown())
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Metrics:
|
|
368
|
+
|
|
369
|
+
| Metric | Checks |
|
|
370
|
+
|---|---|
|
|
371
|
+
| `completeness` | required fields, summary, decisions, files, context, next steps |
|
|
372
|
+
| `clarity` | useful summary length and non-empty decisions |
|
|
373
|
+
| `actionability` | next steps written as concrete actions |
|
|
374
|
+
| `traceability` | important files and context references |
|
|
375
|
+
| `error_awareness` | recorded errors or explicit error-check metadata |
|
|
376
|
+
|
|
377
|
+
## Contract Validators
|
|
378
|
+
|
|
379
|
+
Validators return `ValidationReport` objects that can be serialized, rendered as
|
|
380
|
+
Markdown, or raised as exceptions. Existing APIs stay compatible:
|
|
381
|
+
`HandoffState.validate()` still returns `self` or raises `HandoffValidationError`,
|
|
382
|
+
and `StructuredOutputSchema.validate()` still returns the input dictionary or
|
|
383
|
+
raises `OutputValidationError`.
|
|
384
|
+
|
|
385
|
+
```python
|
|
386
|
+
from handoffkit import (
|
|
387
|
+
HandoffStateValidator,
|
|
388
|
+
StructuredOutputSchema,
|
|
389
|
+
StructuredOutputValidator,
|
|
390
|
+
ToolSchemaValidator,
|
|
391
|
+
tool,
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
@tool
|
|
395
|
+
def add(a: int, b: int) -> int:
|
|
396
|
+
"""Add two integers."""
|
|
397
|
+
return a + b
|
|
398
|
+
|
|
399
|
+
schema = StructuredOutputSchema(
|
|
400
|
+
name="Plan",
|
|
401
|
+
fields={"title": str, "ready": bool},
|
|
402
|
+
required=["title", "ready"],
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
print(StructuredOutputValidator().validate(schema, {"title": "Demo", "ready": True}).to_json())
|
|
406
|
+
print(ToolSchemaValidator().validate(add).to_markdown())
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Provider Tool Formats
|
|
410
|
+
|
|
411
|
+
`ProviderToolAdapter` can render tools as HandoffKit, OpenAI function-style, or
|
|
412
|
+
Anthropic `tool_use` schemas. It also parses provider-native tool call payloads
|
|
413
|
+
back into HandoffKit `ToolCall` objects.
|
|
414
|
+
|
|
415
|
+
```python
|
|
416
|
+
from handoffkit import ProviderToolAdapter, tool
|
|
417
|
+
|
|
418
|
+
@tool
|
|
419
|
+
def read_file(path: str) -> str:
|
|
420
|
+
"""Read a file."""
|
|
421
|
+
return f"read:{path}"
|
|
422
|
+
|
|
423
|
+
openai_adapter = ProviderToolAdapter(provider_format="openai")
|
|
424
|
+
anthropic_adapter = ProviderToolAdapter(provider_format="anthropic")
|
|
425
|
+
|
|
426
|
+
print(openai_adapter.tools_to_provider_format([read_file]))
|
|
427
|
+
print(anthropic_adapter.tools_to_provider_format([read_file]))
|
|
428
|
+
|
|
429
|
+
calls = openai_adapter.parse_tool_calls({
|
|
430
|
+
"tool_calls": [
|
|
431
|
+
{
|
|
432
|
+
"id": "call_123",
|
|
433
|
+
"type": "function",
|
|
434
|
+
"function": {"name": "read_file", "arguments": "{\"path\":\"README.md\"}"},
|
|
435
|
+
}
|
|
436
|
+
]
|
|
437
|
+
})
|
|
438
|
+
print(calls[0].tool_name, calls[0].arguments, calls[0].call_id)
|
|
439
|
+
```
|
|
440
|
+
|
|
327
441
|
## Memory + Project Context
|
|
328
442
|
|
|
329
443
|
HandoffKit can store durable project decisions, index local files, retrieve
|
|
@@ -530,6 +644,9 @@ handoffkit demo-recipe
|
|
|
530
644
|
handoffkit demo-extension
|
|
531
645
|
handoffkit demo-structured
|
|
532
646
|
handoffkit demo-provider-tools
|
|
647
|
+
handoffkit demo-quality
|
|
648
|
+
handoffkit demo-validators
|
|
649
|
+
handoffkit demo-provider-formats
|
|
533
650
|
```
|
|
534
651
|
|
|
535
652
|
## Examples
|
|
@@ -543,6 +660,9 @@ python examples/tool_execution_demo.py
|
|
|
543
660
|
python examples/fake_provider_tool_call_demo.py
|
|
544
661
|
python examples/structured_output_demo.py
|
|
545
662
|
python examples/provider_tool_adapter_demo.py
|
|
663
|
+
python examples/handoff_quality_demo.py
|
|
664
|
+
python examples/contract_validation_demo.py
|
|
665
|
+
python examples/provider_tool_formats_demo.py
|
|
546
666
|
python examples/structured_recipe_demo.py
|
|
547
667
|
python examples/recipe_demo.py
|
|
548
668
|
python examples/coding_review_recipe.py
|
|
@@ -602,11 +722,10 @@ HandoffKit is a developer library, not a copy of that repository.
|
|
|
602
722
|
|
|
603
723
|
## Roadmap
|
|
604
724
|
|
|
605
|
-
- richer
|
|
725
|
+
- richer memory persistence adapters,
|
|
606
726
|
- broader tool schema coverage,
|
|
607
|
-
- provider
|
|
608
|
-
-
|
|
609
|
-
- handoff quality metrics,
|
|
727
|
+
- provider-specific integration examples,
|
|
728
|
+
- benchmark-inspired quality comparisons,
|
|
610
729
|
- memory integrations,
|
|
611
730
|
- project context retrieval,
|
|
612
731
|
- benchmark-inspired examples,
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Contract validation demo without external APIs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from handoffkit import (
|
|
10
|
+
HandoffState,
|
|
11
|
+
HandoffStateValidator,
|
|
12
|
+
StructuredOutputSchema,
|
|
13
|
+
StructuredOutputValidator,
|
|
14
|
+
ToolSchemaValidator,
|
|
15
|
+
tool,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
19
|
+
REPORTS_DIR = ROOT / "reports"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@tool
|
|
23
|
+
def add(a: int, b: int) -> int:
|
|
24
|
+
"""Add two integers."""
|
|
25
|
+
return a + b
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def build_state() -> HandoffState:
|
|
29
|
+
"""Build a deterministic valid handoff state."""
|
|
30
|
+
return HandoffState(
|
|
31
|
+
task="Create a Python CLI calculator.",
|
|
32
|
+
from_agent="Architect",
|
|
33
|
+
to_agent="Coder",
|
|
34
|
+
summary="Architecture plan is ready for implementation.",
|
|
35
|
+
decisions=["Use argparse.", "Return non-zero on invalid input."],
|
|
36
|
+
important_files=["calculator.py"],
|
|
37
|
+
next_steps=["Implement calculator.py", "Run pytest -q"],
|
|
38
|
+
context_refs=["docs/calculator-contract.md"],
|
|
39
|
+
metadata={"errors_checked": True},
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _json_default(value: Any) -> Any:
|
|
44
|
+
if hasattr(value, "to_dict"):
|
|
45
|
+
return value.to_dict()
|
|
46
|
+
return str(value)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def main() -> None:
|
|
50
|
+
"""Validate handoff, structured output, and tool contracts."""
|
|
51
|
+
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
52
|
+
schema = StructuredOutputSchema(
|
|
53
|
+
name="CalculatorPlan",
|
|
54
|
+
fields={"title": str, "steps": list, "ready": bool},
|
|
55
|
+
required=["title", "steps", "ready"],
|
|
56
|
+
)
|
|
57
|
+
reports = {
|
|
58
|
+
"handoff": HandoffStateValidator().validate(build_state()),
|
|
59
|
+
"structured_output": StructuredOutputValidator().validate(
|
|
60
|
+
schema,
|
|
61
|
+
{"title": "CLI calculator", "steps": ["implement", "test"], "ready": True},
|
|
62
|
+
),
|
|
63
|
+
"tool_schema": ToolSchemaValidator().validate(add),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
markdown = "# Contract Validation Demo\n\n" + "\n\n".join(
|
|
67
|
+
f"## {name}\n\n{report.to_markdown()}" for name, report in reports.items()
|
|
68
|
+
)
|
|
69
|
+
payload = {name: report.to_dict() for name, report in reports.items()}
|
|
70
|
+
|
|
71
|
+
markdown_path = REPORTS_DIR / "contract_validation_demo.md"
|
|
72
|
+
json_path = REPORTS_DIR / "contract_validation_demo.json"
|
|
73
|
+
markdown_path.write_text(markdown, encoding="utf-8")
|
|
74
|
+
json_text = json.dumps(payload, ensure_ascii=False, indent=2, default=_json_default)
|
|
75
|
+
json_path.write_text(json_text, encoding="utf-8")
|
|
76
|
+
|
|
77
|
+
print(f"Handoff validation: {reports['handoff'].success}")
|
|
78
|
+
print(f"Structured output validation: {reports['structured_output'].success}")
|
|
79
|
+
print(f"Tool schema validation: {reports['tool_schema'].success}")
|
|
80
|
+
print(f"Markdown report: {markdown_path}")
|
|
81
|
+
print(f"JSON report: {json_path}")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__ == "__main__":
|
|
85
|
+
main()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Handoff quality metrics demo without external APIs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from handoffkit import HandoffQualityEvaluator, HandoffState
|
|
8
|
+
|
|
9
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
10
|
+
REPORTS_DIR = ROOT / "reports"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_state() -> HandoffState:
|
|
14
|
+
"""Build a deterministic handoff state for quality scoring."""
|
|
15
|
+
return HandoffState(
|
|
16
|
+
task="Create a Python CLI calculator.",
|
|
17
|
+
from_agent="Architect",
|
|
18
|
+
to_agent="Coder",
|
|
19
|
+
summary="Build a dependency-free argparse calculator with pure operations and tests.",
|
|
20
|
+
decisions=["Use argparse for CLI parsing.", "Keep math operations in pure functions."],
|
|
21
|
+
important_files=["calculator.py", "test_calculator.py"],
|
|
22
|
+
next_steps=["Implement calculator.py", "Write pytest coverage", "Run pytest -q"],
|
|
23
|
+
context_refs=["README.md#real-task-demo"],
|
|
24
|
+
metadata={"errors_checked": True},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main() -> None:
|
|
29
|
+
"""Evaluate one handoff state and write Markdown/JSON reports."""
|
|
30
|
+
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
31
|
+
report = HandoffQualityEvaluator().evaluate(build_state())
|
|
32
|
+
|
|
33
|
+
markdown_path = REPORTS_DIR / "handoff_quality_demo.md"
|
|
34
|
+
json_path = REPORTS_DIR / "handoff_quality_demo.json"
|
|
35
|
+
markdown_path.write_text(report.to_markdown(), encoding="utf-8")
|
|
36
|
+
json_path.write_text(report.to_json(), encoding="utf-8")
|
|
37
|
+
|
|
38
|
+
print(f"Handoff quality success: {report.success}")
|
|
39
|
+
print(f"Score: {report.score:.3f}")
|
|
40
|
+
print(f"Grade: {report.grade}")
|
|
41
|
+
print(f"Markdown report: {markdown_path}")
|
|
42
|
+
print(f"JSON report: {json_path}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
main()
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Provider tool format demo without external APIs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from handoffkit import ProviderToolAdapter, tool
|
|
10
|
+
|
|
11
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
12
|
+
REPORTS_DIR = ROOT / "reports"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@tool
|
|
16
|
+
def add(a: int, b: int) -> int:
|
|
17
|
+
"""Add two integers."""
|
|
18
|
+
return a + b
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _json_default(value: Any) -> Any:
|
|
22
|
+
if hasattr(value, "to_dict"):
|
|
23
|
+
return value.to_dict()
|
|
24
|
+
return str(value)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def main() -> None:
|
|
28
|
+
"""Render provider-native tool schemas and parse provider tool calls."""
|
|
29
|
+
REPORTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
handoffkit_adapter = ProviderToolAdapter(provider_format="handoffkit")
|
|
31
|
+
openai_adapter = ProviderToolAdapter(provider_format="openai")
|
|
32
|
+
anthropic_adapter = ProviderToolAdapter(provider_format="anthropic")
|
|
33
|
+
|
|
34
|
+
schemas = {
|
|
35
|
+
"handoffkit": handoffkit_adapter.tools_to_provider_format([add]),
|
|
36
|
+
"openai": openai_adapter.tools_to_provider_format([add]),
|
|
37
|
+
"anthropic": anthropic_adapter.tools_to_provider_format([add]),
|
|
38
|
+
}
|
|
39
|
+
parsed_calls = {
|
|
40
|
+
"openai": [
|
|
41
|
+
call.to_dict()
|
|
42
|
+
for call in openai_adapter.parse_tool_calls(
|
|
43
|
+
{
|
|
44
|
+
"tool_calls": [
|
|
45
|
+
{
|
|
46
|
+
"id": "call_openai_1",
|
|
47
|
+
"type": "function",
|
|
48
|
+
"function": {"name": "add", "arguments": '{"a":2,"b":3}'},
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
],
|
|
54
|
+
"anthropic": [
|
|
55
|
+
call.to_dict()
|
|
56
|
+
for call in anthropic_adapter.parse_tool_calls(
|
|
57
|
+
{
|
|
58
|
+
"content": [
|
|
59
|
+
{
|
|
60
|
+
"type": "tool_use",
|
|
61
|
+
"id": "toolu_1",
|
|
62
|
+
"name": "add",
|
|
63
|
+
"input": {"a": 5, "b": 8},
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
],
|
|
69
|
+
}
|
|
70
|
+
payload = {"schemas": schemas, "parsed_calls": parsed_calls}
|
|
71
|
+
parsed_json = json.dumps(parsed_calls, ensure_ascii=False, indent=2)
|
|
72
|
+
markdown = (
|
|
73
|
+
"# Provider Tool Formats Demo\n\n"
|
|
74
|
+
"## Formats\n\n"
|
|
75
|
+
f"- handoffkit tools: {len(schemas['handoffkit'])}\n"
|
|
76
|
+
f"- openai tools: {len(schemas['openai'])}\n"
|
|
77
|
+
f"- anthropic tools: {len(schemas['anthropic'])}\n\n"
|
|
78
|
+
"## Parsed Calls\n\n"
|
|
79
|
+
f"```json\n{parsed_json}\n```\n"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
markdown_path = REPORTS_DIR / "provider_tool_formats_demo.md"
|
|
83
|
+
json_path = REPORTS_DIR / "provider_tool_formats_demo.json"
|
|
84
|
+
markdown_path.write_text(markdown, encoding="utf-8")
|
|
85
|
+
json_text = json.dumps(payload, ensure_ascii=False, indent=2, default=_json_default)
|
|
86
|
+
json_path.write_text(json_text, encoding="utf-8")
|
|
87
|
+
|
|
88
|
+
print(f"HandoffKit format tools: {len(schemas['handoffkit'])}")
|
|
89
|
+
print(f"OpenAI format tools: {len(schemas['openai'])}")
|
|
90
|
+
print(f"Anthropic format tools: {len(schemas['anthropic'])}")
|
|
91
|
+
print(f"OpenAI call id: {parsed_calls['openai'][0]['call_id']}")
|
|
92
|
+
print(f"Anthropic call id: {parsed_calls['anthropic'][0]['call_id']}")
|
|
93
|
+
print(f"Markdown report: {markdown_path}")
|
|
94
|
+
print(f"JSON report: {json_path}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
if __name__ == "__main__":
|
|
98
|
+
main()
|
|
@@ -16,8 +16,14 @@ from handoffkit.protocol import HandoffProtocol
|
|
|
16
16
|
from handoffkit.provider_adapters import (
|
|
17
17
|
ProviderCapabilities,
|
|
18
18
|
ProviderToolAdapter,
|
|
19
|
+
ProviderToolFormat,
|
|
19
20
|
ToolCallParser,
|
|
20
21
|
)
|
|
22
|
+
from handoffkit.quality import (
|
|
23
|
+
HandoffQualityEvaluator,
|
|
24
|
+
HandoffQualityMetric,
|
|
25
|
+
HandoffQualityReport,
|
|
26
|
+
)
|
|
21
27
|
from handoffkit.recipes import (
|
|
22
28
|
Recipe,
|
|
23
29
|
RecipeRunner,
|
|
@@ -40,8 +46,15 @@ from handoffkit.tool_execution import (
|
|
|
40
46
|
ToolRegistry,
|
|
41
47
|
ToolResult,
|
|
42
48
|
)
|
|
49
|
+
from handoffkit.validation import (
|
|
50
|
+
HandoffStateValidator,
|
|
51
|
+
StructuredOutputValidator,
|
|
52
|
+
ToolSchemaValidator,
|
|
53
|
+
ValidationIssue,
|
|
54
|
+
ValidationReport,
|
|
55
|
+
)
|
|
43
56
|
|
|
44
|
-
__version__ = "0.
|
|
57
|
+
__version__ = "0.7.0"
|
|
45
58
|
|
|
46
59
|
__all__ = [
|
|
47
60
|
"Agent",
|
|
@@ -53,7 +66,11 @@ __all__ = [
|
|
|
53
66
|
"ExtensionRegistry",
|
|
54
67
|
"HandoffValidationError",
|
|
55
68
|
"HandoffProtocol",
|
|
69
|
+
"HandoffQualityEvaluator",
|
|
70
|
+
"HandoffQualityMetric",
|
|
71
|
+
"HandoffQualityReport",
|
|
56
72
|
"HandoffState",
|
|
73
|
+
"HandoffStateValidator",
|
|
57
74
|
"JsonMemoryStore",
|
|
58
75
|
"JsonOutputParser",
|
|
59
76
|
"MemoryItem",
|
|
@@ -64,12 +81,14 @@ __all__ = [
|
|
|
64
81
|
"ProjectIndexer",
|
|
65
82
|
"ProviderCapabilities",
|
|
66
83
|
"ProviderToolAdapter",
|
|
84
|
+
"ProviderToolFormat",
|
|
67
85
|
"Recipe",
|
|
68
86
|
"RecipeRunResult",
|
|
69
87
|
"RecipeRunner",
|
|
70
88
|
"RecipeStep",
|
|
71
89
|
"StructuredOutputResult",
|
|
72
90
|
"StructuredOutputSchema",
|
|
91
|
+
"StructuredOutputValidator",
|
|
73
92
|
"Team",
|
|
74
93
|
"TeamRunResult",
|
|
75
94
|
"Tool",
|
|
@@ -78,6 +97,9 @@ __all__ = [
|
|
|
78
97
|
"ToolExecutionReport",
|
|
79
98
|
"ToolRegistry",
|
|
80
99
|
"ToolResult",
|
|
100
|
+
"ToolSchemaValidator",
|
|
101
|
+
"ValidationIssue",
|
|
102
|
+
"ValidationReport",
|
|
81
103
|
"WorkflowTemplate",
|
|
82
104
|
"__version__",
|
|
83
105
|
"tool",
|