Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/getting-started/first-steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ class AppConfig(State):
max_retries: int = 3
```

State instances are immutable—use `.updated()` to create tweaked copies.
State instances are immutable—use `.updating()` to create tweaked copies.

```python
config = AppConfig(environment="staging")
updated = config.updated(max_retries=5)
updated = config.updating(max_retries=5)
```

## Model your data
Expand Down
2 changes: 1 addition & 1 deletion docs/getting-started/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Throughout the getting-started journey you will assemble:
## Core Concepts

1. **State management** – mutable-looking, immutable-under-the-hood `State` classes represent
configuration and runtime data. You update them with methods like `State.updated(...)` to keep
configuration and runtime data. You update them with methods like `State.updating(...)` to keep
histories, snapshots, and metrics consistent.
1. **Context scoping** – `ctx.scope(...)` activates a stack of `State` instances and disposables for
a logical unit of work, ensuring structured concurrency and clean teardown.
Expand Down
4 changes: 2 additions & 2 deletions docs/guides/AdvancedState.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ initial: Mutable = Mutable(
value=42,
)
# update one of the fields by creating a copy
updated: Mutable = initial.updated(identifier="post")
updated: Mutable = initial.updating(identifier="post")
# update initial state once more - this will be another copy
final: Mutable = initial.updated(value=21)
final: Mutable = initial.updating(value=21)

print("initial", initial)
print("updated", updated)
Expand Down
6 changes: 3 additions & 3 deletions docs/guides/BasicStageUsage.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ reliable pipelines with confidence.

- **Stage** – an async callable that produces or transforms multimodal content.
- **StageState** – an immutable snapshot holding context entries and the latest result. Always
return `state.updated(...)` instead of mutating in place.
return `state.updating(...)` instead of mutating in place.
- **MultimodalContent** – container for text, images, artifacts, and resources used as model inputs
or outputs.
- **`ctx.scope(...)`** – binds providers, disposables, and logging/metrics for the lifetime of your
Expand Down Expand Up @@ -189,7 +189,7 @@ async def merge_results(
) -> StageState:
successful = [branch for branch in branches if isinstance(branch, StageState)]
combined = MultimodalContent.of(*[state.result for state in successful])
return successful[0].updated(result=combined)
return successful[0].updating(result=combined)

concurrent_stage = Stage.concurrent(
Stage.completion("Analyze aspect A."),
Expand Down Expand Up @@ -292,7 +292,7 @@ from draive import MultimodalContent, StageState, stage
@stage
async def custom_processor(*, state: StageState) -> StageState:
processed = MultimodalContent.of(f"Processed: {state.result.as_string()}")
return state.updated(result=processed)
return state.updating(result=processed)
```

