Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

Morse Python SDK

Unified observability for AI agents and infrastructure. One SDK.

Install

pip install morse-ai

With an adapter (installs the underlying provider SDK too):

pip install "morse-ai[anthropic]"
pip install "morse-ai[openai]"
pip install "morse-ai[openai_agents]"
pip install "morse-ai[langgraph]"     # also pulls langchain-core
pip install "morse-ai[all]"           # every adapter

The import name is morse_ai (underscore) regardless of which extra you install — import morse_ai.

Quick Start

import morse_ai

morse_ai.init(api_key="mhq_xxxxx")  # pragma: allowlist secret

morse_ai.record(
    agent="lead-qualifier",
    success=True,
    outcome="qualified",
    cost=0.043,
)

Or trace a multi-step run:

with morse_ai.run("lead-qualifier") as r:
    with morse_ai.span("fetch-lead", "tool"):
        ...
    r.set_outcome(True, "qualified")

record()/run()/span()/@trace_agent all send real OpenTelemetry spans over OTLP by default — see Transport below.

Zero-config instrumentation

pip install "morse-ai[anthropic]"
export MORSE_API_KEY=mhq_xxxxx
# your existing anthropic / openai-agents / langgraph / claude-agent-sdk
# code is now traced — no morse_ai.wrap() calls anywhere

When the SDK initializes, it scans sys.modules for installed provider SDKs — and registers an import hook for ones imported later — and calls each detected adapter's install(). Today that covers anthropic, openai-agents (imports as agents), langgraph, and claude-agent-sdk. Priority rule: when both LangGraph and generic LangChain are importable, only the LangGraph adapter installs, to avoid duplicate spans. Opt out entirely with MORSE_DISABLE_AUTO_DETECT=1; you can still call any adapter's install()/wrap() directly.

The plain LangChain adapter (MorseCallbackHandler, for chains outside a LangGraph graph) is not auto-installed — pass it via callbacks=[...] explicitly (see below).

Adapters

Anthropic

from morse_ai.adapters import anthropic as av_anthropic
import anthropic

client = av_anthropic.wrap(anthropic.Anthropic())
# every messages.create call now emits an `llm` span automatically
response = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=1024,
    messages=[{"role": "user", "content": "Qualify this lead..."}],
)

wrap() is idempotent and works whether or not auto-detect already installed the adapter globally. Pass summarize=True (or use the summarize() context manager per-call) to also emit a sibling memory span.

OpenAI

from morse_ai.adapters import openai as av_openai
import openai

client = av_openai.wrap(openai.OpenAI())
response = client.chat.completions.create(
    model="gpt-4.1",
    messages=[{"role": "user", "content": "Qualify this lead..."}],
)

Context segments (messages + tools) are captured automatically on every call — no separate extraction step.

OpenAI Agents SDK, LangGraph, Claude Agent SDK

These three are auto-installed globally by zero-config detection (see above) — no wrap call needed once the package is importable and MORSE_API_KEY is set. To install manually (e.g. to control ordering, or with auto-detect disabled):

from morse_ai.adapters import openai_agents, langgraph, claude_agent_sdk

openai_agents.install()      # patches agents.Runner.run / run_sync / run_streamed
langgraph.install()          # registers a callback handler globally with LangChain
claude_agent_sdk.install()   # patches ClaudeSDKClient.receive_response

Each emits an outer agent span, per-call llm + tool spans, and adapter-specific spans (LangGraph: nested chain spans; OpenAI Agents: subagent.spawn-shaped handoff spans; Claude Agent SDK: subagent.spawn spans for Task-tool fan-out and hook spans for pre_tool/post_tool/on_error).

Plain LangChain (chains, LLMs, tools, retrievers)

Not auto-installed — attach MorseCallbackHandler via callbacks:

from morse_ai.adapters.langchain import MorseCallbackHandler

handler = MorseCallbackHandler("my-agent")
chain = SomeChain(..., callbacks=[handler])

Called inside an existing morse_ai.run() context, the handler appends child spans to that trace. Called standalone (no enclosing run()), it auto-creates a trace that flushes when the outermost chain completes — no morse_ai.run() wrapper required.

Capturing logs alongside agent traces

If you already use stdlib logging or structlog, you can ship every log line to Morse with one line of config. Each record is auto-correlated with the active trace (the one entered via morse_ai.run(), @morse_ai.trace_agent, or any active morse_ai.span(...)) and appears under the trace's Logs tab on the dashboard.

The shim re-uses the SDK's batched transport — no blocking I/O on your hot path.

Stdlib logging

import logging
import morse_ai
from morse_ai.logging import MorseHandler

morse_ai.init(api_key="mhq_xxxxx")  # pragma: allowlist secret
logging.basicConfig(level=logging.INFO, handlers=[MorseHandler()])

structlog

import structlog
import morse_ai
from morse_ai.logging import structlog_processor

morse_ai.init(api_key="mhq_xxxxx")  # pragma: allowlist secret
structlog.configure(processors=[structlog_processor, structlog.processors.JSONRenderer()])

Log lines emitted outside any trace context still flow to Morse — they just won't be correlated to a specific trace. Errors inside the handler / processor are absorbed and never propagate into your logger.info(...) call.

