morse-ai 0.4.0rc1__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.
- morse_ai-0.4.0rc1/.gitignore +93 -0
- morse_ai-0.4.0rc1/CHANGELOG.md +48 -0
- morse_ai-0.4.0rc1/LICENSE +21 -0
- morse_ai-0.4.0rc1/PKG-INFO +317 -0
- morse_ai-0.4.0rc1/README.md +269 -0
- morse_ai-0.4.0rc1/package.json +12 -0
- morse_ai-0.4.0rc1/poetry.lock +2909 -0
- morse_ai-0.4.0rc1/pyproject.toml +89 -0
- morse_ai-0.4.0rc1/src/morse_ai/__init__.py +596 -0
- morse_ai-0.4.0rc1/src/morse_ai/_internal/__init__.py +0 -0
- morse_ai-0.4.0rc1/src/morse_ai/_internal/active_framework.py +68 -0
- morse_ai-0.4.0rc1/src/morse_ai/_internal/auto_detect.py +236 -0
- morse_ai-0.4.0rc1/src/morse_ai/_internal/env.py +234 -0
- morse_ai-0.4.0rc1/src/morse_ai/_internal/errors.py +66 -0
- morse_ai-0.4.0rc1/src/morse_ai/_internal/redaction.py +334 -0
- morse_ai-0.4.0rc1/src/morse_ai/_internal/sentry_reporter.py +250 -0
- morse_ai-0.4.0rc1/src/morse_ai/_version.py +20 -0
- morse_ai-0.4.0rc1/src/morse_ai/adapters/__init__.py +30 -0
- morse_ai-0.4.0rc1/src/morse_ai/adapters/anthropic.py +1280 -0
- morse_ai-0.4.0rc1/src/morse_ai/adapters/base.py +142 -0
- morse_ai-0.4.0rc1/src/morse_ai/adapters/claude_agent_sdk.py +950 -0
- morse_ai-0.4.0rc1/src/morse_ai/adapters/langchain.py +804 -0
- morse_ai-0.4.0rc1/src/morse_ai/adapters/langgraph.py +1620 -0
- morse_ai-0.4.0rc1/src/morse_ai/adapters/openai.py +741 -0
- morse_ai-0.4.0rc1/src/morse_ai/adapters/openai_agents.py +798 -0
- morse_ai-0.4.0rc1/src/morse_ai/client.py +321 -0
- morse_ai-0.4.0rc1/src/morse_ai/context/__init__.py +1 -0
- morse_ai-0.4.0rc1/src/morse_ai/context/decomposer.py +345 -0
- morse_ai-0.4.0rc1/src/morse_ai/data/__init__.py +1 -0
- morse_ai-0.4.0rc1/src/morse_ai/data/compat.py +50 -0
- morse_ai-0.4.0rc1/src/morse_ai/data/compat_manifest.json +145 -0
- morse_ai-0.4.0rc1/src/morse_ai/data/pricing.json +133 -0
- morse_ai-0.4.0rc1/src/morse_ai/infra_capture.py +507 -0
- morse_ai-0.4.0rc1/src/morse_ai/instrumentation/__init__.py +0 -0
- morse_ai-0.4.0rc1/src/morse_ai/instrumentation/http_classifier.py +53 -0
- morse_ai-0.4.0rc1/src/morse_ai/instrumentation/imports.py +164 -0
- morse_ai-0.4.0rc1/src/morse_ai/logging.py +305 -0
- morse_ai-0.4.0rc1/src/morse_ai/otel_provider.py +655 -0
- morse_ai-0.4.0rc1/src/morse_ai/pricing.py +141 -0
- morse_ai-0.4.0rc1/src/morse_ai/propagation.py +132 -0
- morse_ai-0.4.0rc1/src/morse_ai/py.typed +0 -0
- morse_ai-0.4.0rc1/src/morse_ai/queue.py +61 -0
- morse_ai-0.4.0rc1/src/morse_ai/record.py +42 -0
- morse_ai-0.4.0rc1/src/morse_ai/spans.py +102 -0
- morse_ai-0.4.0rc1/src/morse_ai/tracing.py +802 -0
- morse_ai-0.4.0rc1/src/morse_ai/transport.py +528 -0
- morse_ai-0.4.0rc1/tests/__init__.py +0 -0
- morse_ai-0.4.0rc1/tests/conftest.py +75 -0
- morse_ai-0.4.0rc1/tests/integration/__init__.py +0 -0
- morse_ai-0.4.0rc1/tests/integration/test_anthropic_live.py +235 -0
- morse_ai-0.4.0rc1/tests/test_adapters.py +92 -0
- morse_ai-0.4.0rc1/tests/test_age267_roundtrip.py +105 -0
- morse_ai-0.4.0rc1/tests/test_anthropic_context_segments.py +363 -0
- morse_ai-0.4.0rc1/tests/test_anthropic_summarize.py +338 -0
- morse_ai-0.4.0rc1/tests/test_anthropic_wrap.py +443 -0
- morse_ai-0.4.0rc1/tests/test_auto_detect.py +176 -0
- morse_ai-0.4.0rc1/tests/test_claude_agent_sdk_adapter.py +1097 -0
- morse_ai-0.4.0rc1/tests/test_compat_manifest.py +63 -0
- morse_ai-0.4.0rc1/tests/test_context_decomposer.py +340 -0
- morse_ai-0.4.0rc1/tests/test_http_classifier.py +66 -0
- morse_ai-0.4.0rc1/tests/test_import_scanner.py +131 -0
- morse_ai-0.4.0rc1/tests/test_infra_capture.py +141 -0
- morse_ai-0.4.0rc1/tests/test_infra_capture_http.py +203 -0
- morse_ai-0.4.0rc1/tests/test_infra_capture_streaming.py +107 -0
- morse_ai-0.4.0rc1/tests/test_integration.py +69 -0
- morse_ai-0.4.0rc1/tests/test_langchain_adapter.py +503 -0
- morse_ai-0.4.0rc1/tests/test_langchain_state_capture.py +367 -0
- morse_ai-0.4.0rc1/tests/test_langgraph_context_segments.py +473 -0
- morse_ai-0.4.0rc1/tests/test_langgraph_memory_ops.py +880 -0
- morse_ai-0.4.0rc1/tests/test_langgraph_trace_context_bridges.py +571 -0
- morse_ai-0.4.0rc1/tests/test_logging_handler.py +375 -0
- morse_ai-0.4.0rc1/tests/test_openai_agents_adapter.py +448 -0
- morse_ai-0.4.0rc1/tests/test_openai_context_segments.py +478 -0
- morse_ai-0.4.0rc1/tests/test_openai_summarize.py +251 -0
- morse_ai-0.4.0rc1/tests/test_otel_provider.py +326 -0
- morse_ai-0.4.0rc1/tests/test_performance.py +55 -0
- morse_ai-0.4.0rc1/tests/test_pricing.py +92 -0
- morse_ai-0.4.0rc1/tests/test_propagation.py +371 -0
- morse_ai-0.4.0rc1/tests/test_provider_reported_cost.py +266 -0
- morse_ai-0.4.0rc1/tests/test_queue.py +47 -0
- morse_ai-0.4.0rc1/tests/test_record.py +50 -0
- morse_ai-0.4.0rc1/tests/test_redaction.py +550 -0
- morse_ai-0.4.0rc1/tests/test_resilience.py +362 -0
- morse_ai-0.4.0rc1/tests/test_retriever_adapter.py +127 -0
- morse_ai-0.4.0rc1/tests/test_sentry_isolation.py +242 -0
- morse_ai-0.4.0rc1/tests/test_set_context.py +85 -0
- morse_ai-0.4.0rc1/tests/test_tracing.py +494 -0
- morse_ai-0.4.0rc1/tests/test_transport.py +101 -0
- morse_ai-0.4.0rc1/tests/test_transport_retry.py +366 -0
- morse_ai-0.4.0rc1/tests/test_v2_api.py +165 -0
- morse_ai-0.4.0rc1/tests/test_v2_env_init.py +244 -0
- morse_ai-0.4.0rc1/tests/test_v2_framework_attr.py +283 -0
- morse_ai-0.4.0rc1/tests/test_v2_source_attr.py +201 -0
- morse_ai-0.4.0rc1/tests/test_v2_tracing_auto_init.py +63 -0
- morse_ai-0.4.0rc1/uv.lock +2122 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# OS
|
|
2
|
+
.DS_Store
|
|
3
|
+
Thumbs.db
|
|
4
|
+
*.sw?
|
|
5
|
+
|
|
6
|
+
# Node / JS / TS
|
|
7
|
+
node_modules/
|
|
8
|
+
.pnpm-store/
|
|
9
|
+
.npm-cache/
|
|
10
|
+
dist/
|
|
11
|
+
build/
|
|
12
|
+
coverage/
|
|
13
|
+
.nyc_output/
|
|
14
|
+
.turbo/
|
|
15
|
+
.next/
|
|
16
|
+
out/
|
|
17
|
+
*.tsbuildinfo
|
|
18
|
+
pnpm-debug.log*
|
|
19
|
+
npm-debug.log*
|
|
20
|
+
yarn-debug.log*
|
|
21
|
+
yarn-error.log*
|
|
22
|
+
|
|
23
|
+
# Vite/Vitest
|
|
24
|
+
.vite/
|
|
25
|
+
vitest.config.ts.timestamp-*
|
|
26
|
+
|
|
27
|
+
# Expo / React Native
|
|
28
|
+
.expo/
|
|
29
|
+
.expo-shared/
|
|
30
|
+
ios/
|
|
31
|
+
android/
|
|
32
|
+
|
|
33
|
+
# Python
|
|
34
|
+
__pycache__/
|
|
35
|
+
*.py[cod]
|
|
36
|
+
*.pyo
|
|
37
|
+
*.pyd
|
|
38
|
+
*.so
|
|
39
|
+
*.egg-info/
|
|
40
|
+
.venv/
|
|
41
|
+
venv/
|
|
42
|
+
env/
|
|
43
|
+
.mypy_cache/
|
|
44
|
+
.pytest_cache/
|
|
45
|
+
.ruff_cache/
|
|
46
|
+
.coverage
|
|
47
|
+
htmlcov/
|
|
48
|
+
*.sqlite3
|
|
49
|
+
coverage.xml
|
|
50
|
+
.hypothesis/
|
|
51
|
+
|
|
52
|
+
# Django collected static files (built artifact — produced by collectstatic
|
|
53
|
+
# at Docker build time and at runtime; never checked in).
|
|
54
|
+
apps/api/staticfiles/
|
|
55
|
+
|
|
56
|
+
# Env files
|
|
57
|
+
.env
|
|
58
|
+
.env.local
|
|
59
|
+
.env.*.local
|
|
60
|
+
.env.*
|
|
61
|
+
!.env.example
|
|
62
|
+
!.env.*.example
|
|
63
|
+
|
|
64
|
+
# GSD / planning backups
|
|
65
|
+
.planning/STATE.md.bak-*
|
|
66
|
+
|
|
67
|
+
# Test artifacts
|
|
68
|
+
test-results/
|
|
69
|
+
|
|
70
|
+
# Screen recordings (marketing video build artifacts — scripts/record-video.sh)
|
|
71
|
+
apps/web/recordings/
|
|
72
|
+
|
|
73
|
+
# Local agent/tool folders
|
|
74
|
+
.agent/
|
|
75
|
+
.agents/
|
|
76
|
+
.codex/
|
|
77
|
+
.gemini/
|
|
78
|
+
.openpackage/
|
|
79
|
+
.aider*
|
|
80
|
+
.claude/worktrees/
|
|
81
|
+
.claude/settings.local.json
|
|
82
|
+
.serena/
|
|
83
|
+
.remember/
|
|
84
|
+
apps/landing/playwright-report/
|
|
85
|
+
# orchestration env (covered by .env.* above, but kept explicit for clarity)
|
|
86
|
+
.env.orchestration
|
|
87
|
+
.env.orchestration.bak
|
|
88
|
+
|
|
89
|
+
# QA bot run artifacts (machine-generated, high churn, binaries)
|
|
90
|
+
qa/state/
|
|
91
|
+
|
|
92
|
+
# Local scratch worktrees (never tracked — a stray gitlink was committed 2026-07-25)
|
|
93
|
+
.worktrees/
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `morse-ai` are documented here.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
|
|
6
|
+
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
The Python and TypeScript SDKs version **independently** — they share `0.4.0` as
|
|
9
|
+
their first public release because they reached feature parity there, not
|
|
10
|
+
because they are released in lockstep. Expect the numbers to diverge.
|
|
11
|
+
|
|
12
|
+
## [0.4.0] — 2026-07-31
|
|
13
|
+
|
|
14
|
+
First public release on PyPI.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Six framework adapters**, each dogfooded end-to-end against a real
|
|
19
|
+
third-party application before release: Anthropic Messages API, Claude Agent
|
|
20
|
+
SDK, OpenAI (raw client), OpenAI Agents SDK, LangChain (LCEL + `bind_tools`),
|
|
21
|
+
and LangGraph.
|
|
22
|
+
- `input_data` / `output_data` capture on LLM spans for the Anthropic
|
|
23
|
+
(MHQ-772) and OpenAI (MHQ-774) adapters — prompt and completion payloads are
|
|
24
|
+
now visible in the trace, subject to the redaction config.
|
|
25
|
+
- OTLP export path alongside the REST transport, including W3C trace-context
|
|
26
|
+
propagation (`extract_traceparent` / `inject_traceparent`).
|
|
27
|
+
- Retry/backoff on the telemetry transport: 3 attempts with jittered
|
|
28
|
+
exponential backoff, and quota feedback read from the `X-Morse-Quota-*`
|
|
29
|
+
response headers.
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Anthropic adapter LLM spans always reported ~0ms duration (MHQ-771).
|
|
34
|
+
- OpenAI adapter LLM spans had the same 0ms duration bug.
|
|
35
|
+
- Claude Agent SDK sub-agent spawns never finalized when the stream ended
|
|
36
|
+
without a terminal task message — they now finalize via a `ResultMessage`
|
|
37
|
+
fallback (MHQ-783), and in-flight spawn hooks are drained on shutdown
|
|
38
|
+
(MHQ-762).
|
|
39
|
+
- API keys are redacted from telemetry under both the current `mhq_` prefix and
|
|
40
|
+
the legacy `av_` prefix, so keys issued before the rename are still scrubbed.
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- **The SDK version is now declared in exactly one place** (`pyproject.toml`)
|
|
45
|
+
and read back from package metadata at import time. It was previously
|
|
46
|
+
copy-pasted into four locations; the copies could drift from the released
|
|
47
|
+
version and would then misreport `sdk_version` on every ingested span and in
|
|
48
|
+
the `User-Agent` header.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Morse
|
|
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,317 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: morse-ai
|
|
3
|
+
Version: 0.4.0rc1
|
|
4
|
+
Summary: Unified observability for AI agents and infrastructure. One SDK.
|
|
5
|
+
Project-URL: Homepage, https://morsehq.dev
|
|
6
|
+
Project-URL: Documentation, https://docs.morsehq.dev
|
|
7
|
+
Author-email: Morse <hello@morsehq.dev>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: <4.0,>=3.11
|
|
20
|
+
Requires-Dist: httpx<1.0,>=0.25.0
|
|
21
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.27.0
|
|
22
|
+
Requires-Dist: opentelemetry-sdk>=1.27.0
|
|
23
|
+
Provides-Extra: all
|
|
24
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'all'
|
|
25
|
+
Requires-Dist: langchain-core>=1.0; extra == 'all'
|
|
26
|
+
Requires-Dist: langgraph>=1.0; extra == 'all'
|
|
27
|
+
Requires-Dist: openai-agents<1.0,>=0.15.0; extra == 'all'
|
|
28
|
+
Requires-Dist: openai>=1.50.0; extra == 'all'
|
|
29
|
+
Provides-Extra: anthropic
|
|
30
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'anthropic'
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest-benchmark>=5.0; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: respx>=0.22; extra == 'dev'
|
|
37
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
38
|
+
Provides-Extra: langchain
|
|
39
|
+
Requires-Dist: langchain-core>=1.0; extra == 'langchain'
|
|
40
|
+
Provides-Extra: langgraph
|
|
41
|
+
Requires-Dist: langchain-core>=1.0; extra == 'langgraph'
|
|
42
|
+
Requires-Dist: langgraph>=1.0; extra == 'langgraph'
|
|
43
|
+
Provides-Extra: openai
|
|
44
|
+
Requires-Dist: openai>=1.50.0; extra == 'openai'
|
|
45
|
+
Provides-Extra: openai-agents
|
|
46
|
+
Requires-Dist: openai-agents<1.0,>=0.15.0; extra == 'openai-agents'
|
|
47
|
+
Description-Content-Type: text/markdown
|
|
48
|
+
|
|
49
|
+
# Morse Python SDK
|
|
50
|
+
|
|
51
|
+
Unified observability for AI agents and infrastructure. One SDK.
|
|
52
|
+
|
|
53
|
+
## Install
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install morse-ai
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
With an adapter (installs the underlying provider SDK too):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install "morse-ai[anthropic]"
|
|
63
|
+
pip install "morse-ai[openai]"
|
|
64
|
+
pip install "morse-ai[openai_agents]"
|
|
65
|
+
pip install "morse-ai[langgraph]" # also pulls langchain-core
|
|
66
|
+
pip install "morse-ai[all]" # every adapter
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The import name is `morse_ai` (underscore) regardless of which extra
|
|
70
|
+
you install — `import morse_ai`.
|
|
71
|
+
|
|
72
|
+
## Quick Start
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
import morse_ai
|
|
76
|
+
|
|
77
|
+
morse_ai.init(api_key="mhq_xxxxx") # pragma: allowlist secret
|
|
78
|
+
|
|
79
|
+
morse_ai.record(
|
|
80
|
+
agent="lead-qualifier",
|
|
81
|
+
success=True,
|
|
82
|
+
outcome="qualified",
|
|
83
|
+
cost=0.043,
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Or trace a multi-step run:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
with morse_ai.run("lead-qualifier") as r:
|
|
91
|
+
with morse_ai.span("fetch-lead", "tool"):
|
|
92
|
+
...
|
|
93
|
+
r.set_outcome(True, "qualified")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`record()`/`run()`/`span()`/`@trace_agent` all send real OpenTelemetry
|
|
97
|
+
spans over OTLP by default — see [Transport](#transport) below.
|
|
98
|
+
|
|
99
|
+
## Zero-config instrumentation
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install "morse-ai[anthropic]"
|
|
103
|
+
export MORSE_API_KEY=mhq_xxxxx
|
|
104
|
+
# your existing anthropic / openai-agents / langgraph / claude-agent-sdk
|
|
105
|
+
# code is now traced — no morse_ai.wrap() calls anywhere
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
When the SDK initializes, it scans `sys.modules` for installed
|
|
109
|
+
provider SDKs — and registers an import hook for ones imported later —
|
|
110
|
+
and calls each detected adapter's `install()`. Today that covers
|
|
111
|
+
`anthropic`, `openai-agents` (imports as `agents`), `langgraph`, and
|
|
112
|
+
`claude-agent-sdk`. Priority rule: when both LangGraph and generic
|
|
113
|
+
LangChain are importable, only the LangGraph adapter installs, to
|
|
114
|
+
avoid duplicate spans. Opt out entirely with
|
|
115
|
+
`MORSE_DISABLE_AUTO_DETECT=1`; you can still call any adapter's
|
|
116
|
+
`install()`/`wrap()` directly.
|
|
117
|
+
|
|
118
|
+
The plain LangChain adapter (`MorseCallbackHandler`, for chains
|
|
119
|
+
outside a LangGraph graph) is **not** auto-installed — pass it via
|
|
120
|
+
`callbacks=[...]` explicitly (see below).
|
|
121
|
+
|
|
122
|
+
## Adapters
|
|
123
|
+
|
|
124
|
+
### Anthropic
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from morse_ai.adapters import anthropic as av_anthropic
|
|
128
|
+
import anthropic
|
|
129
|
+
|
|
130
|
+
client = av_anthropic.wrap(anthropic.Anthropic())
|
|
131
|
+
# every messages.create call now emits an `llm` span automatically
|
|
132
|
+
response = client.messages.create(
|
|
133
|
+
model="claude-sonnet-4-6",
|
|
134
|
+
max_tokens=1024,
|
|
135
|
+
messages=[{"role": "user", "content": "Qualify this lead..."}],
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`wrap()` is idempotent and works whether or not auto-detect already
|
|
140
|
+
installed the adapter globally. Pass `summarize=True` (or use the
|
|
141
|
+
`summarize()` context manager per-call) to also emit a sibling
|
|
142
|
+
`memory` span.
|
|
143
|
+
|
|
144
|
+
### OpenAI
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from morse_ai.adapters import openai as av_openai
|
|
148
|
+
import openai
|
|
149
|
+
|
|
150
|
+
client = av_openai.wrap(openai.OpenAI())
|
|
151
|
+
response = client.chat.completions.create(
|
|
152
|
+
model="gpt-4.1",
|
|
153
|
+
messages=[{"role": "user", "content": "Qualify this lead..."}],
|
|
154
|
+
)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Context segments (messages + tools) are captured automatically on
|
|
158
|
+
every call — no separate extraction step.
|
|
159
|
+
|
|
160
|
+
### OpenAI Agents SDK, LangGraph, Claude Agent SDK
|
|
161
|
+
|
|
162
|
+
These three are auto-installed globally by zero-config detection (see
|
|
163
|
+
above) — no wrap call needed once the package is importable and
|
|
164
|
+
`MORSE_API_KEY` is set. To install manually (e.g. to control ordering,
|
|
165
|
+
or with auto-detect disabled):
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
from morse_ai.adapters import openai_agents, langgraph, claude_agent_sdk
|
|
169
|
+
|
|
170
|
+
openai_agents.install() # patches agents.Runner.run / run_sync / run_streamed
|
|
171
|
+
langgraph.install() # registers a callback handler globally with LangChain
|
|
172
|
+
claude_agent_sdk.install() # patches ClaudeSDKClient.receive_response
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Each emits an outer **agent** span, per-call **llm** + **tool** spans,
|
|
176
|
+
and adapter-specific spans (LangGraph: nested chain spans; OpenAI
|
|
177
|
+
Agents: **subagent.spawn**-shaped `handoff` spans; Claude Agent SDK:
|
|
178
|
+
**subagent.spawn** spans for Task-tool fan-out and **hook** spans for
|
|
179
|
+
`pre_tool`/`post_tool`/`on_error`).
|
|
180
|
+
|
|
181
|
+
### Plain LangChain (chains, LLMs, tools, retrievers)
|
|
182
|
+
|
|
183
|
+
Not auto-installed — attach `MorseCallbackHandler` via `callbacks`:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from morse_ai.adapters.langchain import MorseCallbackHandler
|
|
187
|
+
|
|
188
|
+
handler = MorseCallbackHandler("my-agent")
|
|
189
|
+
chain = SomeChain(..., callbacks=[handler])
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Called inside an existing `morse_ai.run()` context, the handler
|
|
193
|
+
appends child spans to that trace. Called standalone (no enclosing
|
|
194
|
+
`run()`), it auto-creates a trace that flushes when the outermost
|
|
195
|
+
chain completes — no `morse_ai.run()` wrapper required.
|
|
196
|
+
|
|
197
|
+
## Capturing logs alongside agent traces
|
|
198
|
+
|
|
199
|
+
If you already use stdlib `logging` or `structlog`, you can ship every
|
|
200
|
+
log line to Morse with one line of config. Each record is
|
|
201
|
+
auto-correlated with the active trace (the one entered via
|
|
202
|
+
`morse_ai.run()`, `@morse_ai.trace_agent`, or any active
|
|
203
|
+
`morse_ai.span(...)`) and appears under the trace's **Logs** tab on
|
|
204
|
+
the dashboard.
|
|
205
|
+
|
|
206
|
+
The shim re-uses the SDK's batched transport — no blocking I/O on your
|
|
207
|
+
hot path.
|
|
208
|
+
|
|
209
|
+
### Stdlib `logging`
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
import logging
|
|
213
|
+
import morse_ai
|
|
214
|
+
from morse_ai.logging import MorseHandler
|
|
215
|
+
|
|
216
|
+
morse_ai.init(api_key="mhq_xxxxx") # pragma: allowlist secret
|
|
217
|
+
logging.basicConfig(level=logging.INFO, handlers=[MorseHandler()])
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### `structlog`
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
import structlog
|
|
224
|
+
import morse_ai
|
|
225
|
+
from morse_ai.logging import structlog_processor
|
|
226
|
+
|
|
227
|
+
morse_ai.init(api_key="mhq_xxxxx") # pragma: allowlist secret
|
|
228
|
+
structlog.configure(processors=[structlog_processor, structlog.processors.JSONRenderer()])
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Log lines emitted outside any trace context still flow to Morse — they
|
|
232
|
+
just won't be correlated to a specific trace. Errors inside the
|
|
233
|
+
handler / processor are absorbed and never propagate into your
|
|
234
|
+
`logger.info(...)` call.
|
|
235
|
+
|
|
236
|
+
## Cost
|
|
237
|
+
|
|
238
|
+
`cost_usd` is computed automatically from the model + token counts on
|
|
239
|
+
every `llm` span the adapters above emit, using a bundled pricing
|
|
240
|
+
table — no separate call needed. `record()` doesn't auto-compute —
|
|
241
|
+
pass `cost` yourself if you have it.
|
|
242
|
+
|
|
243
|
+
## Transport
|
|
244
|
+
|
|
245
|
+
`run()`/`span()`/`trace_agent`/`record()` emit real OpenTelemetry spans
|
|
246
|
+
— `TracerProvider` / `BatchSpanProcessor` / `TraceIdRatioBased` sampler
|
|
247
|
+
— over OTLP (protobuf+gzip). This is a **core dependency**
|
|
248
|
+
(`opentelemetry-sdk`, `opentelemetry-exporter-otlp-proto-http`), not an
|
|
249
|
+
optional extra — every install speaks OTLP. `track()`/`batch_track()`
|
|
250
|
+
are a lower-level, non-trace-shaped escape hatch of their own — prefer
|
|
251
|
+
`record()`.
|
|
252
|
+
|
|
253
|
+
## PII Redaction
|
|
254
|
+
|
|
255
|
+
The SDK ships with PII redaction **on by default**. Span attributes and
|
|
256
|
+
event payloads are scrubbed inside your process before any data leaves
|
|
257
|
+
for the Morse ingest endpoint. You can opt out per-pattern, opt out
|
|
258
|
+
entirely, or extend the catalogue with your own regex.
|
|
259
|
+
|
|
260
|
+
### What gets redacted by default
|
|
261
|
+
|
|
262
|
+
| Pattern key | Matches | Replacement |
|
|
263
|
+
| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
|
|
264
|
+
| `api_key_prefixed` | `sk-…`, `sk_live_…`, `sk_test_…`, `mhq_…`, GitHub PATs (`ghp_…`/`gho_…`/…), Slack tokens (`xoxb-…`), AWS access keys (`AKIA…`/`ASIA…`/…) | `[REDACTED:api_key]` |
|
|
265
|
+
| `jwt` | Three-segment base64url tokens (`eyJ…`) | `[REDACTED:jwt]` |
|
|
266
|
+
| `credit_card` | 13-19 digit groups passing the Luhn checksum | `[REDACTED:credit_card]` |
|
|
267
|
+
| `email` | RFC-5322-ish address regex | `[REDACTED:email]` |
|
|
268
|
+
| `phone_us` | US-format phone numbers | `[REDACTED:phone]` |
|
|
269
|
+
| `phone_e164` | International E.164 phone numbers | `[REDACTED:phone]` |
|
|
270
|
+
| `ssn_us` | US Social Security numbers (`999-99-9999`) | `[REDACTED:ssn]` |
|
|
271
|
+
| `ip_address` | IPv4 / IPv6 — **disabled by default**, opt-in | `[REDACTED:ip]` |
|
|
272
|
+
|
|
273
|
+
### Configuring redaction
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
import morse_ai
|
|
277
|
+
|
|
278
|
+
morse_ai.init(
|
|
279
|
+
api_key="mhq_xxxxx", # pragma: allowlist secret
|
|
280
|
+
# All four kwargs below are optional; defaults are shown.
|
|
281
|
+
redaction_enabled=True,
|
|
282
|
+
redaction_disabled_patterns=None, # e.g. ["email", "phone_us"]
|
|
283
|
+
redaction_extra_patterns=None, # e.g. [r"INTERNAL-\w+"]
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- `redaction_enabled=False` turns OFF every built-in pattern at the SDK
|
|
288
|
+
layer. **The server-side mandatory floor still applies** —
|
|
289
|
+
`api_key_prefixed`, `jwt`, `credit_card`, and `ssn_us` are rewritten
|
|
290
|
+
on ingest regardless of SDK configuration. Opting out shifts _where_
|
|
291
|
+
redaction happens, it does not remove it.
|
|
292
|
+
- `redaction_disabled_patterns` lets you skip individual non-mandatory
|
|
293
|
+
keys (`email`, `phone_us`, `phone_e164`, `ip_address`). Trying to
|
|
294
|
+
disable a mandatory pattern raises `ValueError` at `init()`.
|
|
295
|
+
- `redaction_extra_patterns` accepts up to 10 customer regex strings
|
|
296
|
+
(each ≤ 256 chars). Each must compile via `re.compile()`; matches are
|
|
297
|
+
replaced with `[REDACTED:custom]`. Built-in patterns run before extra
|
|
298
|
+
patterns, so a custom regex sees the already-redacted text.
|
|
299
|
+
|
|
300
|
+
### What is NOT touched
|
|
301
|
+
|
|
302
|
+
Non-string leaves (numbers, booleans, `None`, opaque objects) pass
|
|
303
|
+
through untouched. Span IDs, timestamps, durations, token counts, and
|
|
304
|
+
other metadata are never redacted.
|
|
305
|
+
|
|
306
|
+
## Docs
|
|
307
|
+
|
|
308
|
+
Full reference — every adapter, all `init()` options, configuration,
|
|
309
|
+
troubleshooting — lives at **[docs.morsehq.dev](https://docs.morsehq.dev)**.
|
|
310
|
+
This README stays a quickstart.
|
|
311
|
+
|
|
312
|
+
## Naming
|
|
313
|
+
|
|
314
|
+
The PyPI project is `morse-ai`; the import name is `morse_ai`
|
|
315
|
+
(Python's underscore convention). The npm package for the TypeScript
|
|
316
|
+
SDK is `@morsehq-dev/sdk` — see that package's README for why it
|
|
317
|
+
doesn't match this one's naming.
|