pyagentic-core 2.9.1.dev1__py3-none-any.whl → 2.10.1.dev2__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/api/__init__.py +21 -0
- pyagentic/api/_app.py +77 -54
- pyagentic/api/_models.py +128 -0
- pyagentic/api/jobs/_models.py +91 -0
- pyagentic/api/jobs/_routes.py +18 -5
- {pyagentic_core-2.9.1.dev1.dist-info → pyagentic_core-2.10.1.dev2.dist-info}/METADATA +1 -1
- {pyagentic_core-2.9.1.dev1.dist-info → pyagentic_core-2.10.1.dev2.dist-info}/RECORD +10 -9
- {pyagentic_core-2.9.1.dev1.dist-info → pyagentic_core-2.10.1.dev2.dist-info}/WHEEL +0 -0
- {pyagentic_core-2.9.1.dev1.dist-info → pyagentic_core-2.10.1.dev2.dist-info}/licenses/LICENSE +0 -0
- {pyagentic_core-2.9.1.dev1.dist-info → pyagentic_core-2.10.1.dev2.dist-info}/top_level.txt +0 -0
pyagentic/api/__init__.py
CHANGED
|
@@ -21,6 +21,17 @@ from pyagentic.api._config import (
|
|
|
21
21
|
)
|
|
22
22
|
from pyagentic.api._docker import generate_dockerfile, write_dockerfile
|
|
23
23
|
from pyagentic.api._mcp_server import mount_mcp
|
|
24
|
+
from pyagentic.api._models import (
|
|
25
|
+
AgentInfo,
|
|
26
|
+
AppAgentEntry,
|
|
27
|
+
AppIndex,
|
|
28
|
+
CreateSessionRequest,
|
|
29
|
+
CreateSessionResponse,
|
|
30
|
+
DeleteSessionResponse,
|
|
31
|
+
HealthResponse,
|
|
32
|
+
ListSessionsResponse,
|
|
33
|
+
SchemaResponse,
|
|
34
|
+
)
|
|
24
35
|
from pyagentic.api._sessions import SessionManager
|
|
25
36
|
|
|
26
37
|
__all__ = [
|
|
@@ -35,4 +46,14 @@ __all__ = [
|
|
|
35
46
|
"JobsConfig",
|
|
36
47
|
"load_config",
|
|
37
48
|
"SessionManager",
|
|
49
|
+
# HTTP request/response models
|
|
50
|
+
"AgentInfo",
|
|
51
|
+
"AppAgentEntry",
|
|
52
|
+
"AppIndex",
|
|
53
|
+
"CreateSessionRequest",
|
|
54
|
+
"CreateSessionResponse",
|
|
55
|
+
"DeleteSessionResponse",
|
|
56
|
+
"HealthResponse",
|
|
57
|
+
"ListSessionsResponse",
|
|
58
|
+
"SchemaResponse",
|
|
38
59
|
]
|
pyagentic/api/_app.py
CHANGED
|
@@ -24,12 +24,22 @@ from typing import Optional, Union
|
|
|
24
24
|
from dotenv import load_dotenv
|
|
25
25
|
from fastapi import APIRouter, FastAPI, HTTPException
|
|
26
26
|
from fastapi.responses import StreamingResponse
|
|
27
|
-
from pydantic import BaseModel
|
|
28
27
|
|
|
29
28
|
from pyagentic._base._agent._agent import BaseAgent
|
|
30
29
|
from pyagentic.models.llm import LLMResponse
|
|
31
30
|
from pyagentic.models.response import AgentResponse, ToolResponse
|
|
32
31
|
from pyagentic.api._config import AgentsConfig, JobsConfig, load_config
|
|
32
|
+
from pyagentic.api._models import (
|
|
33
|
+
AgentInfo,
|
|
34
|
+
AppAgentEntry,
|
|
35
|
+
AppIndex,
|
|
36
|
+
CreateSessionRequest,
|
|
37
|
+
CreateSessionResponse,
|
|
38
|
+
DeleteSessionResponse,
|
|
39
|
+
HealthResponse,
|
|
40
|
+
ListSessionsResponse,
|
|
41
|
+
SchemaResponse,
|
|
42
|
+
)
|
|
33
43
|
from pyagentic.api._sessions import SessionManager
|
|
34
44
|
|
|
35
45
|
# Agents may be passed as a single class, a list of classes, or a
|
|
@@ -37,19 +47,6 @@ from pyagentic.api._sessions import SessionManager
|
|
|
37
47
|
AgentsArg = Union[type[BaseAgent], list[type[BaseAgent]], dict[str, type[BaseAgent]]]
|
|
38
48
|
|
|
39
49
|
|
|
40
|
-
class CreateSessionRequest(BaseModel):
|
|
41
|
-
"""Request body for creating a new agent session.
|
|
42
|
-
|
|
43
|
-
Attributes:
|
|
44
|
-
model (Optional[str]): LLM model string override
|
|
45
|
-
(e.g. ``'openai::gpt-4o'``).
|
|
46
|
-
api_key (Optional[str]): API key for the model provider.
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
model: Optional[str] = None
|
|
50
|
-
api_key: Optional[str] = None
|
|
51
|
-
|
|
52
|
-
|
|
53
50
|
_REQUIRED_AGENT_ATTRS = (
|
|
54
51
|
"__request_model__",
|
|
55
52
|
"__response_model__",
|
|
@@ -105,14 +102,23 @@ def _store_path_for(base: str, prefix: str, multi: bool) -> str:
|
|
|
105
102
|
return str(p.with_name(f"{p.stem}-{slug}{p.suffix}"))
|
|
106
103
|
|
|
107
104
|
|
|
105
|
+
def _normalize_prefix(prefix: str) -> str:
|
|
106
|
+
"""Normalize a mount prefix: '' stays root, others get a single leading slash."""
|
|
107
|
+
prefix = prefix.strip()
|
|
108
|
+
if not prefix or prefix == "/":
|
|
109
|
+
return ""
|
|
110
|
+
return "/" + prefix.strip("/")
|
|
111
|
+
|
|
112
|
+
|
|
108
113
|
def _normalize_agents(agents: AgentsArg) -> dict[str, type[BaseAgent]]:
|
|
109
114
|
"""Normalize the ``agents`` argument into a {prefix: class} mapping.
|
|
110
115
|
|
|
111
116
|
A single class is mounted at the root (""); a list derives a prefix from
|
|
112
|
-
each class name; a dict
|
|
117
|
+
each class name; a dict's keys are used as prefixes (a leading slash is
|
|
118
|
+
added if missing, so ``{"duck": Duck}`` and ``{"/duck": Duck}`` are equivalent).
|
|
113
119
|
"""
|
|
114
120
|
if isinstance(agents, dict):
|
|
115
|
-
mapping =
|
|
121
|
+
mapping = {_normalize_prefix(k): v for k, v in agents.items()}
|
|
116
122
|
elif isinstance(agents, (list, tuple)):
|
|
117
123
|
if len(agents) == 1:
|
|
118
124
|
mapping = {"": agents[0]}
|
|
@@ -212,32 +218,32 @@ def create_router(
|
|
|
212
218
|
|
|
213
219
|
# ---- info routes ----
|
|
214
220
|
|
|
215
|
-
@router.get("/", name=f"{slug}_info")
|
|
216
|
-
async def agent_info() ->
|
|
221
|
+
@router.get("/", response_model=AgentInfo, name=f"{slug}_info")
|
|
222
|
+
async def agent_info() -> AgentInfo:
|
|
217
223
|
"""Return basic agent metadata."""
|
|
218
|
-
return
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
@router.get("/health", name=f"{slug}_health")
|
|
228
|
-
async def health() ->
|
|
224
|
+
return AgentInfo(
|
|
225
|
+
name=name,
|
|
226
|
+
version=version,
|
|
227
|
+
agent_class=agent_class.__name__,
|
|
228
|
+
tools=list(agent_class.__tool_defs__.keys()),
|
|
229
|
+
state_fields=list(agent_class.__state_defs__.keys()),
|
|
230
|
+
linked_agents=list(agent_class.__linked_agents__.keys()),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
@router.get("/health", response_model=HealthResponse, name=f"{slug}_health")
|
|
234
|
+
async def health() -> HealthResponse:
|
|
229
235
|
"""Liveness probe endpoint."""
|
|
230
|
-
return
|
|
236
|
+
return HealthResponse()
|
|
231
237
|
|
|
232
|
-
@router.get("/schema", name=f"{slug}_schema")
|
|
233
|
-
async def schema() ->
|
|
238
|
+
@router.get("/schema", response_model=SchemaResponse, name=f"{slug}_schema")
|
|
239
|
+
async def schema() -> SchemaResponse:
|
|
234
240
|
"""Return JSON schemas for request, response, stream event, and state models."""
|
|
235
|
-
return
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
+
return SchemaResponse(
|
|
242
|
+
request=RequestModel.model_json_schema(),
|
|
243
|
+
response=ResponseModel.model_json_schema(),
|
|
244
|
+
stream_event=StreamEventModel.model_json_schema(),
|
|
245
|
+
state=StateModel.model_json_schema(),
|
|
246
|
+
)
|
|
241
247
|
|
|
242
248
|
if sessions:
|
|
243
249
|
_register_session_routes(
|
|
@@ -307,27 +313,42 @@ def _register_session_routes(
|
|
|
307
313
|
|
|
308
314
|
# ---- session routes ----
|
|
309
315
|
|
|
310
|
-
@router.post(
|
|
311
|
-
|
|
316
|
+
@router.post(
|
|
317
|
+
"/sessions",
|
|
318
|
+
status_code=201,
|
|
319
|
+
response_model=CreateSessionResponse,
|
|
320
|
+
name=f"{slug}_create_session",
|
|
321
|
+
)
|
|
322
|
+
async def create_session(
|
|
323
|
+
req: Optional[CreateSessionRequest] = None,
|
|
324
|
+
) -> CreateSessionResponse:
|
|
312
325
|
"""Create a new agent session."""
|
|
313
326
|
model = req.model if req else None
|
|
314
327
|
api_key = req.api_key if req else None
|
|
315
328
|
session_id = sessions.create(model=model, api_key=api_key)
|
|
316
|
-
return
|
|
329
|
+
return CreateSessionResponse(session_id=session_id)
|
|
317
330
|
|
|
318
|
-
@router.get(
|
|
319
|
-
|
|
331
|
+
@router.get(
|
|
332
|
+
"/sessions",
|
|
333
|
+
response_model=ListSessionsResponse,
|
|
334
|
+
name=f"{slug}_list_sessions",
|
|
335
|
+
)
|
|
336
|
+
async def list_sessions() -> ListSessionsResponse:
|
|
320
337
|
"""List all active session IDs."""
|
|
321
|
-
return
|
|
338
|
+
return ListSessionsResponse(sessions=sessions.list_sessions())
|
|
322
339
|
|
|
323
|
-
@router.delete(
|
|
324
|
-
|
|
340
|
+
@router.delete(
|
|
341
|
+
"/sessions/{session_id}",
|
|
342
|
+
response_model=DeleteSessionResponse,
|
|
343
|
+
name=f"{slug}_delete_session",
|
|
344
|
+
)
|
|
345
|
+
async def delete_session(session_id: str) -> DeleteSessionResponse:
|
|
325
346
|
"""Delete an existing session."""
|
|
326
347
|
try:
|
|
327
348
|
sessions.delete(session_id)
|
|
328
349
|
except KeyError:
|
|
329
350
|
raise HTTPException(status_code=404, detail="Session not found")
|
|
330
|
-
return
|
|
351
|
+
return DeleteSessionResponse(deleted=session_id)
|
|
331
352
|
|
|
332
353
|
# ---- chat routes ----
|
|
333
354
|
|
|
@@ -547,7 +568,9 @@ def create_app(
|
|
|
547
568
|
jobs_config=jobs_cfg,
|
|
548
569
|
)
|
|
549
570
|
app.include_router(router, prefix=prefix)
|
|
550
|
-
index.append(
|
|
571
|
+
index.append(
|
|
572
|
+
AppAgentEntry(agent_class=agent_class.__name__, prefix=prefix or "/")
|
|
573
|
+
)
|
|
551
574
|
if mcp:
|
|
552
575
|
mount_mcp(
|
|
553
576
|
app,
|
|
@@ -566,14 +589,14 @@ def create_app(
|
|
|
566
589
|
# When no agent occupies the root, add a top-level index + health probe.
|
|
567
590
|
if not mounted_at_root:
|
|
568
591
|
|
|
569
|
-
@app.get("/")
|
|
570
|
-
async def app_index() ->
|
|
592
|
+
@app.get("/", response_model=AppIndex)
|
|
593
|
+
async def app_index() -> AppIndex:
|
|
571
594
|
"""List the agents mounted on this app."""
|
|
572
|
-
return
|
|
595
|
+
return AppIndex(name=name, version=version, agents=index)
|
|
573
596
|
|
|
574
|
-
@app.get("/health")
|
|
575
|
-
async def app_health() ->
|
|
597
|
+
@app.get("/health", response_model=HealthResponse)
|
|
598
|
+
async def app_health() -> HealthResponse:
|
|
576
599
|
"""Liveness probe endpoint."""
|
|
577
|
-
return
|
|
600
|
+
return HealthResponse()
|
|
578
601
|
|
|
579
602
|
return app
|
pyagentic/api/_models.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Request and response models for the HTTP API surface.
|
|
3
|
+
|
|
4
|
+
These cover the static (agent-independent) endpoints — info, health, schema,
|
|
5
|
+
sessions CRUD, and the multi-agent app index. The chat, stream, and state
|
|
6
|
+
endpoints are typed directly from each agent's metaclass-generated models
|
|
7
|
+
(``__request_model__`` / ``__response_model__`` / ``__stream_event_model__`` /
|
|
8
|
+
``__state_class__``), so they are not duplicated here.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Optional
|
|
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
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CreateSessionResponse(BaseModel):
|
|
30
|
+
"""Response returned after creating a session.
|
|
31
|
+
|
|
32
|
+
Attributes:
|
|
33
|
+
session_id (str): Identifier of the newly created session.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
session_id: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ListSessionsResponse(BaseModel):
|
|
40
|
+
"""Response listing the active session IDs.
|
|
41
|
+
|
|
42
|
+
Attributes:
|
|
43
|
+
sessions (list[str]): Active session identifiers.
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
sessions: list[str] = Field(default_factory=list)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class DeleteSessionResponse(BaseModel):
|
|
50
|
+
"""Response returned after deleting a session.
|
|
51
|
+
|
|
52
|
+
Attributes:
|
|
53
|
+
deleted (str): Identifier of the deleted session.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
deleted: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class HealthResponse(BaseModel):
|
|
60
|
+
"""Liveness probe response.
|
|
61
|
+
|
|
62
|
+
Attributes:
|
|
63
|
+
status (str): Always ``"ok"`` when the service is up.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
status: str = "ok"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class AgentInfo(BaseModel):
|
|
70
|
+
"""Metadata describing a single mounted agent.
|
|
71
|
+
|
|
72
|
+
Attributes:
|
|
73
|
+
name (str): Display name for the agent.
|
|
74
|
+
version (str): Version string for the deployment.
|
|
75
|
+
agent_class (str): The agent class name.
|
|
76
|
+
tools (list[str]): Names of the tools the agent exposes.
|
|
77
|
+
state_fields (list[str]): Names of the agent's state fields.
|
|
78
|
+
linked_agents (list[str]): Names of agents linked to this one.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
name: str
|
|
82
|
+
version: str
|
|
83
|
+
agent_class: str
|
|
84
|
+
tools: list[str] = Field(default_factory=list)
|
|
85
|
+
state_fields: list[str] = Field(default_factory=list)
|
|
86
|
+
linked_agents: list[str] = Field(default_factory=list)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class SchemaResponse(BaseModel):
|
|
90
|
+
"""JSON schemas for an agent's request/response/stream/state models.
|
|
91
|
+
|
|
92
|
+
Attributes:
|
|
93
|
+
request (dict): JSON schema of the agent's request model.
|
|
94
|
+
response (dict): JSON schema of the agent's response model.
|
|
95
|
+
stream_event (dict): JSON schema of the agent's stream event model.
|
|
96
|
+
state (dict): JSON schema of the agent's state model.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
request: dict
|
|
100
|
+
response: dict
|
|
101
|
+
stream_event: dict
|
|
102
|
+
state: dict
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class AppAgentEntry(BaseModel):
|
|
106
|
+
"""One agent entry in the multi-agent app index.
|
|
107
|
+
|
|
108
|
+
Attributes:
|
|
109
|
+
agent_class (str): The agent class name.
|
|
110
|
+
prefix (str): URL prefix the agent is mounted under.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
agent_class: str
|
|
114
|
+
prefix: str
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class AppIndex(BaseModel):
|
|
118
|
+
"""Top-level index of agents mounted on a multi-agent app.
|
|
119
|
+
|
|
120
|
+
Attributes:
|
|
121
|
+
name (str): App name.
|
|
122
|
+
version (str): App version.
|
|
123
|
+
agents (list[AppAgentEntry]): The mounted agents.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
name: str
|
|
127
|
+
version: str
|
|
128
|
+
agents: list[AppAgentEntry] = Field(default_factory=list)
|
pyagentic/api/jobs/_models.py
CHANGED
|
@@ -124,6 +124,97 @@ class BackendHealth(BaseModel):
|
|
|
124
124
|
detail: dict = Field(default_factory=dict)
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
# --- HTTP request/response models for the /jobs routes ---------------------
|
|
128
|
+
#
|
|
129
|
+
# These are the wire models for the job endpoints. ``JobSnapshot`` carries a
|
|
130
|
+
# loosely-typed ``result`` (Optional[Any]); ``build_jobs_router`` subclasses it
|
|
131
|
+
# per-agent so ``result`` is typed as the agent's ``__response_model__``.
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class JobSummary(BaseModel):
|
|
135
|
+
"""A job's status without its result payload (used in list responses).
|
|
136
|
+
|
|
137
|
+
Attributes:
|
|
138
|
+
job_id (str): Unique job identifier.
|
|
139
|
+
session_id (Optional[str]): Session the job belongs to, if any.
|
|
140
|
+
status (JobStatus): Current lifecycle state.
|
|
141
|
+
created_at (float): Unix timestamp of submission.
|
|
142
|
+
started_at (Optional[float]): Unix timestamp when execution began.
|
|
143
|
+
finished_at (Optional[float]): Unix timestamp of terminal transition.
|
|
144
|
+
error (Optional[str]): Failure description, set when the job fails.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
job_id: str
|
|
148
|
+
session_id: Optional[str] = None
|
|
149
|
+
status: JobStatus
|
|
150
|
+
created_at: float
|
|
151
|
+
started_at: Optional[float] = None
|
|
152
|
+
finished_at: Optional[float] = None
|
|
153
|
+
error: Optional[str] = None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class JobSnapshot(JobSummary):
|
|
157
|
+
"""A job's status plus its result when terminal.
|
|
158
|
+
|
|
159
|
+
Attributes:
|
|
160
|
+
result (Optional[Any]): The final agent response, present once the job
|
|
161
|
+
has succeeded. ``build_jobs_router`` narrows this to the agent's
|
|
162
|
+
``__response_model__`` per deployment.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
result: Optional[Any] = None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class JobSubmitResponse(BaseModel):
|
|
169
|
+
"""Response returned after submitting a job (HTTP 202).
|
|
170
|
+
|
|
171
|
+
Attributes:
|
|
172
|
+
job_id (str): Identifier of the newly queued job.
|
|
173
|
+
status (JobStatus): Lifecycle state at submission time (``queued``).
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
job_id: str
|
|
177
|
+
status: JobStatus
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class JobListResponse(BaseModel):
|
|
181
|
+
"""Response listing jobs newest-first.
|
|
182
|
+
|
|
183
|
+
Attributes:
|
|
184
|
+
jobs (list[JobSummary]): The matching job summaries.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
jobs: list[JobSummary] = Field(default_factory=list)
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class JobUpdateEntry(BaseModel):
|
|
191
|
+
"""One parsed entry of a job's update log.
|
|
192
|
+
|
|
193
|
+
Attributes:
|
|
194
|
+
seq (int): Monotonic per-job sequence number.
|
|
195
|
+
event (str): SSE event name.
|
|
196
|
+
data (Any): The parsed update payload.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
seq: int
|
|
200
|
+
event: str
|
|
201
|
+
data: Any = None
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class JobUpdatesResponse(BaseModel):
|
|
205
|
+
"""Response returning a job's update log past a cursor.
|
|
206
|
+
|
|
207
|
+
Attributes:
|
|
208
|
+
job_id (str): The job identifier.
|
|
209
|
+
status (JobStatus): Current lifecycle state of the job.
|
|
210
|
+
updates (list[JobUpdateEntry]): Updates past the requested cursor.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
job_id: str
|
|
214
|
+
status: JobStatus
|
|
215
|
+
updates: list[JobUpdateEntry] = Field(default_factory=list)
|
|
216
|
+
|
|
217
|
+
|
|
127
218
|
def _build_prompt(request: dict) -> str:
|
|
128
219
|
"""Collapse agent request kwargs into the single prompt string step() accepts.
|
|
129
220
|
|
pyagentic/api/jobs/_routes.py
CHANGED
|
@@ -15,8 +15,12 @@ from pydantic import create_model
|
|
|
15
15
|
from pyagentic._base._agent._agent import BaseAgent
|
|
16
16
|
from pyagentic.api._sessions import SessionManager
|
|
17
17
|
from pyagentic.api.jobs._models import (
|
|
18
|
+
JobListResponse,
|
|
18
19
|
JobRecord,
|
|
20
|
+
JobSnapshot,
|
|
19
21
|
JobStatus,
|
|
22
|
+
JobSubmitResponse,
|
|
23
|
+
JobUpdatesResponse,
|
|
20
24
|
TERMINAL_EVENTS,
|
|
21
25
|
)
|
|
22
26
|
from pyagentic.api.jobs._orchestrator import JobOrchestrator
|
|
@@ -134,6 +138,7 @@ def build_jobs_router(
|
|
|
134
138
|
"""
|
|
135
139
|
router = APIRouter()
|
|
136
140
|
RequestModel = agent_class.__request_model__
|
|
141
|
+
ResponseModel = agent_class.__response_model__
|
|
137
142
|
|
|
138
143
|
SubmitModel = create_model(
|
|
139
144
|
f"{agent_class.__name__}JobSubmitRequest",
|
|
@@ -141,7 +146,15 @@ def build_jobs_router(
|
|
|
141
146
|
session_id=(Optional[str], None),
|
|
142
147
|
)
|
|
143
148
|
|
|
144
|
-
|
|
149
|
+
# Narrow the loosely-typed JobSnapshot.result to this agent's response model
|
|
150
|
+
# so the get/cancel endpoints document the exact result shape.
|
|
151
|
+
SnapshotModel = create_model(
|
|
152
|
+
f"{agent_class.__name__}JobSnapshot",
|
|
153
|
+
__base__=JobSnapshot,
|
|
154
|
+
result=(Optional[ResponseModel], None),
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
@router.post("/jobs", status_code=202, response_model=JobSubmitResponse)
|
|
145
158
|
async def submit_job(req: SubmitModel) -> dict: # type: ignore[valid-type]
|
|
146
159
|
"""Submit an agent run as an async job."""
|
|
147
160
|
await orchestrator.ensure_started()
|
|
@@ -153,7 +166,7 @@ def build_jobs_router(
|
|
|
153
166
|
job = await orchestrator.submit(request, session_id=req.session_id)
|
|
154
167
|
return {"job_id": job.job_id, "status": job.status.value}
|
|
155
168
|
|
|
156
|
-
@router.get("/jobs")
|
|
169
|
+
@router.get("/jobs", response_model=JobListResponse)
|
|
157
170
|
async def list_jobs(
|
|
158
171
|
status: Optional[JobStatus] = None,
|
|
159
172
|
session_id: Optional[str] = None,
|
|
@@ -167,7 +180,7 @@ def build_jobs_router(
|
|
|
167
180
|
)
|
|
168
181
|
return {"jobs": [_snapshot(r, include_result=False) for r in records]}
|
|
169
182
|
|
|
170
|
-
@router.get("/jobs/{job_id}")
|
|
183
|
+
@router.get("/jobs/{job_id}", response_model=SnapshotModel)
|
|
171
184
|
async def get_job(job_id: str) -> dict:
|
|
172
185
|
"""Return a job's status and, when terminal, its result."""
|
|
173
186
|
await orchestrator.ensure_started()
|
|
@@ -176,7 +189,7 @@ def build_jobs_router(
|
|
|
176
189
|
raise HTTPException(status_code=404, detail="Job not found")
|
|
177
190
|
return _snapshot(record)
|
|
178
191
|
|
|
179
|
-
@router.get("/jobs/{job_id}/updates")
|
|
192
|
+
@router.get("/jobs/{job_id}/updates", response_model=JobUpdatesResponse)
|
|
180
193
|
async def get_updates(job_id: str, since: int = -1) -> dict:
|
|
181
194
|
"""Return a job's update log past the given seq cursor.
|
|
182
195
|
|
|
@@ -225,7 +238,7 @@ def build_jobs_router(
|
|
|
225
238
|
headers=_SSE_HEADERS,
|
|
226
239
|
)
|
|
227
240
|
|
|
228
|
-
@router.post("/jobs/{job_id}/cancel")
|
|
241
|
+
@router.post("/jobs/{job_id}/cancel", response_model=SnapshotModel)
|
|
229
242
|
async def cancel_job(job_id: str) -> dict:
|
|
230
243
|
"""Cancel a queued or running job."""
|
|
231
244
|
await orchestrator.ensure_started()
|
|
@@ -18,16 +18,17 @@ pyagentic/_base/_agent/_agent_linking.py,sha256=gSHQkbH-BeKG8moROKDZiLs0ktLzW9bO
|
|
|
18
18
|
pyagentic/_base/_agent/_agent_state.py,sha256=gvx0rQ1gaWfZHHAONX4YNsO_HE0xYQ14_GBKMP4Af-o,11507
|
|
19
19
|
pyagentic/_utils/_typing.py,sha256=5aRZZOVzR0ZgnBfejkw__1yoARhn70P5QzA81sxUkls,3681
|
|
20
20
|
pyagentic/_utils/_warnings.py,sha256=mjlBiYHGnnh5599Gsq5ke8mBFS6WsdhNa7jk20TT9_U,1330
|
|
21
|
-
pyagentic/api/__init__.py,sha256=
|
|
22
|
-
pyagentic/api/_app.py,sha256=
|
|
21
|
+
pyagentic/api/__init__.py,sha256=v-jaDw3mPCxpcllfYAbBCOdyC16qBA3B4eegxcDk4iU,1590
|
|
22
|
+
pyagentic/api/_app.py,sha256=BOaMk28CPBEUOVoiQeg3e8D6CTuHw6h3V7ti0s2h-m8,22558
|
|
23
23
|
pyagentic/api/_config.py,sha256=2YaFBDW7aaRueemHPBd1XzW9m0oqVCBilt8MA-x6QqI,6547
|
|
24
24
|
pyagentic/api/_docker.py,sha256=AoAQ66gT-Uf5WhiYpImKSpTPyboiTaqWvxG7mBLe54I,2229
|
|
25
25
|
pyagentic/api/_mcp_server.py,sha256=9aOC18-FSv1qS7umqFHxMKV5FujblziR-_NDfNsXmFo,5032
|
|
26
|
+
pyagentic/api/_models.py,sha256=1wI-GVYNwuNkDCktmmfcicMVsAgNEcUkJXnqN_ff4eo,3411
|
|
26
27
|
pyagentic/api/_sessions.py,sha256=HU2pkCIA_b1syr7n6qIl0m2Ad5tvPzVplzYAeKS_9HM,3636
|
|
27
28
|
pyagentic/api/jobs/__init__.py,sha256=SGZOE86xVwv4hX2zjpm8uLZZhizz0rv3EV5suUuiM8M,979
|
|
28
|
-
pyagentic/api/jobs/_models.py,sha256=
|
|
29
|
+
pyagentic/api/jobs/_models.py,sha256=vIFZaES7v5hD68MvyPG-2D_miQDMNaWp_5IH_7TYxcw,6950
|
|
29
30
|
pyagentic/api/jobs/_orchestrator.py,sha256=XgvFiQ9R-rLMGjkTGUUXhU6YsTg-x6nABfABvpfQX-E,17159
|
|
30
|
-
pyagentic/api/jobs/_routes.py,sha256=
|
|
31
|
+
pyagentic/api/jobs/_routes.py,sha256=At5nmn_7a3igaLPIgFnt3_xwTM3yYSsD_ZFRfmalymk,9764
|
|
31
32
|
pyagentic/api/jobs/backends/__init__.py,sha256=LdlWjve6cdl04X09sJvlV-aUPK2euImC_cW73lg3tBM,1325
|
|
32
33
|
pyagentic/api/jobs/backends/_base.py,sha256=LITAW0FNWKAdEhmnixe5UakbBrxYY7rWbZD7jK6VZL8,4453
|
|
33
34
|
pyagentic/api/jobs/backends/_in_process.py,sha256=5ltFfS39noeddXacp2GCUHW9A_SyMNw0xHVPbKcgtFI,4425
|
|
@@ -51,8 +52,8 @@ pyagentic/tracing/__init__.py,sha256=ddMOl_-Nf3WOhu-PYxSJ3kqHZ3eQXAOM5aySn9YmDtA
|
|
|
51
52
|
pyagentic/tracing/_basic.py,sha256=jUBKdkOPveCjBzEhHKS_Rvp5l38iZg-5nFIl6xEwOVk,8906
|
|
52
53
|
pyagentic/tracing/_langfuse.py,sha256=5b5tdT-gyRJP5HDcvCS4tn_k3AFDI6eNsXOd6On5gKQ,13986
|
|
53
54
|
pyagentic/tracing/_tracer.py,sha256=HMisWfRLAQ9sdcjOvImiLij6OASd3siM8K-7nUfM3xs,7656
|
|
54
|
-
pyagentic_core-2.
|
|
55
|
-
pyagentic_core-2.
|
|
56
|
-
pyagentic_core-2.
|
|
57
|
-
pyagentic_core-2.
|
|
58
|
-
pyagentic_core-2.
|
|
55
|
+
pyagentic_core-2.10.1.dev2.dist-info/licenses/LICENSE,sha256=wcOzTj82hOc96HztJk2VzLB2hFHpdIGpjz8vvbpP1_s,1069
|
|
56
|
+
pyagentic_core-2.10.1.dev2.dist-info/METADATA,sha256=oQsV2I4CSW0si5Q8YueIq1W2bqt3lNzJXSZmv787VAs,8212
|
|
57
|
+
pyagentic_core-2.10.1.dev2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
58
|
+
pyagentic_core-2.10.1.dev2.dist-info/top_level.txt,sha256=lAWfd-ay434uEpSg2FM0ySN0o4vgNGE6D9dJkIhGyfY,10
|
|
59
|
+
pyagentic_core-2.10.1.dev2.dist-info/RECORD,,
|
|
File without changes
|
{pyagentic_core-2.9.1.dev1.dist-info → pyagentic_core-2.10.1.dev2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|