agentplane-primitives 0.1.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.
- agentplane_primitives/__init__.py +10 -0
- agentplane_primitives/_version.py +3 -0
- agentplane_primitives/enums/__init__.py +25 -0
- agentplane_primitives/enums/base_enum.py +15 -0
- agentplane_primitives/enums/finish_reason.py +17 -0
- agentplane_primitives/enums/kind.py +14 -0
- agentplane_primitives/enums/link_type.py +12 -0
- agentplane_primitives/enums/message_role.py +12 -0
- agentplane_primitives/enums/model_source.py +17 -0
- agentplane_primitives/enums/state.py +13 -0
- agentplane_primitives/enums/status.py +14 -0
- agentplane_primitives/models/__init__.py +46 -0
- agentplane_primitives/models/base.py +20 -0
- agentplane_primitives/models/content_block.py +19 -0
- agentplane_primitives/models/envelope.py +94 -0
- agentplane_primitives/models/envelope_status.py +28 -0
- agentplane_primitives/models/link.py +16 -0
- agentplane_primitives/models/llm_input.py +33 -0
- agentplane_primitives/models/llm_output.py +43 -0
- agentplane_primitives/models/llm_params.py +19 -0
- agentplane_primitives/models/message.py +26 -0
- agentplane_primitives/models/output_error.py +18 -0
- agentplane_primitives/models/reasoning_block.py +12 -0
- agentplane_primitives/models/refusal_block.py +12 -0
- agentplane_primitives/models/registry.py +15 -0
- agentplane_primitives/models/span.py +48 -0
- agentplane_primitives/models/text_block.py +12 -0
- agentplane_primitives/models/tool_call_block.py +16 -0
- agentplane_primitives/models/tool_input.py +15 -0
- agentplane_primitives/models/tool_output.py +17 -0
- agentplane_primitives/models/trace.py +29 -0
- agentplane_primitives/models/trace_context.py +29 -0
- agentplane_primitives/models/usage.py +21 -0
- agentplane_primitives/py.typed +0 -0
- agentplane_primitives/schemas/Envelope.json +808 -0
- agentplane_primitives/schemas/README.md +39 -0
- agentplane_primitives/schemas/Span.json +116 -0
- agentplane_primitives/schemas/Trace.json +88 -0
- agentplane_primitives/types/__init__.py +36 -0
- agentplane_primitives/types/id_types.py +48 -0
- agentplane_primitives/types/metadata_types.py +32 -0
- agentplane_primitives/types/provider_types.py +18 -0
- agentplane_primitives/types/schema_version_types.py +18 -0
- agentplane_primitives/utils/__init__.py +25 -0
- agentplane_primitives/utils/id_utils.py +32 -0
- agentplane_primitives/utils/lifecycle_utils.py +33 -0
- agentplane_primitives/utils/metadata_utils.py +11 -0
- agentplane_primitives/utils/schema_utils.py +23 -0
- agentplane_primitives/utils/traceparent_utils.py +63 -0
- agentplane_primitives-0.1.0.dist-info/METADATA +211 -0
- agentplane_primitives-0.1.0.dist-info/RECORD +53 -0
- agentplane_primitives-0.1.0.dist-info/WHEEL +4 -0
- agentplane_primitives-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""AgentPlane primitives: shared, versioned domain objects.
|
|
2
|
+
|
|
3
|
+
Used by Chronicle, the control plane and TokenOps. This package holds data models only:
|
|
4
|
+
no I/O and no business logic. The entities (Trace, Span, Envelope) are
|
|
5
|
+
added in later releases; see ``docs/design.md``.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from agentplane_primitives._version import __version__
|
|
9
|
+
|
|
10
|
+
__all__ = ["__version__"]
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Closed sets of values shared by the entities (design section 2.9).
|
|
2
|
+
|
|
3
|
+
Each enum has its own module. A new member of any enum is a schema change: see
|
|
4
|
+
``docs/versioning.md``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from agentplane_primitives.enums.base_enum import BaseEnum
|
|
8
|
+
from agentplane_primitives.enums.finish_reason import FinishReason
|
|
9
|
+
from agentplane_primitives.enums.kind import Kind
|
|
10
|
+
from agentplane_primitives.enums.link_type import LinkType
|
|
11
|
+
from agentplane_primitives.enums.message_role import MessageRole
|
|
12
|
+
from agentplane_primitives.enums.model_source import ModelSource
|
|
13
|
+
from agentplane_primitives.enums.state import State
|
|
14
|
+
from agentplane_primitives.enums.status import Status
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"BaseEnum",
|
|
18
|
+
"FinishReason",
|
|
19
|
+
"Kind",
|
|
20
|
+
"LinkType",
|
|
21
|
+
"MessageRole",
|
|
22
|
+
"ModelSource",
|
|
23
|
+
"State",
|
|
24
|
+
"Status",
|
|
25
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""``BaseEnum``: the parent of every closed enum in this package.
|
|
2
|
+
|
|
3
|
+
Members are strings, and ``str()`` and f-strings give the value (``"llm"``), not the member name
|
|
4
|
+
(``"Kind.LLM"``), on every supported Python version. ``enum.StrEnum`` would do this but needs
|
|
5
|
+
Python 3.11; this package supports 3.10.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseEnum(str, Enum):
|
|
12
|
+
"""A string enum whose text form is its value."""
|
|
13
|
+
|
|
14
|
+
def __str__(self) -> str:
|
|
15
|
+
return str(self.value)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""``FinishReason``: why an LLM call stopped (design section 2.9).
|
|
2
|
+
|
|
3
|
+
Closed enum of the canonical set. The provider's own original value stays in ``output.raw``, so
|
|
4
|
+
nothing is lost when a provider value is mapped onto one of these.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from agentplane_primitives.enums.base_enum import BaseEnum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class FinishReason(BaseEnum):
|
|
11
|
+
"""The canonical reason an LLM response ended."""
|
|
12
|
+
|
|
13
|
+
STOP = "stop"
|
|
14
|
+
LENGTH = "length"
|
|
15
|
+
TOOL_CALLS = "tool_calls"
|
|
16
|
+
CONTENT_FILTER = "content_filter"
|
|
17
|
+
OTHER = "other"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""``Kind``: what an envelope records (design sections 2.3 and 2.9).
|
|
2
|
+
|
|
3
|
+
Closed enum: there are no custom or unregistered kinds, and any other value is rejected at
|
|
4
|
+
validation. A new kind is a schema change (see ``docs/versioning.md`` and ADR 0002).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from agentplane_primitives.enums.base_enum import BaseEnum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Kind(BaseEnum):
|
|
11
|
+
"""The kind of boundary crossing an envelope records. It selects the input and output shape."""
|
|
12
|
+
|
|
13
|
+
LLM = "llm"
|
|
14
|
+
TOOL = "tool"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""``LinkType``: how one envelope relates to another besides parent and child (design 2.5).
|
|
2
|
+
|
|
3
|
+
Closed enum for now. ``joined`` (fan-in) is reserved for future scope and is not a member yet.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from agentplane_primitives.enums.base_enum import BaseEnum
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class LinkType(BaseEnum):
|
|
10
|
+
"""The relationship a link expresses."""
|
|
11
|
+
|
|
12
|
+
RETRY_OF = "retry_of"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""``MessageRole``: who a message in an LLM input is from (design section 2.9). Closed enum."""
|
|
2
|
+
|
|
3
|
+
from agentplane_primitives.enums.base_enum import BaseEnum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MessageRole(BaseEnum):
|
|
7
|
+
"""The author of a message."""
|
|
8
|
+
|
|
9
|
+
SYSTEM = "system"
|
|
10
|
+
USER = "user"
|
|
11
|
+
ASSISTANT = "assistant"
|
|
12
|
+
TOOL = "tool"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""``ModelSource``: where ``output.model`` came from (design sections 2.3 and 2.9).
|
|
2
|
+
|
|
3
|
+
Closed enum. Some SDKs do not return the served model; then the adapter copies the requested one
|
|
4
|
+
and says so here, so a consumer knows how much to trust it.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from agentplane_primitives.enums.base_enum import BaseEnum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ModelSource(BaseEnum):
|
|
11
|
+
"""How the model name in the output was obtained."""
|
|
12
|
+
|
|
13
|
+
SERVED = "served"
|
|
14
|
+
"""The response named the model that served the call."""
|
|
15
|
+
|
|
16
|
+
REQUESTED = "requested"
|
|
17
|
+
"""The response had no model; the adapter copied the requested one."""
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""``State``: where a Trace, Span or Envelope is in its lifecycle (design section 2.4).
|
|
2
|
+
|
|
3
|
+
Closed enum. A record is mutable while open and immutable once closed.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from agentplane_primitives.enums.base_enum import BaseEnum
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class State(BaseEnum):
|
|
10
|
+
"""Lifecycle state shared by Trace, Span and Envelope."""
|
|
11
|
+
|
|
12
|
+
OPEN = "open"
|
|
13
|
+
CLOSED = "closed"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""``Status``: the result of a Span or Envelope (design sections 2.2 and 2.9).
|
|
2
|
+
|
|
3
|
+
Closed enum. A Trace has no status of its own. There is no ``aborted`` value: a deliberate abort
|
|
4
|
+
is a ``failure`` whose ``output.error.type`` is ``AbortCall``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from agentplane_primitives.enums.base_enum import BaseEnum
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Status(BaseEnum):
|
|
11
|
+
"""High-level result of a Span or Envelope."""
|
|
12
|
+
|
|
13
|
+
SUCCESS = "success"
|
|
14
|
+
FAILURE = "failure"
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""The models: Trace, Span, Envelope with its parts, the TraceContext carrier, the base class."""
|
|
2
|
+
|
|
3
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
4
|
+
from agentplane_primitives.models.content_block import ContentBlock
|
|
5
|
+
from agentplane_primitives.models.envelope import Envelope
|
|
6
|
+
from agentplane_primitives.models.envelope_status import EnvelopeStatus
|
|
7
|
+
from agentplane_primitives.models.link import Link
|
|
8
|
+
from agentplane_primitives.models.llm_input import LlmInput
|
|
9
|
+
from agentplane_primitives.models.llm_output import LlmOutput
|
|
10
|
+
from agentplane_primitives.models.llm_params import LlmParams
|
|
11
|
+
from agentplane_primitives.models.message import Message
|
|
12
|
+
from agentplane_primitives.models.output_error import ABORT_CALL, OutputError
|
|
13
|
+
from agentplane_primitives.models.reasoning_block import ReasoningBlock
|
|
14
|
+
from agentplane_primitives.models.refusal_block import RefusalBlock
|
|
15
|
+
from agentplane_primitives.models.span import Span
|
|
16
|
+
from agentplane_primitives.models.text_block import TextBlock
|
|
17
|
+
from agentplane_primitives.models.tool_call_block import ToolCallBlock
|
|
18
|
+
from agentplane_primitives.models.tool_input import ToolInput
|
|
19
|
+
from agentplane_primitives.models.tool_output import ToolOutput
|
|
20
|
+
from agentplane_primitives.models.trace import Trace
|
|
21
|
+
from agentplane_primitives.models.trace_context import TraceContext
|
|
22
|
+
from agentplane_primitives.models.usage import Usage
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"ABORT_CALL",
|
|
26
|
+
"ContentBlock",
|
|
27
|
+
"Envelope",
|
|
28
|
+
"EnvelopeStatus",
|
|
29
|
+
"Link",
|
|
30
|
+
"LlmInput",
|
|
31
|
+
"LlmOutput",
|
|
32
|
+
"LlmParams",
|
|
33
|
+
"Message",
|
|
34
|
+
"OutputError",
|
|
35
|
+
"PrimitiveModel",
|
|
36
|
+
"ReasoningBlock",
|
|
37
|
+
"RefusalBlock",
|
|
38
|
+
"Span",
|
|
39
|
+
"TextBlock",
|
|
40
|
+
"ToolCallBlock",
|
|
41
|
+
"ToolInput",
|
|
42
|
+
"ToolOutput",
|
|
43
|
+
"Trace",
|
|
44
|
+
"TraceContext",
|
|
45
|
+
"Usage",
|
|
46
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""``PrimitiveModel``: the base class every model in this package inherits from.
|
|
2
|
+
|
|
3
|
+
It holds the settings all models share, so they are decided once (design section 2.8).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PrimitiveModel(BaseModel):
|
|
10
|
+
"""Shared model settings.
|
|
11
|
+
|
|
12
|
+
- Unknown fields are ignored on read. This only matters when writer and reader run
|
|
13
|
+
different versions (for example during a rolling upgrade); the control plane keeps the
|
|
14
|
+
full original payload, so nothing is lost.
|
|
15
|
+
- Assigning a bad value to a field is validated, not silently accepted. Changing a
|
|
16
|
+
container in place (for example ``model.metadata[key] = value``) is not checked: Python
|
|
17
|
+
gives pydantic no hook for it, so such rules hold when the model is built or assigned.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
model_config = ConfigDict(extra="ignore", validate_assignment=True)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""``ContentBlock``: one piece of LLM content (design sections 2.3 and 2.9).
|
|
2
|
+
|
|
3
|
+
A closed set chosen by ``type``: ``text``, ``tool_call``, ``reasoning`` or ``refusal``. Any other
|
|
4
|
+
``type`` is rejected. Each block has its own module.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
|
|
9
|
+
from pydantic import Field
|
|
10
|
+
|
|
11
|
+
from agentplane_primitives.models.reasoning_block import ReasoningBlock
|
|
12
|
+
from agentplane_primitives.models.refusal_block import RefusalBlock
|
|
13
|
+
from agentplane_primitives.models.text_block import TextBlock
|
|
14
|
+
from agentplane_primitives.models.tool_call_block import ToolCallBlock
|
|
15
|
+
|
|
16
|
+
ContentBlock = Annotated[
|
|
17
|
+
TextBlock | ToolCallBlock | ReasoningBlock | RefusalBlock,
|
|
18
|
+
Field(discriminator="type"),
|
|
19
|
+
]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""``Envelope``: one boundary crossing, as a single record (design sections 2.2 to 2.5).
|
|
2
|
+
|
|
3
|
+
The fields fall into the five sections of the design. The identity fields sit at the top level,
|
|
4
|
+
as in the design's example payloads; the other four sections are nested:
|
|
5
|
+
|
|
6
|
+
- identity: ``envelope_id``, ``span_id``, ``trace_id``, ``parent_envelope_id``, ``name``,
|
|
7
|
+
``kind``, ``attempt``, ``links``
|
|
8
|
+
- ``envelope_status``, ``input``, ``output`` and ``metadata``
|
|
9
|
+
|
|
10
|
+
``kind`` selects the shape of ``input`` and ``output``: ``tool`` uses ``ToolInput`` and
|
|
11
|
+
``ToolOutput``, ``llm`` uses ``LlmInput`` and ``LlmOutput``. A payload of the other kind's shape
|
|
12
|
+
is rejected. An envelope is open until closed; a closed envelope must have an ``output`` (an
|
|
13
|
+
error is recorded there).
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel, Field, model_validator
|
|
19
|
+
|
|
20
|
+
from agentplane_primitives.enums import Kind, State
|
|
21
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
22
|
+
from agentplane_primitives.models.envelope_status import EnvelopeStatus
|
|
23
|
+
from agentplane_primitives.models.link import Link
|
|
24
|
+
from agentplane_primitives.models.llm_input import LlmInput
|
|
25
|
+
from agentplane_primitives.models.llm_output import LlmOutput
|
|
26
|
+
from agentplane_primitives.models.tool_input import ToolInput
|
|
27
|
+
from agentplane_primitives.models.tool_output import ToolOutput
|
|
28
|
+
from agentplane_primitives.types import (
|
|
29
|
+
SCHEMA_VERSION,
|
|
30
|
+
EnvelopeId,
|
|
31
|
+
Metadata,
|
|
32
|
+
SchemaVersion,
|
|
33
|
+
SpanId,
|
|
34
|
+
TraceId,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
_PAYLOADS: dict[Kind, tuple[type[BaseModel], type[BaseModel]]] = {
|
|
38
|
+
Kind.TOOL: (ToolInput, ToolOutput),
|
|
39
|
+
Kind.LLM: (LlmInput, LlmOutput),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class Envelope(PrimitiveModel):
|
|
44
|
+
"""The record of one call across a boundary inside a span."""
|
|
45
|
+
|
|
46
|
+
schema_version: SchemaVersion = SCHEMA_VERSION
|
|
47
|
+
|
|
48
|
+
# identity
|
|
49
|
+
envelope_id: EnvelopeId
|
|
50
|
+
span_id: SpanId
|
|
51
|
+
trace_id: TraceId
|
|
52
|
+
parent_envelope_id: EnvelopeId | None = None
|
|
53
|
+
name: str = Field(min_length=1, description="Boundary name, e.g. agent.chat.")
|
|
54
|
+
kind: Kind
|
|
55
|
+
attempt: int = Field(default=1, ge=1, description="1 for the first try, 2 for a retry, ...")
|
|
56
|
+
links: list[Link] = Field(default_factory=list)
|
|
57
|
+
|
|
58
|
+
envelope_status: EnvelopeStatus
|
|
59
|
+
input: ToolInput | LlmInput = Field(description="Shape chosen by kind: tool or llm input.")
|
|
60
|
+
output: ToolOutput | LlmOutput | None = Field(
|
|
61
|
+
default=None, description="Shape chosen by kind: tool or llm output."
|
|
62
|
+
)
|
|
63
|
+
metadata: Metadata = Field(default_factory=dict)
|
|
64
|
+
|
|
65
|
+
@model_validator(mode="before")
|
|
66
|
+
@classmethod
|
|
67
|
+
def _read_payloads_by_kind(cls, data: Any) -> Any:
|
|
68
|
+
# The two payload shapes overlap when unknown fields are ignored, so the kind, not the
|
|
69
|
+
# shape, decides which model reads input and output.
|
|
70
|
+
if not isinstance(data, dict):
|
|
71
|
+
return data
|
|
72
|
+
try:
|
|
73
|
+
input_model, output_model = _PAYLOADS[Kind(data.get("kind"))]
|
|
74
|
+
except ValueError:
|
|
75
|
+
# Not swallowed: the ``kind`` field is validated next and rejects the value with its
|
|
76
|
+
# own error. This package does no I/O, so there is nothing to log.
|
|
77
|
+
return data
|
|
78
|
+
data = dict(data)
|
|
79
|
+
if isinstance(data.get("input"), dict):
|
|
80
|
+
data["input"] = input_model.model_validate(data["input"])
|
|
81
|
+
if isinstance(data.get("output"), dict):
|
|
82
|
+
data["output"] = output_model.model_validate(data["output"])
|
|
83
|
+
return data
|
|
84
|
+
|
|
85
|
+
@model_validator(mode="after")
|
|
86
|
+
def _check_consistency(self) -> "Envelope":
|
|
87
|
+
input_model, output_model = _PAYLOADS[self.kind]
|
|
88
|
+
if not isinstance(self.input, input_model):
|
|
89
|
+
raise ValueError(f"input does not match kind {self.kind.value!r}")
|
|
90
|
+
if self.output is not None and not isinstance(self.output, output_model):
|
|
91
|
+
raise ValueError(f"output does not match kind {self.kind.value!r}")
|
|
92
|
+
if self.envelope_status.state is State.CLOSED and self.output is None:
|
|
93
|
+
raise ValueError("a closed envelope needs an output")
|
|
94
|
+
return self
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""``EnvelopeStatus``: the lifecycle and result of an envelope (design sections 2.2 and 2.4).
|
|
2
|
+
|
|
3
|
+
An envelope is one record that is mutable until it is closed. While ``open`` it has a start time
|
|
4
|
+
and nothing else; once ``closed`` it has a result and an end time. To close an envelope, build a
|
|
5
|
+
new ``EnvelopeStatus``: changing the fields one by one would pass through an invalid state.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from pydantic import AwareDatetime, model_validator
|
|
9
|
+
|
|
10
|
+
from agentplane_primitives.enums import State, Status
|
|
11
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
12
|
+
from agentplane_primitives.utils.lifecycle_utils import validate_state_status_and_times
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class EnvelopeStatus(PrimitiveModel):
|
|
16
|
+
"""State, result and timing of an envelope. Times must carry a time zone."""
|
|
17
|
+
|
|
18
|
+
state: State
|
|
19
|
+
status: Status | None = None
|
|
20
|
+
started_at: AwareDatetime
|
|
21
|
+
ended_at: AwareDatetime | None = None
|
|
22
|
+
|
|
23
|
+
@model_validator(mode="after")
|
|
24
|
+
def _validate_state_fields(self) -> "EnvelopeStatus":
|
|
25
|
+
validate_state_status_and_times(
|
|
26
|
+
self.state, self.status, self.started_at, self.ended_at, "envelope"
|
|
27
|
+
)
|
|
28
|
+
return self
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""``Link``: a typed pointer from one envelope to another (design section 2.5).
|
|
2
|
+
|
|
3
|
+
Retries are declared explicitly: an envelope that retries an earlier one carries a ``retry_of``
|
|
4
|
+
link to it. The linked envelope is in the same trace.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from agentplane_primitives.enums import LinkType
|
|
8
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
9
|
+
from agentplane_primitives.types import EnvelopeId
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Link(PrimitiveModel):
|
|
13
|
+
"""A relationship to another envelope in the same trace."""
|
|
14
|
+
|
|
15
|
+
type: LinkType
|
|
16
|
+
envelope_id: EnvelopeId
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""``LlmInput``: the input of an ``llm`` envelope (design sections 2.3 and 2.9).
|
|
2
|
+
|
|
3
|
+
Input is what the caller passed on this call: the requested model, the messages and the
|
|
4
|
+
instructions, tool definitions and sampling settings. ``raw`` holds the arguments exactly as
|
|
5
|
+
they were passed to the boundaried method, as JSON by argument name, and is always stored; the
|
|
6
|
+
canonical fields are derived from it.
|
|
7
|
+
|
|
8
|
+
``tools``, ``tool_choice`` and ``response_format`` are kept as JSON objects because their shape
|
|
9
|
+
differs by provider. The design does not fix their structure.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pydantic import Field, JsonValue
|
|
13
|
+
|
|
14
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
15
|
+
from agentplane_primitives.models.llm_params import LlmParams
|
|
16
|
+
from agentplane_primitives.models.message import Message
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LlmInput(PrimitiveModel):
|
|
20
|
+
"""What was sent to the model."""
|
|
21
|
+
|
|
22
|
+
model: str = Field(min_length=1, description="The model or alias the caller asked for.")
|
|
23
|
+
messages: list[Message]
|
|
24
|
+
system: str | None = Field(default=None, description="Instructions the caller supplied.")
|
|
25
|
+
tools: list[dict[str, JsonValue]] | None = Field(
|
|
26
|
+
default=None, description="Tool definitions offered to the model."
|
|
27
|
+
)
|
|
28
|
+
tool_choice: JsonValue = Field(
|
|
29
|
+
default=None, description="Whether, or which, tool the model must use."
|
|
30
|
+
)
|
|
31
|
+
response_format: dict[str, JsonValue] | None = None
|
|
32
|
+
params: LlmParams = Field(default_factory=LlmParams)
|
|
33
|
+
raw: dict[str, JsonValue] = Field(description="Arguments as passed, by name, as JSON.")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""``LlmOutput``: the output of an ``llm`` envelope (design sections 2.3 and 2.9).
|
|
2
|
+
|
|
3
|
+
Output is what is known only from the response. It carries the fixed homes for cost data, which a
|
|
4
|
+
consumer such as TokenOps reads and nothing else:
|
|
5
|
+
|
|
6
|
+
- ``provider``: who served the call (``unknown`` when it cannot be determined)
|
|
7
|
+
- ``model`` and ``model_source``: what served it, and whether the response said so
|
|
8
|
+
- ``usage``: exclusive, additive token buckets
|
|
9
|
+
|
|
10
|
+
A call that failed has only ``error``; everything else is then absent. ``usage`` is null for a
|
|
11
|
+
custom boundary with no adapter, and consumers must tolerate that. ``raw`` is the value returned
|
|
12
|
+
by the boundaried method, as JSON, and is always stored.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from pydantic import Field, JsonValue, model_validator
|
|
16
|
+
|
|
17
|
+
from agentplane_primitives.enums import FinishReason, ModelSource
|
|
18
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
19
|
+
from agentplane_primitives.models.content_block import ContentBlock
|
|
20
|
+
from agentplane_primitives.models.output_error import OutputError
|
|
21
|
+
from agentplane_primitives.models.usage import Usage
|
|
22
|
+
from agentplane_primitives.types import UNKNOWN_PROVIDER, Provider
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class LlmOutput(PrimitiveModel):
|
|
26
|
+
"""What came back from the model, or the error it raised."""
|
|
27
|
+
|
|
28
|
+
content: list[ContentBlock] = Field(default_factory=list)
|
|
29
|
+
finish_reason: FinishReason | None = None
|
|
30
|
+
provider: Provider = UNKNOWN_PROVIDER
|
|
31
|
+
model: str | None = Field(default=None, description="The model that served the call.")
|
|
32
|
+
model_source: ModelSource | None = None
|
|
33
|
+
usage: Usage | None = None
|
|
34
|
+
response_id: str | None = None
|
|
35
|
+
deployment_id: str | None = None
|
|
36
|
+
raw: JsonValue = Field(default=None, description="The returned value, as JSON.")
|
|
37
|
+
error: OutputError | None = None
|
|
38
|
+
|
|
39
|
+
@model_validator(mode="after")
|
|
40
|
+
def _model_and_source_go_together(self) -> "LlmOutput":
|
|
41
|
+
if (self.model is None) != (self.model_source is None):
|
|
42
|
+
raise ValueError("model and model_source must be given together")
|
|
43
|
+
return self
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""``LlmParams``: the sampling settings the caller passed (design section 2.3).
|
|
2
|
+
|
|
3
|
+
Every field is optional: only what the caller set is recorded. Defaults baked into a client's
|
|
4
|
+
configuration are metadata, not input.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LlmParams(PrimitiveModel):
|
|
13
|
+
"""Sampling parameters passed on this call."""
|
|
14
|
+
|
|
15
|
+
temperature: float | None = None
|
|
16
|
+
top_p: float | None = None
|
|
17
|
+
max_tokens: int | None = Field(default=None, ge=0)
|
|
18
|
+
seed: int | None = None
|
|
19
|
+
stop: list[str] | None = None
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""``Message``: one message in an LLM input (design sections 2.3 and 2.9).
|
|
2
|
+
|
|
3
|
+
``content`` is plain text or a list of content blocks. A ``tool`` message (a tool result being
|
|
4
|
+
handed back to the model) names the call it answers in ``tool_call_id``: the ``id`` of the
|
|
5
|
+
``ToolCallBlock`` the model produced, copied from the provider's own id (OpenAI ``tool_call_id``,
|
|
6
|
+
Anthropic ``tool_use_id``, Bedrock ``toolUseId``). Not every provider sends one, so it is
|
|
7
|
+
optional, and it is not checked against earlier messages. Anthropic carries a tool result as a
|
|
8
|
+
``tool_result`` block inside a user message; an adapter maps that to a ``tool`` message here. The
|
|
9
|
+
provider's exact message shape is kept in ``input.raw``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pydantic import Field
|
|
13
|
+
|
|
14
|
+
from agentplane_primitives.enums import MessageRole
|
|
15
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
16
|
+
from agentplane_primitives.models.content_block import ContentBlock
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Message(PrimitiveModel):
|
|
20
|
+
"""A single message with a role."""
|
|
21
|
+
|
|
22
|
+
role: MessageRole
|
|
23
|
+
content: str | list[ContentBlock]
|
|
24
|
+
tool_call_id: str | None = Field(
|
|
25
|
+
default=None, description="For role=tool: the id of the tool call this answers."
|
|
26
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""``OutputError``: how a failed call is described inside an output (design sections 2.3, 2.9).
|
|
2
|
+
|
|
3
|
+
Error information belongs to ``output``, not to metadata. ``type`` is the exception class name; the
|
|
4
|
+
reserved value ``"AbortCall"`` means a hook stopped the call on purpose.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
10
|
+
|
|
11
|
+
ABORT_CALL = "AbortCall"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class OutputError(PrimitiveModel):
|
|
15
|
+
"""An error raised by the call, recorded in ``output.error``."""
|
|
16
|
+
|
|
17
|
+
type: str = Field(min_length=1, description="Exception class name, e.g. RateLimitError.")
|
|
18
|
+
message: str = Field(description="The exception message.")
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""``ReasoningBlock``: the model's visible reasoning text (design section 2.3)."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ReasoningBlock(PrimitiveModel):
|
|
9
|
+
"""Reasoning content returned by the model."""
|
|
10
|
+
|
|
11
|
+
type: Literal["reasoning"] = "reasoning"
|
|
12
|
+
text: str
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""``RefusalBlock``: the model declining to answer (design section 2.3)."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RefusalBlock(PrimitiveModel):
|
|
9
|
+
"""A refusal returned by the model."""
|
|
10
|
+
|
|
11
|
+
type: Literal["refusal"] = "refusal"
|
|
12
|
+
text: str
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""``PUBLIC_MODELS``: the models that get a committed JSON Schema file.
|
|
2
|
+
|
|
3
|
+
Add a model here in the same pull request that introduces it, then run
|
|
4
|
+
``python scripts/generate_schemas.py`` and commit the new file. Helper models that only appear
|
|
5
|
+
inside another model (such as ``Link`` inside ``Envelope``) do not need an entry: they are
|
|
6
|
+
included in that model's schema.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
from agentplane_primitives.models.envelope import Envelope
|
|
12
|
+
from agentplane_primitives.models.span import Span
|
|
13
|
+
from agentplane_primitives.models.trace import Trace
|
|
14
|
+
|
|
15
|
+
PUBLIC_MODELS: tuple[type[BaseModel], ...] = (Trace, Span, Envelope)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""``Span``: one service's handling of a request (design sections 2.1 and 2.2).
|
|
2
|
+
|
|
3
|
+
A Span sits between the Trace and its Envelopes: one process, one agent run. It is minted by its
|
|
4
|
+
own service, so ``span_id`` is unique within the trace. ``parent_envelope_id`` is the envelope in
|
|
5
|
+
the caller's span that made the outbound call; it is the only link between services, and it is
|
|
6
|
+
empty for the first span in a trace.
|
|
7
|
+
|
|
8
|
+
``status`` is ``success`` or ``failure`` and is set when the span closes, by the entry point:
|
|
9
|
+
``failure`` if the request handler raised, otherwise ``success``. It is independent of envelope
|
|
10
|
+
failures (a retry that was handled does not fail the span). Like an envelope, a span is open until
|
|
11
|
+
closed; to close one, build a new ``Span``, because the fields are validated together.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from pydantic import AwareDatetime, Field, model_validator
|
|
15
|
+
|
|
16
|
+
from agentplane_primitives.enums import State, Status
|
|
17
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
18
|
+
from agentplane_primitives.types import (
|
|
19
|
+
SCHEMA_VERSION,
|
|
20
|
+
EnvelopeId,
|
|
21
|
+
Metadata,
|
|
22
|
+
SchemaVersion,
|
|
23
|
+
SpanId,
|
|
24
|
+
TraceId,
|
|
25
|
+
)
|
|
26
|
+
from agentplane_primitives.utils.lifecycle_utils import validate_state_status_and_times
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Span(PrimitiveModel):
|
|
30
|
+
"""One service's handling of a request inside a trace. Times must carry a time zone."""
|
|
31
|
+
|
|
32
|
+
schema_version: SchemaVersion = SCHEMA_VERSION
|
|
33
|
+
span_id: SpanId
|
|
34
|
+
trace_id: TraceId
|
|
35
|
+
parent_envelope_id: EnvelopeId | None = None
|
|
36
|
+
service: str = Field(min_length=1, description="Service or agent name.")
|
|
37
|
+
state: State
|
|
38
|
+
status: Status | None = None
|
|
39
|
+
started_at: AwareDatetime
|
|
40
|
+
ended_at: AwareDatetime | None = None
|
|
41
|
+
metadata: Metadata = Field(default_factory=dict, description="Span-level setup.")
|
|
42
|
+
|
|
43
|
+
@model_validator(mode="after")
|
|
44
|
+
def _validate_state_fields(self) -> "Span":
|
|
45
|
+
validate_state_status_and_times(
|
|
46
|
+
self.state, self.status, self.started_at, self.ended_at, "span"
|
|
47
|
+
)
|
|
48
|
+
return self
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""``TextBlock``: plain text produced or sent in an LLM call (design section 2.3)."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from agentplane_primitives.models.base import PrimitiveModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TextBlock(PrimitiveModel):
|
|
9
|
+
"""A piece of text."""
|
|
10
|
+
|
|
11
|
+
type: Literal["text"] = "text"
|
|
12
|
+
text: str
|