Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/loongsuite_lint_0.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jobs:
shell: bash
env:
LOONGSUITE_ALL_JOBS: >-
[{"name": "lint-loongsuite-instrumentation-agentscope", "package": "loongsuite-instrumentation-agentscope", "tox_env": "lint-loongsuite-instrumentation-agentscope", "ui_name": "loongsuite-instrumentation-agentscope"}, {"name": "lint-loongsuite-instrumentation-dashscope", "package": "loongsuite-instrumentation-dashscope", "tox_env": "lint-loongsuite-instrumentation-dashscope", "ui_name": "loongsuite-instrumentation-dashscope"}, {"name": "lint-loongsuite-instrumentation-claude-agent-sdk", "package": "loongsuite-instrumentation-claude-agent-sdk", "tox_env": "lint-loongsuite-instrumentation-claude-agent-sdk", "ui_name": "loongsuite-instrumentation-claude-agent-sdk"}, {"name": "lint-loongsuite-instrumentation-google-adk", "package": "loongsuite-instrumentation-google-adk", "tox_env": "lint-loongsuite-instrumentation-google-adk", "ui_name": "loongsuite-instrumentation-google-adk"}, {"name": "lint-loongsuite-instrumentation-langchain", "package": "loongsuite-instrumentation-langchain", "tox_env": "lint-loongsuite-instrumentation-langchain", "ui_name": "loongsuite-instrumentation-langchain"}, {"name": "lint-loongsuite-instrumentation-langgraph", "package": "loongsuite-instrumentation-langgraph", "tox_env": "lint-loongsuite-instrumentation-langgraph", "ui_name": "loongsuite-instrumentation-langgraph"}, {"name": "lint-loongsuite-instrumentation-qwen-agent", "package": "loongsuite-instrumentation-qwen-agent", "tox_env": "lint-loongsuite-instrumentation-qwen-agent", "ui_name": "loongsuite-instrumentation-qwen-agent"}, {"name": "lint-loongsuite-instrumentation-mem0", "package": "loongsuite-instrumentation-mem0", "tox_env": "lint-loongsuite-instrumentation-mem0", "ui_name": "loongsuite-instrumentation-mem0"}, {"name": "lint-util-genai", "package": "util-genai", "tox_env": "lint-util-genai", "ui_name": "util-genai"}, {"name": "lint-loongsuite-instrumentation-litellm", "package": "loongsuite-instrumentation-litellm", "tox_env": "lint-loongsuite-instrumentation-litellm", "ui_name": "loongsuite-instrumentation-litellm"}, {"name": "lint-loongsuite-instrumentation-crewai", "package": "loongsuite-instrumentation-crewai", "tox_env": "lint-loongsuite-instrumentation-crewai", "ui_name": "loongsuite-instrumentation-crewai"}, {"name": "lint-loongsuite-instrumentation-qwenpaw", "package": "loongsuite-instrumentation-qwenpaw", "tox_env": "lint-loongsuite-instrumentation-qwenpaw", "ui_name": "loongsuite-instrumentation-qwenpaw"}]
[{"name": "lint-loongsuite-instrumentation-agentscope", "package": "loongsuite-instrumentation-agentscope", "tox_env": "lint-loongsuite-instrumentation-agentscope", "ui_name": "loongsuite-instrumentation-agentscope"}, {"name": "lint-loongsuite-instrumentation-dashscope", "package": "loongsuite-instrumentation-dashscope", "tox_env": "lint-loongsuite-instrumentation-dashscope", "ui_name": "loongsuite-instrumentation-dashscope"}, {"name": "lint-loongsuite-instrumentation-claude-agent-sdk", "package": "loongsuite-instrumentation-claude-agent-sdk", "tox_env": "lint-loongsuite-instrumentation-claude-agent-sdk", "ui_name": "loongsuite-instrumentation-claude-agent-sdk"}, {"name": "lint-loongsuite-instrumentation-google-adk", "package": "loongsuite-instrumentation-google-adk", "tox_env": "lint-loongsuite-instrumentation-google-adk", "ui_name": "loongsuite-instrumentation-google-adk"}, {"name": "lint-loongsuite-instrumentation-langchain", "package": "loongsuite-instrumentation-langchain", "tox_env": "lint-loongsuite-instrumentation-langchain", "ui_name": "loongsuite-instrumentation-langchain"}, {"name": "lint-loongsuite-instrumentation-langgraph", "package": "loongsuite-instrumentation-langgraph", "tox_env": "lint-loongsuite-instrumentation-langgraph", "ui_name": "loongsuite-instrumentation-langgraph"}, {"name": "lint-loongsuite-instrumentation-qwen-agent", "package": "loongsuite-instrumentation-qwen-agent", "tox_env": "lint-loongsuite-instrumentation-qwen-agent", "ui_name": "loongsuite-instrumentation-qwen-agent"}, {"name": "lint-loongsuite-instrumentation-deepagents", "package": "loongsuite-instrumentation-deepagents", "tox_env": "lint-loongsuite-instrumentation-deepagents", "ui_name": "loongsuite-instrumentation-deepagents"}, {"name": "lint-loongsuite-instrumentation-mem0", "package": "loongsuite-instrumentation-mem0", "tox_env": "lint-loongsuite-instrumentation-mem0", "ui_name": "loongsuite-instrumentation-mem0"}, {"name": "lint-util-genai", "package": "util-genai", "tox_env": "lint-util-genai", "ui_name": "util-genai"}, {"name": "lint-loongsuite-instrumentation-litellm", "package": "loongsuite-instrumentation-litellm", "tox_env": "lint-loongsuite-instrumentation-litellm", "ui_name": "loongsuite-instrumentation-litellm"}, {"name": "lint-loongsuite-instrumentation-crewai", "package": "loongsuite-instrumentation-crewai", "tox_env": "lint-loongsuite-instrumentation-crewai", "ui_name": "loongsuite-instrumentation-crewai"}, {"name": "lint-loongsuite-instrumentation-qwenpaw", "package": "loongsuite-instrumentation-qwenpaw", "tox_env": "lint-loongsuite-instrumentation-qwenpaw", "ui_name": "loongsuite-instrumentation-qwenpaw"}]
LOONGSUITE_FULL: ${{ steps.detect.outputs.full }}
LOONGSUITE_PACKAGES: ${{ steps.detect.outputs.packages }}
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/loongsuite_test_0.yml

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion instrumentation-loongsuite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
| [loongsuite-instrumentation-claude-agent-sdk](./loongsuite-instrumentation-claude-agent-sdk) | claude-agent-sdk >= 0.1.0 | No | development
| [loongsuite-instrumentation-crewai](./loongsuite-instrumentation-crewai) | crewai >= 0.80.0 | No | development
| [loongsuite-instrumentation-dashscope](./loongsuite-instrumentation-dashscope) | dashscope >= 1.0.0 | No | development
| [loongsuite-instrumentation-deepagents](./loongsuite-instrumentation-deepagents) | deepagents >= 0.6.0, < 0.7.0 | Yes | development
| [loongsuite-instrumentation-dify](./loongsuite-instrumentation-dify) | dify | No | development
| [loongsuite-instrumentation-google-adk](./loongsuite-instrumentation-google-adk) | google-adk >= 0.1.0 | No | development
| [loongsuite-instrumentation-hermes-agent](./loongsuite-instrumentation-hermes-agent) | openai >= 1.0.0 | No | development
Expand All @@ -15,4 +16,4 @@
| [loongsuite-instrumentation-mcp](./loongsuite-instrumentation-mcp) | mcp >= 1.3.0, <= 1.25.0 | No | development
| [loongsuite-instrumentation-mem0](./loongsuite-instrumentation-mem0) | mem0ai >= 1.0.0, < 2.0.0 | No | development
| [loongsuite-instrumentation-qwen-agent](./loongsuite-instrumentation-qwen-agent) | qwen-agent >= 0.0.20 | No | development
| [loongsuite-instrumentation-qwenpaw](./loongsuite-instrumentation-qwenpaw) | qwenpaw >= 1.1.0; copaw >= 0.1.0, <= 1.0.2 (legacy) | No | development
| [loongsuite-instrumentation-qwenpaw](./loongsuite-instrumentation-qwenpaw) | qwenpaw >= 1.1.0; copaw >= 0.1.0, <= 1.0.2 (legacy) | No | development
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

