planar 0.5.0__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.
Files changed (289) hide show
  1. planar/.__init__.py.un~ +0 -0
  2. planar/._version.py.un~ +0 -0
  3. planar/.app.py.un~ +0 -0
  4. planar/.cli.py.un~ +0 -0
  5. planar/.config.py.un~ +0 -0
  6. planar/.context.py.un~ +0 -0
  7. planar/.db.py.un~ +0 -0
  8. planar/.di.py.un~ +0 -0
  9. planar/.engine.py.un~ +0 -0
  10. planar/.files.py.un~ +0 -0
  11. planar/.log_context.py.un~ +0 -0
  12. planar/.log_metadata.py.un~ +0 -0
  13. planar/.logging.py.un~ +0 -0
  14. planar/.object_registry.py.un~ +0 -0
  15. planar/.otel.py.un~ +0 -0
  16. planar/.server.py.un~ +0 -0
  17. planar/.session.py.un~ +0 -0
  18. planar/.sqlalchemy.py.un~ +0 -0
  19. planar/.task_local.py.un~ +0 -0
  20. planar/.test_app.py.un~ +0 -0
  21. planar/.test_config.py.un~ +0 -0
  22. planar/.test_object_config.py.un~ +0 -0
  23. planar/.test_sqlalchemy.py.un~ +0 -0
  24. planar/.test_utils.py.un~ +0 -0
  25. planar/.util.py.un~ +0 -0
  26. planar/.utils.py.un~ +0 -0
  27. planar/__init__.py +26 -0
  28. planar/_version.py +1 -0
  29. planar/ai/.__init__.py.un~ +0 -0
  30. planar/ai/._models.py.un~ +0 -0
  31. planar/ai/.agent.py.un~ +0 -0
  32. planar/ai/.agent_utils.py.un~ +0 -0
  33. planar/ai/.events.py.un~ +0 -0
  34. planar/ai/.files.py.un~ +0 -0
  35. planar/ai/.models.py.un~ +0 -0
  36. planar/ai/.providers.py.un~ +0 -0
  37. planar/ai/.pydantic_ai.py.un~ +0 -0
  38. planar/ai/.pydantic_ai_agent.py.un~ +0 -0
  39. planar/ai/.pydantic_ai_provider.py.un~ +0 -0
  40. planar/ai/.step.py.un~ +0 -0
  41. planar/ai/.test_agent.py.un~ +0 -0
  42. planar/ai/.test_agent_serialization.py.un~ +0 -0
  43. planar/ai/.test_providers.py.un~ +0 -0
  44. planar/ai/.utils.py.un~ +0 -0
  45. planar/ai/__init__.py +15 -0
  46. planar/ai/agent.py +457 -0
  47. planar/ai/agent_utils.py +205 -0
  48. planar/ai/models.py +140 -0
  49. planar/ai/providers.py +1088 -0
  50. planar/ai/test_agent.py +1298 -0
  51. planar/ai/test_agent_serialization.py +229 -0
  52. planar/ai/test_providers.py +463 -0
  53. planar/ai/utils.py +102 -0
  54. planar/app.py +494 -0
  55. planar/cli.py +282 -0
  56. planar/config.py +544 -0
  57. planar/db/.db.py.un~ +0 -0
  58. planar/db/__init__.py +17 -0
  59. planar/db/alembic/env.py +136 -0
  60. planar/db/alembic/script.py.mako +28 -0
  61. planar/db/alembic/versions/3476068c153c_initial_system_tables_migration.py +339 -0
  62. planar/db/alembic.ini +128 -0
  63. planar/db/db.py +318 -0
  64. planar/files/.config.py.un~ +0 -0
  65. planar/files/.local.py.un~ +0 -0
  66. planar/files/.local_filesystem.py.un~ +0 -0
  67. planar/files/.model.py.un~ +0 -0
  68. planar/files/.models.py.un~ +0 -0
  69. planar/files/.s3.py.un~ +0 -0
  70. planar/files/.storage.py.un~ +0 -0
  71. planar/files/.test_files.py.un~ +0 -0
  72. planar/files/__init__.py +2 -0
  73. planar/files/models.py +162 -0
  74. planar/files/storage/.__init__.py.un~ +0 -0
  75. planar/files/storage/.base.py.un~ +0 -0
  76. planar/files/storage/.config.py.un~ +0 -0
  77. planar/files/storage/.context.py.un~ +0 -0
  78. planar/files/storage/.local_directory.py.un~ +0 -0
  79. planar/files/storage/.test_local_directory.py.un~ +0 -0
  80. planar/files/storage/.test_s3.py.un~ +0 -0
  81. planar/files/storage/base.py +61 -0
  82. planar/files/storage/config.py +44 -0
  83. planar/files/storage/context.py +15 -0
  84. planar/files/storage/local_directory.py +188 -0
  85. planar/files/storage/s3.py +220 -0
  86. planar/files/storage/test_local_directory.py +162 -0
  87. planar/files/storage/test_s3.py +299 -0
  88. planar/files/test_files.py +283 -0
  89. planar/human/.human.py.un~ +0 -0
  90. planar/human/.test_human.py.un~ +0 -0
  91. planar/human/__init__.py +2 -0
  92. planar/human/human.py +458 -0
  93. planar/human/models.py +80 -0
  94. planar/human/test_human.py +385 -0
  95. planar/logging/.__init__.py.un~ +0 -0
  96. planar/logging/.attributes.py.un~ +0 -0
  97. planar/logging/.formatter.py.un~ +0 -0
  98. planar/logging/.logger.py.un~ +0 -0
  99. planar/logging/.otel.py.un~ +0 -0
  100. planar/logging/.tracer.py.un~ +0 -0
  101. planar/logging/__init__.py +10 -0
  102. planar/logging/attributes.py +54 -0
  103. planar/logging/context.py +14 -0
  104. planar/logging/formatter.py +113 -0
  105. planar/logging/logger.py +114 -0
  106. planar/logging/otel.py +51 -0
  107. planar/modeling/.mixin.py.un~ +0 -0
  108. planar/modeling/.storage.py.un~ +0 -0
  109. planar/modeling/__init__.py +0 -0
  110. planar/modeling/field_helpers.py +59 -0
  111. planar/modeling/json_schema_generator.py +94 -0
  112. planar/modeling/mixins/__init__.py +10 -0
  113. planar/modeling/mixins/auditable.py +52 -0
  114. planar/modeling/mixins/test_auditable.py +97 -0
  115. planar/modeling/mixins/test_timestamp.py +134 -0
  116. planar/modeling/mixins/test_uuid_primary_key.py +52 -0
  117. planar/modeling/mixins/timestamp.py +53 -0
  118. planar/modeling/mixins/uuid_primary_key.py +19 -0
  119. planar/modeling/orm/.planar_base_model.py.un~ +0 -0
  120. planar/modeling/orm/__init__.py +18 -0
  121. planar/modeling/orm/planar_base_entity.py +29 -0
  122. planar/modeling/orm/query_filter_builder.py +122 -0
  123. planar/modeling/orm/reexports.py +15 -0
  124. planar/object_config/.object_config.py.un~ +0 -0
  125. planar/object_config/__init__.py +11 -0
  126. planar/object_config/models.py +114 -0
  127. planar/object_config/object_config.py +378 -0
  128. planar/object_registry.py +100 -0
  129. planar/registry_items.py +65 -0
  130. planar/routers/.__init__.py.un~ +0 -0
  131. planar/routers/.agents_router.py.un~ +0 -0
  132. planar/routers/.crud.py.un~ +0 -0
  133. planar/routers/.decision.py.un~ +0 -0
  134. planar/routers/.event.py.un~ +0 -0
  135. planar/routers/.file_attachment.py.un~ +0 -0
  136. planar/routers/.files.py.un~ +0 -0
  137. planar/routers/.files_router.py.un~ +0 -0
  138. planar/routers/.human.py.un~ +0 -0
  139. planar/routers/.info.py.un~ +0 -0
  140. planar/routers/.models.py.un~ +0 -0
  141. planar/routers/.object_config_router.py.un~ +0 -0
  142. planar/routers/.rule.py.un~ +0 -0
  143. planar/routers/.test_object_config_router.py.un~ +0 -0
  144. planar/routers/.test_workflow_router.py.un~ +0 -0
  145. planar/routers/.workflow.py.un~ +0 -0
  146. planar/routers/__init__.py +13 -0
  147. planar/routers/agents_router.py +197 -0
  148. planar/routers/entity_router.py +143 -0
  149. planar/routers/event.py +91 -0
  150. planar/routers/files.py +142 -0
  151. planar/routers/human.py +151 -0
  152. planar/routers/info.py +131 -0
  153. planar/routers/models.py +170 -0
  154. planar/routers/object_config_router.py +133 -0
  155. planar/routers/rule.py +108 -0
  156. planar/routers/test_agents_router.py +174 -0
  157. planar/routers/test_object_config_router.py +367 -0
  158. planar/routers/test_routes_security.py +169 -0
  159. planar/routers/test_rule_router.py +470 -0
  160. planar/routers/test_workflow_router.py +274 -0
  161. planar/routers/workflow.py +468 -0
  162. planar/rules/.decorator.py.un~ +0 -0
  163. planar/rules/.runner.py.un~ +0 -0
  164. planar/rules/.test_rules.py.un~ +0 -0
  165. planar/rules/__init__.py +23 -0
  166. planar/rules/decorator.py +184 -0
  167. planar/rules/models.py +355 -0
  168. planar/rules/rule_configuration.py +191 -0
  169. planar/rules/runner.py +64 -0
  170. planar/rules/test_rules.py +750 -0
  171. planar/scaffold_templates/app/__init__.py.j2 +0 -0
  172. planar/scaffold_templates/app/db/entities.py.j2 +11 -0
  173. planar/scaffold_templates/app/flows/process_invoice.py.j2 +67 -0
  174. planar/scaffold_templates/main.py.j2 +13 -0
  175. planar/scaffold_templates/planar.dev.yaml.j2 +34 -0
  176. planar/scaffold_templates/planar.prod.yaml.j2 +28 -0
  177. planar/scaffold_templates/pyproject.toml.j2 +10 -0
  178. planar/security/.jwt_middleware.py.un~ +0 -0
  179. planar/security/auth_context.py +148 -0
  180. planar/security/authorization.py +388 -0
  181. planar/security/default_policies.cedar +77 -0
  182. planar/security/jwt_middleware.py +116 -0
  183. planar/security/security_context.py +18 -0
  184. planar/security/tests/test_authorization_context.py +78 -0
  185. planar/security/tests/test_cedar_basics.py +41 -0
  186. planar/security/tests/test_cedar_policies.py +158 -0
  187. planar/security/tests/test_jwt_principal_context.py +179 -0
  188. planar/session.py +40 -0
  189. planar/sse/.constants.py.un~ +0 -0
  190. planar/sse/.example.html.un~ +0 -0
  191. planar/sse/.hub.py.un~ +0 -0
  192. planar/sse/.model.py.un~ +0 -0
  193. planar/sse/.proxy.py.un~ +0 -0
  194. planar/sse/constants.py +1 -0
  195. planar/sse/example.html +126 -0
  196. planar/sse/hub.py +216 -0
  197. planar/sse/model.py +8 -0
  198. planar/sse/proxy.py +257 -0
  199. planar/task_local.py +37 -0
  200. planar/test_app.py +51 -0
  201. planar/test_cli.py +372 -0
  202. planar/test_config.py +512 -0
  203. planar/test_object_config.py +527 -0
  204. planar/test_object_registry.py +14 -0
  205. planar/test_sqlalchemy.py +158 -0
  206. planar/test_utils.py +105 -0
  207. planar/testing/.client.py.un~ +0 -0
  208. planar/testing/.memory_storage.py.un~ +0 -0
  209. planar/testing/.planar_test_client.py.un~ +0 -0
  210. planar/testing/.predictable_tracer.py.un~ +0 -0
  211. planar/testing/.synchronizable_tracer.py.un~ +0 -0
  212. planar/testing/.test_memory_storage.py.un~ +0 -0
  213. planar/testing/.workflow_observer.py.un~ +0 -0
  214. planar/testing/__init__.py +0 -0
  215. planar/testing/memory_storage.py +78 -0
  216. planar/testing/planar_test_client.py +54 -0
  217. planar/testing/synchronizable_tracer.py +153 -0
  218. planar/testing/test_memory_storage.py +143 -0
  219. planar/testing/workflow_observer.py +73 -0
  220. planar/utils.py +70 -0
  221. planar/workflows/.__init__.py.un~ +0 -0
  222. planar/workflows/.builtin_steps.py.un~ +0 -0
  223. planar/workflows/.concurrency_tracing.py.un~ +0 -0
  224. planar/workflows/.context.py.un~ +0 -0
  225. planar/workflows/.contrib.py.un~ +0 -0
  226. planar/workflows/.decorators.py.un~ +0 -0
  227. planar/workflows/.durable_test.py.un~ +0 -0
  228. planar/workflows/.errors.py.un~ +0 -0
  229. planar/workflows/.events.py.un~ +0 -0
  230. planar/workflows/.exceptions.py.un~ +0 -0
  231. planar/workflows/.execution.py.un~ +0 -0
  232. planar/workflows/.human.py.un~ +0 -0
  233. planar/workflows/.lock.py.un~ +0 -0
  234. planar/workflows/.misc.py.un~ +0 -0
  235. planar/workflows/.model.py.un~ +0 -0
  236. planar/workflows/.models.py.un~ +0 -0
  237. planar/workflows/.notifications.py.un~ +0 -0
  238. planar/workflows/.orchestrator.py.un~ +0 -0
  239. planar/workflows/.runtime.py.un~ +0 -0
  240. planar/workflows/.serialization.py.un~ +0 -0
  241. planar/workflows/.step.py.un~ +0 -0
  242. planar/workflows/.step_core.py.un~ +0 -0
  243. planar/workflows/.sub_workflow_runner.py.un~ +0 -0
  244. planar/workflows/.sub_workflow_scheduler.py.un~ +0 -0
  245. planar/workflows/.test_concurrency.py.un~ +0 -0
  246. planar/workflows/.test_concurrency_detection.py.un~ +0 -0
  247. planar/workflows/.test_human.py.un~ +0 -0
  248. planar/workflows/.test_lock_timeout.py.un~ +0 -0
  249. planar/workflows/.test_orchestrator.py.un~ +0 -0
  250. planar/workflows/.test_race_conditions.py.un~ +0 -0
  251. planar/workflows/.test_serialization.py.un~ +0 -0
  252. planar/workflows/.test_suspend_deserialization.py.un~ +0 -0
  253. planar/workflows/.test_workflow.py.un~ +0 -0
  254. planar/workflows/.tracing.py.un~ +0 -0
  255. planar/workflows/.types.py.un~ +0 -0
  256. planar/workflows/.util.py.un~ +0 -0
  257. planar/workflows/.utils.py.un~ +0 -0
  258. planar/workflows/.workflow.py.un~ +0 -0
  259. planar/workflows/.workflow_wrapper.py.un~ +0 -0
  260. planar/workflows/.wrappers.py.un~ +0 -0
  261. planar/workflows/__init__.py +42 -0
  262. planar/workflows/context.py +44 -0
  263. planar/workflows/contrib.py +190 -0
  264. planar/workflows/decorators.py +217 -0
  265. planar/workflows/events.py +185 -0
  266. planar/workflows/exceptions.py +34 -0
  267. planar/workflows/execution.py +198 -0
  268. planar/workflows/lock.py +229 -0
  269. planar/workflows/misc.py +5 -0
  270. planar/workflows/models.py +154 -0
  271. planar/workflows/notifications.py +96 -0
  272. planar/workflows/orchestrator.py +383 -0
  273. planar/workflows/query.py +256 -0
  274. planar/workflows/serialization.py +409 -0
  275. planar/workflows/step_core.py +373 -0
  276. planar/workflows/step_metadata.py +357 -0
  277. planar/workflows/step_testing_utils.py +86 -0
  278. planar/workflows/sub_workflow_runner.py +191 -0
  279. planar/workflows/test_concurrency_detection.py +120 -0
  280. planar/workflows/test_lock_timeout.py +140 -0
  281. planar/workflows/test_serialization.py +1195 -0
  282. planar/workflows/test_suspend_deserialization.py +231 -0
  283. planar/workflows/test_workflow.py +1967 -0
  284. planar/workflows/tracing.py +106 -0
  285. planar/workflows/wrappers.py +41 -0
  286. planar-0.5.0.dist-info/METADATA +285 -0
  287. planar-0.5.0.dist-info/RECORD +289 -0
  288. planar-0.5.0.dist-info/WHEEL +4 -0
  289. planar-0.5.0.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,205 @@
