Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,46 @@ dynamic = ["version"]
description = "OpenTelemetry AgentScope Instrumentation"
readme = "README.md"
license = "Apache-2.0"
requires-python = ">=3.8, <3.13"
requires-python = ">=3.9"
authors = [
{ name = "LoongSuite Python Agent Authros", email = "[email protected]" },
{ name = "LoongSuite Python Agent Authors", email = "[email protected]" },
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"opentelemetry-api>=1.37.0",
"opentelemetry-sdk>=1.37.0",
"opentelemetry-semantic-conventions>=0.58b0",
"wrapt",
"opentelemetry-api ~= 1.37",
"opentelemetry-instrumentation ~= 0.58b0",
"opentelemetry-semantic-conventions ~= 0.58b0",
]

[project.optional-dependencies]
instruments = [
"agentscope",
]
# 支持两个版本的 agentscope 测试
test = [
"agentscope",
"pytest",
"opentelemetry-sdk",
"respx",
]
test-v0 = [
"agentscope>=0.1.0,<1.0.0",
"pytest",
"opentelemetry-sdk",
"respx",
]
test-v1 = [
"agentscope>=1.0.0",
"pytest",
"opentelemetry-sdk",
"respx",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT,
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT_MAX_LENGTH,
OTEL_INSTRUMENTATION_GENAI_MESSAGE_STRATEGY,
AgentScopeGenAiProviderName,
)
from .telemetry_options import (
GenAITelemetryOptions,
Expand All @@ -44,6 +45,7 @@
"ToolRequestAttributes",
# Enums
"GenAiSpanKind",
"AgentScopeGenAiProviderName",
# Attribute constants
"CommonAttributes",
]
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,8 @@ class ToolCallResponsePart(MessagePart):
class GenericPart(MessagePart):
"""通用部件,支持任意属性"""

# FIXME: ruff failed
def __init__(self, type: str, **kwargs: Any): # noqa: A002
super().__init__(type)
def __init__(self, part_type: str, **kwargs: Any):
super().__init__(type=part_type)
for key, value in kwargs.items():
setattr(self, key, value)

Expand Down Expand Up @@ -101,24 +100,22 @@ def create_text_part(content: str) -> TextPart:
@staticmethod
def create_tool_call_part(
name: str,
# FIXME: ruff failed
id: Optional[str] = None, # noqa: A002
call_id: Optional[str] = None,
arguments: Optional[Dict[str, Any]] = None,
) -> ToolCallRequestPart:
"""创建工具调用部件"""
return ToolCallRequestPart(
type="tool_call", name=name, id=id, arguments=arguments
type="tool_call", name=name, id=call_id, arguments=arguments
)

@staticmethod
def create_tool_response_part(
response: Any,
# FIXME: ruff failed
id: Optional[str] = None, # noqa: A002
call_id: Optional[str] = None,
) -> ToolCallResponsePart:
"""创建工具响应部件"""
return ToolCallResponsePart(
type="tool_call_response", response=response, id=id
type="tool_call_response", response=response, id=call_id
)

@staticmethod
Expand Down Expand Up @@ -184,7 +181,8 @@ def parse_message(self, msg: Dict[str, Any]) -> ChatMessage:
# 处理tool消息
if role == "tool":
part = PartFactory.create_tool_response_part(
response=msg.get("content", ""), id=msg.get("tool_call_id", "")
response=msg.get("content", ""),
call_id=msg.get("tool_call_id", ""),
)
parts.append(part)
else:
Expand All @@ -198,7 +196,7 @@ def parse_message(self, msg: Dict[str, Any]) -> ChatMessage:

