Skip to main content

Blend Agents Observability

PyPI version License Build Status

Enterprise-grade observability library for instrumenting multi-step AI agent systems. Capture, process, and visualize complex agent execution graphs with ease.

Note: This package is owned by Blend360 and is intended for internal usage only.

Features

  • Manual Instrumentation: Explicit control over trace creation and node lifecycle for precise observability.
  • AWS Kinesis Integration: Stream observability events directly to AWS Kinesis Data Streams.
  • Type-Safe Events: Leverages Pydantic for robust event validation, ensuring data integrity.
  • Resilient by Design: Gracefully handles errors and fails silently, preventing observability from impacting your application'''s stability.
  • Detailed Agent Tracking: Capture fine-grained details of agent execution, including reasoning steps and tool usage.
  • Parallel Workflow Support: Built-in support for tracing parallel execution branches and sub-traces.

Installation

pip install blend-agents-observability

Quick Start

Here is an example of how to use the logger with Strands Agents:

from observability_logger import AgentLogger, generate_id
from strands import Agent, tool

# 1. Create a trace
trace_id = generate_id("trace_")
logger = AgentLogger(
    trace_id=trace_id,
    title="My Workflow",
    workflow_id="workflow-v1"
)

# 2. Create an agent node
agent_node = logger.strands.agent(
    node_id=generate_id("node_"),
    config={"name": "Assistant", "description": "Helpful agent"}
)

# 3. Execute agent with callback handler
# The agent_node acts as a callback handler to capture streaming events
agent_node.set_input(prompt)
result = agent(prompt, callback_handler=agent_node)
agent_node.complete(result=result)

# 4. End the trace
logger.end(status="completed", final_output={"result": result.message})

Concepts

Traces

A trace represents a single workflow execution. It contains nodes (operations) and edges (connections between nodes).

logger = AgentLogger(
    trace_id=generate_id("trace_"),  # Unique identifier
    title="My Workflow",              # Human-readable title
    workflow_id="workflow-v1",        # Workflow version identifier
    parent_trace_id=None              # Optional parent for hierarchical traces
)

Nodes

Nodes represent individual operations in the workflow. There are four node types:

Agent Node

For AI agent execution with real-time step capture:

agent_node = logger.strands.agent(
    node_id=generate_id("node_"),
    config={"name": "Assistant", "description": "Helpful agent"},
    metadata={"tools": ["search", "calculate"]}
)

# Set input before execution
agent_node.set_input(prompt)

# Execute with callback handler for real-time capture
result = agent(prompt, callback_handler=agent_node)

# Complete with result (extracts output and token usage automatically)
agent_node.complete(result=result)

Router Node

For routing/branching decisions (entry points):

router = logger.router(
    node_id=generate_id("node_"),
    config={"name": "Workflow Router", "description": "Entry point"},
    metadata={"input_length": len(text)}
)

# ... routing logic ...

router.complete(status="completed")

Parallel Node

For concurrent execution branches:

parallel = logger.parallel(
    node_id=generate_id("node_"),
    config={"name": "Process Items", "description": "Parallel processing"},
    metadata={"parallel_count": 3}
)

# Link to child trace
child_logger = AgentLogger(
    trace_id=generate_id("trace_"),
    parent_trace_id=logger.trace_id
)
parallel.subTrace(child_logger)

# ... child execution ...

parallel.complete(status="completed")

Miscellaneous Node

For general operations (auto-completed on creation):

output = logger.miscellaneous(
    node_id=generate_id("node_"),
    config={"name": "Final Output", "description": "Format results"},
    content="Processed 100 items successfully",
    metadata={"item_count": 100}
)
# Node is already completed

Edges

Edges define the execution flow between nodes:

router = logger.router(node_id1, config1)
agent = logger.strands.agent(node_id2, config2)
output = logger.miscellaneous(node_id3, config3, content)

# Create edges
logger.edge(router, agent)
logger.edge(agent, output)

API Reference

AgentLogger

Main entry point for observability logging.

AgentLogger(
    trace_id: str,                    # Unique trace identifier
    workflow_id: Optional[str],       # Workflow identifier (defaults to trace_id)
    title: Optional[str],             # Human-readable title (defaults to trace_id)
    parent_trace_id: Optional[str],   # Parent trace for hierarchical traces
    auto_create: bool = True          # Auto-emit trace_updated on init
)

Methods:

Method Description
miscellaneous(node_id, config, content, metadata) Create miscellaneous node (auto-completed)
parallel(node_id, config, content, metadata) Create parallel node
router(node_id, config, content, metadata) Create router node
edge(source_node, target_node) Create edge between nodes
end(status, final_output) End the trace

Node Types

Common Methods

All nodes share these methods:

Method Description
create() Create node (called automatically)
complete(status, payload, metadata) Complete the node
status Current node status property
is_created Whether node was created
is_completed Whether node was completed

AgentNode Methods

Method Description
set_input(text) Set agent input prompt
set_output(text) Set agent output manually
set_error(error) Set error information
set_token_usage(input, output, total) Set token metrics
complete(result=result) Complete with agent result
__call__(**kwargs) Callback handler for streaming

ParallelNode Methods

Method Description
subTrace(child_logger) Link child trace to parallel node
complete(status, metadata, execution_time_ms) Complete with timing

Utilities

from observability_logger import generate_id, get_current_timestamp_ms

# Generate unique IDs with prefix
trace_id = generate_id("trace_")  # trace_a1b2c3d4-...
node_id = generate_id("node_")    # node_e5f6g7h8-...

# Get current timestamp in milliseconds
timestamp = get_current_timestamp_ms()  # 1700000000000

Usage Guides

Callback Mode (Real-time Capture)

Capture agent execution steps in real-time:

from observability_logger import AgentLogger, generate_id
from strands import Agent, tool

# Define tools
@tool
def search(query: str) -> str:
    """Search for information."""
    return f"Results for: {query}"

# Create agent
agent = Agent(tools=[search], name="SearchAgent")

# Initialize trace and node
logger = AgentLogger(trace_id=generate_id("trace_"), title="Search Workflow")
agent_node = logger.strands.agent(
    node_id=generate_id("node_"),
    config={"name": "Search Agent"}
)

# Execute with callback
agent_node.set_input(prompt)
result = agent(prompt, callback_handler=agent_node)
agent_node.complete(result=result)

logger.end(status="completed")

Multi-node Workflows

Create workflows with multiple nodes and edges:

logger = AgentLogger(trace_id=generate_id("trace_"), title="Analysis Workflow")

# Router node (entry point)
router = logger.router(
    node_id=generate_id("node_"),
    config={"name": "Router"}
)
router.complete(status="completed")

# Agent node
agent_node = logger.strands.agent(
    node_id=generate_id("node_"),
    config={"name": "Analyzer"}
)
agent_node.set_input(prompt)
result = agent(prompt, callback_handler=agent_node)
agent_node.complete(result=result)

# Output node (auto-completed)
output = logger.miscellaneous(
    node_id=generate_id("node_"),
    config={"name": "Output"},
    content=result.message
)

# Create edges
logger.edge(router, agent_node)
logger.edge(agent_node, output)

logger.end(status="completed")

Hierarchical Traces (Parallel Execution)

Create parent-child trace relationships:

# Parent trace
parent_logger = AgentLogger(
    trace_id=generate_id("trace_"),
    title="Orchestrator"
)

# Parallel node in parent
parallel = parent_logger.parallel(
    node_id=generate_id("node_"),
    config={"name": "Parallel Processing"}
)

# Child trace
child_logger = AgentLogger(
    trace_id=generate_id("trace_"),
    title="Child Workflow",
    parent_trace_id=parent_logger.trace_id
)

# Link child to parallel node
parallel.subTrace(child_logger)

# Execute child workflow
child_agent = child_logger.strands.agent(
    node_id=generate_id("node_"),
    config={"name": "Child Agent"}
)
# ... child execution ...
child_agent.complete(result=result)
child_logger.end(status="completed")

# Complete parallel node
parallel.complete(status="completed")

parent_logger.end(status="completed")

Configuration

Environment Variables

Variable Required Default Description
KINESIS_OBSERVABILTY_LOGGER_STREAM_ARN Yes - Kinesis stream ARN
AWS_REGION No us-east-1 AWS region
OBSERVABILITY_LOG_LEVEL No WARNING Log level
OBSERVABILITY_ENABLE_VALIDATION No true Enable validation

AWS Credentials

The library uses the standard boto3 credential chain:

  1. Environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
  2. Shared credentials file (~/.aws/credentials)
  3. IAM role (for EC2/Lambda)

Error Handling

try:
    logger = AgentLogger(trace_id=generate_id("trace_"), title="My Workflow")

    agent_node = logger.strands.agent(
        node_id=generate_id("node_"),
        config={"name": "Agent"}
    )
    agent_node.set_input(prompt)
    result = agent(prompt, callback_handler=agent_node)
    agent_node.complete(result=result)

    logger.end(status="completed", final_output={"result": result.message})

except Exception as e:
    # Complete with error
    if agent_node and not agent_node.is_completed:
        agent_node.set_error(e)
        agent_node.complete(status="failed")

    logger.end(status="failed", final_output={"error": str(e)})

Node Lifecycle

Node Type Auto-Complete Lifecycle
agent No create() -> set_input() -> execute -> complete()
router No create() -> complete()
parallel No create() -> subTrace() -> complete()
miscellaneous Yes create() (auto-completes)

Data Flow

Agent Code -> AgentLogger -> Kinesis -> Lambda -> DynamoDB + S3 -> Dashboard
  • DynamoDB: Stores trace/node/edge metadata
  • S3: Stores node payloads (input, output, steps)

Metadata

Release files for blend-agents-observability 1.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for blend-agents-observability 1.1.0
File Size Uploaded Provenance
blend_agents_observability-1.1.0.tar.gz 107.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for blend-agents-observability 1.1.0
File Interpreter ABI Platform
blend_agents_observability-1.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 210.6 kB

Release files / blend_agents_observability-1.1.0.tar.gz

Download URL blend_agents_observability-1.1.0.tar.gz
Size 107.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d84494537dc7992163f492955b7977bea561d220e41fc7fff2ddd512567a3510
BLAKE2b-256 checksum
How to use checksums
451f49451d2735030c6cb3ec56e80a09ccc37d492b57acf79cfd09c011058ab0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.10 {"installer":{"name":"uv","version":"0.9.10"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"25.10","id":"questing","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / blend_agents_observability-1.1.0-py3-none-any.whl

Download URL blend_agents_observability-1.1.0-py3-none-any.whl
Size 102.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b1b5921e28f69f94f58a76dceb6034bffed55152497ec948c0fc652dca1858f6
BLAKE2b-256 checksum
How to use checksums
1a6d564881ac1f2c6bfc145d5116aaa8277ade775a424d676167bd27ed7e72de
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.10 {"installer":{"name":"uv","version":"0.9.10"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"25.10","id":"questing","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for blend-agents-observability 1.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page