pyagentic-core 2.9.1.dev1__tar.gz → 2.10.1.dev2__tar.gz

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.
Files changed (130) hide show
  1. {pyagentic_core-2.9.1.dev1/pyagentic_core.egg-info → pyagentic_core-2.10.1.dev2}/PKG-INFO +1 -1
  2. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/__init__.py +21 -0
  3. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/_app.py +77 -54
  4. pyagentic_core-2.10.1.dev2/pyagentic/api/_models.py +128 -0
  5. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/jobs/_models.py +91 -0
  6. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/jobs/_routes.py +18 -5
  7. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2/pyagentic_core.egg-info}/PKG-INFO +1 -1
  8. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic_core.egg-info/SOURCES.txt +1 -0
  9. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  10. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  11. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/.github/workflows/docs.yml +0 -0
  12. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/.github/workflows/release.yml +0 -0
  13. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/.github/workflows/testing.yml +0 -0
  14. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/.gitignore +0 -0
  15. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/CHANGELOG.md +0 -0
  16. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/LICENSE +0 -0
  17. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/README.md +0 -0
  18. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/agent-linking.md +0 -0
  19. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/api/creating-an-app.md +0 -0
  20. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/api/deploying.md +0 -0
  21. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/api/index.md +0 -0
  22. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/api/jobs.md +0 -0
  23. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/api/running.md +0 -0
  24. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/diagrams/declaration.svg +0 -0
  25. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/diagrams/instantiation.svg +0 -0
  26. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/diagrams/runtime.svg +0 -0
  27. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/diagrams/source/README.md +0 -0
  28. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/diagrams/source/declaration.d2 +0 -0
  29. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/diagrams/source/instantiation.d2 +0 -0
  30. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/diagrams/source/runtime.d2 +0 -0
  31. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/execution-modes.md +0 -0
  32. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/getting-started.md +0 -0
  33. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/images/langfuse.png +0 -0
  34. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/index.md +0 -0
  35. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/inheritance.md +0 -0
  36. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/observability.md +0 -0
  37. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/phases.md +0 -0
  38. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/policies.md +0 -0
  39. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/reference/architecture.md +0 -0
  40. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/reference/modules.md +0 -0
  41. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/responses.md +0 -0
  42. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/states.md +0 -0
  43. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/structured-output.md +0 -0
  44. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/docs/tools.md +0 -0
  45. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/mkdocs.yml +0 -0
  46. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/__init__.py +0 -0
  47. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/__init__.py +0 -0
  48. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_agent/__init__.py +0 -0
  49. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_agent/_agent.py +0 -0
  50. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_agent/_agent_linking.py +0 -0
  51. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_agent/_agent_state.py +0 -0
  52. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_exceptions.py +0 -0
  53. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_info.py +0 -0
  54. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_mcp.py +0 -0
  55. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_metaclasses.py +0 -0
  56. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_ref.py +0 -0
  57. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_spec.py +0 -0
  58. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_state.py +0 -0
  59. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_tool.py +0 -0
  60. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_base/_validation.py +0 -0
  61. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_utils/_typing.py +0 -0
  62. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_utils/_warnings.py +0 -0
  63. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/_version_scheme.py +0 -0
  64. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/_config.py +0 -0
  65. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/_docker.py +0 -0
  66. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/_mcp_server.py +0 -0
  67. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/_sessions.py +0 -0
  68. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/jobs/__init__.py +0 -0
  69. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/jobs/_orchestrator.py +0 -0
  70. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/jobs/backends/__init__.py +0 -0
  71. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/jobs/backends/_base.py +0 -0
  72. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/jobs/backends/_in_process.py +0 -0
  73. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/jobs/store/__init__.py +0 -0
  74. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/jobs/store/_base.py +0 -0
  75. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/api/jobs/store/_sqlite.py +0 -0
  76. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/llm/__init__.py +0 -0
  77. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/llm/_anthropic.py +0 -0
  78. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/llm/_gemini.py +0 -0
  79. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/llm/_mock.py +0 -0
  80. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/llm/_openai.py +0 -0
  81. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/llm/_openaiv1.py +0 -0
  82. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/llm/_provider.py +0 -0
  83. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/logging.py +0 -0
  84. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/models/llm.py +0 -0
  85. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/models/response.py +0 -0
  86. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/models/tracing.py +0 -0
  87. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/policies/__init__.py +0 -0
  88. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/policies/_events.py +0 -0
  89. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/policies/_policy.py +0 -0
  90. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/tracing/__init__.py +0 -0
  91. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/tracing/_basic.py +0 -0
  92. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/tracing/_langfuse.py +0 -0
  93. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/tracing/_tracer.py +0 -0
  94. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic/updates.py +0 -0
  95. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic_core.egg-info/dependency_links.txt +0 -0
  96. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic_core.egg-info/requires.txt +0 -0
  97. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyagentic_core.egg-info/top_level.txt +0 -0
  98. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/pyproject.toml +0 -0
  99. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/setup.cfg +0 -0
  100. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/setup.py +0 -0
  101. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/__init__.py +0 -0
  102. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/_base/__init__.py +0 -0
  103. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/_base/test_agent.py +0 -0
  104. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/_base/test_agent_inheritance.py +0 -0
  105. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/_base/test_agent_linking.py +0 -0
  106. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/_base/test_agent_provider.py +0 -0
  107. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/_base/test_params.py +0 -0
  108. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/_base/test_phases.py +0 -0
  109. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/_base/test_state.py +0 -0
  110. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/_base/test_tool.py +0 -0
  111. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/_base/test_validator.py +0 -0
  112. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/__init__.py +0 -0
  113. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/jobs/__init__.py +0 -0
  114. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/jobs/test_orchestrator.py +0 -0
  115. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/jobs/test_routes.py +0 -0
  116. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/jobs/test_store_sqlite.py +0 -0
  117. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/test_app.py +0 -0
  118. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/test_config.py +0 -0
  119. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/test_docker.py +0 -0
  120. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/test_mcp_server.py +0 -0
  121. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/test_metaclass_models.py +0 -0
  122. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/test_router.py +0 -0
  123. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/api/test_sessions.py +0 -0
  124. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/conftest.py +0 -0
  125. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/models/test_responses.py +0 -0
  126. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/tracing/__init__.py +0 -0
  127. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/tracing/test_basic_tracer.py +0 -0
  128. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/tracing/test_models.py +0 -0
  129. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/tests/tracing/test_tracer.py +0 -0
  130. {pyagentic_core-2.9.1.dev1 → pyagentic_core-2.10.1.dev2}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyagentic-core