part = PartFactory.create_tool_call_part(
name=function_info.get("name", "unknown_tool"),
id=tool_call.get("id", "unknown_id"),
call_id=tool_call.get("id", "unknown_id"),
arguments=arguments,
)
parts.append(part)
Expand Down Expand Up @@ -295,7 +293,7 @@ def _parse_content_list(
parts.append(
PartFactory.create_tool_call_part(
name=item.get("name", ""),
id=item.get("id", ""),
call_id=item.get("id", ""),
arguments=item.get("input", {}),
)
)
Expand All @@ -306,7 +304,7 @@ def _parse_content_list(
parts.append(
PartFactory.create_tool_response_part(
response=result_text,
id=item.get("tool_use_id", ""),
call_id=item.get("tool_use_id", ""),
)
)
elif content_type == "image":
Expand Down Expand Up @@ -377,7 +375,7 @@ def _parse_parts_list(self, parts_list: List[Any]) -> List[MessagePart]:
parts.append(
PartFactory.create_tool_call_part(
name=func_call.get("name", ""),
id=func_call.get("id", ""),
call_id=func_call.get("id", ""),
arguments=func_call.get("args", {}),
)
)
Expand All @@ -391,7 +389,8 @@ def _parse_parts_list(self, parts_list: List[Any]) -> List[MessagePart]:
)
parts.append(
PartFactory.create_tool_response_part(
response=result_text, id=func_resp.get("id", "")
response=result_text,
call_id=func_resp.get("id", ""),
)
)
elif "inline_data" in item:
Expand Down Expand Up @@ -439,7 +438,7 @@ def parse_message(self, msg: Dict[str, Any]) -> ChatMessage:
parts.append(
PartFactory.create_tool_response_part(
response=msg.get("content", ""),
id=msg.get("tool_call_id", ""),
call_id=msg.get("tool_call_id", ""),
)
)
else:
Expand All @@ -454,7 +453,7 @@ def parse_message(self, msg: Dict[str, Any]) -> ChatMessage:
parts.append(
PartFactory.create_tool_call_part(
name=function_info.get("name", ""),
id=tool_call.get("id", ""),
call_id=tool_call.get("id", ""),
arguments=arguments,
)
)
Expand Down Expand Up @@ -519,7 +518,7 @@ def parse_message(self, msg: Dict[str, Any]) -> ChatMessage:
parts.append(
PartFactory.create_tool_response_part(
response=msg.get("content", ""),
id=msg.get("tool_call_id", ""),
call_id=msg.get("tool_call_id", ""),
)
)
else:
Expand Down Expand Up @@ -577,7 +576,7 @@ def parse_message(self, msg: Dict[str, Any]) -> ChatMessage:
parts.append(
PartFactory.create_tool_response_part(
response=msg.get("content", ""),
id=msg.get("tool_call_id", ""),
call_id=msg.get("tool_call_id", ""),
)
)
else:
Expand All @@ -592,7 +591,7 @@ def parse_message(self, msg: Dict[str, Any]) -> ChatMessage:
parts.append(
PartFactory.create_tool_call_part(
name=function_info.get("name", ""),
id=tool_call.get("id", ""),
call_id=tool_call.get("id", ""),
arguments=arguments,
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
"""顶层测试配置,配置 Python 路径以便测试可以导入包"""

import sys
from pathlib import Path

# 获取项目根目录
TESTS_ROOT = Path(__file__).resolve().parent
PROJECT_ROOT = TESTS_ROOT.parent
SRC_PATH = PROJECT_ROOT / "src"

# 将 src 目录添加到 Python 路径
# 这样测试就可以直接导入 opentelemetry.instrumentation.agentscope
if str(SRC_PATH) not in sys.path:
sys.path.insert(0, str(SRC_PATH))

# 将 tests 目录的父目录添加到 Python 路径
# 这样测试就可以使用 `from tests.shared.version_utils import ...`
# 无论 pytest 从哪个目录运行
if str(PROJECT_ROOT) not in sys.path:
sys.path.insert(0, str(PROJECT_ROOT))
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
# -*- coding: utf-8 -*-
"""版本检测和测试跳过工具"""

import os
import sys
import importlib.util

import pytest

# 添加src目录到Python路径
src_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "src"
)
if src_path not in sys.path:
sys.path.insert(0, src_path)

# FIXME: ruff failed
from opentelemetry.instrumentation.agentscope.utils import ( # noqa: E402
from opentelemetry.instrumentation.agentscope.utils import (
_AGENTSCOPE_VERSION,
is_agentscope_v1,
)
Expand Down Expand Up @@ -43,16 +34,6 @@ def skip_if_not_v1():

def skip_if_no_agentscope():
"""如果没有安装agentscope则跳过测试"""
try:
# test the import of agentscope, skip the warning
import agentscope # noqa: F401, PLC0415

return pytest.mark.skipif(False, reason="")
except ImportError:
if importlib.util.find_spec("agentscope") is None:
return pytest.mark.skipif(True, reason="需要安装 agentscope")


# pytest markers
pytestmark = [
pytest.mark.agentscope, # 标记所有agentscope相关测试
]
return pytest.mark.skipif(False, reason="")
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""v1版本的AgentScope测试"""

import asyncio
from typing import Generator

import agentscope
Expand Down Expand Up @@ -84,8 +85,6 @@ def test_agentscope_v1_basic(
msg_task = Msg("user", "compute 1615114134*4343434343 for me", "user")

# 使用 asyncio 来运行异步函数
import asyncio # noqa: PLC0415

async def run_agent():
response = await agent(msg_task)
# 如果是异步生成器,需要消费它
Expand All @@ -96,10 +95,10 @@ async def run_agent():
return result
return response

# FIXME: ruff failed
response = asyncio.run(run_agent()) # noqa: F841
# 运行异步函数以生成 spans
_ = asyncio.run(run_agent())

check_model, check_tool = False, False
check_model = False
spans = in_memory_span_exporter.get_finished_spans()

# 调试:打印所有 span 名称
Expand All @@ -109,9 +108,6 @@ async def run_agent():
# 检查是否是聊天模型的 span(格式:chat model_name)
if span.name.startswith("chat "):
check_model = True
if "tool" in span.name.lower():
# FIXME: ruff failed
check_tool = True # noqa: F841

# 先检查是否至少有模型调用 span
assert check_model, (
Expand Down Expand Up @@ -144,8 +140,6 @@ def test_agentscope_v1_simple_chat(

msg_task = Msg("user", "Hello, how are you?", "user")

import asyncio # noqa: PLC0415

async def run_agent():
response = await agent(msg_task)
if hasattr(response, "__aiter__"):
Expand Down Expand Up @@ -187,8 +181,6 @@ def test_agentscope_v1_model_direct(
# 直接调用模型(使用字典格式避免 Msg 对象问题)
messages = [{"role": "user", "content": "Hello, what is 1+1?"}]

import asyncio # noqa: PLC0415

async def call_model():
response = await model(messages)
if hasattr(response, "__aiter__"):
Expand Down Expand Up @@ -230,8 +222,6 @@ def test_agentscope_v1_span_attributes(
# 直接调用模型(使用字典格式)
messages = [{"role": "user", "content": "Simple test message"}]

import asyncio # noqa: PLC0415

async def call_model():
response = await model(messages)
if hasattr(response, "__aiter__"):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ dynamic = ["version"]
description = "OpenTelemetry Agno Instrumentation"
readme = "README.md"
license = "Apache-2.0"
requires-python = ">=3.8, <3.13"
requires-python = ">=3.9"
authors = [
{ name = "LoongSuite Python Agent Authors", email = "[email protected]" },
]
Expand All @@ -18,15 +18,16 @@ classifiers = [
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = [
"wrapt",
"opentelemetry-api ~= 1.37",
"opentelemetry-instrumentation ~= 0.58b0",
"opentelemetry-semantic-conventions ~= 0.58b0",
]

[project.optional-dependencies]
Expand All @@ -35,8 +36,10 @@ instruments = [
]
test = [
"agno",
"openai",
"pytest",
"opentelemetry-sdk",
"yfinance",
]
type-check = []

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,12 @@ def extract(
f"{GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS}.image",
json.dumps(response.image.to_dict(), indent=2),
)
# FIXME: ruff failed
for idx, exec in enumerate( # noqa: A001
for idx, tool_execution in enumerate(
getattr(response, "tool_executions", []) or []
):
yield (
f"{GenAIAttributes.GEN_AI_RESPONSE_FINISH_REASONS}.tool_executions.{idx}",
json.dumps(exec.to_dict(), indent=2),
json.dumps(tool_execution.to_dict(), indent=2),
)
# other metadata
if getattr(response, "event", None):
Expand Down
Loading