planar 0.5.0__py3-none-any.whl → 0.8.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 (211) hide show
  1. planar/_version.py +1 -1
  2. planar/ai/agent.py +155 -283
  3. planar/ai/agent_base.py +170 -0
  4. planar/ai/agent_utils.py +7 -0
  5. planar/ai/pydantic_ai.py +638 -0
  6. planar/ai/test_agent_serialization.py +1 -1
  7. planar/app.py +64 -20
  8. planar/cli.py +39 -27
  9. planar/config.py +45 -36
  10. planar/db/db.py +2 -1
  11. planar/files/storage/azure_blob.py +343 -0
  12. planar/files/storage/base.py +7 -0
  13. planar/files/storage/config.py +70 -7
  14. planar/files/storage/s3.py +6 -6
  15. planar/files/storage/test_azure_blob.py +435 -0
  16. planar/logging/formatter.py +17 -4
  17. planar/logging/test_formatter.py +327 -0
  18. planar/registry_items.py +2 -1
  19. planar/routers/agents_router.py +3 -1
  20. planar/routers/files.py +11 -2
  21. planar/routers/models.py +14 -1
  22. planar/routers/test_agents_router.py +1 -1
  23. planar/routers/test_files_router.py +49 -0
  24. planar/routers/test_routes_security.py +5 -7
  25. planar/routers/test_workflow_router.py +270 -3
  26. planar/routers/workflow.py +95 -36
  27. planar/rules/models.py +36 -39
  28. planar/rules/test_data/account_dormancy_management.json +223 -0
  29. planar/rules/test_data/airline_loyalty_points_calculator.json +262 -0
  30. planar/rules/test_data/applicant_risk_assessment.json +435 -0
  31. planar/rules/test_data/booking_fraud_detection.json +407 -0
  32. planar/rules/test_data/cellular_data_rollover_system.json +258 -0
  33. planar/rules/test_data/clinical_trial_eligibility_screener.json +437 -0
  34. planar/rules/test_data/customer_lifetime_value.json +143 -0
  35. planar/rules/test_data/import_duties_calculator.json +289 -0
  36. planar/rules/test_data/insurance_prior_authorization.json +443 -0
  37. planar/rules/test_data/online_check_in_eligibility_system.json +254 -0
  38. planar/rules/test_data/order_consolidation_system.json +375 -0
  39. planar/rules/test_data/portfolio_risk_monitor.json +471 -0
  40. planar/rules/test_data/supply_chain_risk.json +253 -0
  41. planar/rules/test_data/warehouse_cross_docking.json +237 -0
  42. planar/rules/test_rules.py +750 -6
  43. planar/scaffold_templates/planar.dev.yaml.j2 +6 -6
  44. planar/scaffold_templates/planar.prod.yaml.j2 +9 -5
  45. planar/scaffold_templates/pyproject.toml.j2 +1 -1
  46. planar/security/auth_context.py +21 -0
  47. planar/security/{jwt_middleware.py → auth_middleware.py} +70 -17
  48. planar/security/authorization.py +9 -15
  49. planar/security/tests/test_auth_middleware.py +162 -0
  50. planar/sse/proxy.py +4 -9
  51. planar/test_app.py +92 -1
  52. planar/test_cli.py +81 -59
  53. planar/test_config.py +17 -14
  54. planar/testing/fixtures.py +325 -0
  55. planar/testing/planar_test_client.py +5 -2
  56. planar/utils.py +41 -1
  57. planar/workflows/execution.py +1 -1
  58. planar/workflows/orchestrator.py +5 -0
  59. planar/workflows/serialization.py +12 -6
  60. planar/workflows/step_core.py +3 -1
  61. planar/workflows/test_serialization.py +9 -1
  62. {planar-0.5.0.dist-info → planar-0.8.0.dist-info}/METADATA +30 -5
  63. planar-0.8.0.dist-info/RECORD +166 -0
  64. planar/.__init__.py.un~ +0 -0
  65. planar/._version.py.un~ +0 -0
  66. planar/.app.py.un~ +0 -0
  67. planar/.cli.py.un~ +0 -0
  68. planar/.config.py.un~ +0 -0
  69. planar/.context.py.un~ +0 -0
  70. planar/.db.py.un~ +0 -0
  71. planar/.di.py.un~ +0 -0
  72. planar/.engine.py.un~ +0 -0
  73. planar/.files.py.un~ +0 -0
  74. planar/.log_context.py.un~ +0 -0
  75. planar/.log_metadata.py.un~ +0 -0
  76. planar/.logging.py.un~ +0 -0
  77. planar/.object_registry.py.un~ +0 -0
  78. planar/.otel.py.un~ +0 -0
  79. planar/.server.py.un~ +0 -0
  80. planar/.session.py.un~ +0 -0
  81. planar/.sqlalchemy.py.un~ +0 -0
  82. planar/.task_local.py.un~ +0 -0
  83. planar/.test_app.py.un~ +0 -0
  84. planar/.test_config.py.un~ +0 -0
  85. planar/.test_object_config.py.un~ +0 -0
  86. planar/.test_sqlalchemy.py.un~ +0 -0
  87. planar/.test_utils.py.un~ +0 -0
  88. planar/.util.py.un~ +0 -0
  89. planar/.utils.py.un~ +0 -0
  90. planar/ai/.__init__.py.un~ +0 -0
  91. planar/ai/._models.py.un~ +0 -0
  92. planar/ai/.agent.py.un~ +0 -0
  93. planar/ai/.agent_utils.py.un~ +0 -0
  94. planar/ai/.events.py.un~ +0 -0
  95. planar/ai/.files.py.un~ +0 -0
  96. planar/ai/.models.py.un~ +0 -0
  97. planar/ai/.providers.py.un~ +0 -0
  98. planar/ai/.pydantic_ai.py.un~ +0 -0
  99. planar/ai/.pydantic_ai_agent.py.un~ +0 -0
  100. planar/ai/.pydantic_ai_provider.py.un~ +0 -0
  101. planar/ai/.step.py.un~ +0 -0
  102. planar/ai/.test_agent.py.un~ +0 -0
  103. planar/ai/.test_agent_serialization.py.un~ +0 -0
  104. planar/ai/.test_providers.py.un~ +0 -0
  105. planar/ai/.utils.py.un~ +0 -0
  106. planar/ai/providers.py +0 -1088
  107. planar/ai/test_agent.py +0 -1298
  108. planar/ai/test_providers.py +0 -463
  109. planar/db/.db.py.un~ +0 -0
  110. planar/files/.config.py.un~ +0 -0
  111. planar/files/.local.py.un~ +0 -0
  112. planar/files/.local_filesystem.py.un~ +0 -0
  113. planar/files/.model.py.un~ +0 -0
  114. planar/files/.models.py.un~ +0 -0
  115. planar/files/.s3.py.un~ +0 -0
  116. planar/files/.storage.py.un~ +0 -0
  117. planar/files/.test_files.py.un~ +0 -0
  118. planar/files/storage/.__init__.py.un~ +0 -0
  119. planar/files/storage/.base.py.un~ +0 -0
  120. planar/files/storage/.config.py.un~ +0 -0
  121. planar/files/storage/.context.py.un~ +0 -0
  122. planar/files/storage/.local_directory.py.un~ +0 -0
  123. planar/files/storage/.test_local_directory.py.un~ +0 -0
  124. planar/files/storage/.test_s3.py.un~ +0 -0
  125. planar/human/.human.py.un~ +0 -0
  126. planar/human/.test_human.py.un~ +0 -0
  127. planar/logging/.__init__.py.un~ +0 -0
  128. planar/logging/.attributes.py.un~ +0 -0
  129. planar/logging/.formatter.py.un~ +0 -0
  130. planar/logging/.logger.py.un~ +0 -0
  131. planar/logging/.otel.py.un~ +0 -0
  132. planar/logging/.tracer.py.un~ +0 -0
  133. planar/modeling/.mixin.py.un~ +0 -0
  134. planar/modeling/.storage.py.un~ +0 -0
  135. planar/modeling/orm/.planar_base_model.py.un~ +0 -0
  136. planar/object_config/.object_config.py.un~ +0 -0
  137. planar/routers/.__init__.py.un~ +0 -0
  138. planar/routers/.agents_router.py.un~ +0 -0
  139. planar/routers/.crud.py.un~ +0 -0
  140. planar/routers/.decision.py.un~ +0 -0
  141. planar/routers/.event.py.un~ +0 -0
  142. planar/routers/.file_attachment.py.un~ +0 -0
  143. planar/routers/.files.py.un~ +0 -0
  144. planar/routers/.files_router.py.un~ +0 -0
  145. planar/routers/.human.py.un~ +0 -0
  146. planar/routers/.info.py.un~ +0 -0
  147. planar/routers/.models.py.un~ +0 -0
  148. planar/routers/.object_config_router.py.un~ +0 -0
  149. planar/routers/.rule.py.un~ +0 -0
  150. planar/routers/.test_object_config_router.py.un~ +0 -0
  151. planar/routers/.test_workflow_router.py.un~ +0 -0
  152. planar/routers/.workflow.py.un~ +0 -0
  153. planar/rules/.decorator.py.un~ +0 -0
  154. planar/rules/.runner.py.un~ +0 -0
  155. planar/rules/.test_rules.py.un~ +0 -0
  156. planar/security/.jwt_middleware.py.un~ +0 -0
  157. planar/sse/.constants.py.un~ +0 -0
  158. planar/sse/.example.html.un~ +0 -0
  159. planar/sse/.hub.py.un~ +0 -0
  160. planar/sse/.model.py.un~ +0 -0
  161. planar/sse/.proxy.py.un~ +0 -0
  162. planar/testing/.client.py.un~ +0 -0
  163. planar/testing/.memory_storage.py.un~ +0 -0
  164. planar/testing/.planar_test_client.py.un~ +0 -0
  165. planar/testing/.predictable_tracer.py.un~ +0 -0
  166. planar/testing/.synchronizable_tracer.py.un~ +0 -0
  167. planar/testing/.test_memory_storage.py.un~ +0 -0
  168. planar/testing/.workflow_observer.py.un~ +0 -0
  169. planar/workflows/.__init__.py.un~ +0 -0
  170. planar/workflows/.builtin_steps.py.un~ +0 -0
  171. planar/workflows/.concurrency_tracing.py.un~ +0 -0
  172. planar/workflows/.context.py.un~ +0 -0
  173. planar/workflows/.contrib.py.un~ +0 -0
  174. planar/workflows/.decorators.py.un~ +0 -0
  175. planar/workflows/.durable_test.py.un~ +0 -0
  176. planar/workflows/.errors.py.un~ +0 -0
  177. planar/workflows/.events.py.un~ +0 -0
  178. planar/workflows/.exceptions.py.un~ +0 -0
  179. planar/workflows/.execution.py.un~ +0 -0
  180. planar/workflows/.human.py.un~ +0 -0
  181. planar/workflows/.lock.py.un~ +0 -0
  182. planar/workflows/.misc.py.un~ +0 -0
  183. planar/workflows/.model.py.un~ +0 -0
  184. planar/workflows/.models.py.un~ +0 -0
  185. planar/workflows/.notifications.py.un~ +0 -0
  186. planar/workflows/.orchestrator.py.un~ +0 -0
  187. planar/workflows/.runtime.py.un~ +0 -0
  188. planar/workflows/.serialization.py.un~ +0 -0
  189. planar/workflows/.step.py.un~ +0 -0
  190. planar/workflows/.step_core.py.un~ +0 -0
  191. planar/workflows/.sub_workflow_runner.py.un~ +0 -0
  192. planar/workflows/.sub_workflow_scheduler.py.un~ +0 -0
  193. planar/workflows/.test_concurrency.py.un~ +0 -0
  194. planar/workflows/.test_concurrency_detection.py.un~ +0 -0
  195. planar/workflows/.test_human.py.un~ +0 -0
  196. planar/workflows/.test_lock_timeout.py.un~ +0 -0
  197. planar/workflows/.test_orchestrator.py.un~ +0 -0
  198. planar/workflows/.test_race_conditions.py.un~ +0 -0
  199. planar/workflows/.test_serialization.py.un~ +0 -0
  200. planar/workflows/.test_suspend_deserialization.py.un~ +0 -0
  201. planar/workflows/.test_workflow.py.un~ +0 -0
  202. planar/workflows/.tracing.py.un~ +0 -0
  203. planar/workflows/.types.py.un~ +0 -0
  204. planar/workflows/.util.py.un~ +0 -0
  205. planar/workflows/.utils.py.un~ +0 -0
  206. planar/workflows/.workflow.py.un~ +0 -0
  207. planar/workflows/.workflow_wrapper.py.un~ +0 -0
  208. planar/workflows/.wrappers.py.un~ +0 -0
  209. planar-0.5.0.dist-info/RECORD +0 -289
  210. {planar-0.5.0.dist-info → planar-0.8.0.dist-info}/WHEEL +0 -0
  211. {planar-0.5.0.dist-info → planar-0.8.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,170 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ from dataclasses import dataclass, field
5
+ from typing import (
6
+ Any,
7
+ Callable,
8
+ Coroutine,
9
+ Type,
10
+ cast,
11
+ overload,
12
+ )
13
+
14
+ from pydantic import BaseModel
15
+
16
+ from planar.ai.agent_utils import AgentEventEmitter
17
+ from planar.ai.models import (
18
+ AgentConfig,
19
+ AgentRunResult,
20
+ )
21
+ from planar.logging import get_logger
22
+ from planar.modeling.field_helpers import JsonSchema
23
+ from planar.utils import P, R, T, U
24
+ from planar.workflows import as_step
25
+ from planar.workflows.models import StepType
26
+
27
+ logger = get_logger(__name__)
28
+
29
+
30
+ @dataclass
31
+ class AgentBase[
32
+ # TODO: add `= str` default when we upgrade to 3.13
33
+ TInput: BaseModel | str,
34
+ TOutput: BaseModel | str,
35
+ ](abc.ABC):
36
+ """An LLM-powered agent that can be called directly within workflows."""
37
+
38
+ name: str
39
+ system_prompt: str
40
+ output_type: Type[TOutput] | None = None
41
+ input_type: Type[TInput] | None = None
42
+ user_prompt: str = ""
43
+ tools: list[Callable] = field(default_factory=list)
44
+ max_turns: int = 2
45
+ model_parameters: dict[str, Any] = field(default_factory=dict)
46
+ event_emitter: AgentEventEmitter | None = None
47
+ durable: bool = True
48
+
49
+ # TODO: move here to serialize to frontend
50
+ #
51
+ # built_in_vars: Dict[str, str] = field(default_factory=lambda: {
52
+ # "datetime_now": datetime.datetime.now().isoformat(),
53
+ # "date_today": datetime.date.today().isoformat(),
54
+ # })
55
+
56
+ def __post_init__(self):
57
+ if self.input_type:
58
+ if (
59
+ not issubclass(self.input_type, BaseModel)
60
+ and self.input_type is not str
61
+ ):
62
+ raise ValueError(
63
+ "input_type must be 'str' or a subclass of a Pydantic model"
64
+ )
65
+ if self.max_turns < 1:
66
+ raise ValueError("Max_turns must be greater than or equal to 1.")
67
+ if self.tools and self.max_turns <= 1:
68
+ raise ValueError(
69
+ "For tool calling to work, max_turns must be greater than 1."
70
+ )
71
+
72
+ def input_schema(self) -> JsonSchema | None:
73
+ if self.input_type is None:
74
+ return None
75
+ if self.input_type is str:
76
+ return None
77
+ assert issubclass(self.input_type, BaseModel), (
78
+ "input_type must be a subclass of BaseModel or str"
79
+ )
80
+ return self.input_type.model_json_schema()
81
+
82
+ def output_schema(self) -> JsonSchema | None:
83
+ if self.output_type is None:
84
+ return None
85
+ if self.output_type is str:
86
+ return None
87
+ assert issubclass(self.output_type, BaseModel), (
88
+ "output_type must be a subclass of BaseModel or str"
89
+ )
90
+ return self.output_type.model_json_schema()
91
+
92
+ @overload
93
+ async def __call__(
94
+ self: "AgentBase[TInput, str]",
95
+ input_value: TInput,
96
+ ) -> AgentRunResult[str]: ...
97
+
98
+ @overload
99
+ async def __call__(
100
+ self: "AgentBase[TInput, TOutput]",
101
+ input_value: TInput,
102
+ ) -> AgentRunResult[TOutput]: ...
103
+
104
+ def as_step_if_durable(
105
+ self,
106
+ func: Callable[P, Coroutine[T, U, R]],
107
+ step_type: StepType,
108
+ display_name: str | None = None,
109
+ return_type: Type[R] | None = None,
110
+ ) -> Callable[P, Coroutine[T, U, R]]:
111
+ if not self.durable:
112
+ return func
113
+ return as_step(
114
+ func,
115
+ step_type=step_type,
116
+ display_name=display_name or self.name,
117
+ return_type=return_type,
118
+ )
119
+
120
+ async def __call__(
121
+ self,
122
+ input_value: TInput,
123
+ ) -> AgentRunResult[Any]:
124
+ if self.input_type is not None and not isinstance(input_value, self.input_type):
125
+ raise ValueError(
126
+ f"Input value must be of type {self.input_type}, but got {type(input_value)}"
127
+ )
128
+ elif not isinstance(input_value, (str, BaseModel)):
129
+ # Should not happen based on type constraints, but just in case
130
+ # user does not have type checking enabled
131
+ raise ValueError(
132
+ "Input value must be a string or a Pydantic model if input_type is not provided"
133
+ )
134
+
135
+ if self.output_type is None:
136
+ run_step = self.as_step_if_durable(
137
+ self.run_step,
138
+ step_type=StepType.AGENT,
139
+ display_name=self.name,
140
+ return_type=AgentRunResult[str],
141
+ )
142
+ else:
143
+ run_step = self.as_step_if_durable(
144
+ self.run_step,
145
+ step_type=StepType.AGENT,
146
+ display_name=self.name,
147
+ return_type=AgentRunResult[self.output_type],
148
+ )
149
+
150
+ result = await run_step(input_value=input_value)
151
+ # Cast the result to ensure type compatibility
152
+ return cast(AgentRunResult[TOutput], result)
153
+
154
+ @abc.abstractmethod
155
+ async def run_step(
156
+ self,
157
+ input_value: TInput,
158
+ ) -> AgentRunResult[TOutput]: ...
159
+
160
+ @abc.abstractmethod
161
+ def get_model_str(self) -> str: ...
162
+
163
+ def to_config(self) -> AgentConfig:
164
+ return AgentConfig(
165
+ system_prompt=self.system_prompt,
166
+ user_prompt=self.user_prompt,
167
+ model=self.get_model_str(),
168
+ max_turns=self.max_turns,
169
+ model_parameters=self.model_parameters,
170
+ )
planar/ai/agent_utils.py CHANGED
@@ -26,6 +26,13 @@ from planar.workflows import step
26
26
  logger = get_logger(__name__)
27
27
 
28
28
 
29
+ class ModelSpec(BaseModel):
30
+ """Pydantic model for AI model specifications."""
31
+
32
+ model_id: str
33
+ parameters: dict[str, Any] = {}
34
+
35
+
29
36
  class AgentEventType(str, Enum):
30
37
  """Valid event types that can be emitted by an Agent."""
31
38