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.
- planar/_version.py +1 -1
- planar/ai/agent.py +155 -283
- planar/ai/agent_base.py +170 -0
- planar/ai/agent_utils.py +7 -0
- planar/ai/pydantic_ai.py +638 -0
- planar/ai/test_agent_serialization.py +1 -1
- planar/app.py +64 -20
- planar/cli.py +39 -27
- planar/config.py +45 -36
- planar/db/db.py +2 -1
- planar/files/storage/azure_blob.py +343 -0
- planar/files/storage/base.py +7 -0
- planar/files/storage/config.py +70 -7
- planar/files/storage/s3.py +6 -6
- planar/files/storage/test_azure_blob.py +435 -0
- planar/logging/formatter.py +17 -4
- planar/logging/test_formatter.py +327 -0
- planar/registry_items.py +2 -1
- planar/routers/agents_router.py +3 -1
- planar/routers/files.py +11 -2
- planar/routers/models.py +14 -1
- planar/routers/test_agents_router.py +1 -1
- planar/routers/test_files_router.py +49 -0
- planar/routers/test_routes_security.py +5 -7
- planar/routers/test_workflow_router.py +270 -3
- planar/routers/workflow.py +95 -36
- planar/rules/models.py +36 -39
- planar/rules/test_data/account_dormancy_management.json +223 -0
- planar/rules/test_data/airline_loyalty_points_calculator.json +262 -0
- planar/rules/test_data/applicant_risk_assessment.json +435 -0
- planar/rules/test_data/booking_fraud_detection.json +407 -0
- planar/rules/test_data/cellular_data_rollover_system.json +258 -0
- planar/rules/test_data/clinical_trial_eligibility_screener.json +437 -0
- planar/rules/test_data/customer_lifetime_value.json +143 -0
- planar/rules/test_data/import_duties_calculator.json +289 -0
- planar/rules/test_data/insurance_prior_authorization.json +443 -0
- planar/rules/test_data/online_check_in_eligibility_system.json +254 -0
- planar/rules/test_data/order_consolidation_system.json +375 -0
- planar/rules/test_data/portfolio_risk_monitor.json +471 -0
- planar/rules/test_data/supply_chain_risk.json +253 -0
- planar/rules/test_data/warehouse_cross_docking.json +237 -0
- planar/rules/test_rules.py +750 -6
- planar/scaffold_templates/planar.dev.yaml.j2 +6 -6
- planar/scaffold_templates/planar.prod.yaml.j2 +9 -5
- planar/scaffold_templates/pyproject.toml.j2 +1 -1
- planar/security/auth_context.py +21 -0
- planar/security/{jwt_middleware.py → auth_middleware.py} +70 -17
- planar/security/authorization.py +9 -15
- planar/security/tests/test_auth_middleware.py +162 -0
- planar/sse/proxy.py +4 -9
- planar/test_app.py +92 -1
- planar/test_cli.py +81 -59
- planar/test_config.py +17 -14
- planar/testing/fixtures.py +325 -0
- planar/testing/planar_test_client.py +5 -2
- planar/utils.py +41 -1
- planar/workflows/execution.py +1 -1
- planar/workflows/orchestrator.py +5 -0
- planar/workflows/serialization.py +12 -6
- planar/workflows/step_core.py +3 -1
- planar/workflows/test_serialization.py +9 -1
- {planar-0.5.0.dist-info → planar-0.8.0.dist-info}/METADATA +30 -5
- planar-0.8.0.dist-info/RECORD +166 -0
- planar/.__init__.py.un~ +0 -0
- planar/._version.py.un~ +0 -0
- planar/.app.py.un~ +0 -0
- planar/.cli.py.un~ +0 -0
- planar/.config.py.un~ +0 -0
- planar/.context.py.un~ +0 -0
- planar/.db.py.un~ +0 -0
- planar/.di.py.un~ +0 -0
- planar/.engine.py.un~ +0 -0
- planar/.files.py.un~ +0 -0
- planar/.log_context.py.un~ +0 -0
- planar/.log_metadata.py.un~ +0 -0
- planar/.logging.py.un~ +0 -0
- planar/.object_registry.py.un~ +0 -0
- planar/.otel.py.un~ +0 -0
- planar/.server.py.un~ +0 -0
- planar/.session.py.un~ +0 -0
- planar/.sqlalchemy.py.un~ +0 -0
- planar/.task_local.py.un~ +0 -0
- planar/.test_app.py.un~ +0 -0
- planar/.test_config.py.un~ +0 -0
- planar/.test_object_config.py.un~ +0 -0
- planar/.test_sqlalchemy.py.un~ +0 -0
- planar/.test_utils.py.un~ +0 -0
- planar/.util.py.un~ +0 -0
- planar/.utils.py.un~ +0 -0
- planar/ai/.__init__.py.un~ +0 -0
- planar/ai/._models.py.un~ +0 -0
- planar/ai/.agent.py.un~ +0 -0
- planar/ai/.agent_utils.py.un~ +0 -0
- planar/ai/.events.py.un~ +0 -0
- planar/ai/.files.py.un~ +0 -0
- planar/ai/.models.py.un~ +0 -0
- planar/ai/.providers.py.un~ +0 -0
- planar/ai/.pydantic_ai.py.un~ +0 -0
- planar/ai/.pydantic_ai_agent.py.un~ +0 -0
- planar/ai/.pydantic_ai_provider.py.un~ +0 -0
- planar/ai/.step.py.un~ +0 -0
- planar/ai/.test_agent.py.un~ +0 -0
- planar/ai/.test_agent_serialization.py.un~ +0 -0
- planar/ai/.test_providers.py.un~ +0 -0
- planar/ai/.utils.py.un~ +0 -0
- planar/ai/providers.py +0 -1088
- planar/ai/test_agent.py +0 -1298
- planar/ai/test_providers.py +0 -463
- planar/db/.db.py.un~ +0 -0
- planar/files/.config.py.un~ +0 -0
- planar/files/.local.py.un~ +0 -0
- planar/files/.local_filesystem.py.un~ +0 -0
- planar/files/.model.py.un~ +0 -0
- planar/files/.models.py.un~ +0 -0
- planar/files/.s3.py.un~ +0 -0
- planar/files/.storage.py.un~ +0 -0
- planar/files/.test_files.py.un~ +0 -0
- planar/files/storage/.__init__.py.un~ +0 -0
- planar/files/storage/.base.py.un~ +0 -0
- planar/files/storage/.config.py.un~ +0 -0
- planar/files/storage/.context.py.un~ +0 -0
- planar/files/storage/.local_directory.py.un~ +0 -0
- planar/files/storage/.test_local_directory.py.un~ +0 -0
- planar/files/storage/.test_s3.py.un~ +0 -0
- planar/human/.human.py.un~ +0 -0
- planar/human/.test_human.py.un~ +0 -0
- planar/logging/.__init__.py.un~ +0 -0
- planar/logging/.attributes.py.un~ +0 -0
- planar/logging/.formatter.py.un~ +0 -0
- planar/logging/.logger.py.un~ +0 -0
- planar/logging/.otel.py.un~ +0 -0
- planar/logging/.tracer.py.un~ +0 -0
- planar/modeling/.mixin.py.un~ +0 -0
- planar/modeling/.storage.py.un~ +0 -0
- planar/modeling/orm/.planar_base_model.py.un~ +0 -0
- planar/object_config/.object_config.py.un~ +0 -0
- planar/routers/.__init__.py.un~ +0 -0
- planar/routers/.agents_router.py.un~ +0 -0
- planar/routers/.crud.py.un~ +0 -0
- planar/routers/.decision.py.un~ +0 -0
- planar/routers/.event.py.un~ +0 -0
- planar/routers/.file_attachment.py.un~ +0 -0
- planar/routers/.files.py.un~ +0 -0
- planar/routers/.files_router.py.un~ +0 -0
- planar/routers/.human.py.un~ +0 -0
- planar/routers/.info.py.un~ +0 -0
- planar/routers/.models.py.un~ +0 -0
- planar/routers/.object_config_router.py.un~ +0 -0
- planar/routers/.rule.py.un~ +0 -0
- planar/routers/.test_object_config_router.py.un~ +0 -0
- planar/routers/.test_workflow_router.py.un~ +0 -0
- planar/routers/.workflow.py.un~ +0 -0
- planar/rules/.decorator.py.un~ +0 -0
- planar/rules/.runner.py.un~ +0 -0
- planar/rules/.test_rules.py.un~ +0 -0
- planar/security/.jwt_middleware.py.un~ +0 -0
- planar/sse/.constants.py.un~ +0 -0
- planar/sse/.example.html.un~ +0 -0
- planar/sse/.hub.py.un~ +0 -0
- planar/sse/.model.py.un~ +0 -0
- planar/sse/.proxy.py.un~ +0 -0
- planar/testing/.client.py.un~ +0 -0
- planar/testing/.memory_storage.py.un~ +0 -0
- planar/testing/.planar_test_client.py.un~ +0 -0
- planar/testing/.predictable_tracer.py.un~ +0 -0
- planar/testing/.synchronizable_tracer.py.un~ +0 -0
- planar/testing/.test_memory_storage.py.un~ +0 -0
- planar/testing/.workflow_observer.py.un~ +0 -0
- planar/workflows/.__init__.py.un~ +0 -0
- planar/workflows/.builtin_steps.py.un~ +0 -0
- planar/workflows/.concurrency_tracing.py.un~ +0 -0
- planar/workflows/.context.py.un~ +0 -0
- planar/workflows/.contrib.py.un~ +0 -0
- planar/workflows/.decorators.py.un~ +0 -0
- planar/workflows/.durable_test.py.un~ +0 -0
- planar/workflows/.errors.py.un~ +0 -0
- planar/workflows/.events.py.un~ +0 -0
- planar/workflows/.exceptions.py.un~ +0 -0
- planar/workflows/.execution.py.un~ +0 -0
- planar/workflows/.human.py.un~ +0 -0
- planar/workflows/.lock.py.un~ +0 -0
- planar/workflows/.misc.py.un~ +0 -0
- planar/workflows/.model.py.un~ +0 -0
- planar/workflows/.models.py.un~ +0 -0
- planar/workflows/.notifications.py.un~ +0 -0
- planar/workflows/.orchestrator.py.un~ +0 -0
- planar/workflows/.runtime.py.un~ +0 -0
- planar/workflows/.serialization.py.un~ +0 -0
- planar/workflows/.step.py.un~ +0 -0
- planar/workflows/.step_core.py.un~ +0 -0
- planar/workflows/.sub_workflow_runner.py.un~ +0 -0
- planar/workflows/.sub_workflow_scheduler.py.un~ +0 -0
- planar/workflows/.test_concurrency.py.un~ +0 -0
- planar/workflows/.test_concurrency_detection.py.un~ +0 -0
- planar/workflows/.test_human.py.un~ +0 -0
- planar/workflows/.test_lock_timeout.py.un~ +0 -0
- planar/workflows/.test_orchestrator.py.un~ +0 -0
- planar/workflows/.test_race_conditions.py.un~ +0 -0
- planar/workflows/.test_serialization.py.un~ +0 -0
- planar/workflows/.test_suspend_deserialization.py.un~ +0 -0
- planar/workflows/.test_workflow.py.un~ +0 -0
- planar/workflows/.tracing.py.un~ +0 -0
- planar/workflows/.types.py.un~ +0 -0
- planar/workflows/.util.py.un~ +0 -0
- planar/workflows/.utils.py.un~ +0 -0
- planar/workflows/.workflow.py.un~ +0 -0
- planar/workflows/.workflow_wrapper.py.un~ +0 -0
- planar/workflows/.wrappers.py.un~ +0 -0
- planar-0.5.0.dist-info/RECORD +0 -289
- {planar-0.5.0.dist-info → planar-0.8.0.dist-info}/WHEEL +0 -0
- {planar-0.5.0.dist-info → planar-0.8.0.dist-info}/entry_points.txt +0 -0
planar/ai/agent_base.py
ADDED
@@ -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
|
|