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.
Files changed (30) hide show
  1. parlot_core-0.1.0/.gitignore +61 -0
  2. parlot_core-0.1.0/LICENSE +21 -0
  3. parlot_core-0.1.0/PKG-INFO +48 -0
  4. parlot_core-0.1.0/README.md +19 -0
  5. parlot_core-0.1.0/pyproject.toml +58 -0
  6. parlot_core-0.1.0/src/parlot/core/__init__.py +81 -0
  7. parlot_core-0.1.0/src/parlot/core/attrs.py +304 -0
  8. parlot_core-0.1.0/src/parlot/core/bootstrap.py +44 -0
  9. parlot_core-0.1.0/src/parlot/core/configure.py +130 -0
  10. parlot_core-0.1.0/src/parlot/core/diagnostics.py +187 -0
  11. parlot_core-0.1.0/src/parlot/core/escalation.py +52 -0
  12. parlot_core-0.1.0/src/parlot/core/export.py +29 -0
  13. parlot_core-0.1.0/src/parlot/core/genai_content_capture.py +65 -0
  14. parlot_core-0.1.0/src/parlot/core/goal_lifecycle.py +118 -0
  15. parlot_core-0.1.0/src/parlot/core/ids.py +28 -0
  16. parlot_core-0.1.0/src/parlot/core/intent.py +51 -0
  17. parlot_core-0.1.0/src/parlot/core/language.py +37 -0
  18. parlot_core-0.1.0/src/parlot/core/logging.py +111 -0
  19. parlot_core-0.1.0/src/parlot/core/logs_capture.py +84 -0
  20. parlot_core-0.1.0/src/parlot/core/metadata.py +67 -0
  21. parlot_core-0.1.0/src/parlot/core/platform_refs.py +96 -0
  22. parlot_core-0.1.0/src/parlot/core/processor.py +127 -0
  23. parlot_core-0.1.0/src/parlot/core/provider.py +115 -0
  24. parlot_core-0.1.0/src/parlot/core/py.typed +0 -0
  25. parlot_core-0.1.0/src/parlot/core/recording.py +56 -0
  26. parlot_core-0.1.0/src/parlot/core/runtime.py +186 -0
  27. parlot_core-0.1.0/src/parlot/core/sdk_version.py +45 -0
  28. parlot_core-0.1.0/src/parlot/core/session.py +83 -0
  29. parlot_core-0.1.0/src/parlot/core/session_logs.py +393 -0
  30. 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