## Version 0.6.0.dev

### Added

- Initial implementation of DeepAgents instrumentation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# LoongSuite deepagents Instrumentation

This package adds the deepagents-specific telemetry that is not already
covered by `loongsuite-instrumentation-langchain` and
`loongsuite-instrumentation-langgraph`.

It intentionally adds only three integration points:

- wraps `deepagents.graph.create_deep_agent` and the returned graph instance's
`invoke`, `ainvoke`, `stream`, and `astream` methods to create the outer
`ENTRY` span;
- injects a sidecar LangChain callback handler that enriches existing
LoongSuite LangChain spans with deepagents framework and SubAgent metadata;
- installs a `SpanProcessor` that emits the `genai_calls_*` and
`genai_llm_*` metrics from completed GenAI spans.

The `task` tool remains a `TOOL` span and is marked with
`gen_ai.tool.type=agent`. The SubAgent itself is represented by the nested
`AGENT` span emitted by the LangChain/LangGraph instrumentation.

## Local Install

Install the shared GenAI utility from the same source tree first, then install
the dependent LangChain, LangGraph, and deepagents instrumentations:
Comment on lines +21 to +24

```bash
pip install -e ./util/opentelemetry-util-genai
pip install -e ./instrumentation-loongsuite/loongsuite-instrumentation-langchain
pip install -e ./instrumentation-loongsuite/loongsuite-instrumentation-langgraph
pip install -e ./instrumentation-loongsuite/loongsuite-instrumentation-deepagents
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "loongsuite-instrumentation-deepagents"
dynamic = ["version"]
description = "LoongSuite deepagents instrumentation"
readme = "README.md"
license = "Apache-2.0"
requires-python = ">=3.10"
authors = [
{ name = "OpenTelemetry Authors", email = "cncf-opentelemetry-contributors@lists.cncf.io" },
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"loongsuite-instrumentation-langchain",
"loongsuite-instrumentation-langgraph",
"opentelemetry-api ~= 1.37",
"opentelemetry-instrumentation >= 0.58b0",
"opentelemetry-sdk ~= 1.37",
"opentelemetry-semantic-conventions >= 0.58b0",
"opentelemetry-util-genai",
"wrapt >= 1.0.0, < 2.0.0",
]

[project.optional-dependencies]
instruments = [
"deepagents >= 0.6.0, < 0.7.0",
]

[project.entry-points.opentelemetry_instrumentor]
deepagents = "opentelemetry.instrumentation.deepagents:DeepAgentsInstrumentor"

[project.urls]
Homepage = "https://github.com/alibaba/loongsuite-python-agent/tree/main/instrumentation-loongsuite/loongsuite-instrumentation-deepagents"
Repository = "https://github.com/alibaba/loongsuite-python-agent"

[tool.hatch.version]
path = "src/opentelemetry/instrumentation/deepagents/version.py"

[tool.hatch.build.targets.sdist]
include = [
"src",
"tests",
]

[tool.hatch.build.targets.wheel]
packages = ["src/opentelemetry"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""LoongSuite instrumentation for langchain-ai deepagents."""

from __future__ import annotations

import logging
from importlib import import_module
from typing import Any, Collection

from opentelemetry import metrics, trace
from opentelemetry.instrumentation.deepagents.internal._enricher import (
install_enricher_callback,
uninstall_enricher_callback,
)
from opentelemetry.instrumentation.deepagents.internal._entry_patch import (
instrument_entry_patch,
uninstrument_entry_patch,
)
from opentelemetry.instrumentation.deepagents.internal._metrics_processor import ( # noqa: E501
install_metrics_processor,
shutdown_metrics_processors,
)
from opentelemetry.instrumentation.deepagents.package import _instruments
from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
from opentelemetry.util.genai.extended_handler import ExtendedTelemetryHandler

__all__ = ["DeepAgentsInstrumentor"]

_logger = logging.getLogger(__name__)


def _instrument_dependency(
module_name: str,
class_name: str,
**kwargs: Any,
) -> None:
"""Instrument a required base package when it is installed."""
try:
module = import_module(module_name)
except ModuleNotFoundError as exc:
if exc.name == module_name or (
exc.name is not None and module_name.startswith(f"{exc.name}.")
):
_logger.warning(
"deepagents instrumentation requires %s; continuing with "
"ENTRY/metrics only.",
module_name,
)
return
raise

instrumentor_type = getattr(module, class_name, None)
if instrumentor_type is None:
_logger.warning(
"deepagents instrumentation could not find %s.%s",
module_name,
class_name,
)
return

instrumentor = instrumentor_type()
if instrumentor.is_instrumented_by_opentelemetry:
return
instrumentor.instrument(**kwargs)


class DeepAgentsInstrumentor(BaseInstrumentor):
"""Instrumentation for deepagents.

The plugin is intentionally additive: LangChain and LangGraph keep owning
AGENT/CHAIN/STEP/LLM/TOOL spans, while this plugin contributes the ENTRY
wrapper, a sidecar metadata enricher, and GenAI metrics from finished spans.
"""

def instrumentation_dependencies(self) -> Collection[str]:
return _instruments

def _instrument(self, **kwargs: Any) -> None:
tracer_provider = kwargs.get("tracer_provider")
meter_provider = kwargs.get("meter_provider")
logger_provider = kwargs.get("logger_provider")

_instrument_dependency(
"opentelemetry.instrumentation.langchain",
"LangChainInstrumentor",
tracer_provider=tracer_provider,
meter_provider=meter_provider,
logger_provider=logger_provider,
)
_instrument_dependency(
"opentelemetry.instrumentation.langgraph",
"LangGraphInstrumentor",
tracer_provider=tracer_provider,
meter_provider=meter_provider,
logger_provider=logger_provider,
)

handler = ExtendedTelemetryHandler(
tracer_provider=tracer_provider,
meter_provider=meter_provider,
logger_provider=logger_provider,
)
instrument_entry_patch(handler)
install_enricher_callback()

if tracer_provider is None:
tracer_provider = trace.get_tracer_provider()
if meter_provider is None:
_logger.warning(
"deepagents instrumentation meter_provider was not supplied; "
"using the global MeterProvider for GenAI metrics."
)
meter_provider = metrics.get_meter_provider()
install_metrics_processor(
tracer_provider=tracer_provider,
meter_provider=meter_provider,
)

def _uninstrument(self, **kwargs: Any) -> None:
uninstrument_entry_patch()
uninstall_enricher_callback()
shutdown_metrics_processors()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Copyright The OpenTelemetry Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

"""Shared constants for deepagents instrumentation."""

from __future__ import annotations

FRAMEWORK_NAME = "deepagents"

GEN_AI_AGENT_DESCRIPTION = "gen_ai.agent.description"
GEN_AI_AGENT_NAME = "gen_ai.agent.name"
GEN_AI_AGENT_TYPE = "gen_ai.agent.type"
GEN_AI_FRAMEWORK = "gen_ai.framework"
GEN_AI_FRAMEWORK_VERSION = "gen_ai.framework.version"
GEN_AI_OPERATION_NAME = "gen_ai.operation.name"
GEN_AI_REQUEST_MODEL = "gen_ai.request.model"
GEN_AI_RESPONSE_MODEL = "gen_ai.response.model"
GEN_AI_RESPONSE_TIME_TO_FIRST_TOKEN = "gen_ai.response.time_to_first_token"
GEN_AI_SESSION_ID = "gen_ai.session.id"
GEN_AI_SPAN_KIND = "gen_ai.span.kind"
GEN_AI_TOOL_NAME = "gen_ai.tool.name"
GEN_AI_TOOL_TYPE = "gen_ai.tool.type"
GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS = (
"gen_ai.usage.cache_creation.input_tokens"
)
GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS = (
"gen_ai.usage.cache_read.input_tokens"
)
GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens"
GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens"
GEN_AI_USAGE_TOTAL_TOKENS = "gen_ai.usage.total_tokens"

SPAN_KIND_AGENT = "AGENT"
SPAN_KIND_CHAIN = "CHAIN"
SPAN_KIND_EMBEDDING = "EMBEDDING"
SPAN_KIND_ENTRY = "ENTRY"
SPAN_KIND_LLM = "LLM"
SPAN_KIND_RERANKER = "RERANKER"
SPAN_KIND_RETRIEVER = "RETRIEVER"
SPAN_KIND_STEP = "STEP"
SPAN_KIND_TASK = "TASK"
SPAN_KIND_TOOL = "TOOL"

ENTRY_PARENT_KINDS = {SPAN_KIND_ENTRY, SPAN_KIND_AGENT}
GENAI_SPAN_KINDS = {
SPAN_KIND_AGENT,
SPAN_KIND_CHAIN,
SPAN_KIND_EMBEDDING,
SPAN_KIND_ENTRY,
SPAN_KIND_LLM,
SPAN_KIND_RERANKER,
SPAN_KIND_RETRIEVER,
SPAN_KIND_STEP,
SPAN_KIND_TASK,
SPAN_KIND_TOOL,
}

METADATA_LS_INTEGRATION = "ls_integration"
METADATA_LS_AGENT_TYPE = "ls_agent_type"
METADATA_LC_AGENT_NAME = "lc_agent_name"
METADATA_VERSIONS = "versions"
METADATA_DEEPAGENTS_VERSION = "deepagents"
METADATA_SUBAGENT_DESCRIPTION = "loongsuite_deepagents_subagent_description"

SUBAGENT_TYPE = "subagent"
TASK_TOOL_NAME = "task"
TOOL_TYPE_AGENT = "agent"

GRAPH_ATTR = "_loongsuite_deepagents_graph"
GRAPH_VERSION_ATTR = "_loongsuite_deepagents_version"
GRAPH_METADATA_ATTR = "_loongsuite_deepagents_metadata"
GRAPH_REGISTRY_ATTR = "_loongsuite_deepagents_subagent_registry"
GRAPH_ORIGINAL_METHODS_ATTR = "_loongsuite_deepagents_original_methods"
GRAPH_METHODS_WRAPPED_ATTR = "_loongsuite_deepagents_methods_wrapped"
LANGGRAPH_REACT_AGENT_METADATA_KEY = "_loongsuite_react_agent"

CREATE_DEEP_AGENT_MODULE = "deepagents.graph"
CREATE_DEEP_AGENT_NAME = "create_deep_agent"
BUILD_TASK_TOOL_MODULE = "deepagents.middleware.subagents"
BUILD_TASK_TOOL_NAME = "_build_task_tool"

METRIC_CALLS_COUNT = "genai_calls_count"
METRIC_CALLS_DURATION_SECONDS = "genai_calls_duration_seconds"
METRIC_CALLS_ERROR_COUNT = "genai_calls_error_count"
METRIC_CALLS_SLOW_COUNT = "genai_calls_slow_count"
METRIC_LLM_FIRST_TOKEN_SECONDS = "genai_llm_first_token_seconds"
METRIC_LLM_USAGE_TOKENS = "genai_llm_usage_tokens"

DEFAULT_SLOW_THRESHOLDS_SECONDS = {
SPAN_KIND_ENTRY: 60.0,
SPAN_KIND_AGENT: 30.0,
SPAN_KIND_CHAIN: 10.0,
SPAN_KIND_STEP: 10.0,
SPAN_KIND_LLM: 10.0,
SPAN_KIND_TOOL: 10.0,
SPAN_KIND_RETRIEVER: 5.0,
SPAN_KIND_RERANKER: 5.0,
SPAN_KIND_EMBEDDING: 5.0,
SPAN_KIND_TASK: 30.0,
}

USAGE_TOKEN_ATTRIBUTES = {
"input": GEN_AI_USAGE_INPUT_TOKENS,
"output": GEN_AI_USAGE_OUTPUT_TOKENS,
"total": GEN_AI_USAGE_TOTAL_TOKENS,
"cache_creation": GEN_AI_USAGE_CACHE_CREATION_INPUT_TOKENS,
"cache_read": GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
}
Loading
Loading