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.
- 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/__init__.py +26 -0
- planar/_version.py +1 -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/__init__.py +15 -0
- planar/ai/agent.py +457 -0
- planar/ai/agent_utils.py +205 -0
- planar/ai/models.py +140 -0
- planar/ai/providers.py +1088 -0
- planar/ai/test_agent.py +1298 -0
- planar/ai/test_agent_serialization.py +229 -0
- planar/ai/test_providers.py +463 -0
- planar/ai/utils.py +102 -0
- planar/app.py +494 -0
- planar/cli.py +282 -0
- planar/config.py +544 -0
- planar/db/.db.py.un~ +0 -0
- planar/db/__init__.py +17 -0
- planar/db/alembic/env.py +136 -0
- planar/db/alembic/script.py.mako +28 -0
- planar/db/alembic/versions/3476068c153c_initial_system_tables_migration.py +339 -0
- planar/db/alembic.ini +128 -0
- planar/db/db.py +318 -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/__init__.py +2 -0
- planar/files/models.py +162 -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/files/storage/base.py +61 -0
- planar/files/storage/config.py +44 -0
- planar/files/storage/context.py +15 -0
- planar/files/storage/local_directory.py +188 -0
- planar/files/storage/s3.py +220 -0
- planar/files/storage/test_local_directory.py +162 -0
- planar/files/storage/test_s3.py +299 -0
- planar/files/test_files.py +283 -0
- planar/human/.human.py.un~ +0 -0
- planar/human/.test_human.py.un~ +0 -0
- planar/human/__init__.py +2 -0
- planar/human/human.py +458 -0
- planar/human/models.py +80 -0
- planar/human/test_human.py +385 -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/logging/__init__.py +10 -0
- planar/logging/attributes.py +54 -0
- planar/logging/context.py +14 -0
- planar/logging/formatter.py +113 -0
- planar/logging/logger.py +114 -0
- planar/logging/otel.py +51 -0
- planar/modeling/.mixin.py.un~ +0 -0
- planar/modeling/.storage.py.un~ +0 -0
- planar/modeling/__init__.py +0 -0
- planar/modeling/field_helpers.py +59 -0
- planar/modeling/json_schema_generator.py +94 -0
- planar/modeling/mixins/__init__.py +10 -0
- planar/modeling/mixins/auditable.py +52 -0
- planar/modeling/mixins/test_auditable.py +97 -0
- planar/modeling/mixins/test_timestamp.py +134 -0
- planar/modeling/mixins/test_uuid_primary_key.py +52 -0
- planar/modeling/mixins/timestamp.py +53 -0
- planar/modeling/mixins/uuid_primary_key.py +19 -0
- planar/modeling/orm/.planar_base_model.py.un~ +0 -0
- planar/modeling/orm/__init__.py +18 -0
- planar/modeling/orm/planar_base_entity.py +29 -0
- planar/modeling/orm/query_filter_builder.py +122 -0
- planar/modeling/orm/reexports.py +15 -0
- planar/object_config/.object_config.py.un~ +0 -0
- planar/object_config/__init__.py +11 -0
- planar/object_config/models.py +114 -0
- planar/object_config/object_config.py +378 -0
- planar/object_registry.py +100 -0
- planar/registry_items.py +65 -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/routers/__init__.py +13 -0
- planar/routers/agents_router.py +197 -0
- planar/routers/entity_router.py +143 -0
- planar/routers/event.py +91 -0
- planar/routers/files.py +142 -0
- planar/routers/human.py +151 -0
- planar/routers/info.py +131 -0
- planar/routers/models.py +170 -0
- planar/routers/object_config_router.py +133 -0
- planar/routers/rule.py +108 -0
- planar/routers/test_agents_router.py +174 -0
- planar/routers/test_object_config_router.py +367 -0
- planar/routers/test_routes_security.py +169 -0
- planar/routers/test_rule_router.py +470 -0
- planar/routers/test_workflow_router.py +274 -0
- planar/routers/workflow.py +468 -0
- planar/rules/.decorator.py.un~ +0 -0
- planar/rules/.runner.py.un~ +0 -0
- planar/rules/.test_rules.py.un~ +0 -0
- planar/rules/__init__.py +23 -0
- planar/rules/decorator.py +184 -0
- planar/rules/models.py +355 -0
- planar/rules/rule_configuration.py +191 -0
- planar/rules/runner.py +64 -0
- planar/rules/test_rules.py +750 -0
- planar/scaffold_templates/app/__init__.py.j2 +0 -0
- planar/scaffold_templates/app/db/entities.py.j2 +11 -0
- planar/scaffold_templates/app/flows/process_invoice.py.j2 +67 -0
- planar/scaffold_templates/main.py.j2 +13 -0
- planar/scaffold_templates/planar.dev.yaml.j2 +34 -0
- planar/scaffold_templates/planar.prod.yaml.j2 +28 -0
- planar/scaffold_templates/pyproject.toml.j2 +10 -0
- planar/security/.jwt_middleware.py.un~ +0 -0
- planar/security/auth_context.py +148 -0
- planar/security/authorization.py +388 -0
- planar/security/default_policies.cedar +77 -0
- planar/security/jwt_middleware.py +116 -0
- planar/security/security_context.py +18 -0
- planar/security/tests/test_authorization_context.py +78 -0
- planar/security/tests/test_cedar_basics.py +41 -0
- planar/security/tests/test_cedar_policies.py +158 -0
- planar/security/tests/test_jwt_principal_context.py +179 -0
- planar/session.py +40 -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/sse/constants.py +1 -0
- planar/sse/example.html +126 -0
- planar/sse/hub.py +216 -0
- planar/sse/model.py +8 -0
- planar/sse/proxy.py +257 -0
- planar/task_local.py +37 -0
- planar/test_app.py +51 -0
- planar/test_cli.py +372 -0
- planar/test_config.py +512 -0
- planar/test_object_config.py +527 -0
- planar/test_object_registry.py +14 -0
- planar/test_sqlalchemy.py +158 -0
- planar/test_utils.py +105 -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/testing/__init__.py +0 -0
- planar/testing/memory_storage.py +78 -0
- planar/testing/planar_test_client.py +54 -0
- planar/testing/synchronizable_tracer.py +153 -0
- planar/testing/test_memory_storage.py +143 -0
- planar/testing/workflow_observer.py +73 -0
- planar/utils.py +70 -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/workflows/__init__.py +42 -0
- planar/workflows/context.py +44 -0
- planar/workflows/contrib.py +190 -0
- planar/workflows/decorators.py +217 -0
- planar/workflows/events.py +185 -0
- planar/workflows/exceptions.py +34 -0
- planar/workflows/execution.py +198 -0
- planar/workflows/lock.py +229 -0
- planar/workflows/misc.py +5 -0
- planar/workflows/models.py +154 -0
- planar/workflows/notifications.py +96 -0
- planar/workflows/orchestrator.py +383 -0
- planar/workflows/query.py +256 -0
- planar/workflows/serialization.py +409 -0
- planar/workflows/step_core.py +373 -0
- planar/workflows/step_metadata.py +357 -0
- planar/workflows/step_testing_utils.py +86 -0
- planar/workflows/sub_workflow_runner.py +191 -0
- planar/workflows/test_concurrency_detection.py +120 -0
- planar/workflows/test_lock_timeout.py +140 -0
- planar/workflows/test_serialization.py +1195 -0
- planar/workflows/test_suspend_deserialization.py +231 -0
- planar/workflows/test_workflow.py +1967 -0
- planar/workflows/tracing.py +106 -0
- planar/workflows/wrappers.py +41 -0
- planar-0.5.0.dist-info/METADATA +285 -0
- planar-0.5.0.dist-info/RECORD +289 -0
- planar-0.5.0.dist-info/WHEEL +4 -0
- planar-0.5.0.dist-info/entry_points.txt +3 -0
planar/ai/agent_utils.py
ADDED
@@ -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)
|