pyagentic-core 2.10.1.dev2__py3-none-any.whl → 2.11.1.dev1__py3-none-any.whl
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.
- pyagentic/__init__.py +13 -1
- pyagentic/_base/_agent/_agent.py +2 -0
- pyagentic/_base/_depends.py +59 -0
- pyagentic/_base/_metaclasses.py +105 -0
- pyagentic/api/__init__.py +5 -2
- pyagentic/api/_app.py +65 -8
- pyagentic/api/_build.py +205 -0
- pyagentic/api/_models.py +11 -17
- pyagentic/api/_sessions.py +23 -25
- pyagentic/api/jobs/_models.py +4 -0
- pyagentic/api/jobs/_orchestrator.py +7 -1
- pyagentic/api/jobs/_routes.py +19 -3
- pyagentic/api/jobs/backends/__init__.py +4 -0
- pyagentic/api/jobs/backends/_in_process.py +11 -4
- pyagentic/api/jobs/store/_sqlite.py +20 -11
- {pyagentic_core-2.10.1.dev2.dist-info → pyagentic_core-2.11.1.dev1.dist-info}/METADATA +1 -1
- {pyagentic_core-2.10.1.dev2.dist-info → pyagentic_core-2.11.1.dev1.dist-info}/RECORD +20 -18
- {pyagentic_core-2.10.1.dev2.dist-info → pyagentic_core-2.11.1.dev1.dist-info}/WHEEL +0 -0
- {pyagentic_core-2.10.1.dev2.dist-info → pyagentic_core-2.11.1.dev1.dist-info}/licenses/LICENSE +0 -0
- {pyagentic_core-2.10.1.dev2.dist-info → pyagentic_core-2.11.1.dev1.dist-info}/top_level.txt +0 -0
pyagentic/__init__.py
CHANGED
|
@@ -30,6 +30,7 @@ Main Components:
|
|
|
30
30
|
- tool: Decorator for marking methods as LLM-callable tools
|
|
31
31
|
- State: Type annotation for persistent state fields
|
|
32
32
|
- Link: Type annotation for linking agents together
|
|
33
|
+
- Depends: Type annotation for dependency-injected agent fields
|
|
33
34
|
- spec: Configuration factory for state, params, and agent links
|
|
34
35
|
- ref: Dynamic state reference system for tool parameters
|
|
35
36
|
- AgentExtension: Base class for creating reusable agent mixins
|
|
@@ -42,6 +43,17 @@ from pyagentic._base._mcp import MCPLink
|
|
|
42
43
|
from pyagentic._base._tool import tool
|
|
43
44
|
|
|
44
45
|
from pyagentic._base._state import State
|
|
46
|
+
from pyagentic._base._depends import Depends
|
|
45
47
|
from pyagentic._base._ref import ref
|
|
46
48
|
|
|
47
|
-
__all__ = [
|
|
49
|
+
__all__ = [
|
|
50
|
+
"BaseAgent",
|
|
51
|
+
"AgentExtension",
|
|
52
|
+
"tool",
|
|
53
|
+
"spec",
|
|
54
|
+
"State",
|
|
55
|
+
"Link",
|
|
56
|
+
"Depends",
|
|
57
|
+
"MCPLink",
|
|
58
|
+
"ref",
|
|
59
|
+
]
|
pyagentic/_base/_agent/_agent.py
CHANGED
|
@@ -163,6 +163,7 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
163
163
|
__state_defs__: ClassVar[dict[str, _StateDefinition]] # State field definitions
|
|
164
164
|
__linked_agents__: ClassVar[dict[str, "_LinkedAgentDefinition"]] # Linked agent definitions
|
|
165
165
|
__mcp_defs__: ClassVar[dict[str, "_MCPDefinition"]] # MCP server definitions
|
|
166
|
+
__dependencies__: ClassVar[dict[str, type]] # Depends[T] field -> dependency type
|
|
166
167
|
|
|
167
168
|
# User-set Class Attributes (defined in subclass)
|
|
168
169
|
__system_message__: ClassVar[str] # Required: system prompt for the agent
|
|
@@ -173,6 +174,7 @@ class BaseAgent(metaclass=AgentMeta):
|
|
|
173
174
|
|
|
174
175
|
# Generated Class Attributes (built by metaclass)
|
|
175
176
|
__request_model__: ClassVar[Type[BaseModel]] = None # Pydantic request model from __call__
|
|
177
|
+
__construct_model__: ClassVar[Type[BaseModel]] = None # Recursive constructor model
|
|
176
178
|
__response_model__: ClassVar[Type[AgentResponse]] = None # Pydantic response model
|
|
177
179
|
__stream_event_model__: ClassVar[Type[BaseModel]] = None # Typed SSE stream event union
|
|
178
180
|
__state_class__: ClassVar[Type[_AgentState]] = None # Generated state class
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from typing import TypeVar, Generic
|
|
2
|
+
|
|
3
|
+
T = TypeVar("T")
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Depends(Generic[T]):
|
|
7
|
+
"""
|
|
8
|
+
Type annotation for declaring a dependency-injected agent field.
|
|
9
|
+
|
|
10
|
+
``Depends`` marks a field as a server-supplied dependency rather than
|
|
11
|
+
serializable per-session state. Use it in an agent's class body wherever you
|
|
12
|
+
would otherwise use :class:`State`, for resources that cannot (or should not)
|
|
13
|
+
travel over the wire — database handles, HTTP clients, pre-configured
|
|
14
|
+
providers, and similar.
|
|
15
|
+
|
|
16
|
+
Unlike ``State`` and ``Link``, a ``Depends`` field is **excluded** from the
|
|
17
|
+
agent's generated construct model: clients never provide it. Instead it is
|
|
18
|
+
supplied once by the developer at :func:`pyagentic.api.create_router` /
|
|
19
|
+
:func:`pyagentic.api.create_app` via the ``dependencies`` argument, and
|
|
20
|
+
resolved by type across the (possibly nested) agent tree.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
T: The dependency type. Used both as the field's type hint and as the key
|
|
24
|
+
for by-type resolution at serve time.
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
```python
|
|
28
|
+
from pyagentic import BaseAgent, State, Depends
|
|
29
|
+
|
|
30
|
+
class ResearchAgent(BaseAgent):
|
|
31
|
+
__system_message__ = "You research topics"
|
|
32
|
+
|
|
33
|
+
topic: State[TopicState] # client-provided per session
|
|
34
|
+
db: Depends[Database] # injected by the developer, by type
|
|
35
|
+
|
|
36
|
+
@tool("Look up a record")
|
|
37
|
+
def lookup(self, key: str) -> str:
|
|
38
|
+
return self.db.get(key)
|
|
39
|
+
```
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __class_getitem__(cls, item):
|
|
43
|
+
"""
|
|
44
|
+
Creates a generic Depends type marker for a given dependency class.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
item: The dependency class this field requires.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
type: Special marker type that the metaclass detects and records in
|
|
51
|
+
``__dependencies__`` (and excludes from the construct model).
|
|
52
|
+
"""
|
|
53
|
+
# Return a special marker type that the metaclass can detect, mirroring
|
|
54
|
+
# the State[T] / Link[T] markers.
|
|
55
|
+
return type(
|
|
56
|
+
f"Depends[{item.__name__}]",
|
|
57
|
+
(),
|
|
58
|
+
{"__origin__": Depends, "__args__": (item,), "__dependency_type__": item},
|
|
59
|
+
)
|
pyagentic/_base/_metaclasses.py
CHANGED
|
@@ -15,6 +15,7 @@ from pyagentic._base._agent._agent_state import _AgentState
|
|
|
15
15
|
from pyagentic._base._tool import _ToolDefinition, tool
|
|
16
16
|
from pyagentic._base._state import State, StateInfo, _StateDefinition
|
|
17
17
|
from pyagentic._base._agent._agent_linking import Link, _LinkedAgentDefinition
|
|
18
|
+
from pyagentic._base._depends import Depends
|
|
18
19
|
from pyagentic._base._mcp import MCPLink, _MCPDefinition
|
|
19
20
|
|
|
20
21
|
from pyagentic.models.response import AgentResponse, ToolResponse
|
|
@@ -323,6 +324,92 @@ class AgentMeta(type):
|
|
|
323
324
|
|
|
324
325
|
return MappingProxyType(mcp_defs)
|
|
325
326
|
|
|
327
|
+
@staticmethod
|
|
328
|
+
def _extract_dependencies(annotations, namespace) -> Mapping[str, type]:
|
|
329
|
+
"""Extracts dependency-injected fields declared with ``Depends[T]``.
|
|
330
|
+
|
|
331
|
+
Looks for annotations whose marker has ``__origin__ is Depends`` and
|
|
332
|
+
records the wrapped dependency type. These fields are injected at serve
|
|
333
|
+
time (by type) rather than supplied by clients, so they are excluded from
|
|
334
|
+
the generated construct model.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
annotations (dict): Combined annotations from the class hierarchy.
|
|
338
|
+
namespace (dict): The class namespace.
|
|
339
|
+
|
|
340
|
+
Returns:
|
|
341
|
+
Mapping[str, type]: Immutable mapping of field name to dependency type.
|
|
342
|
+
"""
|
|
343
|
+
dependencies: dict[str, type] = {}
|
|
344
|
+
|
|
345
|
+
for attr_name, attr_type in annotations.items():
|
|
346
|
+
if getattr(attr_type, "__origin__", None) is Depends:
|
|
347
|
+
dependencies[attr_name] = attr_type.__dependency_type__
|
|
348
|
+
|
|
349
|
+
return MappingProxyType(dependencies)
|
|
350
|
+
|
|
351
|
+
@staticmethod
|
|
352
|
+
def _build_construct_model(cls) -> Type[BaseModel]:
|
|
353
|
+
"""Build a recursive Pydantic model mirroring the agent's constructor.
|
|
354
|
+
|
|
355
|
+
The construct model is the serializable construction contract for the
|
|
356
|
+
agent: it carries the data a client must supply to instantiate one, in a
|
|
357
|
+
way that mirrors writing the construction in Python.
|
|
358
|
+
|
|
359
|
+
- State fields → the raw per-field model, required when the StateInfo
|
|
360
|
+
has no default/default_factory, otherwise optional.
|
|
361
|
+
- Linked agents → that agent's own ``__construct_model__`` (nested),
|
|
362
|
+
optional when the AgentLink provides a default, otherwise required.
|
|
363
|
+
- ``model`` / ``api_key`` → optional scalars for provider selection.
|
|
364
|
+
|
|
365
|
+
``Depends`` and MCP fields are excluded — they are injected/configured
|
|
366
|
+
server-side, not provided by clients.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
cls: The agent class whose constructor to mirror.
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Type[BaseModel]: A dynamically created Pydantic model.
|
|
373
|
+
"""
|
|
374
|
+
from typing import Optional
|
|
375
|
+
from pydantic import ConfigDict
|
|
376
|
+
|
|
377
|
+
fields: dict[str, Any] = {}
|
|
378
|
+
|
|
379
|
+
# State fields: type is the raw per-field model (e.g. UserProfile).
|
|
380
|
+
# Check for a default's *existence* without invoking factories.
|
|
381
|
+
for name, state_def in cls.__state_defs__.items():
|
|
382
|
+
info = state_def.info
|
|
383
|
+
if info.default_factory is not None:
|
|
384
|
+
fields[name] = (state_def.model, Field(default_factory=info.default_factory))
|
|
385
|
+
elif info.default is not None:
|
|
386
|
+
fields[name] = (state_def.model, Field(default=info.default))
|
|
387
|
+
else:
|
|
388
|
+
fields[name] = (state_def.model, Field(...))
|
|
389
|
+
|
|
390
|
+
# Linked agents: nest each child's construct model. A link is optional
|
|
391
|
+
# when its AgentLink declares any default (don't call the factory here).
|
|
392
|
+
for name, linked_def in cls.__linked_agents__.items():
|
|
393
|
+
child_model = linked_def.agent.__construct_model__
|
|
394
|
+
info = linked_def.info
|
|
395
|
+
has_default = info is not None and (
|
|
396
|
+
info.default is not None or info.default_factory is not None
|
|
397
|
+
)
|
|
398
|
+
if has_default:
|
|
399
|
+
fields[name] = (Optional[child_model], Field(default=None))
|
|
400
|
+
else:
|
|
401
|
+
fields[name] = (child_model, Field(...))
|
|
402
|
+
|
|
403
|
+
# Provider selection scalars.
|
|
404
|
+
fields["model"] = (Optional[str], Field(default=None))
|
|
405
|
+
fields["api_key"] = (Optional[str], Field(default=None))
|
|
406
|
+
|
|
407
|
+
return create_model(
|
|
408
|
+
f"{cls.__name__}Construct",
|
|
409
|
+
__config__=ConfigDict(arbitrary_types_allowed=True),
|
|
410
|
+
**fields,
|
|
411
|
+
)
|
|
412
|
+
|
|
326
413
|
@staticmethod
|
|
327
414
|
def _build_request_model(cls) -> Type[BaseModel]:
|
|
328
415
|
"""Build a Pydantic request model from the agent's __call__ signature.
|
|
@@ -458,6 +545,16 @@ class AgentMeta(type):
|
|
|
458
545
|
# MCP fields are config-only, not constructor args
|
|
459
546
|
elif field_name in agent_cls.__mcp_defs__:
|
|
460
547
|
continue
|
|
548
|
+
# Dependency-injected fields are optional (default None); they are
|
|
549
|
+
# supplied at serve time or passed explicitly for direct/test use.
|
|
550
|
+
elif field_name in agent_cls.__dependencies__:
|
|
551
|
+
param = inspect.Parameter(
|
|
552
|
+
field_name,
|
|
553
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
554
|
+
default=None,
|
|
555
|
+
annotation=field_type,
|
|
556
|
+
)
|
|
557
|
+
optional.append(param)
|
|
461
558
|
# Other annotated fields (like model, api_key, tracer, etc.)
|
|
462
559
|
else:
|
|
463
560
|
default = getattr(agent_cls, field_name, inspect._empty)
|
|
@@ -611,12 +708,14 @@ class AgentMeta(type):
|
|
|
611
708
|
annotations, inherited_namespace | namespace, mcs.__BaseAgent__
|
|
612
709
|
)
|
|
613
710
|
mcp_defs = mcs._extract_mcp_defs(annotations, inherited_namespace | namespace)
|
|
711
|
+
dependencies = mcs._extract_dependencies(annotations, inherited_namespace | namespace)
|
|
614
712
|
with mcs._lock:
|
|
615
713
|
cls.__tool_defs__ = tool_defs
|
|
616
714
|
cls.__annotations__ = annotations
|
|
617
715
|
cls.__state_defs__ = state_defs
|
|
618
716
|
cls.__linked_agents__ = linked_agents
|
|
619
717
|
cls.__mcp_defs__ = mcp_defs
|
|
718
|
+
cls.__dependencies__ = dependencies
|
|
620
719
|
|
|
621
720
|
# Create response models at class declaration time, giving the agent a predetermined
|
|
622
721
|
# output structure. This allows developers to know exactly what the output of the
|
|
@@ -668,10 +767,16 @@ class AgentMeta(type):
|
|
|
668
767
|
ResponseModel=ResponseModel,
|
|
669
768
|
)
|
|
670
769
|
|
|
770
|
+
# Build the recursive construct model mirroring the agent's constructor.
|
|
771
|
+
# Linked agents' construct models already exist because their classes are
|
|
772
|
+
# defined before this one (same guarantee relied on for __response_model__).
|
|
773
|
+
ConstructModel = mcs._build_construct_model(cls)
|
|
774
|
+
|
|
671
775
|
with mcs._lock:
|
|
672
776
|
cls.__tool_response_models__ = MappingProxyType(tool_response_models)
|
|
673
777
|
cls.__response_model__ = ResponseModel
|
|
674
778
|
cls.__request_model__ = RequestModel
|
|
779
|
+
cls.__construct_model__ = ConstructModel
|
|
675
780
|
cls.__stream_event_model__ = StreamEventModel
|
|
676
781
|
cls.__state_class__ = StateClass
|
|
677
782
|
|
pyagentic/api/__init__.py
CHANGED
|
@@ -11,7 +11,9 @@ Configuration lives in ``agents.toml`` beside your ``pyproject.toml`` (see
|
|
|
11
11
|
:class:`AgentsConfig`).
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
+
from pyagentic._base._depends import Depends
|
|
14
15
|
from pyagentic.api._app import create_app, create_router
|
|
16
|
+
from pyagentic.api._build import build_agent, validate_dependencies
|
|
15
17
|
from pyagentic.api._config import (
|
|
16
18
|
AgentsConfig,
|
|
17
19
|
AppConfig,
|
|
@@ -25,7 +27,6 @@ from pyagentic.api._models import (
|
|
|
25
27
|
AgentInfo,
|
|
26
28
|
AppAgentEntry,
|
|
27
29
|
AppIndex,
|
|
28
|
-
CreateSessionRequest,
|
|
29
30
|
CreateSessionResponse,
|
|
30
31
|
DeleteSessionResponse,
|
|
31
32
|
HealthResponse,
|
|
@@ -38,6 +39,9 @@ __all__ = [
|
|
|
38
39
|
"create_app",
|
|
39
40
|
"create_router",
|
|
40
41
|
"mount_mcp",
|
|
42
|
+
"Depends",
|
|
43
|
+
"build_agent",
|
|
44
|
+
"validate_dependencies",
|
|
41
45
|
"generate_dockerfile",
|
|
42
46
|
"write_dockerfile",
|
|
43
47
|
"AgentsConfig",
|
|
@@ -50,7 +54,6 @@ __all__ = [
|
|
|
50
54
|
"AgentInfo",
|
|
51
55
|
"AppAgentEntry",
|
|
52
56
|
"AppIndex",
|
|
53
|
-
"CreateSessionRequest",
|
|
54
57
|
"CreateSessionResponse",
|
|
55
58
|
"DeleteSessionResponse",
|
|
56
59
|
"HealthResponse",
|
pyagentic/api/_app.py
CHANGED
|
@@ -23,17 +23,19 @@ from typing import Optional, Union
|
|
|
23
23
|
|
|
24
24
|
from dotenv import load_dotenv
|
|
25
25
|
from fastapi import APIRouter, FastAPI, HTTPException
|
|
26
|
+
from fastapi.exceptions import RequestValidationError
|
|
26
27
|
from fastapi.responses import StreamingResponse
|
|
28
|
+
from pydantic import ValidationError
|
|
27
29
|
|
|
28
30
|
from pyagentic._base._agent._agent import BaseAgent
|
|
29
31
|
from pyagentic.models.llm import LLMResponse
|
|
30
32
|
from pyagentic.models.response import AgentResponse, ToolResponse
|
|
33
|
+
from pyagentic.api._build import validate_dependencies
|
|
31
34
|
from pyagentic.api._config import AgentsConfig, JobsConfig, load_config
|
|
32
35
|
from pyagentic.api._models import (
|
|
33
36
|
AgentInfo,
|
|
34
37
|
AppAgentEntry,
|
|
35
38
|
AppIndex,
|
|
36
|
-
CreateSessionRequest,
|
|
37
39
|
CreateSessionResponse,
|
|
38
40
|
DeleteSessionResponse,
|
|
39
41
|
HealthResponse,
|
|
@@ -49,12 +51,14 @@ AgentsArg = Union[type[BaseAgent], list[type[BaseAgent]], dict[str, type[BaseAge
|
|
|
49
51
|
|
|
50
52
|
_REQUIRED_AGENT_ATTRS = (
|
|
51
53
|
"__request_model__",
|
|
54
|
+
"__construct_model__",
|
|
52
55
|
"__response_model__",
|
|
53
56
|
"__stream_event_model__",
|
|
54
57
|
"__state_class__",
|
|
55
58
|
"__tool_defs__",
|
|
56
59
|
"__state_defs__",
|
|
57
60
|
"__linked_agents__",
|
|
61
|
+
"__dependencies__",
|
|
58
62
|
)
|
|
59
63
|
|
|
60
64
|
|
|
@@ -140,6 +144,26 @@ def _normalize_agents(agents: AgentsArg) -> dict[str, type[BaseAgent]]:
|
|
|
140
144
|
return mapping
|
|
141
145
|
|
|
142
146
|
|
|
147
|
+
def _deps_for(
|
|
148
|
+
prefix: str,
|
|
149
|
+
agent_class: type[BaseAgent],
|
|
150
|
+
dependencies: Optional[Union[list, dict]],
|
|
151
|
+
) -> Optional[list]:
|
|
152
|
+
"""Resolve the dependency list for one mounted agent.
|
|
153
|
+
|
|
154
|
+
A flat list applies to every agent; a dict is scoped by mount prefix (with or
|
|
155
|
+
without a leading slash) or by agent class.
|
|
156
|
+
"""
|
|
157
|
+
if dependencies is None:
|
|
158
|
+
return None
|
|
159
|
+
if isinstance(dependencies, dict):
|
|
160
|
+
for key in (prefix, prefix.strip("/"), "/" + prefix.strip("/"), agent_class):
|
|
161
|
+
if key in dependencies:
|
|
162
|
+
return dependencies[key]
|
|
163
|
+
return None
|
|
164
|
+
return dependencies
|
|
165
|
+
|
|
166
|
+
|
|
143
167
|
def create_router(
|
|
144
168
|
agent_class: type[BaseAgent],
|
|
145
169
|
*,
|
|
@@ -147,6 +171,7 @@ def create_router(
|
|
|
147
171
|
name: Optional[str] = None,
|
|
148
172
|
version: str = "0.1.0",
|
|
149
173
|
tags: Optional[list[str]] = None,
|
|
174
|
+
dependencies: Optional[list] = None,
|
|
150
175
|
sessions: bool = True,
|
|
151
176
|
jobs: bool = False,
|
|
152
177
|
jobs_config: Optional["JobsConfig"] = None,
|
|
@@ -185,6 +210,9 @@ def create_router(
|
|
|
185
210
|
version (str): Version string for the info route.
|
|
186
211
|
tags (Optional[list[str]]): OpenAPI tags applied to every route on the
|
|
187
212
|
router (groups them in the docs). ``None`` leaves routes untagged.
|
|
213
|
+
dependencies (Optional[list]): Instances or factories satisfying the
|
|
214
|
+
agent tree's ``Depends[T]`` fields, resolved by type. Validated up
|
|
215
|
+
front so a missing dependency fails fast at router-build time.
|
|
188
216
|
sessions (bool): If True, mount the session-based routes (create/list/
|
|
189
217
|
delete sessions plus synchronous chat, streaming chat, and state).
|
|
190
218
|
If False, those routes are omitted — interaction happens only via
|
|
@@ -203,18 +231,23 @@ def create_router(
|
|
|
203
231
|
|
|
204
232
|
Raises:
|
|
205
233
|
TypeError: If agent_class is missing required metaclass attributes.
|
|
234
|
+
ValueError: If a ``Depends[T]`` field in the agent tree has no provider.
|
|
206
235
|
"""
|
|
207
236
|
_validate_agent_class(agent_class)
|
|
237
|
+
validate_dependencies(agent_class, dependencies)
|
|
208
238
|
|
|
209
239
|
name = name or agent_class.__name__
|
|
210
240
|
slug = _slugify(name)
|
|
211
241
|
RequestModel = agent_class.__request_model__
|
|
242
|
+
ConstructModel = agent_class.__construct_model__
|
|
212
243
|
ResponseModel = agent_class.__response_model__
|
|
213
244
|
StreamEventModel = agent_class.__stream_event_model__
|
|
214
245
|
StateModel = agent_class.__state_class__
|
|
215
246
|
|
|
216
247
|
router = APIRouter(tags=tags)
|
|
217
|
-
session_manager = SessionManager(
|
|
248
|
+
session_manager = SessionManager(
|
|
249
|
+
agent_class, default_model=model, dependencies=dependencies
|
|
250
|
+
)
|
|
218
251
|
|
|
219
252
|
# ---- info routes ----
|
|
220
253
|
|
|
@@ -228,6 +261,7 @@ def create_router(
|
|
|
228
261
|
tools=list(agent_class.__tool_defs__.keys()),
|
|
229
262
|
state_fields=list(agent_class.__state_defs__.keys()),
|
|
230
263
|
linked_agents=list(agent_class.__linked_agents__.keys()),
|
|
264
|
+
dependencies=list(agent_class.__dependencies__.keys()),
|
|
231
265
|
)
|
|
232
266
|
|
|
233
267
|
@router.get("/health", response_model=HealthResponse, name=f"{slug}_health")
|
|
@@ -237,8 +271,9 @@ def create_router(
|
|
|
237
271
|
|
|
238
272
|
@router.get("/schema", response_model=SchemaResponse, name=f"{slug}_schema")
|
|
239
273
|
async def schema() -> SchemaResponse:
|
|
240
|
-
"""Return JSON schemas for request, response, stream
|
|
274
|
+
"""Return JSON schemas for construct, request, response, stream, and state models."""
|
|
241
275
|
return SchemaResponse(
|
|
276
|
+
construct_schema=ConstructModel.model_json_schema(),
|
|
242
277
|
request=RequestModel.model_json_schema(),
|
|
243
278
|
response=ResponseModel.model_json_schema(),
|
|
244
279
|
stream_event=StreamEventModel.model_json_schema(),
|
|
@@ -250,6 +285,7 @@ def create_router(
|
|
|
250
285
|
router,
|
|
251
286
|
slug,
|
|
252
287
|
session_manager,
|
|
288
|
+
ConstructModel,
|
|
253
289
|
RequestModel,
|
|
254
290
|
ResponseModel,
|
|
255
291
|
StreamEventModel,
|
|
@@ -268,6 +304,7 @@ def create_router(
|
|
|
268
304
|
jobs_config or JobsConfig(),
|
|
269
305
|
model,
|
|
270
306
|
tags,
|
|
307
|
+
dependencies,
|
|
271
308
|
)
|
|
272
309
|
router.orchestrator = orchestrator
|
|
273
310
|
|
|
@@ -281,6 +318,7 @@ def _mount_jobs_router(
|
|
|
281
318
|
jobs_config: "JobsConfig",
|
|
282
319
|
default_model: Optional[str],
|
|
283
320
|
tags: Optional[list[str]],
|
|
321
|
+
dependencies: Optional[list] = None,
|
|
284
322
|
) -> "JobOrchestrator":
|
|
285
323
|
"""Build a job orchestrator and include its /jobs router; return the orchestrator."""
|
|
286
324
|
from pyagentic.api.jobs import JobOrchestrator, build_backend, build_store
|
|
@@ -292,6 +330,7 @@ def _mount_jobs_router(
|
|
|
292
330
|
sessions,
|
|
293
331
|
max_concurrency=jobs_config.max_concurrency,
|
|
294
332
|
default_model=default_model,
|
|
333
|
+
dependencies=dependencies,
|
|
295
334
|
)
|
|
296
335
|
orchestrator = JobOrchestrator(store, backend, jobs_config, sessions)
|
|
297
336
|
router.include_router(
|
|
@@ -304,6 +343,7 @@ def _register_session_routes(
|
|
|
304
343
|
router: APIRouter,
|
|
305
344
|
slug: str,
|
|
306
345
|
sessions: SessionManager,
|
|
346
|
+
ConstructModel: type,
|
|
307
347
|
RequestModel: type,
|
|
308
348
|
ResponseModel: type,
|
|
309
349
|
StreamEventModel: type,
|
|
@@ -320,12 +360,23 @@ def _register_session_routes(
|
|
|
320
360
|
name=f"{slug}_create_session",
|
|
321
361
|
)
|
|
322
362
|
async def create_session(
|
|
323
|
-
req: Optional[
|
|
363
|
+
req: Optional[ConstructModel] = None, # type: ignore[valid-type]
|
|
324
364
|
) -> CreateSessionResponse:
|
|
325
|
-
"""Create a new agent session.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
365
|
+
"""Create a new agent session.
|
|
366
|
+
|
|
367
|
+
The body is the agent's construct model — the serializable construction
|
|
368
|
+
data (state, nested linked-agent state, model/api_key). Required state
|
|
369
|
+
and linked agents (those without defaults) must be supplied here, exactly
|
|
370
|
+
as when constructing the agent in code. The body may be omitted only when
|
|
371
|
+
the agent has no required construction fields.
|
|
372
|
+
"""
|
|
373
|
+
if req is None:
|
|
374
|
+
# No body: valid only when nothing is required; otherwise 422.
|
|
375
|
+
try:
|
|
376
|
+
req = ConstructModel()
|
|
377
|
+
except ValidationError as exc:
|
|
378
|
+
raise RequestValidationError(exc.errors())
|
|
379
|
+
session_id = sessions.create(construct_data=req)
|
|
329
380
|
return CreateSessionResponse(session_id=session_id)
|
|
330
381
|
|
|
331
382
|
@router.get(
|
|
@@ -483,6 +534,7 @@ def create_app(
|
|
|
483
534
|
version: Optional[str] = None,
|
|
484
535
|
description: Optional[str] = None,
|
|
485
536
|
model: Optional[str] = None,
|
|
537
|
+
dependencies: Optional[Union[list, dict]] = None,
|
|
486
538
|
mcp: bool = False,
|
|
487
539
|
sessions: bool = True,
|
|
488
540
|
jobs: bool = False,
|
|
@@ -512,6 +564,10 @@ def create_app(
|
|
|
512
564
|
description (Optional[str]): App description. Overrides config.
|
|
513
565
|
model (Optional[str]): Default model for all agents' sessions. Overrides
|
|
514
566
|
config.
|
|
567
|
+
dependencies (Optional[Union[list, dict]]): Providers for the agents'
|
|
568
|
+
``Depends[T]`` fields (instances or factories, resolved by type). A
|
|
569
|
+
flat list is applied to every mounted agent; a dict keyed by mount
|
|
570
|
+
prefix (or agent class) scopes providers per agent.
|
|
515
571
|
mcp (bool): If True, mount an MCP endpoint per agent at ``<prefix>/mcp``.
|
|
516
572
|
sessions (bool): If True (default), mount the session-based routes
|
|
517
573
|
(sessions CRUD plus synchronous/streaming chat and state) for each
|
|
@@ -563,6 +619,7 @@ def create_app(
|
|
|
563
619
|
model=model,
|
|
564
620
|
name=agent_name,
|
|
565
621
|
version=version,
|
|
622
|
+
dependencies=_deps_for(prefix, agent_class, dependencies),
|
|
566
623
|
sessions=sessions,
|
|
567
624
|
jobs=jobs_enabled,
|
|
568
625
|
jobs_config=jobs_cfg,
|
pyagentic/api/_build.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Construct agents from a (recursive) construct-model payload plus developer-supplied
|
|
3
|
+
dependencies.
|
|
4
|
+
|
|
5
|
+
The metaclass generates ``agent_class.__construct_model__`` — a recursive Pydantic
|
|
6
|
+
model mirroring the constructor (state fields, nested linked-agent construct models,
|
|
7
|
+
and ``model``/``api_key``). ``build_agent`` turns an instance of that model into a
|
|
8
|
+
live agent, recursing into linked agents and injecting ``Depends[T]`` fields by type
|
|
9
|
+
from a flat ``dependencies`` list.
|
|
10
|
+
|
|
11
|
+
Dependencies are passed as bare values:
|
|
12
|
+
- an **instance** of the dependency type, or
|
|
13
|
+
- a zero-arg **factory** whose return annotation is (a subclass of) the type.
|
|
14
|
+
|
|
15
|
+
Resolution is by type. An instance match wins over a factory; factories are called
|
|
16
|
+
fresh for each agent built (so each session/job gets its own).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import inspect
|
|
20
|
+
from typing import Any, Optional
|
|
21
|
+
|
|
22
|
+
from pyagentic._base._agent._agent import BaseAgent
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _as_field_dict(construct_data: Any) -> dict:
|
|
26
|
+
"""Normalize a construct payload (Pydantic model or dict) to a field dict.
|
|
27
|
+
|
|
28
|
+
Nested values (linked-agent construct models) are preserved as-is so the
|
|
29
|
+
recursion can descend into them.
|
|
30
|
+
"""
|
|
31
|
+
if construct_data is None:
|
|
32
|
+
return {}
|
|
33
|
+
if isinstance(construct_data, dict):
|
|
34
|
+
return dict(construct_data)
|
|
35
|
+
# Pydantic model: iterate declared fields, keeping nested model instances.
|
|
36
|
+
model_fields = getattr(type(construct_data), "model_fields", None)
|
|
37
|
+
if model_fields is not None:
|
|
38
|
+
return {name: getattr(construct_data, name) for name in model_fields}
|
|
39
|
+
raise TypeError(f"Unsupported construct payload type: {type(construct_data)!r}")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _matches_factory(value: Any, dep_type: type) -> bool:
|
|
43
|
+
"""Return whether *value* is a zero-arg factory producing *dep_type*."""
|
|
44
|
+
if not callable(value) or isinstance(value, type):
|
|
45
|
+
# Plain classes are treated as instances/values, not factories — a
|
|
46
|
+
# factory is expected to be an annotated callable.
|
|
47
|
+
return False
|
|
48
|
+
try:
|
|
49
|
+
ret = inspect.signature(value).return_annotation
|
|
50
|
+
except (TypeError, ValueError):
|
|
51
|
+
return False
|
|
52
|
+
return isinstance(ret, type) and issubclass(ret, dep_type)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _resolve_dependency(name: str, dep_type: type, dependencies: list) -> Any:
|
|
56
|
+
"""Resolve a single ``Depends[T]`` slot from the dependency list.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
name (str): The field name (used only for error messages).
|
|
60
|
+
dep_type (type): The declared dependency type ``T``.
|
|
61
|
+
dependencies (list): Provided instances/factories.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Any: The resolved dependency instance.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
LookupError: If no provided value satisfies the slot.
|
|
68
|
+
"""
|
|
69
|
+
# Instance match wins.
|
|
70
|
+
for value in dependencies:
|
|
71
|
+
if isinstance(value, dep_type):
|
|
72
|
+
return value
|
|
73
|
+
# Otherwise a factory whose return type matches; called fresh.
|
|
74
|
+
for value in dependencies:
|
|
75
|
+
if _matches_factory(value, dep_type):
|
|
76
|
+
return value()
|
|
77
|
+
raise LookupError(
|
|
78
|
+
f"No dependency provided for '{name}': {dep_type.__name__}. "
|
|
79
|
+
f"Pass an instance of {dep_type.__name__} or a factory returning it "
|
|
80
|
+
f"in create_router(..., dependencies=[...])."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def build_agent(
|
|
85
|
+
agent_class: type[BaseAgent],
|
|
86
|
+
construct_data: Any,
|
|
87
|
+
dependencies: Optional[list] = None,
|
|
88
|
+
*,
|
|
89
|
+
default_model: Optional[str] = None,
|
|
90
|
+
default_api_key: Optional[str] = None,
|
|
91
|
+
) -> BaseAgent:
|
|
92
|
+
"""Instantiate an agent from a construct-model payload and injected dependencies.
|
|
93
|
+
|
|
94
|
+
Linked agents are built first (depth-first) from their nested construct
|
|
95
|
+
submodels, then ``Depends[T]`` fields are resolved by type, and finally the
|
|
96
|
+
agent is constructed with state, ``model``/``api_key``, the built linked
|
|
97
|
+
agents, and the resolved dependencies. The ``default_model``/``default_api_key``
|
|
98
|
+
propagate down the whole tree, filling in any agent whose payload omits them.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
agent_class (type[BaseAgent]): The agent class to instantiate.
|
|
102
|
+
construct_data (Any): An instance of ``agent_class.__construct_model__``
|
|
103
|
+
(or a dict with the same shape).
|
|
104
|
+
dependencies (Optional[list]): Instances or factories used to satisfy
|
|
105
|
+
``Depends[T]`` fields across the agent tree. Defaults to ``[]``.
|
|
106
|
+
default_model (Optional[str]): Model applied to any agent in the tree that
|
|
107
|
+
does not specify its own ``model``.
|
|
108
|
+
default_api_key (Optional[str]): API key applied alongside ``default_model``
|
|
109
|
+
for any agent that does not specify its own.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
BaseAgent: The fully constructed agent instance.
|
|
113
|
+
|
|
114
|
+
Raises:
|
|
115
|
+
LookupError: If a required dependency cannot be resolved.
|
|
116
|
+
"""
|
|
117
|
+
dependencies = dependencies or []
|
|
118
|
+
# A raw dict (e.g. a persisted job payload) is parsed through the construct
|
|
119
|
+
# model so nested state and linked-agent submodels are coerced to typed
|
|
120
|
+
# instances before construction.
|
|
121
|
+
if isinstance(construct_data, dict):
|
|
122
|
+
construct_data = agent_class.__construct_model__(**construct_data)
|
|
123
|
+
fields = _as_field_dict(construct_data)
|
|
124
|
+
kwargs: dict[str, Any] = {}
|
|
125
|
+
|
|
126
|
+
# Build nested linked agents first (defaults propagate down the tree).
|
|
127
|
+
for name, linked_def in agent_class.__linked_agents__.items():
|
|
128
|
+
child_data = fields.get(name)
|
|
129
|
+
if child_data is None:
|
|
130
|
+
# Omitted optional link: let __init__ apply the AgentLink default.
|
|
131
|
+
continue
|
|
132
|
+
kwargs[name] = build_agent(
|
|
133
|
+
linked_def.agent,
|
|
134
|
+
child_data,
|
|
135
|
+
dependencies,
|
|
136
|
+
default_model=default_model,
|
|
137
|
+
default_api_key=default_api_key,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# State fields supplied by the client.
|
|
141
|
+
for name in agent_class.__state_defs__:
|
|
142
|
+
if name in fields and fields[name] is not None:
|
|
143
|
+
kwargs[name] = fields[name]
|
|
144
|
+
|
|
145
|
+
# Provider selection scalars, falling back to the propagated defaults.
|
|
146
|
+
model = fields.get("model") or default_model
|
|
147
|
+
api_key = fields.get("api_key") or default_api_key
|
|
148
|
+
if model is not None:
|
|
149
|
+
kwargs["model"] = model
|
|
150
|
+
if api_key is not None:
|
|
151
|
+
kwargs["api_key"] = api_key
|
|
152
|
+
|
|
153
|
+
# Inject dependencies by type.
|
|
154
|
+
for name, dep_type in agent_class.__dependencies__.items():
|
|
155
|
+
kwargs[name] = _resolve_dependency(name, dep_type, dependencies)
|
|
156
|
+
|
|
157
|
+
return agent_class(**kwargs)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _collect_dependency_slots(
|
|
161
|
+
agent_class: type[BaseAgent],
|
|
162
|
+
_seen: Optional[set] = None,
|
|
163
|
+
) -> list[tuple[str, type]]:
|
|
164
|
+
"""Walk the linked-agent tree collecting every ``Depends[T]`` slot."""
|
|
165
|
+
_seen = _seen if _seen is not None else set()
|
|
166
|
+
if agent_class in _seen:
|
|
167
|
+
return []
|
|
168
|
+
_seen.add(agent_class)
|
|
169
|
+
|
|
170
|
+
slots: list[tuple[str, type]] = list(agent_class.__dependencies__.items())
|
|
171
|
+
for linked_def in agent_class.__linked_agents__.values():
|
|
172
|
+
slots.extend(_collect_dependency_slots(linked_def.agent, _seen))
|
|
173
|
+
return slots
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def validate_dependencies(
|
|
177
|
+
agent_class: type[BaseAgent],
|
|
178
|
+
dependencies: Optional[list] = None,
|
|
179
|
+
) -> None:
|
|
180
|
+
"""Verify every ``Depends[T]`` slot in the agent tree is satisfiable.
|
|
181
|
+
|
|
182
|
+
Intended to run once at ``create_router``/``create_app`` time so misconfigured
|
|
183
|
+
dependencies fail fast rather than per request. Factories are not invoked —
|
|
184
|
+
only their presence/return type is checked.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
agent_class (type[BaseAgent]): The root agent class.
|
|
188
|
+
dependencies (Optional[list]): Provided instances/factories.
|
|
189
|
+
|
|
190
|
+
Raises:
|
|
191
|
+
ValueError: If any dependency slot has no matching provider.
|
|
192
|
+
"""
|
|
193
|
+
dependencies = dependencies or []
|
|
194
|
+
missing: list[str] = []
|
|
195
|
+
for name, dep_type in _collect_dependency_slots(agent_class):
|
|
196
|
+
satisfiable = any(isinstance(v, dep_type) for v in dependencies) or any(
|
|
197
|
+
_matches_factory(v, dep_type) for v in dependencies
|
|
198
|
+
)
|
|
199
|
+
if not satisfiable:
|
|
200
|
+
missing.append(f"'{name}': {dep_type.__name__}")
|
|
201
|
+
if missing:
|
|
202
|
+
raise ValueError(
|
|
203
|
+
f"{agent_class.__name__} has unsatisfied dependencies: {', '.join(missing)}. "
|
|
204
|
+
f"Provide them via dependencies=[...] (an instance or a factory returning the type)."
|
|
205
|
+
)
|
pyagentic/api/_models.py
CHANGED
|
@@ -8,22 +8,7 @@ endpoints are typed directly from each agent's metaclass-generated models
|
|
|
8
8
|
``__state_class__``), so they are not duplicated here.
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
-
from
|
|
12
|
-
|
|
13
|
-
from pydantic import BaseModel, Field
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class CreateSessionRequest(BaseModel):
|
|
17
|
-
"""Request body for creating a new agent session.
|
|
18
|
-
|
|
19
|
-
Attributes:
|
|
20
|
-
model (Optional[str]): LLM model string override
|
|
21
|
-
(e.g. ``'openai::gpt-4o'``).
|
|
22
|
-
api_key (Optional[str]): API key for the model provider.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
model: Optional[str] = None
|
|
26
|
-
api_key: Optional[str] = None
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
27
12
|
|
|
28
13
|
|
|
29
14
|
class CreateSessionResponse(BaseModel):
|
|
@@ -76,6 +61,7 @@ class AgentInfo(BaseModel):
|
|
|
76
61
|
tools (list[str]): Names of the tools the agent exposes.
|
|
77
62
|
state_fields (list[str]): Names of the agent's state fields.
|
|
78
63
|
linked_agents (list[str]): Names of agents linked to this one.
|
|
64
|
+
dependencies (list[str]): Names of the agent's ``Depends[T]`` fields.
|
|
79
65
|
"""
|
|
80
66
|
|
|
81
67
|
name: str
|
|
@@ -84,18 +70,26 @@ class AgentInfo(BaseModel):
|
|
|
84
70
|
tools: list[str] = Field(default_factory=list)
|
|
85
71
|
state_fields: list[str] = Field(default_factory=list)
|
|
86
72
|
linked_agents: list[str] = Field(default_factory=list)
|
|
73
|
+
dependencies: list[str] = Field(default_factory=list)
|
|
87
74
|
|
|
88
75
|
|
|
89
76
|
class SchemaResponse(BaseModel):
|
|
90
|
-
"""JSON schemas for an agent's request/response/stream/state models.
|
|
77
|
+
"""JSON schemas for an agent's construct/request/response/stream/state models.
|
|
91
78
|
|
|
92
79
|
Attributes:
|
|
80
|
+
construct (dict): JSON schema of the agent's construct model (the body
|
|
81
|
+
for creating a session / a sessionless job).
|
|
93
82
|
request (dict): JSON schema of the agent's request model.
|
|
94
83
|
response (dict): JSON schema of the agent's response model.
|
|
95
84
|
stream_event (dict): JSON schema of the agent's stream event model.
|
|
96
85
|
state (dict): JSON schema of the agent's state model.
|
|
97
86
|
"""
|
|
98
87
|
|
|
88
|
+
# Serialized as ``construct``; the Python attribute avoids shadowing the
|
|
89
|
+
# deprecated ``BaseModel.construct`` method (which pydantic warns about).
|
|
90
|
+
model_config = ConfigDict(populate_by_name=True)
|
|
91
|
+
|
|
92
|
+
construct_schema: dict = Field(serialization_alias="construct", validation_alias="construct")
|
|
99
93
|
request: dict
|
|
100
94
|
response: dict
|
|
101
95
|
stream_event: dict
|
pyagentic/api/_sessions.py
CHANGED
|
@@ -4,24 +4,28 @@ independent state and conversation history.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import uuid
|
|
7
|
-
from typing import TYPE_CHECKING, Optional
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
8
|
+
|
|
9
|
+
from pyagentic.api._build import build_agent
|
|
8
10
|
|
|
9
11
|
if TYPE_CHECKING:
|
|
10
12
|
from pyagentic._base._agent._agent import BaseAgent
|
|
11
|
-
from pyagentic.llm._provider import LLMProvider
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class SessionManager:
|
|
15
16
|
"""Manages agent sessions keyed by session ID.
|
|
16
17
|
|
|
17
18
|
Each session holds its own agent instance, so state and conversation
|
|
18
|
-
history are isolated per session.
|
|
19
|
+
history are isolated per session. Agents are constructed from a
|
|
20
|
+
construct-model payload (mirroring the constructor) plus developer-supplied
|
|
21
|
+
dependencies, via :func:`pyagentic.api._build.build_agent`.
|
|
19
22
|
"""
|
|
20
23
|
|
|
21
24
|
def __init__(
|
|
22
25
|
self,
|
|
23
26
|
agent_class: "type[BaseAgent]",
|
|
24
27
|
default_model: Optional[str] = None,
|
|
28
|
+
dependencies: Optional[list] = None,
|
|
25
29
|
) -> None:
|
|
26
30
|
"""Initialize the session manager.
|
|
27
31
|
|
|
@@ -29,42 +33,36 @@ class SessionManager:
|
|
|
29
33
|
agent_class (type[BaseAgent]): The agent class to instantiate for
|
|
30
34
|
each session.
|
|
31
35
|
default_model (Optional[str]): Model string used when a session is
|
|
32
|
-
created without one. ``None`` falls
|
|
33
|
-
own default model.
|
|
36
|
+
created without one in its construct payload. ``None`` falls
|
|
37
|
+
back to the agent class's own default model.
|
|
38
|
+
dependencies (Optional[list]): Instances or factories used to satisfy
|
|
39
|
+
the agent tree's ``Depends[T]`` fields (resolved by type).
|
|
34
40
|
"""
|
|
35
41
|
self._agent_class = agent_class
|
|
36
42
|
self._default_model = default_model
|
|
43
|
+
self._dependencies = dependencies or []
|
|
37
44
|
self._sessions: dict[str, "BaseAgent"] = {}
|
|
38
45
|
|
|
39
|
-
def create(
|
|
40
|
-
self,
|
|
41
|
-
*,
|
|
42
|
-
model: Optional[str] = None,
|
|
43
|
-
api_key: Optional[str] = None,
|
|
44
|
-
provider: Optional["LLMProvider"] = None,
|
|
45
|
-
) -> str:
|
|
46
|
+
def create(self, *, construct_data: Any = None) -> str:
|
|
46
47
|
"""Create a new session with a fresh agent instance.
|
|
47
48
|
|
|
48
49
|
Args:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
instance. Overrides model/api_key.
|
|
50
|
+
construct_data (Any): An instance of the agent's
|
|
51
|
+
``__construct_model__`` (or a dict of the same shape) carrying
|
|
52
|
+
the serializable construction data (state, nested linked-agent
|
|
53
|
+
state, ``model``/``api_key``). ``None`` builds with defaults.
|
|
54
54
|
|
|
55
55
|
Returns:
|
|
56
56
|
str: The new session ID.
|
|
57
57
|
"""
|
|
58
58
|
session_id = uuid.uuid4().hex[:12]
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
kwargs["model"] = resolved_model
|
|
67
|
-
agent = self._agent_class(**kwargs)
|
|
60
|
+
agent = build_agent(
|
|
61
|
+
self._agent_class,
|
|
62
|
+
construct_data,
|
|
63
|
+
self._dependencies,
|
|
64
|
+
default_model=self._default_model,
|
|
65
|
+
)
|
|
68
66
|
|
|
69
67
|
self._sessions[session_id] = agent
|
|
70
68
|
return session_id
|
pyagentic/api/jobs/_models.py
CHANGED
|
@@ -37,6 +37,9 @@ class JobRecord(BaseModel):
|
|
|
37
37
|
session_id (Optional[str]): Session the job belongs to, if any.
|
|
38
38
|
status (JobStatus): Current lifecycle state.
|
|
39
39
|
request (dict): The submitted agent input kwargs.
|
|
40
|
+
construct_payload (Optional[dict]): Construct-model payload for a
|
|
41
|
+
sessionless job, used to build a fresh agent (and survive recovery).
|
|
42
|
+
``None`` for session-bound jobs (which reuse the session's live agent).
|
|
40
43
|
result_json (Optional[str]): Final agent response as a raw JSON
|
|
41
44
|
string, set when the job succeeds.
|
|
42
45
|
error (Optional[str]): Failure description, set when the job fails.
|
|
@@ -49,6 +52,7 @@ class JobRecord(BaseModel):
|
|
|
49
52
|
session_id: Optional[str] = None
|
|
50
53
|
status: JobStatus = JobStatus.QUEUED
|
|
51
54
|
request: dict = Field(default_factory=dict)
|
|
55
|
+
construct_payload: Optional[dict] = None
|
|
52
56
|
result_json: Optional[str] = None
|
|
53
57
|
error: Optional[str] = None
|
|
54
58
|
created_at: float = Field(default_factory=time.time)
|
|
@@ -138,7 +138,10 @@ class JobOrchestrator:
|
|
|
138
138
|
# ---- submission & execution ----
|
|
139
139
|
|
|
140
140
|
async def submit(
|
|
141
|
-
self,
|
|
141
|
+
self,
|
|
142
|
+
request: dict,
|
|
143
|
+
session_id: Optional[str] = None,
|
|
144
|
+
construct: Optional[dict] = None,
|
|
142
145
|
) -> JobRecord:
|
|
143
146
|
"""Create a durable job record and route it for execution.
|
|
144
147
|
|
|
@@ -150,6 +153,8 @@ class JobOrchestrator:
|
|
|
150
153
|
Args:
|
|
151
154
|
request (dict): Agent input kwargs.
|
|
152
155
|
session_id (Optional[str]): Session to bind the job to.
|
|
156
|
+
construct (Optional[dict]): Construct-model payload for building a
|
|
157
|
+
fresh agent (sessionless jobs only). Persisted for recovery.
|
|
153
158
|
|
|
154
159
|
Returns:
|
|
155
160
|
JobRecord: The persisted record in its initial queued state.
|
|
@@ -160,6 +165,7 @@ class JobOrchestrator:
|
|
|
160
165
|
session_id=session_id,
|
|
161
166
|
status=JobStatus.QUEUED,
|
|
162
167
|
request=request,
|
|
168
|
+
construct_payload=construct,
|
|
163
169
|
)
|
|
164
170
|
await self.store.create_job(job)
|
|
165
171
|
self._next_seq[job.job_id] = 0
|
pyagentic/api/jobs/_routes.py
CHANGED
|
@@ -10,7 +10,7 @@ from typing import Optional
|
|
|
10
10
|
|
|
11
11
|
from fastapi import APIRouter, HTTPException, Request
|
|
12
12
|
from fastapi.responses import StreamingResponse
|
|
13
|
-
from pydantic import create_model
|
|
13
|
+
from pydantic import ConfigDict, Field, create_model
|
|
14
14
|
|
|
15
15
|
from pyagentic._base._agent._agent import BaseAgent
|
|
16
16
|
from pyagentic.api._sessions import SessionManager
|
|
@@ -138,12 +138,17 @@ def build_jobs_router(
|
|
|
138
138
|
"""
|
|
139
139
|
router = APIRouter()
|
|
140
140
|
RequestModel = agent_class.__request_model__
|
|
141
|
+
ConstructModel = agent_class.__construct_model__
|
|
141
142
|
ResponseModel = agent_class.__response_model__
|
|
142
143
|
|
|
144
|
+
# ``construct`` is the wire field name; the Python attribute is suffixed to
|
|
145
|
+
# avoid shadowing the deprecated ``BaseModel.construct`` method.
|
|
143
146
|
SubmitModel = create_model(
|
|
144
147
|
f"{agent_class.__name__}JobSubmitRequest",
|
|
148
|
+
__config__=ConfigDict(populate_by_name=True),
|
|
145
149
|
input=(RequestModel, ...),
|
|
146
150
|
session_id=(Optional[str], None),
|
|
151
|
+
construct_payload=(Optional[ConstructModel], Field(None, alias="construct")),
|
|
147
152
|
)
|
|
148
153
|
|
|
149
154
|
# Narrow the loosely-typed JobSnapshot.result to this agent's response model
|
|
@@ -156,14 +161,25 @@ def build_jobs_router(
|
|
|
156
161
|
|
|
157
162
|
@router.post("/jobs", status_code=202, response_model=JobSubmitResponse)
|
|
158
163
|
async def submit_job(req: SubmitModel) -> dict: # type: ignore[valid-type]
|
|
159
|
-
"""Submit an agent run as an async job.
|
|
164
|
+
"""Submit an agent run as an async job.
|
|
165
|
+
|
|
166
|
+
A session-bound job (``session_id`` set) runs on that session's live
|
|
167
|
+
agent. A sessionless job builds a fresh agent from the optional
|
|
168
|
+
``construct`` payload (the agent's construct model) plus the server's
|
|
169
|
+
injected dependencies.
|
|
170
|
+
"""
|
|
160
171
|
await orchestrator.ensure_started()
|
|
161
172
|
if req.session_id is not None and not sessions.exists(req.session_id):
|
|
162
173
|
raise HTTPException(status_code=404, detail="Session not found")
|
|
163
174
|
request = {
|
|
164
175
|
field: getattr(req.input, field) for field in type(req.input).model_fields
|
|
165
176
|
}
|
|
166
|
-
|
|
177
|
+
construct = None
|
|
178
|
+
if req.session_id is None and req.construct_payload is not None:
|
|
179
|
+
construct = req.construct_payload.model_dump()
|
|
180
|
+
job = await orchestrator.submit(
|
|
181
|
+
request, session_id=req.session_id, construct=construct
|
|
182
|
+
)
|
|
167
183
|
return {"job_id": job.job_id, "status": job.status.value}
|
|
168
184
|
|
|
169
185
|
@router.get("/jobs", response_model=JobListResponse)
|
|
@@ -23,6 +23,7 @@ def build_backend(
|
|
|
23
23
|
*,
|
|
24
24
|
max_concurrency: int = 8,
|
|
25
25
|
default_model: Optional[str] = None,
|
|
26
|
+
dependencies: Optional[list] = None,
|
|
26
27
|
) -> ExecutionBackend:
|
|
27
28
|
"""Build the in-process ExecutionBackend.
|
|
28
29
|
|
|
@@ -31,6 +32,8 @@ def build_backend(
|
|
|
31
32
|
sessions (SessionManager): Live session registry.
|
|
32
33
|
max_concurrency (int): Max concurrent agent runs.
|
|
33
34
|
default_model (Optional[str]): Model for sessionless agents.
|
|
35
|
+
dependencies (Optional[list]): Providers for ``Depends[T]`` fields used
|
|
36
|
+
when building a fresh agent for a sessionless job.
|
|
34
37
|
|
|
35
38
|
Returns:
|
|
36
39
|
ExecutionBackend: A configured :class:`InProcessBackend`.
|
|
@@ -40,4 +43,5 @@ def build_backend(
|
|
|
40
43
|
sessions,
|
|
41
44
|
max_concurrency=max_concurrency,
|
|
42
45
|
default_model=default_model,
|
|
46
|
+
dependencies=dependencies,
|
|
43
47
|
)
|
|
@@ -17,6 +17,7 @@ from pyagentic.api.jobs._models import (
|
|
|
17
17
|
_build_prompt,
|
|
18
18
|
)
|
|
19
19
|
from pyagentic.api.jobs.backends._base import EmitFn, LocalExecutionBackend
|
|
20
|
+
from pyagentic.api._build import build_agent
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING:
|
|
22
23
|
from pyagentic.api._sessions import SessionManager
|
|
@@ -37,6 +38,7 @@ class InProcessBackend(LocalExecutionBackend):
|
|
|
37
38
|
*,
|
|
38
39
|
max_concurrency: int = 8,
|
|
39
40
|
default_model: Optional[str] = None,
|
|
41
|
+
dependencies: Optional[list] = None,
|
|
40
42
|
) -> None:
|
|
41
43
|
"""Create the backend.
|
|
42
44
|
|
|
@@ -46,11 +48,14 @@ class InProcessBackend(LocalExecutionBackend):
|
|
|
46
48
|
sessions (SessionManager): Live session registry for session-bound jobs.
|
|
47
49
|
max_concurrency (int): Max concurrent agent runs.
|
|
48
50
|
default_model (Optional[str]): Model for sessionless agents.
|
|
51
|
+
dependencies (Optional[list]): Providers for the agent's ``Depends[T]``
|
|
52
|
+
fields when building a fresh agent for a sessionless job.
|
|
49
53
|
"""
|
|
50
54
|
super().__init__()
|
|
51
55
|
self._agent_class = agent_class
|
|
52
56
|
self._sessions = sessions
|
|
53
57
|
self._default_model = default_model
|
|
58
|
+
self._dependencies = dependencies or []
|
|
54
59
|
self._max_concurrency = max_concurrency
|
|
55
60
|
self._sem = asyncio.Semaphore(max_concurrency)
|
|
56
61
|
|
|
@@ -72,10 +77,12 @@ class InProcessBackend(LocalExecutionBackend):
|
|
|
72
77
|
agent = self._sessions.get(session_id)
|
|
73
78
|
own_agent = False
|
|
74
79
|
else:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
agent = build_agent(
|
|
81
|
+
self._agent_class,
|
|
82
|
+
job.construct_payload,
|
|
83
|
+
self._dependencies,
|
|
84
|
+
default_model=self._default_model,
|
|
85
|
+
)
|
|
79
86
|
own_agent = True
|
|
80
87
|
|
|
81
88
|
prompt = _build_prompt(job.request)
|
|
@@ -23,15 +23,16 @@ from pyagentic.api.jobs.store._base import JobStore
|
|
|
23
23
|
|
|
24
24
|
_SCHEMA = """
|
|
25
25
|
CREATE TABLE IF NOT EXISTS jobs (
|
|
26
|
-
job_id
|
|
27
|
-
session_id
|
|
28
|
-
status
|
|
29
|
-
request_json
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
job_id TEXT PRIMARY KEY,
|
|
27
|
+
session_id TEXT,
|
|
28
|
+
status TEXT NOT NULL,
|
|
29
|
+
request_json TEXT NOT NULL,
|
|
30
|
+
construct_json TEXT,
|
|
31
|
+
result_json TEXT,
|
|
32
|
+
error TEXT,
|
|
33
|
+
created_at REAL NOT NULL,
|
|
34
|
+
started_at REAL,
|
|
35
|
+
finished_at REAL
|
|
35
36
|
);
|
|
36
37
|
CREATE INDEX IF NOT EXISTS idx_jobs_session ON jobs(session_id);
|
|
37
38
|
CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
|
|
@@ -54,6 +55,9 @@ def _row_to_record(row: sqlite3.Row) -> JobRecord:
|
|
|
54
55
|
session_id=row["session_id"],
|
|
55
56
|
status=JobStatus(row["status"]),
|
|
56
57
|
request=json.loads(row["request_json"]),
|
|
58
|
+
construct_payload=(
|
|
59
|
+
json.loads(row["construct_json"]) if row["construct_json"] else None
|
|
60
|
+
),
|
|
57
61
|
result_json=row["result_json"],
|
|
58
62
|
error=row["error"],
|
|
59
63
|
created_at=row["created_at"],
|
|
@@ -128,13 +132,18 @@ class SQLiteJobStore(JobStore):
|
|
|
128
132
|
def _insert(conn: sqlite3.Connection) -> None:
|
|
129
133
|
conn.execute(
|
|
130
134
|
"INSERT INTO jobs (job_id, session_id, status, request_json, "
|
|
131
|
-
"result_json, error, created_at, started_at, finished_at) "
|
|
132
|
-
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
135
|
+
"construct_json, result_json, error, created_at, started_at, finished_at) "
|
|
136
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
133
137
|
(
|
|
134
138
|
job.job_id,
|
|
135
139
|
job.session_id,
|
|
136
140
|
job.status.value,
|
|
137
141
|
json.dumps(job.request, default=str),
|
|
142
|
+
(
|
|
143
|
+
json.dumps(job.construct_payload, default=str)
|
|
144
|
+
if job.construct_payload is not None
|
|
145
|
+
else None
|
|
146
|
+
),
|
|
138
147
|
job.result_json,
|
|
139
148
|
job.error,
|
|
140
149
|
job.created_at,
|
|
@@ -1,40 +1,42 @@
|
|
|
1
|
-
pyagentic/__init__.py,sha256=
|
|
1
|
+
pyagentic/__init__.py,sha256=S1nJnbbDZzkH0AbKYWIAWCPhZJW2a2Ml2zdxi91WSPk,2012
|
|
2
2
|
pyagentic/_version_scheme.py,sha256=hUYPvGhWz5L7RSuVxIqW3h10i4zNjfYXMofmoIk8ZBM,1569
|
|
3
3
|
pyagentic/logging.py,sha256=vWLKGx0jZurWQT5pMr6fLH5NgJnZd_eaB97geBnqlwI,1718
|
|
4
4
|
pyagentic/updates.py,sha256=JrBxbmTRxS-4tfSrai_wPK-dxSyCiNrYwC5MgrzGj54,870
|
|
5
5
|
pyagentic/_base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
pyagentic/_base/_depends.py,sha256=O59cvg4uc4feDrPjn9HqryrMGzqNyulyv8intAmQ8PE,2188
|
|
6
7
|
pyagentic/_base/_exceptions.py,sha256=r_wcuWgc_Y3g_D7DwLQL8NbTphIAeeNpIp-WDHMvvOk,3746
|
|
7
8
|
pyagentic/_base/_info.py,sha256=2bY9_Ua9N7m94hcv6gLJCBZ2guxXTJf3YVOuJAyG4ns,2995
|
|
8
9
|
pyagentic/_base/_mcp.py,sha256=YPRaqVFrgKDA5BZtvVrcScyADjo66cdB4_h6nXpRJBI,8329
|
|
9
|
-
pyagentic/_base/_metaclasses.py,sha256=
|
|
10
|
+
pyagentic/_base/_metaclasses.py,sha256=0PQ3ipidOPKo6xE5Ra2ZBhme74s3Ss9BLj9O0RehueA,34452
|
|
10
11
|
pyagentic/_base/_ref.py,sha256=Ez1Iexxl7NLBK91frcgz_eFbrXfLZpm9fvkYHMFWA_w,3179
|
|
11
12
|
pyagentic/_base/_spec.py,sha256=TH-DZTYFcxXmzxGympH4vATPPlpI2o88Wlv6S7UuNlQ,7112
|
|
12
13
|
pyagentic/_base/_state.py,sha256=swjMl22rOgaIVEuqyL1bG7gpYpumuOZ6ytNImV_2lGw,1794
|
|
13
14
|
pyagentic/_base/_tool.py,sha256=6Zkd20iwQX1uS-rw4i9IA8doM8qjchEZ3EmMk5Li9SE,11040
|
|
14
15
|
pyagentic/_base/_validation.py,sha256=BBscZMrXB09LNNP17pYOAY6I4iCW8r2GBVStqBd-9f8,3833
|
|
15
16
|
pyagentic/_base/_agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
pyagentic/_base/_agent/_agent.py,sha256=
|
|
17
|
+
pyagentic/_base/_agent/_agent.py,sha256=P35dpRp4Ihpc7AzGLW8bo_aj1pR2orvxu3a5777NIL8,39549
|
|
17
18
|
pyagentic/_base/_agent/_agent_linking.py,sha256=gSHQkbH-BeKG8moROKDZiLs0ktLzW9bOVo0DFq3UNs8,1841
|
|
18
19
|
pyagentic/_base/_agent/_agent_state.py,sha256=gvx0rQ1gaWfZHHAONX4YNsO_HE0xYQ14_GBKMP4Af-o,11507
|
|
19
20
|
pyagentic/_utils/_typing.py,sha256=5aRZZOVzR0ZgnBfejkw__1yoARhn70P5QzA81sxUkls,3681
|
|
20
21
|
pyagentic/_utils/_warnings.py,sha256=mjlBiYHGnnh5599Gsq5ke8mBFS6WsdhNa7jk20TT9_U,1330
|
|
21
|
-
pyagentic/api/__init__.py,sha256=
|
|
22
|
-
pyagentic/api/_app.py,sha256=
|
|
22
|
+
pyagentic/api/__init__.py,sha256=7CklNYjxVj5XOyqTWvvWKZU6IYuU8CAfpOhNeGMH1d4,1712
|
|
23
|
+
pyagentic/api/_app.py,sha256=G34AdK-h88EEuJxzmzzNPkskR5dUGKRHz16PE0g8i3E,25146
|
|
24
|
+
pyagentic/api/_build.py,sha256=HfIAr9YREZk2BvyRwy4HmkMONQpnPptqytpxc2l_QB8,8133
|
|
23
25
|
pyagentic/api/_config.py,sha256=2YaFBDW7aaRueemHPBd1XzW9m0oqVCBilt8MA-x6QqI,6547
|
|
24
26
|
pyagentic/api/_docker.py,sha256=AoAQ66gT-Uf5WhiYpImKSpTPyboiTaqWvxG7mBLe54I,2229
|
|
25
27
|
pyagentic/api/_mcp_server.py,sha256=9aOC18-FSv1qS7umqFHxMKV5FujblziR-_NDfNsXmFo,5032
|
|
26
|
-
pyagentic/api/_models.py,sha256=
|
|
27
|
-
pyagentic/api/_sessions.py,sha256=
|
|
28
|
+
pyagentic/api/_models.py,sha256=ReDeuzQWIZ7YYDAwa8yfPYZNR6cPQlBYRMtsni_5crw,3633
|
|
29
|
+
pyagentic/api/_sessions.py,sha256=P-i67Jii3cAfCrotkWGxYnf-8LCkc96Vn9gnDgXrS6Y,3754
|
|
28
30
|
pyagentic/api/jobs/__init__.py,sha256=SGZOE86xVwv4hX2zjpm8uLZZhizz0rv3EV5suUuiM8M,979
|
|
29
|
-
pyagentic/api/jobs/_models.py,sha256=
|
|
30
|
-
pyagentic/api/jobs/_orchestrator.py,sha256=
|
|
31
|
-
pyagentic/api/jobs/_routes.py,sha256=
|
|
32
|
-
pyagentic/api/jobs/backends/__init__.py,sha256=
|
|
31
|
+
pyagentic/api/jobs/_models.py,sha256=Q0E69uVkEibEUItuS1U0C38YG5ixKnp8IzxNvLVkDk8,7234
|
|
32
|
+
pyagentic/api/jobs/_orchestrator.py,sha256=umt6IAIPpCJiDH7HYMSwT8X1SVDVUQ7lqxBpDiI1k4c,17415
|
|
33
|
+
pyagentic/api/jobs/_routes.py,sha256=1tR-_udgiBIwLVy_JIvdW9Ik5BiYMEzAlIbufArzATU,10593
|
|
34
|
+
pyagentic/api/jobs/backends/__init__.py,sha256=giGsZOH4Q2l5HU8ccO5q-sKsKdR23zOzWlECc_n4CwE,1544
|
|
33
35
|
pyagentic/api/jobs/backends/_base.py,sha256=LITAW0FNWKAdEhmnixe5UakbBrxYY7rWbZD7jK6VZL8,4453
|
|
34
|
-
pyagentic/api/jobs/backends/_in_process.py,sha256=
|
|
36
|
+
pyagentic/api/jobs/backends/_in_process.py,sha256=pMyewlmkAUTsIn_q29zmYHPyyKNZNi2zhqZS2gQvyf8,4755
|
|
35
37
|
pyagentic/api/jobs/store/__init__.py,sha256=jxm7hEa2AiV44_1UfYuJ-RofeM6I-DFMCywzYHNk1Nc,507
|
|
36
38
|
pyagentic/api/jobs/store/_base.py,sha256=Ao9mRSRiwEz44mJRFWfqM5aZkXbemULoNwUBDRNDYuM,4856
|
|
37
|
-
pyagentic/api/jobs/store/_sqlite.py,sha256=
|
|
39
|
+
pyagentic/api/jobs/store/_sqlite.py,sha256=hX0MIo1PVYmlsw8Hc6WDb4qM8tyavHzYVd37uK0hRQ0,12675
|
|
38
40
|
pyagentic/llm/__init__.py,sha256=4ABk1NnYmw_yf9qeS0WVZb8dLl3gC1xtoD7RmH9_G2U,966
|
|
39
41
|
pyagentic/llm/_anthropic.py,sha256=oiyin7QCeHlo9hn8YGJri0QheOa_uvacQSvyS9djMsE,8372
|
|
40
42
|
pyagentic/llm/_gemini.py,sha256=cC1borYCdL9EpdJ51j0kJRQuTnSdMsG7JN5ruq3w5Cg,10385
|
|
@@ -52,8 +54,8 @@ pyagentic/tracing/__init__.py,sha256=ddMOl_-Nf3WOhu-PYxSJ3kqHZ3eQXAOM5aySn9YmDtA
|
|
|
52
54
|
pyagentic/tracing/_basic.py,sha256=jUBKdkOPveCjBzEhHKS_Rvp5l38iZg-5nFIl6xEwOVk,8906
|
|
53
55
|
pyagentic/tracing/_langfuse.py,sha256=5b5tdT-gyRJP5HDcvCS4tn_k3AFDI6eNsXOd6On5gKQ,13986
|
|
54
56
|
pyagentic/tracing/_tracer.py,sha256=HMisWfRLAQ9sdcjOvImiLij6OASd3siM8K-7nUfM3xs,7656
|
|
55
|
-
pyagentic_core-2.
|
|
56
|
-
pyagentic_core-2.
|
|
57
|
-
pyagentic_core-2.
|
|
58
|
-
pyagentic_core-2.
|
|
59
|
-
pyagentic_core-2.
|
|
57
|
+
pyagentic_core-2.11.1.dev1.dist-info/licenses/LICENSE,sha256=wcOzTj82hOc96HztJk2VzLB2hFHpdIGpjz8vvbpP1_s,1069
|
|
58
|
+
pyagentic_core-2.11.1.dev1.dist-info/METADATA,sha256=GOf2IQSJPjEu105ZiXEf0WfET_7YC2la3d1LH1_Y77I,8212
|
|
59
|
+
pyagentic_core-2.11.1.dev1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
60
|
+
pyagentic_core-2.11.1.dev1.dist-info/top_level.txt,sha256=lAWfd-ay434uEpSg2FM0ySN0o4vgNGE6D9dJkIhGyfY,10
|
|
61
|
+
pyagentic_core-2.11.1.dev1.dist-info/RECORD,,
|
|
File without changes
|
{pyagentic_core-2.10.1.dev2.dist-info → pyagentic_core-2.11.1.dev1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|