3
- Version: 2.9.1.dev1
3
+ Version: 2.10.1.dev2
4
4
  Summary: Build LLM Agents in a Pythonic way
5
5
  Author-email: Ryan Mikulec <rmikulec.dev@gmail.com>
6
6
  License: MIT
@@ -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
  ]
@@ -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 is used as-is.
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 = dict(agents)
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() -> dict:
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
- "name": name,
220
- "version": version,
221
- "agent_class": agent_class.__name__,
222
- "tools": list(agent_class.__tool_defs__.keys()),
223
- "state_fields": list(agent_class.__state_defs__.keys()),
224
- "linked_agents": list(agent_class.__linked_agents__.keys()),
225
- }
226
-
227
- @router.get("/health", name=f"{slug}_health")
228
- async def health() -> dict:
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 {"status": "ok"}
236
+ return HealthResponse()
231
237
 
232
- @router.get("/schema", name=f"{slug}_schema")
233
- async def schema() -> dict:
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
- "request": RequestModel.model_json_schema(),
237
- "response": ResponseModel.model_json_schema(),
238
- "stream_event": StreamEventModel.model_json_schema(),
239
- "state": StateModel.model_json_schema(),
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("/sessions", status_code=201, name=f"{slug}_create_session")
311
- async def create_session(req: Optional[CreateSessionRequest] = None) -> dict:
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 {"session_id": session_id}
329
+ return CreateSessionResponse(session_id=session_id)
317
330
 
318
- @router.get("/sessions", name=f"{slug}_list_sessions")
319
- async def list_sessions() -> dict:
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 {"sessions": sessions.list_sessions()}
338
+ return ListSessionsResponse(sessions=sessions.list_sessions())
322
339
 
323
- @router.delete("/sessions/{session_id}", name=f"{slug}_delete_session")
324
- async def delete_session(session_id: str) -> dict:
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 {"deleted": session_id}
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({"agent_class": agent_class.__name__, "prefix": prefix or "/"})
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() -> dict:
592
+ @app.get("/", response_model=AppIndex)
593
+ async def app_index() -> AppIndex:
571
594
  """List the agents mounted on this app."""
572
- return {"name": name, "version": version, "agents": index}
595
+ return AppIndex(name=name, version=version, agents=index)
573
596
 
574
- @app.get("/health")
575
- async def app_health() -> dict:
597
+ @app.get("/health", response_model=HealthResponse)
598
+ async def app_health() -> HealthResponse:
576
599
  """Liveness probe endpoint."""
577
- return {"status": "ok"}
600
+ return HealthResponse()
578
601
 
579
602
  return app
@@ -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)
@@ -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
 
@@ -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
- @router.post("/jobs", status_code=202)
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyagentic-core
3
- Version: 2.9.1.dev1
3
+ Version: 2.10.1.dev2
4
4
  Summary: Build LLM Agents in a Pythonic way
5
5
  Author-email: Ryan Mikulec <rmikulec.dev@gmail.com>
6
6
  License: MIT
@@ -63,6 +63,7 @@ pyagentic/api/_app.py
63
63
  pyagentic/api/_config.py
64
64
  pyagentic/api/_docker.py
65
65
  pyagentic/api/_mcp_server.py
66
+ pyagentic/api/_models.py
66
67
  pyagentic/api/_sessions.py
67
68
  pyagentic/api/jobs/__init__.py
68
69
  pyagentic/api/jobs/_models.py