parlot-core 0.1.0__tar.gz
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.
- parlot_core-0.1.0/.gitignore +61 -0
- parlot_core-0.1.0/LICENSE +21 -0
- parlot_core-0.1.0/PKG-INFO +48 -0
- parlot_core-0.1.0/README.md +19 -0
- parlot_core-0.1.0/pyproject.toml +58 -0
- parlot_core-0.1.0/src/parlot/core/__init__.py +81 -0
- parlot_core-0.1.0/src/parlot/core/attrs.py +304 -0
- parlot_core-0.1.0/src/parlot/core/bootstrap.py +44 -0
- parlot_core-0.1.0/src/parlot/core/configure.py +130 -0
- parlot_core-0.1.0/src/parlot/core/diagnostics.py +187 -0
- parlot_core-0.1.0/src/parlot/core/escalation.py +52 -0
- parlot_core-0.1.0/src/parlot/core/export.py +29 -0
- parlot_core-0.1.0/src/parlot/core/genai_content_capture.py +65 -0
- parlot_core-0.1.0/src/parlot/core/goal_lifecycle.py +118 -0
- parlot_core-0.1.0/src/parlot/core/ids.py +28 -0
- parlot_core-0.1.0/src/parlot/core/intent.py +51 -0
- parlot_core-0.1.0/src/parlot/core/language.py +37 -0
- parlot_core-0.1.0/src/parlot/core/logging.py +111 -0
- parlot_core-0.1.0/src/parlot/core/logs_capture.py +84 -0
- parlot_core-0.1.0/src/parlot/core/metadata.py +67 -0
- parlot_core-0.1.0/src/parlot/core/platform_refs.py +96 -0
- parlot_core-0.1.0/src/parlot/core/processor.py +127 -0
- parlot_core-0.1.0/src/parlot/core/provider.py +115 -0
- parlot_core-0.1.0/src/parlot/core/py.typed +0 -0
- parlot_core-0.1.0/src/parlot/core/recording.py +56 -0
- parlot_core-0.1.0/src/parlot/core/runtime.py +186 -0
- parlot_core-0.1.0/src/parlot/core/sdk_version.py +45 -0
- parlot_core-0.1.0/src/parlot/core/session.py +83 -0
- parlot_core-0.1.0/src/parlot/core/session_logs.py +393 -0
- parlot_core-0.1.0/src/parlot/core/topology.py +269 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# OS
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
|
|
5
|
+
# Python
|
|
6
|
+
__pycache__/
|
|
7
|
+
*.py[cod]
|
|
8
|
+
*$py.class
|
|
9
|
+
*.so
|
|
10
|
+
.Python
|
|
11
|
+
*.egg-info/
|
|
12
|
+
.eggs/
|
|
13
|
+
*.egg
|
|
14
|
+
dist/
|
|
15
|
+
build/
|
|
16
|
+
wheels/
|
|
17
|
+
|
|
18
|
+
# Virtual environments
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
env/
|
|
22
|
+
|
|
23
|
+
# Test & type checkers
|
|
24
|
+
.pytest_cache/
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
.ruff_cache/
|
|
27
|
+
.pyright/
|
|
28
|
+
.hypothesis/
|
|
29
|
+
.coverage
|
|
30
|
+
.coverage.*
|
|
31
|
+
htmlcov/
|
|
32
|
+
.tox/
|
|
33
|
+
.nox/
|
|
34
|
+
|
|
35
|
+
# Secrets — never commit (keep *.example templates)
|
|
36
|
+
.env
|
|
37
|
+
.env.*
|
|
38
|
+
!.env.example
|
|
39
|
+
!.env.*.example
|
|
40
|
+
|
|
41
|
+
# IDE (keep shared VS Code settings)
|
|
42
|
+
.idea/
|
|
43
|
+
*.swp
|
|
44
|
+
*.swo
|
|
45
|
+
*~
|
|
46
|
+
.vscode/*
|
|
47
|
+
!.vscode/settings.json
|
|
48
|
+
|
|
49
|
+
# Logs
|
|
50
|
+
*.log
|
|
51
|
+
logs/
|
|
52
|
+
examples/**/compare/**
|
|
53
|
+
|
|
54
|
+
# Node
|
|
55
|
+
node_modules/
|
|
56
|
+
|
|
57
|
+
# Docs site
|
|
58
|
+
docs/build/
|
|
59
|
+
docs/.docusaurus/
|
|
60
|
+
docs/package-lock.json
|
|
61
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nineth LLC
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: parlot-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Parlot core: semantic conventions, base span processor, and shared utilities
|
|
5
|
+
Project-URL: Homepage, https://parlot.ai
|
|
6
|
+
Project-URL: Documentation, https://parlot.ai/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/parlot-ai/sdk
|
|
8
|
+
Author-email: Parlot AI <hi@parlot.ai>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,ai,observability,opentelemetry,parlot,telemetry
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: System :: Monitoring
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: httpx>=0.27.0
|
|
22
|
+
Requires-Dist: opentelemetry-api>=1.27.0
|
|
23
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27.0
|
|
24
|
+
Requires-Dist: opentelemetry-sdk>=1.27.0
|
|
25
|
+
Requires-Dist: opentelemetry-semantic-conventions>=0.48b0
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# parlot-core
|
|
31
|
+
|
|
32
|
+
Shared semantic conventions, base span processor, and utilities for all Parlot instrumentation packages.
|
|
33
|
+
|
|
34
|
+
## Contents
|
|
35
|
+
|
|
36
|
+
- `parlot.core.attrs` — GenAI semconv v1.41 pin, Conversation Contract / GenAI / voice span names, `ATTR_*` constants; TypeScript mirror in `@parlot/core` (`packages/core-ts`, run `uv run python scripts/generate_attrs_ts.py` after edits). Pin surface: `packages/core/genai_semconv.lock.json` (bump lock with `GENAI_SEMCONV_VERSION`)
|
|
37
|
+
- `parlot.core.export` — shared `ExportFilterSpanExporter` (contract ∪ GenAI ∪ voice allowlist)
|
|
38
|
+
- `parlot.core.configure` — `ConfigureProtocol` (keyword-only shared kwargs)
|
|
39
|
+
- `parlot.core.session` — `SessionState`, `get_active_session()` / `session_owned()` for cross-package coexistence
|
|
40
|
+
- `parlot.core.provider` — shared endpoint/api_key resolve + TracerProvider/OTLP bootstrap helpers
|
|
41
|
+
- `parlot.core.bootstrap` — `GET /v1/telemetry/bootstrap` → `ParlotRuntimeContext`
|
|
42
|
+
- `parlot.core.genai_content_capture` — `should_capture_genai_content` (generative AI / tool bodies; see [Concepts](https://parlot.ai/docs/concepts#generative-ai-content-capture))
|
|
43
|
+
- `parlot.core.processor` — `ParlotBaseProcessor(SpanProcessor)` with shared span-mutation helpers
|
|
44
|
+
- `parlot.core.platform_refs` — generic `stamp_platform_refs()` for `platform.ref.*` triples
|
|
45
|
+
|
|
46
|
+
## Not for direct use
|
|
47
|
+
|
|
48
|
+
This package is a dependency of instrumentation packages (`parlot-instrumentation-livekit`, `parlot-instrumentation-langgraph`, etc.). Import from those packages in your agent code, not from here directly.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# parlot-core
|
|
2
|
+
|
|
3
|
+
Shared semantic conventions, base span processor, and utilities for all Parlot instrumentation packages.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
- `parlot.core.attrs` — GenAI semconv v1.41 pin, Conversation Contract / GenAI / voice span names, `ATTR_*` constants; TypeScript mirror in `@parlot/core` (`packages/core-ts`, run `uv run python scripts/generate_attrs_ts.py` after edits). Pin surface: `packages/core/genai_semconv.lock.json` (bump lock with `GENAI_SEMCONV_VERSION`)
|
|
8
|
+
- `parlot.core.export` — shared `ExportFilterSpanExporter` (contract ∪ GenAI ∪ voice allowlist)
|
|
9
|
+
- `parlot.core.configure` — `ConfigureProtocol` (keyword-only shared kwargs)
|
|
10
|
+
- `parlot.core.session` — `SessionState`, `get_active_session()` / `session_owned()` for cross-package coexistence
|
|
11
|
+
- `parlot.core.provider` — shared endpoint/api_key resolve + TracerProvider/OTLP bootstrap helpers
|
|
12
|
+
- `parlot.core.bootstrap` — `GET /v1/telemetry/bootstrap` → `ParlotRuntimeContext`
|
|
13
|
+
- `parlot.core.genai_content_capture` — `should_capture_genai_content` (generative AI / tool bodies; see [Concepts](https://parlot.ai/docs/concepts#generative-ai-content-capture))
|
|
14
|
+
- `parlot.core.processor` — `ParlotBaseProcessor(SpanProcessor)` with shared span-mutation helpers
|
|
15
|
+
- `parlot.core.platform_refs` — generic `stamp_platform_refs()` for `platform.ref.*` triples
|
|
16
|
+
|
|
17
|
+
## Not for direct use
|
|
18
|
+
|
|
19
|
+
This package is a dependency of instrumentation packages (`parlot-instrumentation-livekit`, `parlot-instrumentation-langgraph`, etc.). Import from those packages in your agent code, not from here directly.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "parlot-core"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Parlot core: semantic conventions, base span processor, and shared utilities"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Parlot AI", email = "hi@parlot.ai" },
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.11"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: System :: Monitoring",
|
|
24
|
+
]
|
|
25
|
+
keywords = ["opentelemetry", "telemetry", "ai", "agents", "observability", "parlot"]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"opentelemetry-api>=1.27.0",
|
|
28
|
+
"opentelemetry-sdk>=1.27.0",
|
|
29
|
+
"opentelemetry-exporter-otlp-proto-http>=1.27.0",
|
|
30
|
+
"opentelemetry-semantic-conventions>=0.48b0",
|
|
31
|
+
"httpx>=0.27.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://parlot.ai"
|
|
36
|
+
Documentation = "https://parlot.ai/docs"
|
|
37
|
+
Repository = "https://github.com/parlot-ai/sdk"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
dev = ["pytest>=8.0"]
|
|
41
|
+
|
|
42
|
+
[tool.pytest.ini_options]
|
|
43
|
+
testpaths = ["tests"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/parlot"]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.sdist]
|
|
49
|
+
include = [
|
|
50
|
+
"src/parlot",
|
|
51
|
+
"README.md",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[tool.pyright]
|
|
55
|
+
pythonVersion = "3.11"
|
|
56
|
+
venvPath = "."
|
|
57
|
+
venv = ".venv"
|
|
58
|
+
include = ["src", "tests"]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""parlot-core: shared semantic conventions, base processor, and utilities."""
|
|
2
|
+
|
|
3
|
+
from .attrs import * # noqa: F401, F403 — re-export all attribute constants
|
|
4
|
+
from .diagnostics import (
|
|
5
|
+
diagnostics_enabled,
|
|
6
|
+
init_diagnostics,
|
|
7
|
+
record_diagnostic,
|
|
8
|
+
shutdown_diagnostics,
|
|
9
|
+
)
|
|
10
|
+
from .escalation import human_escalation, record_human_rep
|
|
11
|
+
from .metadata import set_session_attribute, set_session_metadata
|
|
12
|
+
from .platform_refs import add_platform_ref, stamp_platform_refs
|
|
13
|
+
from .processor import ParlotBaseProcessor, assert_sync_span_processors
|
|
14
|
+
from .provider import (
|
|
15
|
+
adopt_existing_tracer_provider,
|
|
16
|
+
build_otlp_http_exporter,
|
|
17
|
+
build_resource,
|
|
18
|
+
build_tracer_provider,
|
|
19
|
+
resolve_api_key,
|
|
20
|
+
resolve_capture_genai_content,
|
|
21
|
+
resolve_endpoint,
|
|
22
|
+
)
|
|
23
|
+
from .bootstrap import fetch_telemetry_bootstrap
|
|
24
|
+
from .configure import BaseConfigureResult, ConfigureProtocol, base_configure, configure_parlot_logging
|
|
25
|
+
from .genai_content_capture import should_capture_genai_content
|
|
26
|
+
from .export import ExportFilterSpanExporter
|
|
27
|
+
from .intent import derive_intent
|
|
28
|
+
from .logs_capture import should_capture_logs
|
|
29
|
+
from .sdk_version import resolve_parlot_sdk_version, stamp_session_sdk_version
|
|
30
|
+
from .session import (
|
|
31
|
+
SessionState,
|
|
32
|
+
clear_active_session,
|
|
33
|
+
get_active_session,
|
|
34
|
+
get_active_session_span,
|
|
35
|
+
session_owned,
|
|
36
|
+
set_active_session,
|
|
37
|
+
)
|
|
38
|
+
from .session_logs import init_session_logs, shutdown_session_logs
|
|
39
|
+
from .topology import SessionTopology
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"BaseConfigureResult",
|
|
43
|
+
"ConfigureProtocol",
|
|
44
|
+
"base_configure",
|
|
45
|
+
"configure_parlot_logging",
|
|
46
|
+
"ExportFilterSpanExporter",
|
|
47
|
+
"ParlotBaseProcessor",
|
|
48
|
+
"assert_sync_span_processors",
|
|
49
|
+
"SessionState",
|
|
50
|
+
"SessionTopology",
|
|
51
|
+
"add_platform_ref",
|
|
52
|
+
"adopt_existing_tracer_provider",
|
|
53
|
+
"build_otlp_http_exporter",
|
|
54
|
+
"build_resource",
|
|
55
|
+
"build_tracer_provider",
|
|
56
|
+
"clear_active_session",
|
|
57
|
+
"derive_intent",
|
|
58
|
+
"diagnostics_enabled",
|
|
59
|
+
"fetch_telemetry_bootstrap",
|
|
60
|
+
"get_active_session",
|
|
61
|
+
"get_active_session_span",
|
|
62
|
+
"human_escalation",
|
|
63
|
+
"init_diagnostics",
|
|
64
|
+
"init_session_logs",
|
|
65
|
+
"record_diagnostic",
|
|
66
|
+
"record_human_rep",
|
|
67
|
+
"resolve_api_key",
|
|
68
|
+
"resolve_capture_genai_content",
|
|
69
|
+
"resolve_endpoint",
|
|
70
|
+
"resolve_parlot_sdk_version",
|
|
71
|
+
"session_owned",
|
|
72
|
+
"set_active_session",
|
|
73
|
+
"set_session_attribute",
|
|
74
|
+
"set_session_metadata",
|
|
75
|
+
"should_capture_genai_content",
|
|
76
|
+
"should_capture_logs",
|
|
77
|
+
"shutdown_diagnostics",
|
|
78
|
+
"shutdown_session_logs",
|
|
79
|
+
"stamp_platform_refs",
|
|
80
|
+
"stamp_session_sdk_version",
|
|
81
|
+
]
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Semantic convention attribute name constants for Parlot instrumentation.
|
|
3
|
+
|
|
4
|
+
TypeScript: ``@parlot/core`` (``packages/core-ts/src/attrs.gen.ts``), generated via
|
|
5
|
+
``uv run python scripts/generate_attrs_ts.py``.
|
|
6
|
+
|
|
7
|
+
LiveKit vendor keys: ``parlot.instrumentation.livekit.attrs`` → ``@parlot/core/livekit``.
|
|
8
|
+
OTel-standard keys (e.g. ``exception.*``) live here, not in the LiveKit vendor module.
|
|
9
|
+
|
|
10
|
+
Three-layer export vocabulary (do not collapse):
|
|
11
|
+
Contract — parlot.session / parlot.turn / close / handoff
|
|
12
|
+
GenAI — OTel GenAI SemConv v1.41.0 (invoke_agent / chat / execute_tool / invoke_workflow)
|
|
13
|
+
Voice — tts / stt / eou_detection / amd (no GenAI equivalent)
|
|
14
|
+
|
|
15
|
+
Attribute layers:
|
|
16
|
+
Layer 1 — gen_ai.* OTel GenAI SemConv (never deviate; pin version below)
|
|
17
|
+
Layer 2 — openinference.* OpenInference semantic model
|
|
18
|
+
Layer 3 — Parlot extensions agent.*, voice.*, session.*, turn.*, participant.*,
|
|
19
|
+
conversation.*, platform.ref.*
|
|
20
|
+
|
|
21
|
+
Bump ``GENAI_SEMCONV_VERSION`` only with ``packages/core/genai_semconv.lock.json``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
# Pin the OTel GenAI semantic conventions revision adapters target.
|
|
27
|
+
# Conventions are Development-status; bump deliberately when remapping.
|
|
28
|
+
GENAI_SEMCONV_VERSION = "1.41.0"
|
|
29
|
+
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
# Layer 1 — OTel GenAI SemConv (gen_ai.*)
|
|
32
|
+
# ---------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
ATTR_GEN_AI_SYSTEM = "gen_ai.system"
|
|
35
|
+
ATTR_GEN_AI_OP_NAME = "gen_ai.operation.name"
|
|
36
|
+
ATTR_GEN_AI_PROVIDER = "gen_ai.provider.name"
|
|
37
|
+
ATTR_GEN_AI_MODEL = "gen_ai.request.model"
|
|
38
|
+
ATTR_GEN_AI_RESPONSE_MODEL = "gen_ai.response.model"
|
|
39
|
+
ATTR_GEN_AI_IN_TOKENS = "gen_ai.usage.input_tokens"
|
|
40
|
+
ATTR_GEN_AI_OUT_TOKENS = "gen_ai.usage.output_tokens"
|
|
41
|
+
ATTR_GEN_AI_CACHED_TOKENS = "gen_ai.usage.input_cached_tokens"
|
|
42
|
+
ATTR_GEN_AI_AUDIO_IN = "gen_ai.usage.input_audio_tokens"
|
|
43
|
+
ATTR_GEN_AI_AUDIO_OUT = "gen_ai.usage.output_audio_tokens"
|
|
44
|
+
ATTR_GEN_AI_COST_USD = "gen_ai.usage.cost_usd"
|
|
45
|
+
ATTR_GEN_AI_CACHE_HIT_RATE = "gen_ai.usage.cache_hit_rate"
|
|
46
|
+
ATTR_GEN_AI_TOOL_NAME = "gen_ai.tool.name"
|
|
47
|
+
|
|
48
|
+
# OTel GenAI operation.name values (Development status, semconv v1.41.0)
|
|
49
|
+
GEN_AI_OP_CHAT = "chat"
|
|
50
|
+
GEN_AI_OP_EXECUTE_TOOL = "execute_tool"
|
|
51
|
+
GEN_AI_OP_INVOKE_AGENT = "invoke_agent"
|
|
52
|
+
GEN_AI_OP_INVOKE_WORKFLOW = "invoke_workflow"
|
|
53
|
+
|
|
54
|
+
# OTel GenAI Agent Spans spec (Development status, May 2026)
|
|
55
|
+
ATTR_GEN_AI_AGENT_ID = "gen_ai.agent.id"
|
|
56
|
+
ATTR_GEN_AI_AGENT_NAME = "gen_ai.agent.name"
|
|
57
|
+
ATTR_GEN_AI_AGENT_VERSION = "gen_ai.agent.version"
|
|
58
|
+
ATTR_GEN_AI_CONVERSATION_ID = "gen_ai.conversation.id" # maps to parlot conversation_id
|
|
59
|
+
|
|
60
|
+
# TTS extension (not yet in spec)
|
|
61
|
+
ATTR_GEN_AI_TTS_TTFB_S = "gen_ai.tts.ttfb_s"
|
|
62
|
+
|
|
63
|
+
# Tool extensions
|
|
64
|
+
ATTR_GEN_AI_TOOL_IS_HANDOFF = "gen_ai.tool.is_handoff"
|
|
65
|
+
ATTR_GEN_AI_TOOL_DURATION_MS = "gen_ai.tool.duration_ms"
|
|
66
|
+
ATTR_TOOL_INPUT_PAYLOAD = "tool.input.payload"
|
|
67
|
+
ATTR_TOOL_INPUT_PAYLOAD_PREVIEW = "tool.input.payload_preview"
|
|
68
|
+
ATTR_TOOL_OUTPUT_PAYLOAD_PREVIEW = "tool.output.payload_preview"
|
|
69
|
+
|
|
70
|
+
# GenAI message events (OTel GenAI events, used for content capture)
|
|
71
|
+
EVENT_GEN_AI_USER_MESSAGE = "gen_ai.user.message"
|
|
72
|
+
EVENT_GEN_AI_ASSISTANT_MESSAGE = "gen_ai.assistant.message"
|
|
73
|
+
EVENT_GEN_AI_TOOL_MESSAGE = "gen_ai.tool.message"
|
|
74
|
+
|
|
75
|
+
# GenAI span name bases (export vocabulary; use helpers for parameterized names)
|
|
76
|
+
SPAN_GEN_AI_CHAT = "chat"
|
|
77
|
+
SPAN_GEN_AI_EXECUTE_TOOL = "execute_tool"
|
|
78
|
+
SPAN_GEN_AI_INVOKE_AGENT = "invoke_agent"
|
|
79
|
+
SPAN_GEN_AI_INVOKE_WORKFLOW = "invoke_workflow"
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def span_name_chat(model: str | None = None) -> str:
|
|
83
|
+
"""OTel GenAI chat span name: ``chat`` or ``chat {model}``."""
|
|
84
|
+
model_name = (model or "").strip()
|
|
85
|
+
if model_name:
|
|
86
|
+
return f"{SPAN_GEN_AI_CHAT} {model_name}"
|
|
87
|
+
return SPAN_GEN_AI_CHAT
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def span_name_execute_tool(tool_name: str) -> str:
|
|
91
|
+
"""OTel GenAI execute_tool span name: ``execute_tool {tool}``."""
|
|
92
|
+
name = (tool_name or "").strip() or "unknown"
|
|
93
|
+
return f"{SPAN_GEN_AI_EXECUTE_TOOL} {name}"
|
|
94
|
+
|
|
95
|
+
# ---------------------------------------------------------------------------
|
|
96
|
+
# Layer 2 — OpenInference
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
ATTR_OI_SPAN_KIND = "openinference.span.kind"
|
|
100
|
+
# Values: AGENT | CHAIN | TOOL | RETRIEVER | LLM | EMBEDDING | EVAL
|
|
101
|
+
|
|
102
|
+
# ---------------------------------------------------------------------------
|
|
103
|
+
# OTel exception recording (exception.* — all frameworks, not vendor-specific)
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
ATTR_EXCEPTION_TYPE = "exception.type"
|
|
107
|
+
ATTR_EXCEPTION_MESSAGE = "exception.message"
|
|
108
|
+
ATTR_EXCEPTION_TRACE = "exception.stacktrace"
|
|
109
|
+
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
# Layer 3 — Parlot extensions
|
|
112
|
+
# ---------------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
# -- Agent identity (gaps the OTel spec does not cover) --------------------
|
|
115
|
+
ATTR_AGENT_ROLE = "agent.role" # pipeline/component role: stt | tts | llm | tool | amd | pipeline | handoff | guardrail (not agent id/name)
|
|
116
|
+
ATTR_AGENT_STAGE = "agent.stage" # lifecycle stage within role: node | request | run | turn | eou | drain | call | transfer | classify
|
|
117
|
+
ATTR_AGENT_FRAMEWORK = "agent.framework" # livekit | crewai | langgraph | adk | custom
|
|
118
|
+
ATTR_AGENT_INSTRUCTIONS_EXCERPT = "agent.instructions_excerpt"
|
|
119
|
+
ATTR_AGENT_STATIC_INSTRUCTIONS_EXCERPT = "agent.static_instructions_excerpt"
|
|
120
|
+
ATTR_AGENT_TOOL_NAMES = "agent.tool.names" # JSON array of tool name strings
|
|
121
|
+
ATTR_AGENT_TOOL_NAME = "agent.tool.name"
|
|
122
|
+
ATTR_AGENT_TOOL_IS_ERROR = "agent.tool.is_error"
|
|
123
|
+
|
|
124
|
+
# -- Agent coordination ----------------------------------------------------
|
|
125
|
+
ATTR_AGENT_TRANSFER_FROM = "agent.transfer.from_agent_id"
|
|
126
|
+
ATTR_AGENT_TRANSFER_TO = "agent.transfer.to_agent_id"
|
|
127
|
+
ATTR_AGENT_TRANSFER_REASON = "agent.transfer.reason"
|
|
128
|
+
ATTR_AGENT_TRANSFER_BYTES = "agent.transfer.context_payload_bytes"
|
|
129
|
+
ATTR_AGENT_TRANSFER_SEQUENCE = "agent.transfer.sequence"
|
|
130
|
+
ATTR_AGENT_TRANSFER_ITEM_ID = "agent.transfer.item_id"
|
|
131
|
+
ATTR_AGENT_TRANSFER_CREATED_AT = "agent.transfer.created_at"
|
|
132
|
+
ATTR_AGENT_TRANSFER_LATENCY_MS = "agent.transfer.transition_latency_ms"
|
|
133
|
+
ATTR_AGENT_COORD_OVERHEAD = "agent.coordination.overhead_ms"
|
|
134
|
+
ATTR_AGENT_TOOL_CALL_INDEX = "agent.tool_call.index"
|
|
135
|
+
ATTR_AGENT_TOOL_TIMING_CORRECTED = "agent.tool.timing_corrected"
|
|
136
|
+
|
|
137
|
+
# -- Session ---------------------------------------------------------------
|
|
138
|
+
ATTR_SESSION_ID = "session.id"
|
|
139
|
+
ATTR_SESSION_CONVERSATION_ID = "session.conversation_id"
|
|
140
|
+
ATTR_SESSION_MODALITY = "session.modality" # voice | text | multimodal
|
|
141
|
+
ATTR_SESSION_PARTICIPANT_COUNT = "session.participant_count"
|
|
142
|
+
ATTR_SESSION_INTENT_LABEL = "session.intent_label"
|
|
143
|
+
ATTR_SESSION_INTENT_SEQUENCE = "session.intent.sequence"
|
|
144
|
+
ATTR_SESSION_AMD = "session.amd" # human | voicemail | ivr | unavailable | unknown
|
|
145
|
+
ATTR_SESSION_TURN_COUNT = "session.turn_count"
|
|
146
|
+
ATTR_SESSION_TOOL_CALL_COUNT = "session.tool_call_count"
|
|
147
|
+
ATTR_SESSION_HANDOFF_COUNT = "session.handoff_count"
|
|
148
|
+
ATTR_SESSION_TOTAL_INPUT_TOKENS = "session.total_input_tokens"
|
|
149
|
+
ATTR_SESSION_TOTAL_OUTPUT_TOKENS = "session.total_output_tokens"
|
|
150
|
+
ATTR_SESSION_TOTAL_COST_USD = "session.total_cost_usd"
|
|
151
|
+
ATTR_SESSION_AGENT_ID = "session.agent_id"
|
|
152
|
+
ATTR_SESSION_AGENT_FRAMEWORK = "session.agent_framework"
|
|
153
|
+
ATTR_SESSION_AGENT_FRAMEWORK_RAW_ID = "session.agent_framework_raw_id"
|
|
154
|
+
ATTR_PARLOT_SDK_VERSION = "parlot.sdk.version"
|
|
155
|
+
|
|
156
|
+
# -- Session custom metadata (public user bag; prefix-passthrough at ingest) --
|
|
157
|
+
ATTR_SESSION_METADATA_PREFIX = "session.metadata."
|
|
158
|
+
|
|
159
|
+
# -- OTLP metric names (Parlot contract; framework adapters map vendor usage here) --
|
|
160
|
+
METRIC_USAGE_LLM_INPUT_TOKENS = "usage.llm_input_tokens"
|
|
161
|
+
METRIC_USAGE_LLM_OUTPUT_TOKENS = "usage.llm_output_tokens"
|
|
162
|
+
METRIC_USAGE_STT_AUDIO_DURATION = "usage.stt_audio_duration"
|
|
163
|
+
METRIC_USAGE_TTS_AUDIO_DURATION = "usage.tts_audio_duration"
|
|
164
|
+
METRIC_USAGE_TTS_CHARACTERS = "usage.tts_characters"
|
|
165
|
+
METRIC_SESSION_TURN_COUNT = "session.turn_count"
|
|
166
|
+
METRIC_SESSION_TOTAL_INPUT_TOKENS = "session.total_input_tokens"
|
|
167
|
+
METRIC_SESSION_TOTAL_OUTPUT_TOKENS = "session.total_output_tokens"
|
|
168
|
+
ATTR_SESSION_AGENT_CHAIN = "session.agent_chain"
|
|
169
|
+
ATTR_SESSION_TOPOLOGY_AGENTS = "session.topology.agents"
|
|
170
|
+
ATTR_SESSION_TOPOLOGY_TOOLS = "session.topology.tools"
|
|
171
|
+
ATTR_SESSION_TOPOLOGY_EDGES = "session.topology.edges"
|
|
172
|
+
ATTR_SESSION_TOPOLOGY_BOOTSTRAP_INSTRUCTIONS = "session.topology.bootstrap_instructions"
|
|
173
|
+
ATTR_SESSION_RECORDING_ANCHOR_WALL_MS = "session.recording.anchor_wall_ms"
|
|
174
|
+
ATTR_SESSION_RECORDING_AUDIO_URI = "session.recording.audio_uri"
|
|
175
|
+
ATTR_SESSION_RECORDING_EGRESS_ID = "session.recording.egress_id"
|
|
176
|
+
ATTR_SESSION_RECORDING_WEBHOOK_ERROR = "session.recording.webhook_error"
|
|
177
|
+
ATTR_SESSION_LANGUAGES = "session.languages"
|
|
178
|
+
ATTR_SESSION_CLOSE_REASON = "session.close_reason"
|
|
179
|
+
ATTR_SESSION_USER_ID = "session.user_id"
|
|
180
|
+
ATTR_SESSION_CLOSE_ERROR = "session.close_error"
|
|
181
|
+
|
|
182
|
+
# -- Framework-agnostic Parlot span names (Conversation Contract) -----------
|
|
183
|
+
SPAN_CONVERSATION_SESSION = "parlot.session"
|
|
184
|
+
SPAN_PARLOT_TURN = "parlot.turn"
|
|
185
|
+
SPAN_PARLOT_SESSION_CLOSE = "parlot.session.close"
|
|
186
|
+
SPAN_AGENT_HANDOFF = "parlot.agent.handoff"
|
|
187
|
+
|
|
188
|
+
CONTRACT_SPAN_NAMES = frozenset({
|
|
189
|
+
SPAN_CONVERSATION_SESSION,
|
|
190
|
+
SPAN_PARLOT_TURN,
|
|
191
|
+
SPAN_PARLOT_SESSION_CLOSE,
|
|
192
|
+
SPAN_AGENT_HANDOFF,
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
# -- Voice operational span names (no GenAI equivalent) ---------------------
|
|
196
|
+
SPAN_VOICE_TTS = "tts"
|
|
197
|
+
SPAN_VOICE_STT = "stt"
|
|
198
|
+
SPAN_VOICE_EOU = "eou_detection"
|
|
199
|
+
SPAN_VOICE_AMD = "amd"
|
|
200
|
+
|
|
201
|
+
VOICE_SPAN_NAMES = frozenset({
|
|
202
|
+
SPAN_VOICE_TTS,
|
|
203
|
+
SPAN_VOICE_STT,
|
|
204
|
+
SPAN_VOICE_EOU,
|
|
205
|
+
SPAN_VOICE_AMD,
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
GENAI_SPAN_NAME_BASES = frozenset({
|
|
209
|
+
SPAN_GEN_AI_CHAT,
|
|
210
|
+
SPAN_GEN_AI_EXECUTE_TOOL,
|
|
211
|
+
SPAN_GEN_AI_INVOKE_AGENT,
|
|
212
|
+
SPAN_GEN_AI_INVOKE_WORKFLOW,
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def is_genai_span_name(name: str | None) -> bool:
|
|
217
|
+
"""True for exact GenAI bases or parameterized ``chat {model}`` / ``execute_tool {name}``."""
|
|
218
|
+
if not name:
|
|
219
|
+
return False
|
|
220
|
+
if name in GENAI_SPAN_NAME_BASES:
|
|
221
|
+
return True
|
|
222
|
+
if name.startswith(f"{SPAN_GEN_AI_CHAT} "):
|
|
223
|
+
return True
|
|
224
|
+
if name.startswith(f"{SPAN_GEN_AI_EXECUTE_TOOL} "):
|
|
225
|
+
return True
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def is_exportable_span_name(name: str | None) -> bool:
|
|
230
|
+
"""Shared export allowlist: Conversation Contract ∪ GenAI ∪ voice."""
|
|
231
|
+
if not name:
|
|
232
|
+
return False
|
|
233
|
+
if name in CONTRACT_SPAN_NAMES or name in VOICE_SPAN_NAMES:
|
|
234
|
+
return True
|
|
235
|
+
return is_genai_span_name(name)
|
|
236
|
+
|
|
237
|
+
# -- Conversation ----------------------------------------------------------
|
|
238
|
+
ATTR_CONVERSATION_ID = "conversation.id"
|
|
239
|
+
ATTR_CONVERSATION_CHANNEL = "conversation.channel" # voice | webchat | sms | whatsapp
|
|
240
|
+
|
|
241
|
+
# -- Turn ------------------------------------------------------------------
|
|
242
|
+
ATTR_TURN_PREV_TRACE_ID = "turn.prev_trace_id"
|
|
243
|
+
ATTR_TURN_INDEX = "turn.index"
|
|
244
|
+
ATTR_TURN_PARTICIPANT_ID = "turn.participant_id"
|
|
245
|
+
ATTR_TURN_PARTICIPANT_ROLE = "turn.participant_role"
|
|
246
|
+
ATTR_TURN_PARTICIPANT_LABEL = "turn.participant_label"
|
|
247
|
+
ATTR_TURN_INPUT_MODALITY = "turn.input_modality" # voice | text (user turns)
|
|
248
|
+
ATTR_TURN_MEDIA_START_MS = "turn.media_segment_start_ms"
|
|
249
|
+
ATTR_TURN_MEDIA_END_MS = "turn.media_segment_end_ms"
|
|
250
|
+
ATTR_TURN_SPEECH_WALL_START_MS = "turn.speech_wall_start_ms"
|
|
251
|
+
ATTR_TURN_SPEECH_WALL_END_MS = "turn.speech_wall_end_ms"
|
|
252
|
+
ATTR_TURN_INTERRUPTED = "turn.interrupted"
|
|
253
|
+
ATTR_TURN_E2E_LATENCY_S = "turn.e2e_latency_s"
|
|
254
|
+
ATTR_TURN_LLM_TTFT_S = "turn.llm_ttft_s"
|
|
255
|
+
ATTR_TURN_TTS_TTFB_S = "turn.tts_ttfb_s"
|
|
256
|
+
ATTR_TURN_TRANSCRIPTION_DELAY_S = "turn.transcription_delay_s"
|
|
257
|
+
ATTR_TURN_EOU_DELAY_S = "turn.eou_delay_s"
|
|
258
|
+
ATTR_TURN_LANGUAGE = "turn.language"
|
|
259
|
+
ATTR_TURN_LANGUAGE_SWITCH = "turn.language_switch"
|
|
260
|
+
ATTR_TURN_USER_TEXT = "turn.user_text"
|
|
261
|
+
ATTR_TURN_AGENT_TEXT = "turn.agent_text"
|
|
262
|
+
ATTR_TURN_INTENT_LABEL = "turn.intent_label"
|
|
263
|
+
ATTR_TURN_INTENT_KEY = "turn.intent_key"
|
|
264
|
+
ATTR_TURN_ACTIVE_AGENT_ID = "turn.active_agent_id"
|
|
265
|
+
|
|
266
|
+
# -- Participant / Diarization ---------------------------------------------
|
|
267
|
+
ATTR_PARTICIPANT_ID = "participant.id"
|
|
268
|
+
ATTR_PARTICIPANT_ROLE = "participant.role"
|
|
269
|
+
ATTR_PARTICIPANT_LABEL = "participant.label"
|
|
270
|
+
ATTR_PARTICIPANT_FIRST_TURN = "participant.first_turn_index"
|
|
271
|
+
ATTR_PARTICIPANT_DIAR_SOURCE = "participant.diarization_source"
|
|
272
|
+
ATTR_PARTICIPANT_CHANNEL_IDENTITY = "participant.channel_identity"
|
|
273
|
+
ATTR_STT_SPEAKER_ID = "stt.speaker_id"
|
|
274
|
+
|
|
275
|
+
# participant.diarization_source values (framework-agnostic; not LiveKit-specific)
|
|
276
|
+
ATTR_DIAR_SOURCE_STT_SPEAKER_ID = "stt_speaker_id"
|
|
277
|
+
ATTR_DIAR_SOURCE_AGENT_ID = "agent_id"
|
|
278
|
+
ATTR_DIAR_SOURCE_POST_SESSION = "post_session"
|
|
279
|
+
ATTR_DIAR_SOURCE_CHANNEL_METADATA = "channel_metadata"
|
|
280
|
+
|
|
281
|
+
# Default turn.participant_id before STT speaker_id is known
|
|
282
|
+
ATTR_TURN_PARTICIPANT_ID_CALLER = "caller"
|
|
283
|
+
|
|
284
|
+
# -- Voice -----------------------------------------------------------------
|
|
285
|
+
ATTR_VOICE_STT_LATENCY_MS = "voice.stt.latency_ms"
|
|
286
|
+
ATTR_VOICE_STT_CONFIDENCE = "voice.stt.confidence"
|
|
287
|
+
ATTR_VOICE_TTS_LATENCY_MS = "voice.tts.latency_ms"
|
|
288
|
+
ATTR_VOICE_EOU_LANGUAGE = "voice.eou.language"
|
|
289
|
+
ATTR_VOICE_AMD_CATEGORY = "voice.amd.category"
|
|
290
|
+
ATTR_VOICE_AUDIO_PACKET_LOSS = "voice.audio.packet_loss"
|
|
291
|
+
ATTR_VOICE_AUDIO_JITTER_MS = "voice.audio.jitter_ms"
|
|
292
|
+
ATTR_VOICE_DEAD_AIR_MS = "voice.interaction.dead_air_ms"
|
|
293
|
+
|
|
294
|
+
# -- Eval (OpenInference, adopted) ----------------------------------------
|
|
295
|
+
ATTR_EVAL_NAME = "eval.name"
|
|
296
|
+
ATTR_EVAL_RESULT = "eval.result"
|
|
297
|
+
ATTR_EVAL_SCORE = "eval.score"
|
|
298
|
+
ATTR_EVAL_EXPLANATION = "eval.explanation"
|
|
299
|
+
|
|
300
|
+
# -- Platform external references (session resolve / debug linking) --------
|
|
301
|
+
ATTR_PLATFORM_REF_PREFIX = "platform.ref." # append kind for flat ref keys
|
|
302
|
+
ATTR_PLATFORM_FRAMEWORK = "platform.ref.framework"
|
|
303
|
+
ATTR_PLATFORM_KIND = "platform.ref.kind"
|
|
304
|
+
ATTR_PLATFORM_VALUE = "platform.ref.value"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Fetch and cache Parlot telemetry bootstrap into ``ParlotRuntimeContext``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger("parlot.core.bootstrap")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def fetch_telemetry_bootstrap(
|
|
12
|
+
endpoint: str,
|
|
13
|
+
api_key: str,
|
|
14
|
+
*,
|
|
15
|
+
timeout: float = 15.0,
|
|
16
|
+
) -> Optional[dict[str, Any]]:
|
|
17
|
+
"""GET ``/v1/telemetry/bootstrap`` and return the JSON payload, or None."""
|
|
18
|
+
if not endpoint or not api_key:
|
|
19
|
+
return None
|
|
20
|
+
|
|
21
|
+
import httpx
|
|
22
|
+
|
|
23
|
+
from parlot.core.runtime import runtime_from_bootstrap, set_runtime
|
|
24
|
+
|
|
25
|
+
url = f"{endpoint.rstrip('/')}/v1/telemetry/bootstrap"
|
|
26
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
27
|
+
try:
|
|
28
|
+
with httpx.Client(timeout=timeout) as client:
|
|
29
|
+
resp = client.get(url, headers=headers)
|
|
30
|
+
if resp.status_code >= 400:
|
|
31
|
+
logger.error(
|
|
32
|
+
"parlot: telemetry bootstrap failed status=%s",
|
|
33
|
+
resp.status_code,
|
|
34
|
+
)
|
|
35
|
+
return None
|
|
36
|
+
payload = resp.json()
|
|
37
|
+
if not isinstance(payload, dict):
|
|
38
|
+
logger.error("parlot: telemetry bootstrap returned non-object JSON")
|
|
39
|
+
return None
|
|
40
|
+
set_runtime(runtime_from_bootstrap(endpoint, api_key, payload))
|
|
41
|
+
return payload
|
|
42
|
+
except Exception:
|
|
43
|
+
logger.exception("parlot: telemetry bootstrap request failed")
|
|
44
|
+
return None
|