Any async function decorated with `@stage` gains the same API as built-in stages and can be mixed
Expand Down
8 changes: 4 additions & 4 deletions docs/guides/BasicUsage.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ async with ctx.scope( # prepare the new context
print("RESULT GPT 3.5 | temperature 0.4:", result)

# we can update the configuration to change any parameter for nested context
with ctx.updated(
with ctx.updating(
# we are updating the current context value instead of making a new one
# this allows to preserve other elements of the configuration
ctx.state(OpenAIResponsesConfig).updated(
ctx.state(OpenAIResponsesConfig).updating(
model="gpt-5",
),
):
Expand Down Expand Up @@ -135,8 +135,8 @@ async with ctx.scope( # prepare the context and see the execution metrics repor
text="Roses are red...",
)

with ctx.updated(
ctx.state(OpenAIResponsesConfig).updated(
with ctx.updating(
ctx.state(OpenAIResponsesConfig).updating(
model="gpt-5",
),
):
Expand Down
8 changes: 4 additions & 4 deletions docs/guides/Basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ properties and an appropriate `__init__` function will be generated as well. Add
types come with built-in validation. Each object is automatically validated during the
initialization ensuring proper types and values for each field. Both types are also immutable by
default - you will receive linting and runtime errors when trying to mutate those. To make a
mutation of an instance of either of those we can use a dedicated `updated` method which makes a
mutation of an instance of either of those we can use a dedicated `updating` method which makes a
copy on the fly and validates mutation as well. Let's have a look:

```python
Expand All @@ -32,7 +32,7 @@ basic_state: BasicState = BasicState(
# )

# prepare an update
updated_state: BasicState = basic_state.updated(
updated_state: BasicState = basic_state.updating(
value=21
) # value of `identifier` field won't change

Expand Down Expand Up @@ -150,7 +150,7 @@ def do_something_contextually() -> None:
```

What will be the current state is defined by the context of the execution. We can change it locally
by using another function from `ctx` called `updated` which allows us to update the state by copying
by using another function from `ctx` called `updating` which allows us to update the state by copying
the context and allowing to enter a new scope with it:

```python
Expand All @@ -160,7 +160,7 @@ async with ctx.scope("basics", basic_state):
do_something_contextually()

# then we can update it locally
with ctx.updated(basic_state.updated(identifier="updated")):
with ctx.updating(basic_state.updating(identifier="updated")):
print("Updated:")
# and access its updated version
do_something_contextually()
Expand Down
4 changes: 2 additions & 2 deletions llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ load_env()
```

## Core primitives
- `State` (from haiway): immutable runtime config/resources. Update via `.updated(...)`.
- `State` (from haiway): immutable runtime config/resources. Update via `.updating(...)`.
- `ctx.scope(label, *state, disposables=(...), observability=...)`: structured async context that activates states and disposables; nests safely.
- `Disposables`: async resources (e.g., `OpenAI()`, `QdrantClient()`) auto-cleaned on scope exit.
- `DataModel`: immutable, validated data containers with `.json_schema()` / `.simplified_schema()` and `.to_str()`.
Expand Down Expand Up @@ -189,5 +189,5 @@ all_ok = all(r.passed for r in results)
- Always run inside `ctx.scope`.
- Keep public APIs strictly typed (no implicit `Any`).
- Avoid blocking calls; everything I/O is async.
- Mutate state via `.updated(...)`; states and data models are immutable by design.
- Mutate state via `.updating(...)`; states and data models are immutable by design.
- Use `MultimodalContent` for any model input/output that mixes text/media/tools; use `.to_str()` for safe logging (binary redacted).
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "uv_build"
[project]
name = "draive"
description = "Framework designed to simplify and accelerate the development of LLM-based applications."
version = "0.98.0"
version = "0.99.0"
readme = "README.md"
maintainers = [
{ name = "Kacper Kaliński", email = "kacper.kalinski@miquido.com" },
Expand All @@ -24,7 +24,7 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Application Frameworks",
]
license = { file = "LICENSE" }
dependencies = ["numpy~=2.3", "haiway~=0.42.0"]
dependencies = ["numpy~=2.3", "haiway~=0.43.0"]

[project.urls]
Homepage = "https://miquido.com"
Expand Down
18 changes: 9 additions & 9 deletions src/draive/helpers/instruction_refinement.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ async def tree_initialization(
)

# Setup initial state
return state.updated(
return state.updating(
_RefinementState(
root=root_node,
nodes={root_node.identifier: root_node},
Expand Down Expand Up @@ -324,7 +324,7 @@ async def explore(
}

# Update the refinement state with the updated nodes
refinement_state = refinement_state.updated(nodes=updated_nodes)
refinement_state = refinement_state.updating(nodes=updated_nodes)

# Log tree statistics
total_nodes: int = len(refinement_state.nodes)
Expand All @@ -336,7 +336,7 @@ async def explore(
f"{active_nodes} active, {pruned_count} pruned"
)

return state.updated(refinement_state)
return state.updating(refinement_state)

async def condition(
*,
Expand Down Expand Up @@ -419,7 +419,7 @@ async def _explore_node[Parameters: DataModel](
# Evaluate with focused suite
ctx.log_info(f"Evaluating strategy '{strategy_name}'...")
focused_evaluation: EvaluatorSuiteResult
with ctx.updated(child_node.patched_instructions_repository):
with ctx.updating(child_node.patched_instructions_repository):
focused_evaluation = await evaluator_suite(focused_suite_cases)

# Check for performance drop
Expand All @@ -431,7 +431,7 @@ async def _explore_node[Parameters: DataModel](
pruned: bool = performance_ratio < performance_drop_threshold

# update node with evaluation data
children[child_node.identifier] = child_node.updated(
children[child_node.identifier] = child_node.updating(
focused_evaluation=focused_evaluation,
pruned=pruned,
)
Expand Down Expand Up @@ -656,14 +656,14 @@ async def tree_finalization(
)

complete_evaluation: EvaluatorSuiteResult
with ctx.updated(candidate_node.patched_instructions_repository):
with ctx.updating(candidate_node.patched_instructions_repository):
complete_evaluation = await evaluator_suite()

# Update node with full eval score
updated_node: _RefinementTreeNode = candidate_node.updated(
updated_node: _RefinementTreeNode = candidate_node.updating(
complete_evaluation=complete_evaluation
)
refinement_state = refinement_state.updated(
refinement_state = refinement_state.updating(
nodes={
**refinement_state.nodes,
# update node in tree
Expand All @@ -688,7 +688,7 @@ async def tree_finalization(
)

# Update result with best instructions and updated state
return state.updated(
return state.updating(
refinement_state,
result=MultimodalContent.of(
TextContent.of(
Expand Down
18 changes: 9 additions & 9 deletions src/draive/models/generative.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ async def _loop(

if not pending_tools:
ctx.log_debug("...loop finished...")
result: ModelOutput = generated.updated(
result: ModelOutput = generated.updating(
# replace generated with forced result
blocks=(
*result_extension,
Expand Down Expand Up @@ -384,7 +384,7 @@ async def _loop(

if tools_result: # tools direct result
ctx.log_debug("...loop finished by tools...")
result: ModelOutput = generated.updated(
result: ModelOutput = generated.updating(
# replace generated with forced result
blocks=(
*result_extension,
Expand Down Expand Up @@ -746,27 +746,27 @@ def _decoded( # noqa: PLR0911
return result

case "text":
return result.updated(blocks=(MultimodalContent.of(*result.content.texts()),))
return result.updating(blocks=(MultimodalContent.of(*result.content.texts()),))

case "image":
return result.updated(blocks=(MultimodalContent.of(*result.content.images()),))
return result.updating(blocks=(MultimodalContent.of(*result.content.images()),))

case "audio":
return result.updated(blocks=(MultimodalContent.of(*result.content.audio()),))
return result.updating(blocks=(MultimodalContent.of(*result.content.audio()),))

case "video":
return result.updated(blocks=(MultimodalContent.of(*result.content.video()),))
return result.updating(blocks=(MultimodalContent.of(*result.content.video()),))

case selection if isinstance(selection, Collection) and not isinstance(selection, str):
selected_parts: tuple[MultimodalContentPart, ...] = tuple(
part
for part in result.content.parts
if _matches_modalities(part, allowed=set(selection))
)
return result.updated(blocks=(MultimodalContent.of(*selected_parts),))
return result.updating(blocks=(MultimodalContent.of(*selected_parts),))

case "json":
return result.updated(
return result.updating(
blocks=(
MultimodalContent.of(
ArtifactContent.of(
Expand All @@ -779,7 +779,7 @@ def _decoded( # noqa: PLR0911

case model:
assert isinstance(model, type) # nosec: B101
return result.updated(
return result.updating(
blocks=(
MultimodalContent.of(
ArtifactContent.of(
Expand Down
2 changes: 1 addition & 1 deletion src/draive/models/tools/function.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def __init__(
def meta(self) -> Meta:
return self.specification.meta

def updated(
def updating(
self,
*,
name: str | None = None,
Expand Down
4 changes: 3 additions & 1 deletion src/draive/parameters/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class DataModelMeta(type):
__SPECIFICATION__: TypeSpecification
__FIELDS__: Sequence[Attribute]
__ALLOWED_FIELDS__: Set[str]
__SERIALIZABLE__: bool

def __new__(
mcs,
Expand Down Expand Up @@ -118,6 +119,7 @@ def __new__(

cls.__SELF_ATTRIBUTE__ = self_attribute # pyright: ignore[reportConstantRedefinition]
cls.__TYPE_PARAMETERS__ = type_parameters # pyright: ignore[reportConstantRedefinition]
cls.__SERIALIZABLE__ = True # pyright: ignore[reportConstantRedefinition]
if not bases:
assert not type_parameters # nosec: B101
cls.__FIELDS__ = () # pyright: ignore[reportAttributeAccessIssue, reportConstantRedefinition]
Expand Down Expand Up @@ -483,7 +485,7 @@ def to_mapping(self) -> Mapping[str, Any]:

return dict_result

def updated(
def updating(
self,
**kwargs: Any,
) -> Self:
Expand Down
2 changes: 1 addition & 1 deletion src/draive/resources/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def __init__(
ResourceReferenceTemplate(
template_uri=template_uri,
mime_type=mime_type,
meta=meta.updated(
meta=meta.updating(
name=name,
description=description,
),
Expand Down
6 changes: 3 additions & 3 deletions src/draive/resources/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def of(
return cls(
uri=uri,
mime_type=mime_type,
meta=Meta.of(meta).updated(
meta=Meta.of(meta).updating(
name=name,
description=description,
),
Expand All @@ -113,7 +113,7 @@ def of(
return cls(
template_uri=template_uri,
mime_type=mime_type,
meta=Meta.of(meta).updated(
meta=Meta.of(meta).updating(
name=name,
description=description,
),
Expand Down Expand Up @@ -288,7 +288,7 @@ def of(
return cls(
uri=uri,
content=resource_content,
meta=Meta.of(meta).updated(
meta=Meta.of(meta).updating(
name=name,
description=description,
),
Expand Down
Loading