Claude Code Skill¶
This page contains a ready-to-use Claude Code skill for py-ai-toolkit. This skill is sufficient to teach any AI agent to write correct py-ai-toolkit code for the core patterns. It covers all public API methods with working examples, validation configs, workflow patterns, and common mistakes to avoid.
Setup¶
Create the file .claude/commands/py-ai-toolkit.md at the root of your project and paste the content below. Then invoke it with /py-ai-toolkit in Claude Code.
---
name: py-ai-toolkit
description: Load the py-ai-toolkit public API reference. Use when writing or reviewing code that uses py-ai-toolkit — covers imports, all method signatures, validation configs, workflow patterns, and common mistakes.
argument-hint: [topic]
allowed-tools: Read, Grep, Glob
---
You are helping a user work with **py-ai-toolkit**, a Python library for building LLM-driven applications. Use this reference to write correct, idiomatic code.
---
## Installation
```bash
uv add py-ai-toolkit
```
---
## Configuration
`PyAIToolkit` reads from environment variables by default:
| Variable | Description |
|---|---|
| `LLM_MODEL` | Model identifier (e.g. `gpt-4o`) |
| `LLM_API_KEY` | API key |
| `LLM_BASE_URL` | Base URL for the API |
| `EMBEDDING_MODEL` | Embedding model identifier |
| `LLM_REASONING_EFFORT` | `low`, `medium`, or `high` |
Or pass `LLMConfig` directly:
```python
from py_ai_toolkit import PyAIToolkit, LLMConfig
toolkit = PyAIToolkit(main_model_config=LLMConfig(model="gpt-4o", api_key="..."))
```
---
## Public API
### Top-level imports
```python
from py_ai_toolkit import (
PyAIToolkit, # Main class
LLMConfig, # Configuration model
CompletionResponse, # Response wrapper
BaseWorkflow, # Base class for custom workflows
WorkflowError, # Exception class for workflow errors
BaseIssue, # Base model for validation issues
Node, # grafo node (re-exported)
TreeExecutor, # grafo executor (re-exported)
Chunk, # grafo chunk (re-exported)
)
```
Validation configs are imported from:
```python
from py_ai_toolkit.core.domain.schemas import (
SingleShotValidationConfig,
ThresholdVotingValidationConfig,
KAheadVotingValidationConfig,
ValidationConfig, # Union type alias
)
```
---
### PyAIToolkit
#### Constructor
```python
toolkit = PyAIToolkit(
main_model_config: LLMConfig | None = None,
alternative_models_configs: list[LLMConfig] | None = None,
)
```
- `main_model_config`: Falls back to env vars if `None`.
- `alternative_models_configs`: When provided, `asend()` randomly selects from these for load balancing.
---
#### `chat()` — plain text response
```python
response = await toolkit.chat(
template="Explain {{ topic }} in one sentence.",
topic="machine learning",
)
print(response.content) # str
```
- `template`: inline string or path to a `.md` file
- `**kwargs`: Jinja2 template variables
- Returns `CompletionResponse` where `.content` is `str`
---
#### `asend()` — structured response
```python
from pydantic import BaseModel
class Purchase(BaseModel):
product: str
quantity: int
response = await toolkit.asend(
response_model=Purchase,
template="Extract purchase from: {{ message }}",
message="I want 5 apples",
)
print(response.content.product) # "apple"
print(response.content.quantity) # 5
```
- Returns `CompletionResponse[T]` where `.content` is an instance of `response_model`
- When `alternative_models_configs` is set, randomly picks one of them
---
#### `stream()` — streaming text
```python
async for chunk in toolkit.stream(
template="Write a story about {{ topic }}",
topic="space exploration",
):
print(chunk.content, end="", flush=True)
```
---
#### `embed()` — text embeddings
```python
response: EmbeddingResponse = await toolkit.embed("some text")
vector = response.embedding # list[float]
usage = response.usage # EmbeddingUsage
```
---
#### `embed_batch()` — batch text embeddings
```python
responses: list[EmbeddingResponse] = await toolkit.embed_batch(["text one", "text two"])
vectors = [r.embedding for r in responses]
```
- Embeds multiple texts in a single API request
- Returns one `EmbeddingResponse` per input, preserving order
- Usage is aggregated across all inputs (same on each response)
---
#### `run_task()` — structured response with validation and retry
```python
from py_ai_toolkit.core.domain.schemas import SingleShotValidationConfig
result = await toolkit.run_task(
template="Extract purchase from: {{ message }}",
response_model=Purchase,
kwargs=dict(message="I want 5 apples"),
config=SingleShotValidationConfig(
issues=["The identified purchase matches the user's request."],
),
echo=False, # set True for debug logging
)
print(result.product) # Direct instance, not wrapped in CompletionResponse
print(result.quantity)
```
- `kwargs` is a `dict`, not `**kwargs`
- Returns the `response_model` instance directly (not a `CompletionResponse`)
- Validation issues are each checked independently by an LLM-based validator
---
#### `inject_types()` — constrain field types at runtime
```python
from typing import Literal
FruitModel = toolkit.inject_types(
Purchase,
fields=[("product", Literal[("apple", "banana", "orange")])],
docstring="Purchase with constrained product choices",
)
```
---
#### `reduce_model_schema()` — compact schema string for prompts
```python
schema_str = toolkit.reduce_model_schema(Purchase, include_description=True)
```
---
### CompletionResponse
```python
class CompletionResponse(BaseModel, Generic[T]):
completion: ChatCompletion | ChatCompletionChunk # raw OpenAI object
content: str | T
@property
def response_model(self) -> T: ... # raises if content is a string
```
---
### Validation Configs
All configs accept `issues: list[str]` — validation criteria evaluated independently.
| Config | `count` | `required_ahead` | `max_retries` | Use case |
|---|---|---|---|---|
| `SingleShotValidationConfig` | 1 | 1 | 3 | Simple tasks |
| `ThresholdVotingValidationConfig` | 3 | 1 | 0 | Moderate confidence |
| `KAheadVotingValidationConfig` | 5 | 3 | 0 | High-stakes tasks |
`count` must always be odd. When validation fails and `max_retries > 0`, the task is re-run with failure reasoning injected into the prompt automatically.
---
### BaseWorkflow — custom workflow orchestration
```python
from py_ai_toolkit import PyAIToolkit, BaseWorkflow, WorkflowError
toolkit = PyAIToolkit()
workflow = BaseWorkflow(
ai_toolkit=toolkit,
error_class=WorkflowError,
echo=False,
)
```
#### `task()` — LLM call inside a workflow
```python
# Text
result: str = await workflow.task(template="...", **kwargs)
# Structured
result: Purchase = await workflow.task(
template="...",
response_model=Purchase,
**kwargs,
)
```
#### `create_task_tree()` — task + validation subtree
```python
executor = await workflow.create_task_tree(
template="Extract purchase: {{ message }}",
response_model=Purchase,
kwargs=dict(message="I want 5 apples"),
config=SingleShotValidationConfig(
issues=["The purchase matches the user's request"],
),
)
results = await executor.run()
purchase = results[0].output
```
#### `build_task_node()` — wraps a task tree in a single Node
```python
node = await workflow.build_task_node(
uuid="purchase_node",
template="...",
response_model=Purchase,
kwargs=dict(...),
config=SingleShotValidationConfig(...),
)
```
Use this to embed a validated task as a node inside a larger grafo graph.
---
### Building graphs with grafo
```python
from py_ai_toolkit import Node, TreeExecutor
# Sequential
node_a = Node[Purchase](
uuid="extract",
coroutine=workflow.task,
kwargs=dict(template="...", response_model=Purchase, message="..."),
)
node_b = Node[str](
uuid="summarize",
coroutine=workflow.task,
kwargs=dict(template="Summarize: {{ purchase }}", purchase=node_a.output),
)
await node_a.connect(node_b)
executor = TreeExecutor(uuid="my_workflow", roots=[node_a])
results = await executor.run()
# Parallel (multiple roots)
executor = TreeExecutor(uuid="parallel", roots=[node_a, node_b, node_c])
```
---
### Custom Workflow class
```python
from py_ai_toolkit import PyAIToolkit, BaseWorkflow, WorkflowError
from py_ai_toolkit.core.domain.schemas import SingleShotValidationConfig
class PurchaseWorkflow(BaseWorkflow):
async def run(self, message: str) -> Purchase:
executor = await self.create_task_tree(
template="Extract purchase: {{ message }}",
response_model=Purchase,
kwargs=dict(message=message),
config=SingleShotValidationConfig(
issues=["The purchase matches the user's request"],
),
)
results = await executor.run()
return results[0].output
toolkit = PyAIToolkit()
workflow = PurchaseWorkflow(ai_toolkit=toolkit, error_class=WorkflowError)
result = await workflow.run("I want 5 apples")
```
---
## Common mistakes to avoid
- **Wrong import path**: validation configs live in `py_ai_toolkit.core.domain.schemas`, NOT `interfaces`
- **Missing `await`**: `chat()`, `asend()`, `stream()`, `embed()`, `embed_batch()`, `run_task()`, `task()`, `create_task_tree()`, and `build_task_node()` are all `async`
- **`run_task` kwargs**: pass as `kwargs=dict(...)`, not `**kwargs`
- **`run_task` return**: returns the model instance directly, not a `CompletionResponse`
- **`asend` return**: returns `CompletionResponse[T]`; access the model via `.content`
- **`count` must be odd** in all validation configs