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
26 changes: 13 additions & 13 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.95.1"
version = "0.96.0"
readme = "README.md"
maintainers = [
{ name = "Kacper Kaliński", email = "kacper.kalinski@miquido.com" },
Expand All @@ -24,25 +24,25 @@ classifiers = [
"Topic :: Software Development :: Libraries :: Application Frameworks",
]
license = { file = "LICENSE" }
dependencies = ["numpy~=2.3", "haiway~=0.40.2"]
dependencies = ["numpy~=2.3", "haiway~=0.42"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

haiway python package 0.42 changelog release notes breaking changes

💡 Result:

I couldn't find a haiway 0.42 release — the latest published haiway release on PyPI is 0.40.2 (uploaded Dec 16, 2025). See PyPI and package listings. [1][2]

Sources:

  • PyPI: haiway (shows latest 0.40.2). [1]
  • piwheels / Safety listings for haiway (release list up to 0.40.2). [2]

🏁 Script executed:

# Find all haiway imports and usages in the codebase
rg "from haiway|import haiway" --type py

Repository: miquido/draive

Length of output: 9980


🏁 Script executed:

# Check the project structure to understand the codebase
git ls-files | head -20

Repository: miquido/draive

Length of output: 528


Correct haiway dependency version—0.42 is not available on PyPI.

haiway 0.42 does not exist; the latest released version on PyPI is 0.40.2 (Dec 16, 2025). Update the dependency to haiway~=0.40 or an available version to allow the project to install.

🤖 Prompt for AI Agents
In @pyproject.toml at line 27, Update the haiway dependency in the
pyproject.toml dependencies entry: replace the unavailable version spec
"haiway~=0.42" with a valid released version like "haiway~=0.40" (or pin to the
latest available 0.40.x, e.g., 0.40.2) so the dependencies line (dependencies =
["numpy~=2.3", "haiway~=0.40"]) references a PyPI-released version.


[project.urls]
Homepage = "https://miquido.com"
Repository = "https://github.com/miquido/draive.git"

[project.optional-dependencies]
cohere = ["cohere~=5.18"]
cohere_bedrock = ["cohere~=5.18", "boto3~=1.40"]
openai = ["openai~=2.8"]
openai_realtime = ["openai[realtime]~=2.8"]
anthropic = ["anthropic~=0.72"]
anthropic_bedrock = ["anthropic[bedrock]~=0.72"]
mistral = ["mistralai~=1.9"]
cohere = ["cohere~=5.20"]
cohere_bedrock = ["cohere~=5.20", "boto3~=1.41"]
openai = ["openai~=2.9"]
openai_realtime = ["openai[realtime]~=2.9"]
anthropic = ["anthropic~=0.75"]
anthropic_bedrock = ["anthropic[bedrock]~=0.75"]
mistral = ["mistralai~=1.10"]
gemini = ["google-genai~=1.50", "google-api-core"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🌐 Web query:

google-api-core latest version pypi

💡 Result:

Latest google-api-core on PyPI: 2.29.0 — uploaded Jan 8, 2026. [1]

Sources:
[1] PyPI project page for google-api-core (release files & upload date).


🏁 Script executed:

# First, let's look at the pyproject.toml file to see the current state
git ls-files | grep -E "pyproject\.toml$"

Repository: miquido/draive

Length of output: 72


🏁 Script executed:

cat -n pyproject.toml | sed -n '35,50p'

Repository: miquido/draive

Length of output: 658


🌐 Web query:

google-genai 1.50 dependencies google-api-core requirements

💡 Result:

Short answer: google-genai 1.50 does NOT list google-api-core as a direct requirement. The google-genai PyPI metadata (v1.50 series) shows runtime deps like google-auth, httpx, pydantic, requests, tenacity, typing-extensions, websockets, etc., but not google-api-core. [1]

If you use Google Cloud/Generative Language helper packages (e.g., google-ai-generativelanguage), those do require google-api-core with bounds similar to:
google-api-core[grpc] !=2.0.,!=2.1.,!=2.2.,!=2.3.,!=2.4.,!=2.5.,!=2.6.,!=2.7.,!=2.8.,!=2.9., <3.0.0, >=1.34.1. [2]

Sources:

  • google-genai (PyPI project page / metadata). [1]
  • google-ai-generativelanguage METADATA showing google-api-core bounds. [2]

🏁 Script executed:

rg "google.api" --type py -i

Repository: miquido/draive

Length of output: 363


🌐 Web query:

google-genai 1.50.0 google-api-core compatibility version requirements

💡 Result:

Do you want me to look up the exact google-api-core requirement for google-genai 1.50.0 on PyPI/GitHub and report the precise version specifier?


🌐 Web query:

google-genai 1.50.0 setup.py requires_dist google-api-core version pypi

💡 Result:

You can see the exact requires_dist for google-genai 1.50.0 from the PyPI JSON for that release. Run:

That returns the google-api-core requirement string declared for that release. PyPI project page / releases: [1].

Sources:
[1] PyPI — google-genai project page. https://pypi.org/project/google-genai/


🏁 Script executed:

curl -s https://pypi.org/pypi/google-genai/1.50.0/json | jq '.info.requires_dist'

Repository: miquido/draive

Length of output: 439


🏁 Script executed:

curl -s https://pypi.org/pypi/google-auth/2.14.1/json | jq '.info.requires_dist'

Repository: miquido/draive

Length of output: 680


🌐 Web query:

google-api-core ResourceExhausted exception minimum version history

💡 Result:

Summary: The google.api_core.exceptions.ResourceExhausted exception has existed in the library since at least google-api-core v1.14.1 and is present in all subsequent 1.x and 2.x releases (e.g. v1.19.0, v1.22.0, v1.28.0, v2.1.0, v2.8.1). See the exceptions docs for those versions showing ResourceExhausted. [1][2][3][4][5]

Sources:

  • google-api-core v1.14.1 exceptions (ResourceExhausted). [1]
  • google-api-core v1.19.0 exceptions (ResourceExhausted). [2]
  • google-api-core v1.28.0 exceptions (ResourceExhausted). [3]
  • google-api-core v2.1.0 exceptions (ResourceExhausted). [4]
  • google-api-core latest (2.x) exceptions (ResourceExhausted). [5]

🏁 Script executed:

# Let me check if there are any test requirements or compatibility information
fd "requirements" -t f | head -20

Repository: miquido/draive

Length of output: 40


Add version constraint for google-api-core.

The gemini dependency group pins google-genai~=1.50 but leaves google-api-core unpinned. Although google-genai does not directly require it, google-api-core is imported in the code (google.api_core.exceptions.ResourceExhausted), making it an indirect dependency that should have a version constraint. The suggested version should be updated to reflect current compatibility; as of January 2026, google-api-core 2.29.0 is available and compatible.

♻️ Suggested fix
-gemini = ["google-genai~=1.50", "google-api-core"]
+gemini = ["google-genai~=1.50", "google-api-core>=2.14"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
gemini = ["google-genai~=1.50", "google-api-core"]
gemini = ["google-genai~=1.50", "google-api-core>=2.14"]
🤖 Prompt for AI Agents
In @pyproject.toml at line 41, The gemini dependency group currently pins
google-genai but leaves google-api-core unpinned; update the gemini entry in
pyproject.toml to pin google-api-core (for example to ~=2.29.0 or another
appropriate 2.x constraint) so the indirect import
google.api_core.exceptions.ResourceExhausted has a guaranteed compatible
version, then regenerate the lockfile (poetry lock / pip-compile) to lock this
change.

ollama = ["ollama~=0.6.0"]
bedrock = ["boto3~=1.40"]
aws = ["boto3~=1.40"]
vllm = ["openai~=2.8"]
ollama = ["ollama~=0.6.1"]
bedrock = ["boto3~=1.41"]
aws = ["boto3~=1.41"]
vllm = ["openai~=2.9"]
mcp = ["mcp~=1.21"]
opentelemetry = [
"haiway[opentelemetry]",
Expand Down
26 changes: 8 additions & 18 deletions src/draive/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
ConfigurationInvalid,
ConfigurationMissing,
ConfigurationRepository,
ContextPreset,
ContextIdentifier,
ContextMissing,
ContextPresets,
ContextStateMissing,
Default,
DefaultValue,
Description,
Disposable,
Disposables,
EventSubscription,
File,
FileAccess,
HTTPClient,
Expand All @@ -34,17 +35,11 @@
MetaTags,
MetaValues,
Missing,
MissingContext,
MissingState,
Observability,
ObservabilityAttribute,
ObservabilityContext,
ObservabilityLevel,
ScopeContext,
ScopeIdentifier,
Specification,
State,
StateContext,
TypeSpecification,
Validating,
ValidationError,
Expand Down Expand Up @@ -227,7 +222,10 @@
"ConfigurationInvalid",
"ConfigurationMissing",
"ConfigurationRepository",
"ContextPreset",
"ContextIdentifier",
"ContextMissing",
"ContextPresets",
"ContextStateMissing",
"Conversation",
"ConversationEvent",
"ConversationInputChunk",
Expand All @@ -238,9 +236,7 @@
"DefaultValue",
"Description",
"Disposable",
"Disposables",
"Embedded",
"EventSubscription",
"File",
"FileAccess",
"FunctionTool",
Expand Down Expand Up @@ -275,8 +271,6 @@
"MetaValues",
"MimeType",
"Missing",
"MissingContext",
"MissingState",
"ModelContext",
"ModelContextElement",
"ModelException",
Expand Down Expand Up @@ -320,7 +314,6 @@
"MultimodalTag",
"Observability",
"ObservabilityAttribute",
"ObservabilityContext",
"ObservabilityLevel",
"RealtimeConversation",
"RealtimeConversationSession",
Expand All @@ -339,14 +332,11 @@
"ResourceTemplate",
"ResourceUploading",
"ResourcesRepository",
"ScopeContext",
"ScopeIdentifier",
"Specification",
"Stage",
"StageException",
"StageState",
"State",
"StateContext",
"Template",
"TemplateDeclaration",
"TemplateMissing",
Expand Down
26 changes: 6 additions & 20 deletions src/draive/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,40 +36,26 @@ def __init__(
name: str,
version: str | None = None,
instructions: str | None = None,
resources: Iterable[ResourceTemplate[Any] | Resource] | None = None,
tools: Toolbox | Iterable[Tool] | None = None,
disposables: Disposables | Collection[Disposable] | None = None,
resources: Iterable[ResourceTemplate[Any] | Resource] = (),
tools: Toolbox | Iterable[Tool] = (),
disposables: Collection[Disposable] = (),
) -> None:
disposable: Disposables
if disposables is None:
disposable = Disposables(())

elif isinstance(disposables, Disposables):
disposable = disposables

else:
disposable = Disposables(disposables)

@asynccontextmanager
async def lifspan(server: Server) -> AsyncGenerator[Iterable[State]]:
state: Iterable[State] = await disposable.prepare()
try:
async with Disposables(disposables) as state:
yield state

finally:
await disposable.dispose()

self._server = Server[Iterable[State]](
name=name,
version=version,
instructions=instructions,
lifespan=lifspan,
)

if resources is not None:
if resources:
self._expose_resources(resources)

if tools is not None:
if tools:
self._expose_tools(tools)

async def run_stdio(
Expand Down
2 changes: 1 addition & 1 deletion src/draive/resources/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async def http_resource_fetching(
await response.body(),
mime_type=response.headers.get(
"Content-Type",
default="application/octet-stream",
"application/octet-stream",
),
)

Expand Down
84 changes: 21 additions & 63 deletions src/draive/stages/stage.py
Original file line number Diff line number Diff line change
Expand Up @@ -1467,7 +1467,7 @@ def with_ctx(
self,
/,
*,
disposables: Disposables | Collection[Disposable],
disposables: Collection[Disposable],
) -> Self: ...

@overload
Expand All @@ -1478,12 +1478,12 @@ def with_ctx(
*state: State,
) -> Self: ...

def with_ctx( # noqa: C901
def with_ctx(
self,
state: State | None = None,
/,
*states: State,
disposables: Disposables | Collection[Disposable] | None = None,
disposables: Collection[Disposable] = (),
) -> Self:
"""
Apply the specified state context to this stage.
Expand All @@ -1502,7 +1502,7 @@ def with_ctx( # noqa: C901
Optional additional `State` objects to include in the state context.
Only applicable when `state_context` is a `State`.

disposables: Disposables | Collection[Disposable] | None
disposables: Collection[Disposable]
Optional Disposables which will be used for execution of this stage.
State produced by disposables will be used within the context state.

Expand All @@ -1513,47 +1513,17 @@ def with_ctx( # noqa: C901
"""
execution: StageExecution = self._execution

resolved_disposables: Disposables | None
if disposables is None:
resolved_disposables = None

elif isinstance(disposables, Disposables):
resolved_disposables = disposables

else:
resolved_disposables = Disposables(disposables)

match (state, resolved_disposables):
case (None, None):
assert not states # nosec: B101
return self # nothing to change...

case (None, ctx_disposables):
assert not states # nosec: B101
if ctx_state := state:
if disposables:

async def stage(
*,
state: StageState,
) -> StageState:
result_state: StageState
try:
with ctx.updated(*await ctx_disposables.prepare()):
result_state = await execution(state=state)

except BaseException as exc:
await ctx_disposables.dispose(
exc_type=type(exc),
exc_val=exc,
exc_tb=exc.__traceback__,
)
raise exc

else:
await ctx_disposables.dispose()

return result_state

case (ctx_state, None):
async with Disposables(disposables) as disposable_state:
with ctx.updated(*disposable_state, ctx_state, *states):
return await execution(state=state)
else:

async def stage(
*,
Expand All @@ -1562,30 +1532,18 @@ async def stage(
with ctx.updated(ctx_state, *states):
return await execution(state=state)

case (ctx_state, ctx_disposables):

async def stage(
*,
state: StageState,
) -> StageState:
disposables_state: Iterable[State] = await ctx_disposables.prepare()
result_state: StageState
try:
with ctx.updated(ctx_state, *disposables_state, *states):
result_state = await execution(state=state)

except BaseException as exc:
await ctx_disposables.dispose(
exc_type=type(exc),
exc_val=exc,
exc_tb=exc.__traceback__,
)
raise exc

else:
await ctx_disposables.dispose()
elif disposables:
assert not states # nosec: B101

return result_state
async def stage(
*,
state: StageState,
) -> StageState:
async with ctx.disposables(*disposables):
return await execution(state=state)
else:
assert not states # nosec: B101
return self # nothing to change...

return self.__class__(
stage,
Expand Down
8 changes: 4 additions & 4 deletions src/draive/stages/types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from collections.abc import Mapping, Sequence
from typing import Any, Literal, Protocol, Self, cast, final, overload, runtime_checkable

from haiway import Meta, MetaValues, MissingState, State
from haiway import ContextStateMissing, Meta, MetaValues, State

from draive.models import ModelContext, ModelOutput
from draive.multimodal import Multimodal, MultimodalContent
Expand Down Expand Up @@ -179,7 +179,7 @@ def get[StateType: DataModel | State](
default : StateType | None
Default value to return if the state is not found.
required : bool
If True, raises MissingState when the state is not found.
If True, raises ContextStateMissing when the state is not found.

Returns
-------
Expand All @@ -188,7 +188,7 @@ def get[StateType: DataModel | State](

Raises
------
MissingState
ContextStateMissing
When required=True and the state is not found.
"""

Expand All @@ -200,7 +200,7 @@ def get[StateType: DataModel | State](
return default

elif required:
raise MissingState(f"{state.__qualname__} is not available within stage state")
raise ContextStateMissing(f"{state.__qualname__} is not available within stage state")

else:
return None
Expand Down
Loading