1
+ import asyncio
2
+ import inspect
3
+ import json
4
+ from collections.abc import AsyncGenerator
5
+ from enum import Enum
6
+ from typing import (
7
+ Any,
8
+ Callable,
9
+ Dict,
10
+ )
11
+
12
+ from jinja2 import StrictUndefined, TemplateError
13
+ from jinja2.sandbox import SandboxedEnvironment
14
+ from pydantic import BaseModel, create_model
15
+
16
+ from planar.ai.models import (
17
+ AgentConfig,
18
+ ToolDefinition,
19
+ )
20
+ from planar.files.models import PlanarFile
21
+ from planar.logging import get_logger
22
+ from planar.object_config import ConfigurableObjectType, ObjectConfigurationIO
23
+ from planar.utils import utc_now
24
+ from planar.workflows import step
25
+
26
+ logger = get_logger(__name__)
27
+
28
+
29
+ class AgentEventType(str, Enum):
30
+ """Valid event types that can be emitted by an Agent."""
31
+
32
+ RESPONSE = "response"
33
+ TOOL_RESPONSE = "tool_response"
34
+ COMPLETED = "completed"
35
+ ERROR = "error"
36
+ THINK = "think"
37
+ TEXT = "text"
38
+
39
+
40
+ class AgentEvent:
41
+ def __init__(
42
+ self,
43
+ event_type: AgentEventType,
44
+ data: BaseModel | str | None,
45
+ ):
46
+ self.event_type = event_type
47
+ self.data = data
48
+ self.timestamp = utc_now().isoformat()
49
+
50
+
51
+ class AgentEventEmitter:
52
+ def __init__(self):
53
+ self.queue: asyncio.Queue[AgentEvent] = asyncio.Queue()
54
+
55
+ def emit(self, event_type: AgentEventType, data: BaseModel | str | None):
56
+ event = AgentEvent(event_type, data)
57
+ self.queue.put_nowait(event)
58
+
59
+ async def get_events(self) -> AsyncGenerator[str, None]:
60
+ while True:
61
+ event = await self.queue.get()
62
+
63
+ if isinstance(event.data, BaseModel):
64
+ data = {
65
+ "data": event.data.model_dump(),
66
+ "event_type": event.event_type,
67
+ }
68
+ else:
69
+ data = {
70
+ "data": event.data,
71
+ "event_type": event.event_type,
72
+ }
73
+
74
+ yield f"data: {json.dumps(data)}\n\n"
75
+
76
+ self.queue.task_done()
77
+
78
+ if event.event_type in (AgentEventType.COMPLETED, AgentEventType.ERROR):
79
+ break
80
+
81
+ def is_empty(self) -> bool:
82
+ """Check if the queue is empty."""
83
+ return self.queue.empty()
84
+
85
+
86
+ # Define JsonData type as a union of valid JSON values
87
+ JsonData = str | int | float | bool | None | dict[str, Any] | list[Any]
88
+
89
+
90
+ class ToolCallResult(BaseModel):
91
+ tool_call_id: str
92
+ tool_call_name: str
93
+ content: BaseModel | JsonData
94
+
95
+
96
+ def extract_files_from_model(
97
+ model: BaseModel | str | None,
98
+ ) -> list[PlanarFile]:
99
+ """
100
+ Extract files from a Pydantic model. We extract any top-level or nested fields
101
+ that are of type `PlanarFile`, or are a list of `PlanarFile`.
102
+
103
+ Args:
104
+ model: The Pydantic model to extract files from.
105
+
106
+ Returns:
107
+ A list of PlanarFile objects.
108
+ """
109
+
110
+ if model is None:
111
+ return []
112
+
113
+ if isinstance(model, PlanarFile):
114
+ return [model]
115
+
116
+ if isinstance(model, str):
117
+ return []
118
+
119
+ files: list[PlanarFile] = []
120
+ for field_name in type(model).model_fields:
121
+ value = getattr(model, field_name)
122
+ match value:
123
+ case PlanarFile() as f:
124
+ files.append(f)
125
+ case BaseModel():
126
+ files.extend(extract_files_from_model(value))
127
+ case [*items]:
128
+ files.extend(item for item in items if isinstance(item, PlanarFile))
129
+ case _:
130
+ pass
131
+ return files
132
+
133
+
134
+ # Jinja environment for safely rendering templates
135
+ _JINJA_ENV = SandboxedEnvironment(undefined=StrictUndefined)
136
+
137
+
138
+ def render_template(template: str, context: Dict[str, Any]) -> str:
139
+ """Render a template string using a sandboxed Jinja environment."""
140
+ try:
141
+ return _JINJA_ENV.from_string(template).render(context)
142
+ except TemplateError as exc:
143
+ logger.exception("error rendering jinja template")
144
+ raise ValueError(f"Error rendering prompt: {exc}") from exc
145
+
146
+
147
+ agent_configuration = ObjectConfigurationIO(AgentConfig, ConfigurableObjectType.AGENT)
148
+
149
+
150
+ @step(display_name="Agent Config")
151
+ async def get_agent_config(agent_name: str, agent_config: AgentConfig) -> AgentConfig:
152
+ """
153
+ Retrieve agent configuration overrides from the database.
154
+
155
+ Args:
156
+ agent_name: Name of the agent instance.
157
+
158
+ Returns:
159
+ AgentOverrideConfig
160
+ """
161
+ logger.debug("getting agent config", agent_name=agent_name)
162
+ configs = await agent_configuration.read_configs_with_default(
163
+ agent_name, agent_config
164
+ )
165
+
166
+ active_config = next((config for config in configs if config.active), None)
167
+
168
+ if not active_config:
169
+ logger.warning("no active configuration found for agent", agent_name=agent_name)
170
+ raise ValueError(f"No active configuration found for agent {agent_name}")
171
+
172
+ logger.info(
173
+ "active configuration found for agent",
174
+ version=active_config.version,
175
+ agent_name=agent_name,
176
+ )
177
+ return active_config.data
178
+
179
+
180
+ def create_tool_definition(tool_fn: Callable) -> ToolDefinition:
181
+ """Create a ToolDefinition from a function using a Pydantic model."""
182
+ sig = inspect.signature(tool_fn)
183
+ doc = inspect.getdoc(tool_fn) or ""
184
+ name = tool_fn.__name__
185
+
186
+ fields = {}
187
+ for param_name, param in sig.parameters.items():
188
+ param_type = (
189
+ param.annotation if param.annotation != inspect.Parameter.empty else Any
190
+ )
191
+ default_value = (
192
+ param.default if param.default != inspect.Parameter.empty else ...
193
+ )
194
+ fields[param_name] = (param_type, default_value)
195
+
196
+ model_name = f"{name.capitalize()}Parameters"
197
+ parameters_model = create_model(
198
+ model_name, __config__={"extra": "forbid"}, **fields
199
+ )
200
+
201
+ return ToolDefinition(
202
+ name=name,
203
+ description=doc,
204
+ parameters=parameters_model.model_json_schema(),
205
+ )
planar/ai/models.py ADDED
@@ -0,0 +1,140 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import (
4
+ Annotated,
5
+ Any,
6
+ Dict,
7
+ List,
8
+ Literal,
9
+ Optional,
10
+ TypeVar,
11
+ Union,
12
+ )
13
+
14
+ from pydantic import BaseModel, Field
15
+
16
+ from planar.files.models import PlanarFile
17
+ from planar.modeling.field_helpers import JsonSchema
18
+ from planar.object_config.object_config import (
19
+ ObjectConfigurationBase,
20
+ )
21
+
22
+ # Type variable for output_type, which can be str or a Pydantic model
23
+ T = TypeVar("T", bound=Union[str, BaseModel])
24
+
25
+
26
+ # Agent configuration overrides.
27
+ # This model allows storing configurations that override the default
28
+ # settings defined in Agent instances.
29
+ class AgentConfig(BaseModel):
30
+ system_prompt: str
31
+ user_prompt: str = Field()
32
+ model: str = Field()
33
+ max_turns: int = Field()
34
+ model_parameters: Dict[str, Any] = Field(default_factory=dict)
35
+
36
+
37
+ class ToolDefinition(BaseModel):
38
+ """Defines a tool that the model can call."""
39
+
40
+ name: str
41
+ description: str
42
+ # This is a json schema string
43
+ parameters: Dict[str, Any]
44
+
45
+
46
+ class ToolCall(BaseModel):
47
+ """Represents a tool call made by the model."""
48
+
49
+ id: Optional[str] = None # Optional ID, included by providers like OpenAI
50
+ name: str
51
+ arguments: Dict[str, Any] # Arguments for the tool call, as a dictionary
52
+
53
+
54
+ class ToolResponse(BaseModel):
55
+ """Represents a response to a tool call."""
56
+
57
+ tool_call_id: Optional[str] = None # ID of the corresponding tool call
58
+ content: str # String content of the tool response
59
+
60
+
61
+ class ModelMessage(BaseModel):
62
+ """Base class for messages exchanged with LLM providers."""
63
+
64
+ content: Optional[str] = None
65
+
66
+
67
+ class AssistantMessage(ModelMessage):
68
+ """Message from the assistant, may include tool calls."""
69
+
70
+ tool_calls: Optional[List[ToolCall]] = None
71
+
72
+
73
+ class UserMessage(ModelMessage):
74
+ """Message from the user."""
75
+
76
+ files: Optional[list[PlanarFile]] = None
77
+
78
+
79
+ class SystemMessage(ModelMessage):
80
+ """System message that provides context/instructions."""
81
+
82
+ pass
83
+
84
+
85
+ class ToolMessage(ModelMessage):
86
+ """Tool message containing a tool response."""
87
+
88
+ tool_call_id: str # ID of the tool call this is responding to
89
+
90
+
91
+ class CompletionResponse[T: BaseModel | str](BaseModel):
92
+ """Response object that may contain content or tool calls."""
93
+
94
+ content: Optional[T] = None # Content as str or parsed Pydantic model
95
+ reasoning_content: Optional[str] = None # Optional reasoning content
96
+ tool_calls: Optional[List[ToolCall]] = None # List of tool calls, if any
97
+
98
+
99
+ class AgentRunResult[TOutput: BaseModel | str](BaseModel):
100
+ output: TOutput
101
+
102
+
103
+ class Base64Content(BaseModel):
104
+ type: Literal["base64"] = "base64"
105
+ content: str
106
+ content_type: str
107
+
108
+ def __repr__(self):
109
+ return f"Base64Content(content_type={self.content_type}, content={self.content[:10]}...)"
110
+
111
+
112
+ class URLContent(BaseModel):
113
+ type: Literal["url"] = "url"
114
+ content: str
115
+
116
+
117
+ class FileIdContent(BaseModel):
118
+ type: Literal["file_id"] = "file_id"
119
+ content: str
120
+
121
+
122
+ FileContent = Annotated[
123
+ Union[Base64Content, URLContent, FileIdContent],
124
+ Field(discriminator="type"),
125
+ ]
126
+
127
+
128
+ class FileMap(BaseModel):
129
+ mapping: dict[str, FileContent]
130
+
131
+
132
+ class AgentSerializeable(BaseModel):
133
+ name: str
134
+ input_schema: JsonSchema | None = None
135
+ output_schema: JsonSchema | None = None
136
+ tool_definitions: list[dict[str, Any]]
137
+ configs: list[ObjectConfigurationBase[AgentConfig]]
138
+
139
+ # TODO: actually fetch built_in_vars from agent object
140
+ built_in_vars: dict[str, str] = Field(default_factory=dict)