Cost

cost_usd is computed automatically from the model + token counts on every llm span the adapters above emit, using a bundled pricing table — no separate call needed. record() doesn't auto-compute — pass cost yourself if you have it.

Transport

run()/span()/trace_agent/record() emit real OpenTelemetry spans — TracerProvider / BatchSpanProcessor / TraceIdRatioBased sampler — over OTLP (protobuf+gzip). This is a core dependency (opentelemetry-sdk, opentelemetry-exporter-otlp-proto-http), not an optional extra — every install speaks OTLP. track()/batch_track() are a lower-level, non-trace-shaped escape hatch of their own — prefer record().

PII Redaction

The SDK ships with PII redaction on by default. Span attributes and event payloads are scrubbed inside your process before any data leaves for the Morse ingest endpoint. You can opt out per-pattern, opt out entirely, or extend the catalogue with your own regex.

What gets redacted by default

Pattern key Matches Replacement
api_key_prefixed sk-…, sk_live_…, sk_test_…, mhq_…, GitHub PATs (ghp_…/gho_…/…), Slack tokens (xoxb-…), AWS access keys (AKIA…/ASIA…/…) [REDACTED:api_key]
jwt Three-segment base64url tokens (eyJ…) [REDACTED:jwt]
credit_card 13-19 digit groups passing the Luhn checksum [REDACTED:credit_card]
email RFC-5322-ish address regex [REDACTED:email]
phone_us US-format phone numbers [REDACTED:phone]
phone_e164 International E.164 phone numbers [REDACTED:phone]
ssn_us US Social Security numbers (999-99-9999) [REDACTED:ssn]
ip_address IPv4 / IPv6 — disabled by default, opt-in [REDACTED:ip]

Configuring redaction

import morse_ai

morse_ai.init(
    api_key="mhq_xxxxx",  # pragma: allowlist secret
    # All four kwargs below are optional; defaults are shown.
    redaction_enabled=True,
    redaction_disabled_patterns=None,    # e.g. ["email", "phone_us"]
    redaction_extra_patterns=None,       # e.g. [r"INTERNAL-\w+"]
)
  • redaction_enabled=False turns OFF every built-in pattern at the SDK layer. The server-side mandatory floor still appliesapi_key_prefixed, jwt, credit_card, and ssn_us are rewritten on ingest regardless of SDK configuration. Opting out shifts where redaction happens, it does not remove it.
  • redaction_disabled_patterns lets you skip individual non-mandatory keys (email, phone_us, phone_e164, ip_address). Trying to disable a mandatory pattern raises ValueError at init().
  • redaction_extra_patterns accepts up to 10 customer regex strings (each ≤ 256 chars). Each must compile via re.compile(); matches are replaced with [REDACTED:custom]. Built-in patterns run before extra patterns, so a custom regex sees the already-redacted text.

What is NOT touched

Non-string leaves (numbers, booleans, None, opaque objects) pass through untouched. Span IDs, timestamps, durations, token counts, and other metadata are never redacted.

Docs

Full reference — every adapter, all init() options, configuration, troubleshooting — lives at docs.morsehq.dev. This README stays a quickstart.

Naming

The PyPI project is morse-ai; the import name is morse_ai (Python's underscore convention). The npm package for the TypeScript SDK is @morsehq-dev/sdk — see that package's README for why it doesn't match this one's naming.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

morse_ai-0.5.0rc1.tar.gz (451.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

morse_ai-0.5.0rc1-py3-none-any.whl (162.2 kB view details)

Uploaded Python 3

File details

Details for the file morse_ai-0.5.0rc1.tar.gz.

File metadata

  • Download URL: morse_ai-0.5.0rc1.tar.gz
  • Upload date:
  • Size: 451.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for morse_ai-0.5.0rc1.tar.gz
Algorithm Hash digest
SHA256 f80b05dbe247ba826c2323c2f0a7927c59f0bb83de5b4c63814ffbac7cff8367
MD5 4fb8d32f367b748c2e2dc8b0ee003755
BLAKE2b-256 801b88c011d6b108b926d0205eb9d4ace2383b88a9dd5d49a845e0554a4e7547

See more details on using hashes here.

Provenance

The following attestation bundles were made for morse_ai-0.5.0rc1.tar.gz:

Publisher: release-python.yml on MorseAI-HQ/morse-ai

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file morse_ai-0.5.0rc1-py3-none-any.whl.

File metadata

  • Download URL: morse_ai-0.5.0rc1-py3-none-any.whl
  • Upload date:
  • Size: 162.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for morse_ai-0.5.0rc1-py3-none-any.whl
Algorithm Hash digest
SHA256 ce7de34a7d3cc42bd4b79e7ea253d014c01e34519ee2bf7622d84060297b1b7e
MD5 79b0a1260f8f4cbcda44aa9024deb999
BLAKE2b-256 a7652483d994f7a30177b4f9c987cb2eae77af0c18c8acd526e0cbcc5eadd584

See more details on using hashes here.

Provenance

The following attestation bundles were made for morse_ai-0.5.0rc1-py3-none-any.whl:

Publisher: release-python.yml on MorseAI-HQ/morse-ai

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page