{"repo": "alibaba/loongsuite-python", "files": {"instrumentation-loongsuite/loongsuite-instrumentation-agentscope/src/opentelemetry/instrumentation/agentscope/utils.py": {"0d88889b2d45e96ad8bd14cf517bc109be900bde": {"t": 1785745990.1402972, "n": 22548, "h": "dd4203458d5271d3", "c": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# -*- coding: utf-8 -*-\n\"\"\"Attributes processor for span attributes.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport enum\nimport inspect\nimport json\nimport logging\nimport mimetypes\nfrom dataclasses import is_dataclass\nfrom enum import Enum\nfrom typing import Any, Dict, List, Optional, Tuple\nfrom urllib.parse import urlparse\n\nfrom agentscope import _config\nfrom agentscope.agent import AgentBase\nfrom agentscope.embedding import EmbeddingModelBase\nfrom agentscope.message import Msg\nfrom agentscope.model import ChatModelBase, ChatResponse\nfrom pydantic import BaseModel\n\nfrom opentelemetry import baggage\nfrom opentelemetry.semconv._incubating.attributes import (\n gen_ai_attributes as GenAIAttributes,\n)\nfrom opentelemetry.util.genai.extended_semconv.gen_ai_extended_attributes import (\n GEN_AI_SESSION_ID,\n GEN_AI_USER_ID,\n)\nfrom opentelemetry.util.genai.extended_types import (\n EmbeddingInvocation,\n InvokeAgentInvocation,\n)\nfrom opentelemetry.util.genai.types import (\n Base64Blob,\n InputMessage,\n LLMInvocation,\n OutputMessage,\n Reasoning,\n Text,\n ToolCall,\n ToolCallResponse,\n Uri,\n)\n\nfrom .message_converter import get_message_converter\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgentScopeGenAiProviderName(str, Enum):\n \"\"\"Extended provider names not in standard OpenTelemetry semantic conventions.\"\"\"\n\n OLLAMA = \"ollama\"\n DASHSCOPE = \"dashscope\"\n MOONSHOT = \"moonshot\"\n\n\n# Provider name mapping based on class names\n_PROVIDER_NAME_MAP = {\n \"openai\": GenAIAttributes.GenAiProviderNameValues.OPENAI.value,\n \"gemini\": GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value,\n \"anthropic\": GenAIAttributes.GenAiProviderNameValues.ANTHROPIC.value,\n \"dashscope\": AgentScopeGenAiProviderName.DASHSCOPE.value,\n \"ollama\": AgentScopeGenAiProviderName.OLLAMA.value,\n}\n\n# Base URL to provider mapping for OpenAI-compatible APIs\n_BASE_URL_PROVIDER_MAP = [\n (\"openai.com\", GenAIAttributes.GenAiProviderNameValues.OPENAI.value),\n (\n \"api.deepseek.com\",\n GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,\n ),\n (\"dashscope.aliyuncs.com\", AgentScopeGenAiProviderName.DASHSCOPE.value),\n]\n\n\ndef _current_baggage_value(key: str) -> str | None:\n try:\n value = baggage.get_baggage(key)\n except Exception:\n return None\n if value is None:\n return None\n text = str(value).strip()\n return text or None\n\n\ndef entry_baggage_identity_attributes() -> dict[str, str]:\n \"\"\"Return entry-level identity from current OpenTelemetry Baggage.\n\n QwenPaw opens an Entry span before AgentScope runs and writes\n ``gen_ai.session.id`` / ``gen_ai.user.id`` into Baggage. AgentScope has\n its own ``run_id``; when both instrumentations are active, entry baggage is\n the request-level identity and should color downstream spans.\n \"\"\"\n attributes: dict[str, str] = {}\n session_id = _current_baggage_value(GEN_AI_SESSION_ID)\n user_id = _current_baggage_value(GEN_AI_USER_ID)\n if session_id:\n attributes[GEN_AI_SESSION_ID] = session_id\n if user_id:\n attributes[GEN_AI_USER_ID] = user_id\n return attributes\n\n\ndef apply_entry_baggage_identity(invocation: Any) -> str | None:\n \"\"\"Copy entry-level identity baggage onto a GenAI invocation.\"\"\"\n attributes = entry_baggage_identity_attributes()\n for key, value in attributes.items():\n invocation.attributes.setdefault(key, value)\n return attributes.get(GEN_AI_SESSION_ID)\n\n\ndef get_provider_name(chat_model: ChatModelBase) -> str:\n \"\"\"Parse chat model provider name\"\"\"\n classname = chat_model.__class__.__name__\n prefix = classname.removesuffix(\"ChatModel\").lower()\n\n # Special handling for DashScopeChatModel with base_http_api_url\n if (\n prefix == \"dashscope\"\n and hasattr(chat_model, \"base_http_api_url\")\n and chat_model.base_http_api_url\n ):\n base_url = chat_model.base_http_api_url\n for url_fragment, provider in _BASE_URL_PROVIDER_MAP:\n if url_fragment in base_url:\n return provider\n\n return _PROVIDER_NAME_MAP.get(prefix, \"unknown\")\n\n\ndef get_embedding_provider_name(embedding_model: EmbeddingModelBase) -> str:\n \"\"\"Parse embedding model provider name\"\"\"\n class_name = embedding_model.__class__.__name__\n\n if \"DashScope\" in class_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n elif \"OpenAI\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n elif \"Gemini\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value\n elif \"Ollama\" in class_name:\n return AgentScopeGenAiProviderName.OLLAMA.value\n else:\n model_name = getattr(embedding_model, \"model_name\", \"\")\n if \"text-embedding\" in model_name:\n if \"qwen\" in model_name or \"dashscope\" in model_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n else:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n return \"agentscope\"\n\n\ndef _get_tool_definitions(\n tools: list[dict[str, Any]] | None,\n tool_choice: str | None,\n) -> str | None:\n \"\"\"Extract and serialize tool definitions for tracing.\"\"\"\n if tools is None or not isinstance(tools, list) or len(tools) == 0:\n return None\n\n if tool_choice == \"none\":\n return None\n\n try:\n flat_tools = []\n for tool in tools:\n if not isinstance(tool, dict) or \"function\" not in tool:\n continue\n\n func_def = tool[\"function\"]\n flat_tool = {\n \"type\": tool.get(\"type\", \"function\"),\n \"name\": func_def.get(\"name\"),\n \"description\": func_def.get(\"description\"),\n \"parameters\": func_def.get(\"parameters\"),\n }\n flat_tool = {k: v for k, v in flat_tool.items() if v is not None}\n flat_tools.append(flat_tool)\n\n if flat_tools:\n return _serialize_to_str(flat_tools)\n return None\n\n except Exception:\n return None\n\n\ndef _to_serializable(obj: Any) -> Any:\n \"\"\"Convert an object to a JSON serializable type.\"\"\"\n if isinstance(obj, (str, int, bool, float, type(None))):\n return obj\n elif isinstance(obj, (list, tuple, set, frozenset)):\n return [_to_serializable(x) for x in obj]\n elif isinstance(obj, dict):\n return {str(key): _to_serializable(val) for (key, val) in obj.items()}\n elif isinstance(obj, (Msg, BaseModel)) or is_dataclass(obj):\n return repr(obj)\n elif inspect.isclass(obj) and issubclass(obj, BaseModel):\n return repr(obj)\n elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):\n return obj.isoformat()\n elif isinstance(obj, datetime.timedelta):\n return obj.total_seconds()\n elif isinstance(obj, enum.Enum):\n return _to_serializable(obj.value)\n else:\n return str(obj)\n\n\ndef _serialize_to_str(value: Any) -> str:\n \"\"\"Serialize value to JSON string\"\"\"\n try:\n return json.dumps(value, ensure_ascii=False)\n except TypeError:\n return json.dumps(_to_serializable(value), ensure_ascii=False)\n\n\ndef _convert_block_to_part(block: Dict[str, Any]) -> Dict[str, Any] | None:\n \"\"\"Convert content block to standardized part format.\"\"\"\n block_type = block.get(\"type\")\n\n if block_type == \"text\":\n return {\"type\": \"text\", \"content\": block.get(\"text\", \"\")}\n\n elif block_type == \"thinking\":\n return {\n \"type\": \"text\",\n \"content\": f\"[Thinking] {block.get('thinking', '')}\",\n }\n\n elif block_type == \"tool_use\":\n return {\n \"type\": \"tool_call\",\n \"id\": block.get(\"id\", \"\"),\n \"name\": block.get(\"name\", \"\"),\n \"arguments\": block.get(\"input\", {}),\n }\n\n elif block_type == \"tool_result\":\n output = block.get(\"output\", \"\")\n result = (\n _serialize_to_str(output)\n if isinstance(output, (list, dict))\n else str(output)\n )\n return {\n \"type\": \"tool_call_response\",\n \"id\": block.get(\"id\", \"\"),\n \"result\": result,\n }\n\n elif block_type in [\"image\", \"audio\", \"video\"]:\n source = block.get(\"source\", {})\n source_type = source.get(\"type\")\n media_type = source.get(\"media_type\")\n\n if source_type == \"url\":\n url = source.get(\"url\", \"\")\n # Infer mime_type from URL extension if not provided\n if not media_type:\n parsed_url = urlparse(url)\n media_type, _ = mimetypes.guess_type(parsed_url.path)\n return {\n \"type\": \"uri\",\n \"uri\": url,\n \"modality\": block_type,\n \"mime_type\": media_type,\n }\n elif source_type == \"base64\":\n if not media_type:\n default_media_types = {\n \"image\": \"image/jpeg\",\n \"audio\": \"audio/wav\",\n \"video\": \"video/mp4\",\n }\n media_type = default_media_types.get(block_type, \"unknown\")\n return {\n \"type\": \"blob\",\n \"content\": source.get(\"data\", \"\"),\n \"media_type\": media_type,\n \"modality\": block_type,\n }\n\n return None\n\n\ndef _format_msg_to_parts(msg: Msg) -> dict[str, Any]:\n \"\"\"Convert Msg to standard format (parts structure)\"\"\"\n try:\n parts = []\n for block in msg.get_content_blocks():\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n formatted_msg = {\"role\": msg.role, \"parts\": parts}\n if msg.name:\n formatted_msg[\"name\"] = msg.name\n\n return formatted_msg\n\n except Exception as e:\n logger.debug(f\"Error formatting message: {e}\")\n return {\n \"role\": msg.role,\n \"parts\": [\n {\n \"type\": \"text\",\n \"content\": str(msg.content) if msg.content else \"\",\n }\n ],\n }\n\n\ndef create_llm_invocation(\n call_instance: ChatModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> LLMInvocation:\n \"\"\"Create LLM invocation from call context.\"\"\"\n provider_name = get_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n # Convert input messages\n input_messages = []\n messages = call_args[0] if call_args else call_kwargs.get(\"messages\")\n if messages:\n try:\n converted_messages = get_message_converter(provider_name)(messages)\n input_messages = convert_agentscope_messages_to_genai_format(\n converted_messages, provider_name=provider_name\n )\n except Exception as e:\n logger.warning(\n f\"Failed to convert input messages: {e}\", exc_info=True\n )\n\n invocation = LLMInvocation(\n request_model=request_model,\n provider=provider_name,\n input_messages=input_messages,\n )\n entry_session_id = apply_entry_baggage_identity(invocation)\n if entry_session_id and invocation.conversation_id is None:\n invocation.conversation_id = entry_session_id\n\n # Set optional request parameters if present\n if call_kwargs.get(\"max_tokens\"):\n invocation.max_tokens = call_kwargs[\"max_tokens\"]\n if call_kwargs.get(\"temperature\"):\n invocation.temperature = call_kwargs[\"temperature\"]\n if call_kwargs.get(\"top_p\"):\n invocation.top_p = call_kwargs[\"top_p\"]\n\n # Set tool definitions if applicable\n if not call_kwargs.get(\"structured_model\", False):\n tool_definitions = _get_tool_definitions(\n call_kwargs.get(\"tools\"), call_kwargs.get(\"tool_choice\")\n )\n if tool_definitions:\n invocation.attributes[\"gen_ai.request.tool_definitions\"] = (\n tool_definitions\n )\n\n return invocation\n\n\ndef create_embedding_invocation(\n call_instance: EmbeddingModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> EmbeddingInvocation:\n \"\"\"Create Embedding invocation from call context.\"\"\"\n provider_name = get_embedding_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n invocation = EmbeddingInvocation(\n request_model=request_model,\n provider=provider_name,\n )\n apply_entry_baggage_identity(invocation)\n\n # Set encoding formats if present\n if call_kwargs.get(\"encoding_formats\"):\n invocation.encoding_formats = call_kwargs[\"encoding_formats\"]\n\n return invocation\n\n\ndef create_agent_invocation(\n reply_instance: AgentBase,\n reply_args: Tuple[Any, ...],\n reply_kwargs: Dict[str, Any],\n) -> InvokeAgentInvocation:\n \"\"\"Create Agent invocation from reply call context.\"\"\"\n # Get provider and model info\n provider_name = None\n request_model = None\n if hasattr(reply_instance, \"model\") and reply_instance.model:\n model = reply_instance.model\n request_model = getattr(model, \"model_name\", None)\n if isinstance(model, ChatModelBase):\n provider_name = get_provider_name(model)\n\n # Convert input messages\n input_messages = []\n msg = reply_args[0] if reply_args else reply_kwargs.get(\"msg\")\n if msg:\n try:\n if isinstance(msg, Msg):\n input_messages = convert_agentscope_messages_to_genai_format(\n [msg]\n )\n elif isinstance(msg, list):\n input_messages = convert_agentscope_messages_to_genai_format(\n msg\n )\n except Exception as e:\n logger.debug(f\"Error converting agent input messages: {e}\")\n\n entry_session_id = _current_baggage_value(GEN_AI_SESSION_ID)\n invocation = InvokeAgentInvocation(\n provider=provider_name,\n agent_name=getattr(reply_instance, \"name\", \"unknown_agent\"),\n agent_id=getattr(reply_instance, \"id\", \"unknown\"),\n agent_description=inspect.getdoc(reply_instance.__class__)\n or \"No description available\",\n conversation_id=entry_session_id or _config.run_id,\n request_model=request_model,\n input_messages=input_messages,\n )\n apply_entry_baggage_identity(invocation)\n\n # Set system instruction if available\n if hasattr(reply_instance, \"sys_prompt\") and reply_instance.sys_prompt:\n sys_prompt = reply_instance.sys_prompt\n if isinstance(sys_prompt, str):\n invocation.system_instruction = [Text(content=sys_prompt)]\n elif isinstance(sys_prompt, list):\n invocation.system_instruction = sys_prompt\n\n return invocation\n\n\ndef get_chatmodel_output_messages(chat_response: Any) -> List[Dict[str, Any]]:\n \"\"\"Convert ChatResponse to OpenTelemetry standard output message format.\"\"\"\n try:\n if not isinstance(chat_response, ChatResponse):\n return []\n\n parts = []\n for block in chat_response.content:\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n if not parts:\n parts.append({\"type\": \"text\", \"content\": \"\"})\n\n return [\n {\n \"role\": \"assistant\",\n \"parts\": parts,\n \"finish_reason\": \"stop\",\n }\n ]\n\n except Exception as e:\n logger.warning(\n f\"Error processing ChatResponse to output messages: {e}\"\n )\n return [\n {\n \"role\": \"assistant\",\n \"parts\": [\n {\"type\": \"text\", \"content\": \"\"}\n ],\n \"finish_reason\": \"error\",\n }\n ]\n\n\ndef convert_agentscope_messages_to_genai_format(\n messages: Any,\n provider_name: Optional[str] = None,\n) -> List[InputMessage]:\n \"\"\"Convert AgentScope messages to opentelemetry-util-genai InputMessage format.\n parse _convert_block_to_part result from agentscope/tracing/_converter.py\n\n This function is used by ExtendedTelemetryHandler which requires InputMessage objects.\n \"\"\"\n if not messages:\n return []\n\n if not isinstance(messages, list):\n messages = [messages]\n\n input_messages = []\n for msg in messages:\n if isinstance(msg, Msg):\n msg_dict = _format_msg_to_parts(msg)\n elif isinstance(msg, dict):\n msg_dict = msg\n else:\n continue\n\n role = msg_dict.get(\"role\", \"user\")\n parts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type == \"tool_call_response\":\n converted_parts.append(\n ToolCallResponse(\n id=part.get(\"id\"),\n response=part[\"response\"]\n if \"response\" in part\n else part.get(\"result\", \"\"),\n )\n )\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type == \"uri\":\n converted_parts.append(\n Uri(\n uri=part.get(\"uri\"),\n modality=part.get(\"modality\"),\n mime_type=part.get(\"mime_type\"),\n )\n )\n elif part_type == \"blob\":\n converted_parts.append(\n Base64Blob(\n content=part.get(\"content\", \"\"),\n modality=part.get(\"modality\"),\n mime_type=part.get(\"media_type\")\n if \"media_type\" in part\n else part.get(\"mime_type\"),\n )\n )\n else:\n # Keep other types as-is\n converted_parts.append(part)\n\n input_messages.append(InputMessage(role=role, parts=converted_parts))\n\n return input_messages\n\n\ndef convert_chatresponse_to_output_messages(\n chat_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert ChatResponse to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n output_dicts = get_chatmodel_output_messages(chat_response)\n if not output_dicts:\n return []\n\n output_messages = []\n for msg_dict in output_dicts:\n role = msg_dict.get(\"role\", \"assistant\")\n parts_dicts = msg_dict.get(\"parts\", [])\n finish_reason = msg_dict.get(\"finish_reason\", \"stop\")\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(part)\n\n output_messages.append(\n OutputMessage(\n role=role,\n parts=converted_parts,\n finish_reason=finish_reason,\n )\n )\n\n return output_messages\n\n\ndef convert_agent_response_to_output_messages(\n agent_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert agent Msg response to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n if not hasattr(agent_response, \"content\"):\n return []\n\n try:\n # Use _format_msg_to_parts to convert Msg to dict, then convert to OutputMessage\n msg_dict = _format_msg_to_parts(agent_response)\n parts_dicts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type in (\"uri\", \"blob\"):\n converted_parts.append(part)\n else:\n converted_parts.append(Text(content=str(part)))\n\n if not converted_parts:\n converted_parts.append(Text(content=\"\"))\n\n return [\n OutputMessage(\n role=\"assistant\",\n parts=converted_parts,\n finish_reason=\"stop\",\n )\n ]\n except Exception as e:\n logger.debug(f\"Failed to convert agent response: {e}\")\n return []\n"}, "a55f07a839529b2d7155d7080acc8b4ac21fab25": {"t": 1785758580.6980226, "n": 22548, "h": "dd4203458d5271d3", "c": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# -*- coding: utf-8 -*-\n\"\"\"Attributes processor for span attributes.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport enum\nimport inspect\nimport json\nimport logging\nimport mimetypes\nfrom dataclasses import is_dataclass\nfrom enum import Enum\nfrom typing import Any, Dict, List, Optional, Tuple\nfrom urllib.parse import urlparse\n\nfrom agentscope import _config\nfrom agentscope.agent import AgentBase\nfrom agentscope.embedding import EmbeddingModelBase\nfrom agentscope.message import Msg\nfrom agentscope.model import ChatModelBase, ChatResponse\nfrom pydantic import BaseModel\n\nfrom opentelemetry import baggage\nfrom opentelemetry.semconv._incubating.attributes import (\n gen_ai_attributes as GenAIAttributes,\n)\nfrom opentelemetry.util.genai.extended_semconv.gen_ai_extended_attributes import (\n GEN_AI_SESSION_ID,\n GEN_AI_USER_ID,\n)\nfrom opentelemetry.util.genai.extended_types import (\n EmbeddingInvocation,\n InvokeAgentInvocation,\n)\nfrom opentelemetry.util.genai.types import (\n Base64Blob,\n InputMessage,\n LLMInvocation,\n OutputMessage,\n Reasoning,\n Text,\n ToolCall,\n ToolCallResponse,\n Uri,\n)\n\nfrom .message_converter import get_message_converter\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgentScopeGenAiProviderName(str, Enum):\n \"\"\"Extended provider names not in standard OpenTelemetry semantic conventions.\"\"\"\n\n OLLAMA = \"ollama\"\n DASHSCOPE = \"dashscope\"\n MOONSHOT = \"moonshot\"\n\n\n# Provider name mapping based on class names\n_PROVIDER_NAME_MAP = {\n \"openai\": GenAIAttributes.GenAiProviderNameValues.OPENAI.value,\n \"gemini\": GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value,\n \"anthropic\": GenAIAttributes.GenAiProviderNameValues.ANTHROPIC.value,\n \"dashscope\": AgentScopeGenAiProviderName.DASHSCOPE.value,\n \"ollama\": AgentScopeGenAiProviderName.OLLAMA.value,\n}\n\n# Base URL to provider mapping for OpenAI-compatible APIs\n_BASE_URL_PROVIDER_MAP = [\n (\"openai.com\", GenAIAttributes.GenAiProviderNameValues.OPENAI.value),\n (\n \"api.deepseek.com\",\n GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,\n ),\n (\"dashscope.aliyuncs.com\", AgentScopeGenAiProviderName.DASHSCOPE.value),\n]\n\n\ndef _current_baggage_value(key: str) -> str | None:\n try:\n value = baggage.get_baggage(key)\n except Exception:\n return None\n if value is None:\n return None\n text = str(value).strip()\n return text or None\n\n\ndef entry_baggage_identity_attributes() -> dict[str, str]:\n \"\"\"Return entry-level identity from current OpenTelemetry Baggage.\n\n QwenPaw opens an Entry span before AgentScope runs and writes\n ``gen_ai.session.id`` / ``gen_ai.user.id`` into Baggage. AgentScope has\n its own ``run_id``; when both instrumentations are active, entry baggage is\n the request-level identity and should color downstream spans.\n \"\"\"\n attributes: dict[str, str] = {}\n session_id = _current_baggage_value(GEN_AI_SESSION_ID)\n user_id = _current_baggage_value(GEN_AI_USER_ID)\n if session_id:\n attributes[GEN_AI_SESSION_ID] = session_id\n if user_id:\n attributes[GEN_AI_USER_ID] = user_id\n return attributes\n\n\ndef apply_entry_baggage_identity(invocation: Any) -> str | None:\n \"\"\"Copy entry-level identity baggage onto a GenAI invocation.\"\"\"\n attributes = entry_baggage_identity_attributes()\n for key, value in attributes.items():\n invocation.attributes.setdefault(key, value)\n return attributes.get(GEN_AI_SESSION_ID)\n\n\ndef get_provider_name(chat_model: ChatModelBase) -> str:\n \"\"\"Parse chat model provider name\"\"\"\n classname = chat_model.__class__.__name__\n prefix = classname.removesuffix(\"ChatModel\").lower()\n\n # Special handling for DashScopeChatModel with base_http_api_url\n if (\n prefix == \"dashscope\"\n and hasattr(chat_model, \"base_http_api_url\")\n and chat_model.base_http_api_url\n ):\n base_url = chat_model.base_http_api_url\n for url_fragment, provider in _BASE_URL_PROVIDER_MAP:\n if url_fragment in base_url:\n return provider\n\n return _PROVIDER_NAME_MAP.get(prefix, \"unknown\")\n\n\ndef get_embedding_provider_name(embedding_model: EmbeddingModelBase) -> str:\n \"\"\"Parse embedding model provider name\"\"\"\n class_name = embedding_model.__class__.__name__\n\n if \"DashScope\" in class_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n elif \"OpenAI\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n elif \"Gemini\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value\n elif \"Ollama\" in class_name:\n return AgentScopeGenAiProviderName.OLLAMA.value\n else:\n model_name = getattr(embedding_model, \"model_name\", \"\")\n if \"text-embedding\" in model_name:\n if \"qwen\" in model_name or \"dashscope\" in model_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n else:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n return \"agentscope\"\n\n\ndef _get_tool_definitions(\n tools: list[dict[str, Any]] | None,\n tool_choice: str | None,\n) -> str | None:\n \"\"\"Extract and serialize tool definitions for tracing.\"\"\"\n if tools is None or not isinstance(tools, list) or len(tools) == 0:\n return None\n\n if tool_choice == \"none\":\n return None\n\n try:\n flat_tools = []\n for tool in tools:\n if not isinstance(tool, dict) or \"function\" not in tool:\n continue\n\n func_def = tool[\"function\"]\n flat_tool = {\n \"type\": tool.get(\"type\", \"function\"),\n \"name\": func_def.get(\"name\"),\n \"description\": func_def.get(\"description\"),\n \"parameters\": func_def.get(\"parameters\"),\n }\n flat_tool = {k: v for k, v in flat_tool.items() if v is not None}\n flat_tools.append(flat_tool)\n\n if flat_tools:\n return _serialize_to_str(flat_tools)\n return None\n\n except Exception:\n return None\n\n\ndef _to_serializable(obj: Any) -> Any:\n \"\"\"Convert an object to a JSON serializable type.\"\"\"\n if isinstance(obj, (str, int, bool, float, type(None))):\n return obj\n elif isinstance(obj, (list, tuple, set, frozenset)):\n return [_to_serializable(x) for x in obj]\n elif isinstance(obj, dict):\n return {str(key): _to_serializable(val) for (key, val) in obj.items()}\n elif isinstance(obj, (Msg, BaseModel)) or is_dataclass(obj):\n return repr(obj)\n elif inspect.isclass(obj) and issubclass(obj, BaseModel):\n return repr(obj)\n elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):\n return obj.isoformat()\n elif isinstance(obj, datetime.timedelta):\n return obj.total_seconds()\n elif isinstance(obj, enum.Enum):\n return _to_serializable(obj.value)\n else:\n return str(obj)\n\n\ndef _serialize_to_str(value: Any) -> str:\n \"\"\"Serialize value to JSON string\"\"\"\n try:\n return json.dumps(value, ensure_ascii=False)\n except TypeError:\n return json.dumps(_to_serializable(value), ensure_ascii=False)\n\n\ndef _convert_block_to_part(block: Dict[str, Any]) -> Dict[str, Any] | None:\n \"\"\"Convert content block to standardized part format.\"\"\"\n block_type = block.get(\"type\")\n\n if block_type == \"text\":\n return {\"type\": \"text\", \"content\": block.get(\"text\", \"\")}\n\n elif block_type == \"thinking\":\n return {\n \"type\": \"text\",\n \"content\": f\"[Thinking] {block.get('thinking', '')}\",\n }\n\n elif block_type == \"tool_use\":\n return {\n \"type\": \"tool_call\",\n \"id\": block.get(\"id\", \"\"),\n \"name\": block.get(\"name\", \"\"),\n \"arguments\": block.get(\"input\", {}),\n }\n\n elif block_type == \"tool_result\":\n output = block.get(\"output\", \"\")\n result = (\n _serialize_to_str(output)\n if isinstance(output, (list, dict))\n else str(output)\n )\n return {\n \"type\": \"tool_call_response\",\n \"id\": block.get(\"id\", \"\"),\n \"result\": result,\n }\n\n elif block_type in [\"image\", \"audio\", \"video\"]:\n source = block.get(\"source\", {})\n source_type = source.get(\"type\")\n media_type = source.get(\"media_type\")\n\n if source_type == \"url\":\n url = source.get(\"url\", \"\")\n # Infer mime_type from URL extension if not provided\n if not media_type:\n parsed_url = urlparse(url)\n media_type, _ = mimetypes.guess_type(parsed_url.path)\n return {\n \"type\": \"uri\",\n \"uri\": url,\n \"modality\": block_type,\n \"mime_type\": media_type,\n }\n elif source_type == \"base64\":\n if not media_type:\n default_media_types = {\n \"image\": \"image/jpeg\",\n \"audio\": \"audio/wav\",\n \"video\": \"video/mp4\",\n }\n media_type = default_media_types.get(block_type, \"unknown\")\n return {\n \"type\": \"blob\",\n \"content\": source.get(\"data\", \"\"),\n \"media_type\": media_type,\n \"modality\": block_type,\n }\n\n return None\n\n\ndef _format_msg_to_parts(msg: Msg) -> dict[str, Any]:\n \"\"\"Convert Msg to standard format (parts structure)\"\"\"\n try:\n parts = []\n for block in msg.get_content_blocks():\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n formatted_msg = {\"role\": msg.role, \"parts\": parts}\n if msg.name:\n formatted_msg[\"name\"] = msg.name\n\n return formatted_msg\n\n except Exception as e:\n logger.debug(f\"Error formatting message: {e}\")\n return {\n \"role\": msg.role,\n \"parts\": [\n {\n \"type\": \"text\",\n \"content\": str(msg.content) if msg.content else \"\",\n }\n ],\n }\n\n\ndef create_llm_invocation(\n call_instance: ChatModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> LLMInvocation:\n \"\"\"Create LLM invocation from call context.\"\"\"\n provider_name = get_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n # Convert input messages\n input_messages = []\n messages = call_args[0] if call_args else call_kwargs.get(\"messages\")\n if messages:\n try:\n converted_messages = get_message_converter(provider_name)(messages)\n input_messages = convert_agentscope_messages_to_genai_format(\n converted_messages, provider_name=provider_name\n )\n except Exception as e:\n logger.warning(\n f\"Failed to convert input messages: {e}\", exc_info=True\n )\n\n invocation = LLMInvocation(\n request_model=request_model,\n provider=provider_name,\n input_messages=input_messages,\n )\n entry_session_id = apply_entry_baggage_identity(invocation)\n if entry_session_id and invocation.conversation_id is None:\n invocation.conversation_id = entry_session_id\n\n # Set optional request parameters if present\n if call_kwargs.get(\"max_tokens\"):\n invocation.max_tokens = call_kwargs[\"max_tokens\"]\n if call_kwargs.get(\"temperature\"):\n invocation.temperature = call_kwargs[\"temperature\"]\n if call_kwargs.get(\"top_p\"):\n invocation.top_p = call_kwargs[\"top_p\"]\n\n # Set tool definitions if applicable\n if not call_kwargs.get(\"structured_model\", False):\n tool_definitions = _get_tool_definitions(\n call_kwargs.get(\"tools\"), call_kwargs.get(\"tool_choice\")\n )\n if tool_definitions:\n invocation.attributes[\"gen_ai.request.tool_definitions\"] = (\n tool_definitions\n )\n\n return invocation\n\n\ndef create_embedding_invocation(\n call_instance: EmbeddingModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> EmbeddingInvocation:\n \"\"\"Create Embedding invocation from call context.\"\"\"\n provider_name = get_embedding_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n invocation = EmbeddingInvocation(\n request_model=request_model,\n provider=provider_name,\n )\n apply_entry_baggage_identity(invocation)\n\n # Set encoding formats if present\n if call_kwargs.get(\"encoding_formats\"):\n invocation.encoding_formats = call_kwargs[\"encoding_formats\"]\n\n return invocation\n\n\ndef create_agent_invocation(\n reply_instance: AgentBase,\n reply_args: Tuple[Any, ...],\n reply_kwargs: Dict[str, Any],\n) -> InvokeAgentInvocation:\n \"\"\"Create Agent invocation from reply call context.\"\"\"\n # Get provider and model info\n provider_name = None\n request_model = None\n if hasattr(reply_instance, \"model\") and reply_instance.model:\n model = reply_instance.model\n request_model = getattr(model, \"model_name\", None)\n if isinstance(model, ChatModelBase):\n provider_name = get_provider_name(model)\n\n # Convert input messages\n input_messages = []\n msg = reply_args[0] if reply_args else reply_kwargs.get(\"msg\")\n if msg:\n try:\n if isinstance(msg, Msg):\n input_messages = convert_agentscope_messages_to_genai_format(\n [msg]\n )\n elif isinstance(msg, list):\n input_messages = convert_agentscope_messages_to_genai_format(\n msg\n )\n except Exception as e:\n logger.debug(f\"Error converting agent input messages: {e}\")\n\n entry_session_id = _current_baggage_value(GEN_AI_SESSION_ID)\n invocation = InvokeAgentInvocation(\n provider=provider_name,\n agent_name=getattr(reply_instance, \"name\", \"unknown_agent\"),\n agent_id=getattr(reply_instance, \"id\", \"unknown\"),\n agent_description=inspect.getdoc(reply_instance.__class__)\n or \"No description available\",\n conversation_id=entry_session_id or _config.run_id,\n request_model=request_model,\n input_messages=input_messages,\n )\n apply_entry_baggage_identity(invocation)\n\n # Set system instruction if available\n if hasattr(reply_instance, \"sys_prompt\") and reply_instance.sys_prompt:\n sys_prompt = reply_instance.sys_prompt\n if isinstance(sys_prompt, str):\n invocation.system_instruction = [Text(content=sys_prompt)]\n elif isinstance(sys_prompt, list):\n invocation.system_instruction = sys_prompt\n\n return invocation\n\n\ndef get_chatmodel_output_messages(chat_response: Any) -> List[Dict[str, Any]]:\n \"\"\"Convert ChatResponse to OpenTelemetry standard output message format.\"\"\"\n try:\n if not isinstance(chat_response, ChatResponse):\n return []\n\n parts = []\n for block in chat_response.content:\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n if not parts:\n parts.append({\"type\": \"text\", \"content\": \"\"})\n\n return [\n {\n \"role\": \"assistant\",\n \"parts\": parts,\n \"finish_reason\": \"stop\",\n }\n ]\n\n except Exception as e:\n logger.warning(\n f\"Error processing ChatResponse to output messages: {e}\"\n )\n return [\n {\n \"role\": \"assistant\",\n \"parts\": [\n {\"type\": \"text\", \"content\": \"\"}\n ],\n \"finish_reason\": \"error\",\n }\n ]\n\n\ndef convert_agentscope_messages_to_genai_format(\n messages: Any,\n provider_name: Optional[str] = None,\n) -> List[InputMessage]:\n \"\"\"Convert AgentScope messages to opentelemetry-util-genai InputMessage format.\n parse _convert_block_to_part result from agentscope/tracing/_converter.py\n\n This function is used by ExtendedTelemetryHandler which requires InputMessage objects.\n \"\"\"\n if not messages:\n return []\n\n if not isinstance(messages, list):\n messages = [messages]\n\n input_messages = []\n for msg in messages:\n if isinstance(msg, Msg):\n msg_dict = _format_msg_to_parts(msg)\n elif isinstance(msg, dict):\n msg_dict = msg\n else:\n continue\n\n role = msg_dict.get(\"role\", \"user\")\n parts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type == \"tool_call_response\":\n converted_parts.append(\n ToolCallResponse(\n id=part.get(\"id\"),\n response=part[\"response\"]\n if \"response\" in part\n else part.get(\"result\", \"\"),\n )\n )\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type == \"uri\":\n converted_parts.append(\n Uri(\n uri=part.get(\"uri\"),\n modality=part.get(\"modality\"),\n mime_type=part.get(\"mime_type\"),\n )\n )\n elif part_type == \"blob\":\n converted_parts.append(\n Base64Blob(\n content=part.get(\"content\", \"\"),\n modality=part.get(\"modality\"),\n mime_type=part.get(\"media_type\")\n if \"media_type\" in part\n else part.get(\"mime_type\"),\n )\n )\n else:\n # Keep other types as-is\n converted_parts.append(part)\n\n input_messages.append(InputMessage(role=role, parts=converted_parts))\n\n return input_messages\n\n\ndef convert_chatresponse_to_output_messages(\n chat_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert ChatResponse to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n output_dicts = get_chatmodel_output_messages(chat_response)\n if not output_dicts:\n return []\n\n output_messages = []\n for msg_dict in output_dicts:\n role = msg_dict.get(\"role\", \"assistant\")\n parts_dicts = msg_dict.get(\"parts\", [])\n finish_reason = msg_dict.get(\"finish_reason\", \"stop\")\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(part)\n\n output_messages.append(\n OutputMessage(\n role=role,\n parts=converted_parts,\n finish_reason=finish_reason,\n )\n )\n\n return output_messages\n\n\ndef convert_agent_response_to_output_messages(\n agent_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert agent Msg response to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n if not hasattr(agent_response, \"content\"):\n return []\n\n try:\n # Use _format_msg_to_parts to convert Msg to dict, then convert to OutputMessage\n msg_dict = _format_msg_to_parts(agent_response)\n parts_dicts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type in (\"uri\", \"blob\"):\n converted_parts.append(part)\n else:\n converted_parts.append(Text(content=str(part)))\n\n if not converted_parts:\n converted_parts.append(Text(content=\"\"))\n\n return [\n OutputMessage(\n role=\"assistant\",\n parts=converted_parts,\n finish_reason=\"stop\",\n )\n ]\n except Exception as e:\n logger.debug(f\"Failed to convert agent response: {e}\")\n return []\n"}, "ebedcb11047d6017694dedfe72a859e241dfeca8": {"t": 1785758582.1956472, "n": 20728, "h": "a89398588bc18094", "c": "# Copyright The OpenTelemetry Authors\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\n# -*- coding: utf-8 -*-\n\"\"\"Attributes processor for span attributes.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport enum\nimport inspect\nimport json\nimport logging\nimport mimetypes\nfrom dataclasses import is_dataclass\nfrom enum import Enum\nfrom typing import Any, Dict, List, Optional, Tuple\nfrom urllib.parse import urlparse\n\nfrom agentscope import _config\nfrom agentscope.agent import AgentBase\nfrom agentscope.embedding import EmbeddingModelBase\nfrom agentscope.message import Msg\nfrom agentscope.model import ChatModelBase, ChatResponse\nfrom pydantic import BaseModel\n\nfrom opentelemetry.semconv._incubating.attributes import (\n gen_ai_attributes as GenAIAttributes,\n)\nfrom opentelemetry.util.genai.extended_types import (\n EmbeddingInvocation,\n InvokeAgentInvocation,\n)\nfrom opentelemetry.util.genai.types import (\n Base64Blob,\n InputMessage,\n LLMInvocation,\n OutputMessage,\n Reasoning,\n Text,\n ToolCall,\n ToolCallResponse,\n Uri,\n)\n\nfrom .message_converter import get_message_converter\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgentScopeGenAiProviderName(str, Enum):\n \"\"\"Extended provider names not in standard OpenTelemetry semantic conventions.\"\"\"\n\n OLLAMA = \"ollama\"\n DASHSCOPE = \"dashscope\"\n MOONSHOT = \"moonshot\"\n\n\n# Provider name mapping based on class names\n_PROVIDER_NAME_MAP = {\n \"openai\": GenAIAttributes.GenAiProviderNameValues.OPENAI.value,\n \"gemini\": GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value,\n \"anthropic\": GenAIAttributes.GenAiProviderNameValues.ANTHROPIC.value,\n \"dashscope\": AgentScopeGenAiProviderName.DASHSCOPE.value,\n \"ollama\": AgentScopeGenAiProviderName.OLLAMA.value,\n}\n\n# Base URL to provider mapping for OpenAI-compatible APIs\n_BASE_URL_PROVIDER_MAP = [\n (\"openai.com\", GenAIAttributes.GenAiProviderNameValues.OPENAI.value),\n (\n \"api.deepseek.com\",\n GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,\n ),\n (\"dashscope.aliyuncs.com\", AgentScopeGenAiProviderName.DASHSCOPE.value),\n]\n\n\ndef get_provider_name(chat_model: ChatModelBase) -> str:\n \"\"\"Parse chat model provider name\"\"\"\n classname = chat_model.__class__.__name__\n prefix = classname.removesuffix(\"ChatModel\").lower()\n\n # Special handling for DashScopeChatModel with base_http_api_url\n if (\n prefix == \"dashscope\"\n and hasattr(chat_model, \"base_http_api_url\")\n and chat_model.base_http_api_url\n ):\n base_url = chat_model.base_http_api_url\n for url_fragment, provider in _BASE_URL_PROVIDER_MAP:\n if url_fragment in base_url:\n return provider\n\n return _PROVIDER_NAME_MAP.get(prefix, \"unknown\")\n\n\ndef get_embedding_provider_name(embedding_model: EmbeddingModelBase) -> str:\n \"\"\"Parse embedding model provider name\"\"\"\n class_name = embedding_model.__class__.__name__\n\n if \"DashScope\" in class_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n elif \"OpenAI\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n elif \"Gemini\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value\n elif \"Ollama\" in class_name:\n return AgentScopeGenAiProviderName.OLLAMA.value\n else:\n model_name = getattr(embedding_model, \"model_name\", \"\")\n if \"text-embedding\" in model_name:\n if \"qwen\" in model_name or \"dashscope\" in model_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n else:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n return \"agentscope\"\n\n\ndef _get_tool_definitions(\n tools: list[dict[str, Any]] | None,\n tool_choice: str | None,\n) -> str | None:\n \"\"\"Extract and serialize tool definitions for tracing.\"\"\"\n if tools is None or not isinstance(tools, list) or len(tools) == 0:\n return None\n\n if tool_choice == \"none\":\n return None\n\n try:\n flat_tools = []\n for tool in tools:\n if not isinstance(tool, dict) or \"function\" not in tool:\n continue\n\n func_def = tool[\"function\"]\n flat_tool = {\n \"type\": tool.get(\"type\", \"function\"),\n \"name\": func_def.get(\"name\"),\n \"description\": func_def.get(\"description\"),\n \"parameters\": func_def.get(\"parameters\"),\n }\n flat_tool = {k: v for k, v in flat_tool.items() if v is not None}\n flat_tools.append(flat_tool)\n\n if flat_tools:\n return _serialize_to_str(flat_tools)\n return None\n\n except Exception:\n return None\n\n\ndef _to_serializable(obj: Any) -> Any:\n \"\"\"Convert an object to a JSON serializable type.\"\"\"\n if isinstance(obj, (str, int, bool, float, type(None))):\n return obj\n elif isinstance(obj, (list, tuple, set, frozenset)):\n return [_to_serializable(x) for x in obj]\n elif isinstance(obj, dict):\n return {str(key): _to_serializable(val) for (key, val) in obj.items()}\n elif isinstance(obj, (Msg, BaseModel)) or is_dataclass(obj):\n return repr(obj)\n elif inspect.isclass(obj) and issubclass(obj, BaseModel):\n return repr(obj)\n elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):\n return obj.isoformat()\n elif isinstance(obj, datetime.timedelta):\n return obj.total_seconds()\n elif isinstance(obj, enum.Enum):\n return _to_serializable(obj.value)\n else:\n return str(obj)\n\n\ndef _serialize_to_str(value: Any) -> str:\n \"\"\"Serialize value to JSON string\"\"\"\n try:\n return json.dumps(value, ensure_ascii=False)\n except TypeError:\n return json.dumps(_to_serializable(value), ensure_ascii=False)\n\n\ndef _convert_block_to_part(block: Dict[str, Any]) -> Dict[str, Any] | None:\n \"\"\"Convert content block to standardized part format.\"\"\"\n block_type = block.get(\"type\")\n\n if block_type == \"text\":\n return {\"type\": \"text\", \"content\": block.get(\"text\", \"\")}\n\n elif block_type == \"thinking\":\n return {\n \"type\": \"text\",\n \"content\": f\"[Thinking] {block.get('thinking', '')}\",\n }\n\n elif block_type == \"tool_use\":\n return {\n \"type\": \"tool_call\",\n \"id\": block.get(\"id\", \"\"),\n \"name\": block.get(\"name\", \"\"),\n \"arguments\": block.get(\"input\", {}),\n }\n\n elif block_type == \"tool_result\":\n output = block.get(\"output\", \"\")\n result = (\n _serialize_to_str(output)\n if isinstance(output, (list, dict))\n else str(output)\n )\n return {\n \"type\": \"tool_call_response\",\n \"id\": block.get(\"id\", \"\"),\n \"result\": result,\n }\n\n elif block_type in [\"image\", \"audio\", \"video\"]:\n source = block.get(\"source\", {})\n source_type = source.get(\"type\")\n media_type = source.get(\"media_type\")\n\n if source_type == \"url\":\n url = source.get(\"url\", \"\")\n # Infer mime_type from URL extension if not provided\n if not media_type:\n parsed_url = urlparse(url)\n media_type, _ = mimetypes.guess_type(parsed_url.path)\n return {\n \"type\": \"uri\",\n \"uri\": url,\n \"modality\": block_type,\n \"mime_type\": media_type,\n }\n elif source_type == \"base64\":\n if not media_type:\n default_media_types = {\n \"image\": \"image/jpeg\",\n \"audio\": \"audio/wav\",\n \"video\": \"video/mp4\",\n }\n media_type = default_media_types.get(block_type, \"unknown\")\n return {\n \"type\": \"blob\",\n \"content\": source.get(\"data\", \"\"),\n \"media_type\": media_type,\n \"modality\": block_type,\n }\n\n return None\n\n\ndef _format_msg_to_parts(msg: Msg) -> dict[str, Any]:\n \"\"\"Convert Msg to standard format (parts structure)\"\"\"\n try:\n parts = []\n for block in msg.get_content_blocks():\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n formatted_msg = {\"role\": msg.role, \"parts\": parts}\n if msg.name:\n formatted_msg[\"name\"] = msg.name\n\n return formatted_msg\n\n except Exception as e:\n logger.debug(f\"Error formatting message: {e}\")\n return {\n \"role\": msg.role,\n \"parts\": [\n {\n \"type\": \"text\",\n \"content\": str(msg.content) if msg.content else \"\",\n }\n ],\n }\n\n\ndef create_llm_invocation(\n call_instance: ChatModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> LLMInvocation:\n \"\"\"Create LLM invocation from call context.\"\"\"\n provider_name = get_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n # Convert input messages\n input_messages = []\n messages = call_args[0] if call_args else call_kwargs.get(\"messages\")\n if messages:\n try:\n converted_messages = get_message_converter(provider_name)(messages)\n input_messages = convert_agentscope_messages_to_genai_format(\n converted_messages, provider_name=provider_name\n )\n except Exception as e:\n logger.warning(\n f\"Failed to convert input messages: {e}\", exc_info=True\n )\n\n invocation = LLMInvocation(\n request_model=request_model,\n provider=provider_name,\n input_messages=input_messages,\n )\n\n # Set optional request parameters if present\n if call_kwargs.get(\"max_tokens\"):\n invocation.max_tokens = call_kwargs[\"max_tokens\"]\n if call_kwargs.get(\"temperature\"):\n invocation.temperature = call_kwargs[\"temperature\"]\n if call_kwargs.get(\"top_p\"):\n invocation.top_p = call_kwargs[\"top_p\"]\n\n # Set tool definitions if applicable\n if not call_kwargs.get(\"structured_model\", False):\n tool_definitions = _get_tool_definitions(\n call_kwargs.get(\"tools\"), call_kwargs.get(\"tool_choice\")\n )\n if tool_definitions:\n invocation.attributes[\"gen_ai.request.tool_definitions\"] = (\n tool_definitions\n )\n\n return invocation\n\n\ndef create_embedding_invocation(\n call_instance: EmbeddingModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> EmbeddingInvocation:\n \"\"\"Create Embedding invocation from call context.\"\"\"\n provider_name = get_embedding_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n invocation = EmbeddingInvocation(\n request_model=request_model,\n provider=provider_name,\n )\n\n # Set encoding formats if present\n if call_kwargs.get(\"encoding_formats\"):\n invocation.encoding_formats = call_kwargs[\"encoding_formats\"]\n\n return invocation\n\n\ndef create_agent_invocation(\n reply_instance: AgentBase,\n reply_args: Tuple[Any, ...],\n reply_kwargs: Dict[str, Any],\n) -> InvokeAgentInvocation:\n \"\"\"Create Agent invocation from reply call context.\"\"\"\n # Get provider and model info\n provider_name = None\n request_model = None\n if hasattr(reply_instance, \"model\") and reply_instance.model:\n model = reply_instance.model\n request_model = getattr(model, \"model_name\", None)\n if isinstance(model, ChatModelBase):\n provider_name = get_provider_name(model)\n\n # Convert input messages\n input_messages = []\n msg = reply_args[0] if reply_args else reply_kwargs.get(\"msg\")\n if msg:\n try:\n if isinstance(msg, Msg):\n input_messages = convert_agentscope_messages_to_genai_format(\n [msg]\n )\n elif isinstance(msg, list):\n input_messages = convert_agentscope_messages_to_genai_format(\n msg\n )\n except Exception as e:\n logger.debug(f\"Error converting agent input messages: {e}\")\n\n invocation = InvokeAgentInvocation(\n provider=provider_name,\n agent_name=getattr(reply_instance, \"name\", \"unknown_agent\"),\n agent_id=getattr(reply_instance, \"id\", \"unknown\"),\n agent_description=inspect.getdoc(reply_instance.__class__)\n or \"No description available\",\n conversation_id=_config.run_id,\n request_model=request_model,\n input_messages=input_messages,\n )\n\n # Set system instruction if available\n if hasattr(reply_instance, \"sys_prompt\") and reply_instance.sys_prompt:\n sys_prompt = reply_instance.sys_prompt\n if isinstance(sys_prompt, str):\n invocation.system_instruction = [Text(content=sys_prompt)]\n elif isinstance(sys_prompt, list):\n invocation.system_instruction = sys_prompt\n\n return invocation\n\n\ndef get_chatmodel_output_messages(chat_response: Any) -> List[Dict[str, Any]]:\n \"\"\"Convert ChatResponse to OpenTelemetry standard output message format.\"\"\"\n try:\n if not isinstance(chat_response, ChatResponse):\n return []\n\n parts = []\n for block in chat_response.content:\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n if not parts:\n parts.append({\"type\": \"text\", \"content\": \"\"})\n\n return [\n {\n \"role\": \"assistant\",\n \"parts\": parts,\n \"finish_reason\": \"stop\",\n }\n ]\n\n except Exception as e:\n logger.warning(\n f\"Error processing ChatResponse to output messages: {e}\"\n )\n return [\n {\n \"role\": \"assistant\",\n \"parts\": [\n {\"type\": \"text\", \"content\": \"\"}\n ],\n \"finish_reason\": \"error\",\n }\n ]\n\n\ndef convert_agentscope_messages_to_genai_format(\n messages: Any,\n provider_name: Optional[str] = None,\n) -> List[InputMessage]:\n \"\"\"Convert AgentScope messages to opentelemetry-util-genai InputMessage format.\n parse _convert_block_to_part result from agentscope/tracing/_converter.py\n\n This function is used by ExtendedTelemetryHandler which requires InputMessage objects.\n \"\"\"\n if not messages:\n return []\n\n if not isinstance(messages, list):\n messages = [messages]\n\n input_messages = []\n for msg in messages:\n if isinstance(msg, Msg):\n msg_dict = _format_msg_to_parts(msg)\n elif isinstance(msg, dict):\n msg_dict = msg\n else:\n continue\n\n role = msg_dict.get(\"role\", \"user\")\n parts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type == \"tool_call_response\":\n converted_parts.append(\n ToolCallResponse(\n id=part.get(\"id\"),\n response=part[\"response\"]\n if \"response\" in part\n else part.get(\"result\", \"\"),\n )\n )\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type == \"uri\":\n converted_parts.append(\n Uri(\n uri=part.get(\"uri\"),\n modality=part.get(\"modality\"),\n mime_type=part.get(\"mime_type\"),\n )\n )\n elif part_type == \"blob\":\n converted_parts.append(\n Base64Blob(\n content=part.get(\"content\", \"\"),\n modality=part.get(\"modality\"),\n mime_type=part.get(\"media_type\")\n if \"media_type\" in part\n else part.get(\"mime_type\"),\n )\n )\n else:\n # Keep other types as-is\n converted_parts.append(part)\n\n input_messages.append(InputMessage(role=role, parts=converted_parts))\n\n return input_messages\n\n\ndef convert_chatresponse_to_output_messages(\n chat_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert ChatResponse to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n output_dicts = get_chatmodel_output_messages(chat_response)\n if not output_dicts:\n return []\n\n output_messages = []\n for msg_dict in output_dicts:\n role = msg_dict.get(\"role\", \"assistant\")\n parts_dicts = msg_dict.get(\"parts\", [])\n finish_reason = msg_dict.get(\"finish_reason\", \"stop\")\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(part)\n\n output_messages.append(\n OutputMessage(\n role=role,\n parts=converted_parts,\n finish_reason=finish_reason,\n )\n )\n\n return output_messages\n\n\ndef convert_agent_response_to_output_messages(\n agent_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert agent Msg response to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n if not hasattr(agent_response, \"content\"):\n return []\n\n try:\n # Use _format_msg_to_parts to convert Msg to dict, then convert to OutputMessage\n msg_dict = _format_msg_to_parts(agent_response)\n parts_dicts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type in (\"uri\", \"blob\"):\n converted_parts.append(part)\n else:\n converted_parts.append(Text(content=str(part)))\n\n if not converted_parts:\n converted_parts.append(Text(content=\"\"))\n\n return [\n OutputMessage(\n role=\"assistant\",\n parts=converted_parts,\n finish_reason=\"stop\",\n )\n ]\n except Exception as e:\n logger.debug(f\"Failed to convert agent response: {e}\")\n return []\n"}, "9fb379443103742c16735c603968cf02eb416be4": {"t": 1785758583.4098816, "n": 20143, "h": "188e1504c8753dcd", "c": "# -*- coding: utf-8 -*-\n\"\"\"Attributes processor for span attributes.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport enum\nimport inspect\nimport json\nimport logging\nimport mimetypes\nfrom dataclasses import is_dataclass\nfrom enum import Enum\nfrom typing import Any, Dict, List, Optional, Tuple\nfrom urllib.parse import urlparse\n\nfrom agentscope import _config\nfrom agentscope.agent import AgentBase\nfrom agentscope.embedding import EmbeddingModelBase\nfrom agentscope.message import Msg\nfrom agentscope.model import ChatModelBase, ChatResponse\nfrom pydantic import BaseModel\n\nfrom opentelemetry.semconv._incubating.attributes import (\n gen_ai_attributes as GenAIAttributes,\n)\nfrom opentelemetry.util.genai.extended_types import (\n EmbeddingInvocation,\n InvokeAgentInvocation,\n)\nfrom opentelemetry.util.genai.types import (\n Base64Blob,\n InputMessage,\n LLMInvocation,\n OutputMessage,\n Reasoning,\n Text,\n ToolCall,\n ToolCallResponse,\n Uri,\n)\n\nfrom .message_converter import get_message_converter\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgentScopeGenAiProviderName(str, Enum):\n \"\"\"Extended provider names not in standard OpenTelemetry semantic conventions.\"\"\"\n\n OLLAMA = \"ollama\"\n DASHSCOPE = \"dashscope\"\n MOONSHOT = \"moonshot\"\n\n\n# Provider name mapping based on class names\n_PROVIDER_NAME_MAP = {\n \"openai\": GenAIAttributes.GenAiProviderNameValues.OPENAI.value,\n \"gemini\": GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value,\n \"anthropic\": GenAIAttributes.GenAiProviderNameValues.ANTHROPIC.value,\n \"dashscope\": AgentScopeGenAiProviderName.DASHSCOPE.value,\n \"ollama\": AgentScopeGenAiProviderName.OLLAMA.value,\n}\n\n# Base URL to provider mapping for OpenAI-compatible APIs\n_BASE_URL_PROVIDER_MAP = [\n (\"openai.com\", GenAIAttributes.GenAiProviderNameValues.OPENAI.value),\n (\n \"api.deepseek.com\",\n GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,\n ),\n (\"dashscope.aliyuncs.com\", AgentScopeGenAiProviderName.DASHSCOPE.value),\n]\n\n\ndef get_provider_name(chat_model: ChatModelBase) -> str:\n \"\"\"Parse chat model provider name\"\"\"\n classname = chat_model.__class__.__name__\n prefix = classname.removesuffix(\"ChatModel\").lower()\n\n # Special handling for DashScopeChatModel with base_http_api_url\n if (\n prefix == \"dashscope\"\n and hasattr(chat_model, \"base_http_api_url\")\n and chat_model.base_http_api_url\n ):\n base_url = chat_model.base_http_api_url\n for url_fragment, provider in _BASE_URL_PROVIDER_MAP:\n if url_fragment in base_url:\n return provider\n\n return _PROVIDER_NAME_MAP.get(prefix, \"unknown\")\n\n\ndef get_embedding_provider_name(embedding_model: EmbeddingModelBase) -> str:\n \"\"\"Parse embedding model provider name\"\"\"\n class_name = embedding_model.__class__.__name__\n\n if \"DashScope\" in class_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n elif \"OpenAI\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n elif \"Gemini\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value\n elif \"Ollama\" in class_name:\n return AgentScopeGenAiProviderName.OLLAMA.value\n else:\n model_name = getattr(embedding_model, \"model_name\", \"\")\n if \"text-embedding\" in model_name:\n if \"qwen\" in model_name or \"dashscope\" in model_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n else:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n return \"agentscope\"\n\n\ndef _get_tool_definitions(\n tools: list[dict[str, Any]] | None,\n tool_choice: str | None,\n) -> str | None:\n \"\"\"Extract and serialize tool definitions for tracing.\"\"\"\n if tools is None or not isinstance(tools, list) or len(tools) == 0:\n return None\n\n if tool_choice == \"none\":\n return None\n\n try:\n flat_tools = []\n for tool in tools:\n if not isinstance(tool, dict) or \"function\" not in tool:\n continue\n\n func_def = tool[\"function\"]\n flat_tool = {\n \"type\": tool.get(\"type\", \"function\"),\n \"name\": func_def.get(\"name\"),\n \"description\": func_def.get(\"description\"),\n \"parameters\": func_def.get(\"parameters\"),\n }\n flat_tool = {k: v for k, v in flat_tool.items() if v is not None}\n flat_tools.append(flat_tool)\n\n if flat_tools:\n return _serialize_to_str(flat_tools)\n return None\n\n except Exception:\n return None\n\n\ndef _to_serializable(obj: Any) -> Any:\n \"\"\"Convert an object to a JSON serializable type.\"\"\"\n if isinstance(obj, (str, int, bool, float, type(None))):\n return obj\n elif isinstance(obj, (list, tuple, set, frozenset)):\n return [_to_serializable(x) for x in obj]\n elif isinstance(obj, dict):\n return {str(key): _to_serializable(val) for (key, val) in obj.items()}\n elif isinstance(obj, (Msg, BaseModel)) or is_dataclass(obj):\n return repr(obj)\n elif inspect.isclass(obj) and issubclass(obj, BaseModel):\n return repr(obj)\n elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):\n return obj.isoformat()\n elif isinstance(obj, datetime.timedelta):\n return obj.total_seconds()\n elif isinstance(obj, enum.Enum):\n return _to_serializable(obj.value)\n else:\n return str(obj)\n\n\ndef _serialize_to_str(value: Any) -> str:\n \"\"\"Serialize value to JSON string\"\"\"\n try:\n return json.dumps(value, ensure_ascii=False)\n except TypeError:\n return json.dumps(_to_serializable(value), ensure_ascii=False)\n\n\ndef _convert_block_to_part(block: Dict[str, Any]) -> Dict[str, Any] | None:\n \"\"\"Convert content block to standardized part format.\"\"\"\n block_type = block.get(\"type\")\n\n if block_type == \"text\":\n return {\"type\": \"text\", \"content\": block.get(\"text\", \"\")}\n\n elif block_type == \"thinking\":\n return {\n \"type\": \"text\",\n \"content\": f\"[Thinking] {block.get('thinking', '')}\",\n }\n\n elif block_type == \"tool_use\":\n return {\n \"type\": \"tool_call\",\n \"id\": block.get(\"id\", \"\"),\n \"name\": block.get(\"name\", \"\"),\n \"arguments\": block.get(\"input\", {}),\n }\n\n elif block_type == \"tool_result\":\n output = block.get(\"output\", \"\")\n result = (\n _serialize_to_str(output)\n if isinstance(output, (list, dict))\n else str(output)\n )\n return {\n \"type\": \"tool_call_response\",\n \"id\": block.get(\"id\", \"\"),\n \"result\": result,\n }\n\n elif block_type in [\"image\", \"audio\", \"video\"]:\n source = block.get(\"source\", {})\n source_type = source.get(\"type\")\n media_type = source.get(\"media_type\")\n\n if source_type == \"url\":\n url = source.get(\"url\", \"\")\n # Infer mime_type from URL extension if not provided\n if not media_type:\n parsed_url = urlparse(url)\n media_type, _ = mimetypes.guess_type(parsed_url.path)\n return {\n \"type\": \"uri\",\n \"uri\": url,\n \"modality\": block_type,\n \"mime_type\": media_type,\n }\n elif source_type == \"base64\":\n if not media_type:\n default_media_types = {\n \"image\": \"image/jpeg\",\n \"audio\": \"audio/wav\",\n \"video\": \"video/mp4\",\n }\n media_type = default_media_types.get(block_type, \"unknown\")\n return {\n \"type\": \"blob\",\n \"content\": source.get(\"data\", \"\"),\n \"media_type\": media_type,\n \"modality\": block_type,\n }\n\n return None\n\n\ndef _format_msg_to_parts(msg: Msg) -> dict[str, Any]:\n \"\"\"Convert Msg to standard format (parts structure)\"\"\"\n try:\n parts = []\n for block in msg.get_content_blocks():\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n formatted_msg = {\"role\": msg.role, \"parts\": parts}\n if msg.name:\n formatted_msg[\"name\"] = msg.name\n\n return formatted_msg\n\n except Exception as e:\n logger.debug(f\"Error formatting message: {e}\")\n return {\n \"role\": msg.role,\n \"parts\": [\n {\n \"type\": \"text\",\n \"content\": str(msg.content) if msg.content else \"\",\n }\n ],\n }\n\n\ndef create_llm_invocation(\n call_instance: ChatModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> LLMInvocation:\n \"\"\"Create LLM invocation from call context.\"\"\"\n provider_name = get_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n # Convert input messages\n input_messages = []\n messages = call_args[0] if call_args else call_kwargs.get(\"messages\")\n if messages:\n try:\n converted_messages = get_message_converter(provider_name)(messages)\n input_messages = convert_agentscope_messages_to_genai_format(\n converted_messages, provider_name=provider_name\n )\n except Exception as e:\n logger.warning(\n f\"Failed to convert input messages: {e}\", exc_info=True\n )\n\n invocation = LLMInvocation(\n request_model=request_model,\n provider=provider_name,\n input_messages=input_messages,\n )\n\n # Set optional request parameters if present\n if call_kwargs.get(\"max_tokens\"):\n invocation.max_tokens = call_kwargs[\"max_tokens\"]\n if call_kwargs.get(\"temperature\"):\n invocation.temperature = call_kwargs[\"temperature\"]\n if call_kwargs.get(\"top_p\"):\n invocation.top_p = call_kwargs[\"top_p\"]\n\n # Set tool definitions if applicable\n if not call_kwargs.get(\"structured_model\", False):\n tool_definitions = _get_tool_definitions(\n call_kwargs.get(\"tools\"), call_kwargs.get(\"tool_choice\")\n )\n if tool_definitions:\n invocation.attributes[\"gen_ai.request.tool_definitions\"] = (\n tool_definitions\n )\n\n return invocation\n\n\ndef create_embedding_invocation(\n call_instance: EmbeddingModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> EmbeddingInvocation:\n \"\"\"Create Embedding invocation from call context.\"\"\"\n provider_name = get_embedding_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n invocation = EmbeddingInvocation(\n request_model=request_model,\n provider=provider_name,\n )\n\n # Set encoding formats if present\n if call_kwargs.get(\"encoding_formats\"):\n invocation.encoding_formats = call_kwargs[\"encoding_formats\"]\n\n return invocation\n\n\ndef create_agent_invocation(\n reply_instance: AgentBase,\n reply_args: Tuple[Any, ...],\n reply_kwargs: Dict[str, Any],\n) -> InvokeAgentInvocation:\n \"\"\"Create Agent invocation from reply call context.\"\"\"\n # Get provider and model info\n provider_name = None\n request_model = None\n if hasattr(reply_instance, \"model\") and reply_instance.model:\n model = reply_instance.model\n request_model = getattr(model, \"model_name\", None)\n if isinstance(model, ChatModelBase):\n provider_name = get_provider_name(model)\n\n # Convert input messages\n input_messages = []\n msg = reply_args[0] if reply_args else reply_kwargs.get(\"msg\")\n if msg:\n try:\n if isinstance(msg, Msg):\n input_messages = convert_agentscope_messages_to_genai_format(\n [msg]\n )\n elif isinstance(msg, list):\n input_messages = convert_agentscope_messages_to_genai_format(\n msg\n )\n except Exception as e:\n logger.debug(f\"Error converting agent input messages: {e}\")\n\n invocation = InvokeAgentInvocation(\n provider=provider_name,\n agent_name=getattr(reply_instance, \"name\", \"unknown_agent\"),\n agent_id=getattr(reply_instance, \"id\", \"unknown\"),\n agent_description=inspect.getdoc(reply_instance.__class__)\n or \"No description available\",\n conversation_id=_config.run_id,\n request_model=request_model,\n input_messages=input_messages,\n )\n\n # Set system instruction if available\n if hasattr(reply_instance, \"sys_prompt\") and reply_instance.sys_prompt:\n sys_prompt = reply_instance.sys_prompt\n if isinstance(sys_prompt, str):\n invocation.system_instruction = [Text(content=sys_prompt)]\n elif isinstance(sys_prompt, list):\n invocation.system_instruction = sys_prompt\n\n return invocation\n\n\ndef get_chatmodel_output_messages(chat_response: Any) -> List[Dict[str, Any]]:\n \"\"\"Convert ChatResponse to OpenTelemetry standard output message format.\"\"\"\n try:\n if not isinstance(chat_response, ChatResponse):\n return []\n\n parts = []\n for block in chat_response.content:\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n if not parts:\n parts.append({\"type\": \"text\", \"content\": \"\"})\n\n return [\n {\n \"role\": \"assistant\",\n \"parts\": parts,\n \"finish_reason\": \"stop\",\n }\n ]\n\n except Exception as e:\n logger.warning(\n f\"Error processing ChatResponse to output messages: {e}\"\n )\n return [\n {\n \"role\": \"assistant\",\n \"parts\": [\n {\"type\": \"text\", \"content\": \"\"}\n ],\n \"finish_reason\": \"error\",\n }\n ]\n\n\ndef convert_agentscope_messages_to_genai_format(\n messages: Any,\n provider_name: Optional[str] = None,\n) -> List[InputMessage]:\n \"\"\"Convert AgentScope messages to opentelemetry-util-genai InputMessage format.\n parse _convert_block_to_part result from agentscope/tracing/_converter.py\n\n This function is used by ExtendedTelemetryHandler which requires InputMessage objects.\n \"\"\"\n if not messages:\n return []\n\n if not isinstance(messages, list):\n messages = [messages]\n\n input_messages = []\n for msg in messages:\n if isinstance(msg, Msg):\n msg_dict = _format_msg_to_parts(msg)\n elif isinstance(msg, dict):\n msg_dict = msg\n else:\n continue\n\n role = msg_dict.get(\"role\", \"user\")\n parts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type == \"tool_call_response\":\n converted_parts.append(\n ToolCallResponse(\n id=part.get(\"id\"),\n response=part[\"response\"]\n if \"response\" in part\n else part.get(\"result\", \"\"),\n )\n )\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type == \"uri\":\n converted_parts.append(\n Uri(\n uri=part.get(\"uri\"),\n modality=part.get(\"modality\"),\n mime_type=part.get(\"mime_type\"),\n )\n )\n elif part_type == \"blob\":\n converted_parts.append(\n Base64Blob(\n content=part.get(\"content\", \"\"),\n modality=part.get(\"modality\"),\n mime_type=part.get(\"media_type\")\n if \"media_type\" in part\n else part.get(\"mime_type\"),\n )\n )\n else:\n # Keep other types as-is\n converted_parts.append(part)\n\n input_messages.append(InputMessage(role=role, parts=converted_parts))\n\n return input_messages\n\n\ndef convert_chatresponse_to_output_messages(\n chat_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert ChatResponse to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n output_dicts = get_chatmodel_output_messages(chat_response)\n if not output_dicts:\n return []\n\n output_messages = []\n for msg_dict in output_dicts:\n role = msg_dict.get(\"role\", \"assistant\")\n parts_dicts = msg_dict.get(\"parts\", [])\n finish_reason = msg_dict.get(\"finish_reason\", \"stop\")\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(part)\n\n output_messages.append(\n OutputMessage(\n role=role,\n parts=converted_parts,\n finish_reason=finish_reason,\n )\n )\n\n return output_messages\n\n\ndef convert_agent_response_to_output_messages(\n agent_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert agent Msg response to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n if not hasattr(agent_response, \"content\"):\n return []\n\n try:\n # Use _format_msg_to_parts to convert Msg to dict, then convert to OutputMessage\n msg_dict = _format_msg_to_parts(agent_response)\n parts_dicts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type in (\"uri\", \"blob\"):\n converted_parts.append(part)\n else:\n converted_parts.append(Text(content=str(part)))\n\n if not converted_parts:\n converted_parts.append(Text(content=\"\"))\n\n return [\n OutputMessage(\n role=\"assistant\",\n parts=converted_parts,\n finish_reason=\"stop\",\n )\n ]\n except Exception as e:\n logger.debug(f\"Failed to convert agent response: {e}\")\n return []\n"}, "e33feb8bb40d4a1cae610b6a95fcc19f5648ccdf": {"t": 1785758584.986063, "n": 19319, "h": "f3dca8c0250a0cc2", "c": "# -*- coding: utf-8 -*-\n\"\"\"Attributes processor for span attributes.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport enum\nimport inspect\nimport json\nimport logging\nimport mimetypes\nfrom dataclasses import is_dataclass\nfrom enum import Enum\nfrom typing import Any, Dict, List, Optional, Tuple\nfrom urllib.parse import urlparse\n\nfrom agentscope import _config\nfrom agentscope.agent import AgentBase\nfrom agentscope.embedding import EmbeddingModelBase\nfrom agentscope.message import Msg\nfrom agentscope.model import ChatModelBase, ChatResponse\nfrom pydantic import BaseModel\n\nfrom opentelemetry.semconv._incubating.attributes import (\n gen_ai_attributes as GenAIAttributes,\n)\nfrom opentelemetry.util.genai.extended_types import (\n EmbeddingInvocation,\n InvokeAgentInvocation,\n)\nfrom opentelemetry.util.genai.types import (\n InputMessage,\n LLMInvocation,\n OutputMessage,\n Reasoning,\n Text,\n ToolCall,\n ToolCallResponse,\n)\n\nfrom .message_converter import get_message_converter\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgentScopeGenAiProviderName(str, Enum):\n \"\"\"Extended provider names not in standard OpenTelemetry semantic conventions.\"\"\"\n\n OLLAMA = \"ollama\"\n DASHSCOPE = \"dashscope\"\n MOONSHOT = \"moonshot\"\n\n\n# Provider name mapping based on class names\n_PROVIDER_NAME_MAP = {\n \"openai\": GenAIAttributes.GenAiProviderNameValues.OPENAI.value,\n \"gemini\": GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value,\n \"anthropic\": GenAIAttributes.GenAiProviderNameValues.ANTHROPIC.value,\n \"dashscope\": AgentScopeGenAiProviderName.DASHSCOPE.value,\n \"ollama\": AgentScopeGenAiProviderName.OLLAMA.value,\n}\n\n# Base URL to provider mapping for OpenAI-compatible APIs\n_BASE_URL_PROVIDER_MAP = [\n (\"openai.com\", GenAIAttributes.GenAiProviderNameValues.OPENAI.value),\n (\n \"api.deepseek.com\",\n GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,\n ),\n (\"dashscope.aliyuncs.com\", AgentScopeGenAiProviderName.DASHSCOPE.value),\n]\n\n\ndef get_provider_name(chat_model: ChatModelBase) -> str:\n \"\"\"Parse chat model provider name\"\"\"\n classname = chat_model.__class__.__name__\n prefix = classname.removesuffix(\"ChatModel\").lower()\n\n # Special handling for DashScopeChatModel with base_http_api_url\n if (\n prefix == \"dashscope\"\n and hasattr(chat_model, \"base_http_api_url\")\n and chat_model.base_http_api_url\n ):\n base_url = chat_model.base_http_api_url\n for url_fragment, provider in _BASE_URL_PROVIDER_MAP:\n if url_fragment in base_url:\n return provider\n\n return _PROVIDER_NAME_MAP.get(prefix, \"unknown\")\n\n\ndef get_embedding_provider_name(embedding_model: EmbeddingModelBase) -> str:\n \"\"\"Parse embedding model provider name\"\"\"\n class_name = embedding_model.__class__.__name__\n\n if \"DashScope\" in class_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n elif \"OpenAI\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n elif \"Gemini\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value\n elif \"Ollama\" in class_name:\n return AgentScopeGenAiProviderName.OLLAMA.value\n else:\n model_name = getattr(embedding_model, \"model_name\", \"\")\n if \"text-embedding\" in model_name:\n if \"qwen\" in model_name or \"dashscope\" in model_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n else:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n return \"agentscope\"\n\n\ndef _get_tool_definitions(\n tools: list[dict[str, Any]] | None,\n tool_choice: str | None,\n) -> str | None:\n \"\"\"Extract and serialize tool definitions for tracing.\"\"\"\n if tools is None or not isinstance(tools, list) or len(tools) == 0:\n return None\n\n if tool_choice == \"none\":\n return None\n\n try:\n flat_tools = []\n for tool in tools:\n if not isinstance(tool, dict) or \"function\" not in tool:\n continue\n\n func_def = tool[\"function\"]\n flat_tool = {\n \"type\": tool.get(\"type\", \"function\"),\n \"name\": func_def.get(\"name\"),\n \"description\": func_def.get(\"description\"),\n \"parameters\": func_def.get(\"parameters\"),\n }\n flat_tool = {k: v for k, v in flat_tool.items() if v is not None}\n flat_tools.append(flat_tool)\n\n if flat_tools:\n return _serialize_to_str(flat_tools)\n return None\n\n except Exception:\n return None\n\n\ndef _to_serializable(obj: Any) -> Any:\n \"\"\"Convert an object to a JSON serializable type.\"\"\"\n if isinstance(obj, (str, int, bool, float, type(None))):\n return obj\n elif isinstance(obj, (list, tuple, set, frozenset)):\n return [_to_serializable(x) for x in obj]\n elif isinstance(obj, dict):\n return {str(key): _to_serializable(val) for (key, val) in obj.items()}\n elif isinstance(obj, (Msg, BaseModel)) or is_dataclass(obj):\n return repr(obj)\n elif inspect.isclass(obj) and issubclass(obj, BaseModel):\n return repr(obj)\n elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):\n return obj.isoformat()\n elif isinstance(obj, datetime.timedelta):\n return obj.total_seconds()\n elif isinstance(obj, enum.Enum):\n return _to_serializable(obj.value)\n else:\n return str(obj)\n\n\ndef _serialize_to_str(value: Any) -> str:\n \"\"\"Serialize value to JSON string\"\"\"\n try:\n return json.dumps(value, ensure_ascii=False)\n except TypeError:\n return json.dumps(_to_serializable(value), ensure_ascii=False)\n\n\ndef _convert_block_to_part(block: Dict[str, Any]) -> Dict[str, Any] | None:\n \"\"\"Convert content block to standardized part format.\"\"\"\n block_type = block.get(\"type\")\n\n if block_type == \"text\":\n return {\"type\": \"text\", \"content\": block.get(\"text\", \"\")}\n\n elif block_type == \"thinking\":\n return {\n \"type\": \"text\",\n \"content\": f\"[Thinking] {block.get('thinking', '')}\",\n }\n\n elif block_type == \"tool_use\":\n return {\n \"type\": \"tool_call\",\n \"id\": block.get(\"id\", \"\"),\n \"name\": block.get(\"name\", \"\"),\n \"arguments\": block.get(\"input\", {}),\n }\n\n elif block_type == \"tool_result\":\n output = block.get(\"output\", \"\")\n result = (\n _serialize_to_str(output)\n if isinstance(output, (list, dict))\n else str(output)\n )\n return {\n \"type\": \"tool_call_response\",\n \"id\": block.get(\"id\", \"\"),\n \"result\": result,\n }\n\n elif block_type in [\"image\", \"audio\", \"video\"]:\n source = block.get(\"source\", {})\n source_type = source.get(\"type\")\n media_type = source.get(\"media_type\")\n\n if source_type == \"url\":\n url = source.get(\"url\", \"\")\n # Infer mime_type from URL extension if not provided\n if not media_type:\n parsed_url = urlparse(url)\n media_type, _ = mimetypes.guess_type(parsed_url.path)\n return {\n \"type\": \"uri\",\n \"uri\": url,\n \"modality\": block_type,\n \"mime_type\": media_type,\n }\n elif source_type == \"base64\":\n if not media_type:\n default_media_types = {\n \"image\": \"image/jpeg\",\n \"audio\": \"audio/wav\",\n \"video\": \"video/mp4\",\n }\n media_type = default_media_types.get(block_type, \"unknown\")\n return {\n \"type\": \"blob\",\n \"content\": source.get(\"data\", \"\"),\n \"media_type\": media_type,\n \"modality\": block_type,\n }\n\n return None\n\n\ndef _format_msg_to_parts(msg: Msg) -> dict[str, Any]:\n \"\"\"Convert Msg to standard format (parts structure)\"\"\"\n try:\n parts = []\n for block in msg.get_content_blocks():\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n formatted_msg = {\"role\": msg.role, \"parts\": parts}\n if msg.name:\n formatted_msg[\"name\"] = msg.name\n\n return formatted_msg\n\n except Exception as e:\n logger.debug(f\"Error formatting message: {e}\")\n return {\n \"role\": msg.role,\n \"parts\": [\n {\n \"type\": \"text\",\n \"content\": str(msg.content) if msg.content else \"\",\n }\n ],\n }\n\n\ndef create_llm_invocation(\n call_instance: ChatModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> LLMInvocation:\n \"\"\"Create LLM invocation from call context.\"\"\"\n provider_name = get_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n # Convert input messages\n input_messages = []\n messages = call_args[0] if call_args else call_kwargs.get(\"messages\")\n if messages:\n try:\n converted_messages = get_message_converter(provider_name)(messages)\n input_messages = convert_agentscope_messages_to_genai_format(\n converted_messages, provider_name=provider_name\n )\n except Exception as e:\n logger.warning(\n f\"Failed to convert input messages: {e}\", exc_info=True\n )\n\n invocation = LLMInvocation(\n request_model=request_model,\n provider=provider_name,\n input_messages=input_messages,\n )\n\n # Set optional request parameters if present\n if call_kwargs.get(\"max_tokens\"):\n invocation.max_tokens = call_kwargs[\"max_tokens\"]\n if call_kwargs.get(\"temperature\"):\n invocation.temperature = call_kwargs[\"temperature\"]\n if call_kwargs.get(\"top_p\"):\n invocation.top_p = call_kwargs[\"top_p\"]\n\n # Set tool definitions if applicable\n if not call_kwargs.get(\"structured_model\", False):\n tool_definitions = _get_tool_definitions(\n call_kwargs.get(\"tools\"), call_kwargs.get(\"tool_choice\")\n )\n if tool_definitions:\n invocation.attributes[\"gen_ai.request.tool_definitions\"] = (\n tool_definitions\n )\n\n return invocation\n\n\ndef create_embedding_invocation(\n call_instance: EmbeddingModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> EmbeddingInvocation:\n \"\"\"Create Embedding invocation from call context.\"\"\"\n provider_name = get_embedding_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n invocation = EmbeddingInvocation(\n request_model=request_model,\n provider=provider_name,\n )\n\n # Set encoding formats if present\n if call_kwargs.get(\"encoding_formats\"):\n invocation.encoding_formats = call_kwargs[\"encoding_formats\"]\n\n return invocation\n\n\ndef create_agent_invocation(\n reply_instance: AgentBase,\n reply_args: Tuple[Any, ...],\n reply_kwargs: Dict[str, Any],\n) -> InvokeAgentInvocation:\n \"\"\"Create Agent invocation from reply call context.\"\"\"\n # Get provider and model info\n provider_name = None\n request_model = None\n if hasattr(reply_instance, \"model\") and reply_instance.model:\n model = reply_instance.model\n request_model = getattr(model, \"model_name\", None)\n if isinstance(model, ChatModelBase):\n provider_name = get_provider_name(model)\n\n # Convert input messages\n input_messages = []\n msg = reply_args[0] if reply_args else reply_kwargs.get(\"msg\")\n if msg:\n try:\n if isinstance(msg, Msg):\n input_messages = convert_agentscope_messages_to_genai_format(\n [msg]\n )\n elif isinstance(msg, list):\n input_messages = convert_agentscope_messages_to_genai_format(\n msg\n )\n except Exception as e:\n logger.debug(f\"Error converting agent input messages: {e}\")\n\n invocation = InvokeAgentInvocation(\n provider=provider_name,\n agent_name=getattr(reply_instance, \"name\", \"unknown_agent\"),\n agent_id=getattr(reply_instance, \"id\", \"unknown\"),\n agent_description=inspect.getdoc(reply_instance.__class__)\n or \"No description available\",\n conversation_id=_config.run_id,\n request_model=request_model,\n input_messages=input_messages,\n )\n\n # Set system instruction if available\n if hasattr(reply_instance, \"sys_prompt\") and reply_instance.sys_prompt:\n sys_prompt = reply_instance.sys_prompt\n if isinstance(sys_prompt, str):\n invocation.system_instruction = [Text(content=sys_prompt)]\n elif isinstance(sys_prompt, list):\n invocation.system_instruction = sys_prompt\n\n return invocation\n\n\ndef get_chatmodel_output_messages(chat_response: Any) -> List[Dict[str, Any]]:\n \"\"\"Convert ChatResponse to OpenTelemetry standard output message format.\"\"\"\n try:\n if not isinstance(chat_response, ChatResponse):\n return []\n\n parts = []\n for block in chat_response.content:\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n if not parts:\n parts.append({\"type\": \"text\", \"content\": \"\"})\n\n return [\n {\n \"role\": \"assistant\",\n \"parts\": parts,\n \"finish_reason\": \"stop\",\n }\n ]\n\n except Exception as e:\n logger.warning(\n f\"Error processing ChatResponse to output messages: {e}\"\n )\n return [\n {\n \"role\": \"assistant\",\n \"parts\": [\n {\"type\": \"text\", \"content\": \"\"}\n ],\n \"finish_reason\": \"error\",\n }\n ]\n\n\ndef convert_agentscope_messages_to_genai_format(\n messages: Any,\n provider_name: Optional[str] = None,\n) -> List[InputMessage]:\n \"\"\"Convert AgentScope messages to opentelemetry-util-genai InputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires InputMessage objects.\n \"\"\"\n if not messages:\n return []\n\n if not isinstance(messages, list):\n messages = [messages]\n\n input_messages = []\n for msg in messages:\n if isinstance(msg, Msg):\n msg_dict = _format_msg_to_parts(msg)\n elif isinstance(msg, dict):\n msg_dict = msg\n else:\n continue\n\n role = msg_dict.get(\"role\", \"user\")\n parts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type == \"tool_call_response\":\n converted_parts.append(\n ToolCallResponse(\n id=part.get(\"id\"),\n response=part.get(\"result\", \"\"),\n )\n )\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type in (\"uri\", \"blob\"):\n converted_parts.append(part)\n else:\n # Keep other types as-is\n converted_parts.append(part)\n\n input_messages.append(InputMessage(role=role, parts=converted_parts))\n\n return input_messages\n\n\ndef convert_chatresponse_to_output_messages(\n chat_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert ChatResponse to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n output_dicts = get_chatmodel_output_messages(chat_response)\n if not output_dicts:\n return []\n\n output_messages = []\n for msg_dict in output_dicts:\n role = msg_dict.get(\"role\", \"assistant\")\n parts_dicts = msg_dict.get(\"parts\", [])\n finish_reason = msg_dict.get(\"finish_reason\", \"stop\")\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(part)\n\n output_messages.append(\n OutputMessage(\n role=role,\n parts=converted_parts,\n finish_reason=finish_reason,\n )\n )\n\n return output_messages\n\n\ndef convert_agent_response_to_output_messages(\n agent_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert agent Msg response to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n if not hasattr(agent_response, \"content\"):\n return []\n\n try:\n # Use _format_msg_to_parts to convert Msg to dict, then convert to OutputMessage\n msg_dict = _format_msg_to_parts(agent_response)\n parts_dicts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type in (\"uri\", \"blob\"):\n converted_parts.append(part)\n else:\n converted_parts.append(Text(content=str(part)))\n\n if not converted_parts:\n converted_parts.append(Text(content=\"\"))\n\n return [\n OutputMessage(\n role=\"assistant\",\n parts=converted_parts,\n finish_reason=\"stop\",\n )\n ]\n except Exception as e:\n logger.debug(f\"Failed to convert agent response: {e}\")\n return []\n"}, "31f28b475dfec73e0954a7596b86f0e48a66cd9f": {"t": 1785758586.554759, "n": 19230, "h": "2ff23aa05b7e967c", "c": "# -*- coding: utf-8 -*-\n\"\"\"Attributes processor for span attributes.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport enum\nimport inspect\nimport json\nimport logging\nimport mimetypes\nfrom dataclasses import is_dataclass\nfrom enum import Enum\nfrom typing import Any, Dict, List, Optional, Tuple\n\nfrom agentscope import _config\nfrom agentscope.agent import AgentBase\nfrom agentscope.embedding import EmbeddingModelBase\nfrom agentscope.message import Msg\nfrom agentscope.model import ChatModelBase, ChatResponse\nfrom pydantic import BaseModel\n\nfrom opentelemetry.semconv._incubating.attributes import (\n gen_ai_attributes as GenAIAttributes,\n)\nfrom opentelemetry.util.genai.extended_types import (\n EmbeddingInvocation,\n InvokeAgentInvocation,\n)\nfrom opentelemetry.util.genai.types import (\n InputMessage,\n LLMInvocation,\n OutputMessage,\n Reasoning,\n Text,\n ToolCall,\n ToolCallResponse,\n)\n\nfrom .message_converter import get_message_converter\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgentScopeGenAiProviderName(str, Enum):\n \"\"\"Extended provider names not in standard OpenTelemetry semantic conventions.\"\"\"\n\n OLLAMA = \"ollama\"\n DASHSCOPE = \"dashscope\"\n MOONSHOT = \"moonshot\"\n\n\n# Provider name mapping based on class names\n_PROVIDER_NAME_MAP = {\n \"openai\": GenAIAttributes.GenAiProviderNameValues.OPENAI.value,\n \"gemini\": GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value,\n \"anthropic\": GenAIAttributes.GenAiProviderNameValues.ANTHROPIC.value,\n \"dashscope\": AgentScopeGenAiProviderName.DASHSCOPE.value,\n \"ollama\": AgentScopeGenAiProviderName.OLLAMA.value,\n}\n\n# Base URL to provider mapping for OpenAI-compatible APIs\n_BASE_URL_PROVIDER_MAP = [\n (\"openai.com\", GenAIAttributes.GenAiProviderNameValues.OPENAI.value),\n (\n \"api.deepseek.com\",\n GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,\n ),\n (\"dashscope.aliyuncs.com\", AgentScopeGenAiProviderName.DASHSCOPE.value),\n]\n\n\ndef get_provider_name(chat_model: ChatModelBase) -> str:\n \"\"\"Parse chat model provider name\"\"\"\n classname = chat_model.__class__.__name__\n prefix = classname.removesuffix(\"ChatModel\").lower()\n\n # Special handling for DashScopeChatModel with base_http_api_url\n if (\n prefix == \"dashscope\"\n and hasattr(chat_model, \"base_http_api_url\")\n and chat_model.base_http_api_url\n ):\n base_url = chat_model.base_http_api_url\n for url_fragment, provider in _BASE_URL_PROVIDER_MAP:\n if url_fragment in base_url:\n return provider\n\n return _PROVIDER_NAME_MAP.get(prefix, \"unknown\")\n\n\ndef get_embedding_provider_name(embedding_model: EmbeddingModelBase) -> str:\n \"\"\"Parse embedding model provider name\"\"\"\n class_name = embedding_model.__class__.__name__\n\n if \"DashScope\" in class_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n elif \"OpenAI\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n elif \"Gemini\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value\n elif \"Ollama\" in class_name:\n return AgentScopeGenAiProviderName.OLLAMA.value\n else:\n model_name = getattr(embedding_model, \"model_name\", \"\")\n if \"text-embedding\" in model_name:\n if \"qwen\" in model_name or \"dashscope\" in model_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n else:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n return \"agentscope\"\n\n\ndef _get_tool_definitions(\n tools: list[dict[str, Any]] | None,\n tool_choice: str | None,\n) -> str | None:\n \"\"\"Extract and serialize tool definitions for tracing.\"\"\"\n if tools is None or not isinstance(tools, list) or len(tools) == 0:\n return None\n\n if tool_choice == \"none\":\n return None\n\n try:\n flat_tools = []\n for tool in tools:\n if not isinstance(tool, dict) or \"function\" not in tool:\n continue\n\n func_def = tool[\"function\"]\n flat_tool = {\n \"type\": tool.get(\"type\", \"function\"),\n \"name\": func_def.get(\"name\"),\n \"description\": func_def.get(\"description\"),\n \"parameters\": func_def.get(\"parameters\"),\n }\n flat_tool = {k: v for k, v in flat_tool.items() if v is not None}\n flat_tools.append(flat_tool)\n\n if flat_tools:\n return _serialize_to_str(flat_tools)\n return None\n\n except Exception:\n return None\n\n\ndef _to_serializable(obj: Any) -> Any:\n \"\"\"Convert an object to a JSON serializable type.\"\"\"\n if isinstance(obj, (str, int, bool, float, type(None))):\n return obj\n elif isinstance(obj, (list, tuple, set, frozenset)):\n return [_to_serializable(x) for x in obj]\n elif isinstance(obj, dict):\n return {str(key): _to_serializable(val) for (key, val) in obj.items()}\n elif isinstance(obj, (Msg, BaseModel)) or is_dataclass(obj):\n return repr(obj)\n elif inspect.isclass(obj) and issubclass(obj, BaseModel):\n return repr(obj)\n elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):\n return obj.isoformat()\n elif isinstance(obj, datetime.timedelta):\n return obj.total_seconds()\n elif isinstance(obj, enum.Enum):\n return _to_serializable(obj.value)\n else:\n return str(obj)\n\n\ndef _serialize_to_str(value: Any) -> str:\n \"\"\"Serialize value to JSON string\"\"\"\n try:\n return json.dumps(value, ensure_ascii=False)\n except TypeError:\n return json.dumps(_to_serializable(value), ensure_ascii=False)\n\n\ndef _convert_block_to_part(block: Dict[str, Any]) -> Dict[str, Any] | None:\n \"\"\"Convert content block to standardized part format.\"\"\"\n block_type = block.get(\"type\")\n\n if block_type == \"text\":\n return {\"type\": \"text\", \"content\": block.get(\"text\", \"\")}\n\n elif block_type == \"thinking\":\n return {\n \"type\": \"text\",\n \"content\": f\"[Thinking] {block.get('thinking', '')}\",\n }\n\n elif block_type == \"tool_use\":\n return {\n \"type\": \"tool_call\",\n \"id\": block.get(\"id\", \"\"),\n \"name\": block.get(\"name\", \"\"),\n \"arguments\": block.get(\"input\", {}),\n }\n\n elif block_type == \"tool_result\":\n output = block.get(\"output\", \"\")\n result = (\n _serialize_to_str(output)\n if isinstance(output, (list, dict))\n else str(output)\n )\n return {\n \"type\": \"tool_call_response\",\n \"id\": block.get(\"id\", \"\"),\n \"result\": result,\n }\n\n elif block_type in [\"image\", \"audio\", \"video\"]:\n source = block.get(\"source\", {})\n source_type = source.get(\"type\")\n media_type = source.get(\"media_type\")\n\n if source_type == \"url\":\n url = source.get(\"url\", \"\")\n # Infer mime_type from URL extension if not provided\n if not media_type:\n media_type, _ = mimetypes.guess_type(url)\n return {\n \"type\": \"uri\",\n \"uri\": url,\n \"modality\": block_type,\n \"mime_type\": media_type,\n }\n elif source_type == \"base64\":\n if not media_type:\n default_media_types = {\n \"image\": \"image/jpeg\",\n \"audio\": \"audio/wav\",\n \"video\": \"video/mp4\",\n }\n media_type = default_media_types.get(block_type, \"unknown\")\n return {\n \"type\": \"blob\",\n \"content\": source.get(\"data\", \"\"),\n \"media_type\": media_type,\n \"modality\": block_type,\n }\n\n return None\n\n\ndef _format_msg_to_parts(msg: Msg) -> dict[str, Any]:\n \"\"\"Convert Msg to standard format (parts structure)\"\"\"\n try:\n parts = []\n for block in msg.get_content_blocks():\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n formatted_msg = {\"role\": msg.role, \"parts\": parts}\n if msg.name:\n formatted_msg[\"name\"] = msg.name\n\n return formatted_msg\n\n except Exception as e:\n logger.debug(f\"Error formatting message: {e}\")\n return {\n \"role\": msg.role,\n \"parts\": [\n {\n \"type\": \"text\",\n \"content\": str(msg.content) if msg.content else \"\",\n }\n ],\n }\n\n\ndef create_llm_invocation(\n call_instance: ChatModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> LLMInvocation:\n \"\"\"Create LLM invocation from call context.\"\"\"\n provider_name = get_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n # Convert input messages\n input_messages = []\n messages = call_args[0] if call_args else call_kwargs.get(\"messages\")\n if messages:\n try:\n converted_messages = get_message_converter(provider_name)(messages)\n input_messages = convert_agentscope_messages_to_genai_format(\n converted_messages, provider_name=provider_name\n )\n except Exception as e:\n logger.warning(\n f\"Failed to convert input messages: {e}\", exc_info=True\n )\n\n invocation = LLMInvocation(\n request_model=request_model,\n provider=provider_name,\n input_messages=input_messages,\n )\n\n # Set optional request parameters if present\n if call_kwargs.get(\"max_tokens\"):\n invocation.max_tokens = call_kwargs[\"max_tokens\"]\n if call_kwargs.get(\"temperature\"):\n invocation.temperature = call_kwargs[\"temperature\"]\n if call_kwargs.get(\"top_p\"):\n invocation.top_p = call_kwargs[\"top_p\"]\n\n # Set tool definitions if applicable\n if not call_kwargs.get(\"structured_model\", False):\n tool_definitions = _get_tool_definitions(\n call_kwargs.get(\"tools\"), call_kwargs.get(\"tool_choice\")\n )\n if tool_definitions:\n invocation.attributes[\"gen_ai.request.tool_definitions\"] = (\n tool_definitions\n )\n\n return invocation\n\n\ndef create_embedding_invocation(\n call_instance: EmbeddingModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> EmbeddingInvocation:\n \"\"\"Create Embedding invocation from call context.\"\"\"\n provider_name = get_embedding_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n invocation = EmbeddingInvocation(\n request_model=request_model,\n provider=provider_name,\n )\n\n # Set encoding formats if present\n if call_kwargs.get(\"encoding_formats\"):\n invocation.encoding_formats = call_kwargs[\"encoding_formats\"]\n\n return invocation\n\n\ndef create_agent_invocation(\n reply_instance: AgentBase,\n reply_args: Tuple[Any, ...],\n reply_kwargs: Dict[str, Any],\n) -> InvokeAgentInvocation:\n \"\"\"Create Agent invocation from reply call context.\"\"\"\n # Get provider and model info\n provider_name = None\n request_model = None\n if hasattr(reply_instance, \"model\") and reply_instance.model:\n model = reply_instance.model\n request_model = getattr(model, \"model_name\", None)\n if isinstance(model, ChatModelBase):\n provider_name = get_provider_name(model)\n\n # Convert input messages\n input_messages = []\n msg = reply_args[0] if reply_args else reply_kwargs.get(\"msg\")\n if msg:\n try:\n if isinstance(msg, Msg):\n input_messages = convert_agentscope_messages_to_genai_format(\n [msg]\n )\n elif isinstance(msg, list):\n input_messages = convert_agentscope_messages_to_genai_format(\n msg\n )\n except Exception as e:\n logger.debug(f\"Error converting agent input messages: {e}\")\n\n invocation = InvokeAgentInvocation(\n provider=provider_name,\n agent_name=getattr(reply_instance, \"name\", \"unknown_agent\"),\n agent_id=getattr(reply_instance, \"id\", \"unknown\"),\n agent_description=inspect.getdoc(reply_instance.__class__)\n or \"No description available\",\n conversation_id=_config.run_id,\n request_model=request_model,\n input_messages=input_messages,\n )\n\n # Set system instruction if available\n if hasattr(reply_instance, \"sys_prompt\") and reply_instance.sys_prompt:\n sys_prompt = reply_instance.sys_prompt\n if isinstance(sys_prompt, str):\n invocation.system_instruction = [Text(content=sys_prompt)]\n elif isinstance(sys_prompt, list):\n invocation.system_instruction = sys_prompt\n\n return invocation\n\n\ndef get_chatmodel_output_messages(chat_response: Any) -> List[Dict[str, Any]]:\n \"\"\"Convert ChatResponse to OpenTelemetry standard output message format.\"\"\"\n try:\n if not isinstance(chat_response, ChatResponse):\n return []\n\n parts = []\n for block in chat_response.content:\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n if not parts:\n parts.append({\"type\": \"text\", \"content\": \"\"})\n\n return [\n {\n \"role\": \"assistant\",\n \"parts\": parts,\n \"finish_reason\": \"stop\",\n }\n ]\n\n except Exception as e:\n logger.warning(\n f\"Error processing ChatResponse to output messages: {e}\"\n )\n return [\n {\n \"role\": \"assistant\",\n \"parts\": [\n {\"type\": \"text\", \"content\": \"\"}\n ],\n \"finish_reason\": \"error\",\n }\n ]\n\n\ndef convert_agentscope_messages_to_genai_format(\n messages: Any,\n provider_name: Optional[str] = None,\n) -> List[InputMessage]:\n \"\"\"Convert AgentScope messages to opentelemetry-util-genai InputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires InputMessage objects.\n \"\"\"\n if not messages:\n return []\n\n if not isinstance(messages, list):\n messages = [messages]\n\n input_messages = []\n for msg in messages:\n if isinstance(msg, Msg):\n msg_dict = _format_msg_to_parts(msg)\n elif isinstance(msg, dict):\n msg_dict = msg\n else:\n continue\n\n role = msg_dict.get(\"role\", \"user\")\n parts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type == \"tool_call_response\":\n converted_parts.append(\n ToolCallResponse(\n id=part.get(\"id\"),\n response=part.get(\"result\", \"\"),\n )\n )\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type in (\"uri\", \"blob\"):\n converted_parts.append(part)\n else:\n # Keep other types as-is\n converted_parts.append(part)\n\n input_messages.append(InputMessage(role=role, parts=converted_parts))\n\n return input_messages\n\n\ndef convert_chatresponse_to_output_messages(\n chat_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert ChatResponse to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n output_dicts = get_chatmodel_output_messages(chat_response)\n if not output_dicts:\n return []\n\n output_messages = []\n for msg_dict in output_dicts:\n role = msg_dict.get(\"role\", \"assistant\")\n parts_dicts = msg_dict.get(\"parts\", [])\n finish_reason = msg_dict.get(\"finish_reason\", \"stop\")\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(part)\n\n output_messages.append(\n OutputMessage(\n role=role,\n parts=converted_parts,\n finish_reason=finish_reason,\n )\n )\n\n return output_messages\n\n\ndef convert_agent_response_to_output_messages(\n agent_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert agent Msg response to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n if not hasattr(agent_response, \"content\"):\n return []\n\n try:\n # Use _format_msg_to_parts to convert Msg to dict, then convert to OutputMessage\n msg_dict = _format_msg_to_parts(agent_response)\n parts_dicts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type in (\"uri\", \"blob\"):\n converted_parts.append(part)\n else:\n converted_parts.append(Text(content=str(part)))\n\n if not converted_parts:\n converted_parts.append(Text(content=\"\"))\n\n return [\n OutputMessage(\n role=\"assistant\",\n parts=converted_parts,\n finish_reason=\"stop\",\n )\n ]\n except Exception as e:\n logger.debug(f\"Failed to convert agent response: {e}\")\n return []\n"}, "232fc7a10beafe96c05b34664b803760754bb07a": {"t": 1785758588.038534, "n": 19037, "h": "2bd433a95ed8b283", "c": "# -*- coding: utf-8 -*-\n\"\"\"Attributes processor for span attributes.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport enum\nimport inspect\nimport json\nimport logging\nfrom dataclasses import is_dataclass\nfrom enum import Enum\nfrom typing import Any, Dict, List, Optional, Tuple\n\nfrom agentscope import _config\nfrom agentscope.agent import AgentBase\nfrom agentscope.embedding import EmbeddingModelBase\nfrom agentscope.message import Msg\nfrom agentscope.model import ChatModelBase, ChatResponse\nfrom pydantic import BaseModel\n\nfrom opentelemetry.semconv._incubating.attributes import (\n gen_ai_attributes as GenAIAttributes,\n)\nfrom opentelemetry.util.genai.extended_types import (\n EmbeddingInvocation,\n InvokeAgentInvocation,\n)\nfrom opentelemetry.util.genai.types import (\n InputMessage,\n LLMInvocation,\n OutputMessage,\n Reasoning,\n Text,\n ToolCall,\n ToolCallResponse,\n)\n\nfrom .message_converter import get_message_converter\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgentScopeGenAiProviderName(str, Enum):\n \"\"\"Extended provider names not in standard OpenTelemetry semantic conventions.\"\"\"\n\n OLLAMA = \"ollama\"\n DASHSCOPE = \"dashscope\"\n MOONSHOT = \"moonshot\"\n\n\n# Provider name mapping based on class names\n_PROVIDER_NAME_MAP = {\n \"openai\": GenAIAttributes.GenAiProviderNameValues.OPENAI.value,\n \"gemini\": GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value,\n \"anthropic\": GenAIAttributes.GenAiProviderNameValues.ANTHROPIC.value,\n \"dashscope\": AgentScopeGenAiProviderName.DASHSCOPE.value,\n \"ollama\": AgentScopeGenAiProviderName.OLLAMA.value,\n}\n\n# Base URL to provider mapping for OpenAI-compatible APIs\n_BASE_URL_PROVIDER_MAP = [\n (\"openai.com\", GenAIAttributes.GenAiProviderNameValues.OPENAI.value),\n (\n \"api.deepseek.com\",\n GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,\n ),\n (\"dashscope.aliyuncs.com\", AgentScopeGenAiProviderName.DASHSCOPE.value),\n]\n\n\ndef get_provider_name(chat_model: ChatModelBase) -> str:\n \"\"\"Parse chat model provider name\"\"\"\n classname = chat_model.__class__.__name__\n prefix = classname.removesuffix(\"ChatModel\").lower()\n\n # Special handling for DashScopeChatModel with base_http_api_url\n if (\n prefix == \"dashscope\"\n and hasattr(chat_model, \"base_http_api_url\")\n and chat_model.base_http_api_url\n ):\n base_url = chat_model.base_http_api_url\n for url_fragment, provider in _BASE_URL_PROVIDER_MAP:\n if url_fragment in base_url:\n return provider\n\n return _PROVIDER_NAME_MAP.get(prefix, \"unknown\")\n\n\ndef get_embedding_provider_name(embedding_model: EmbeddingModelBase) -> str:\n \"\"\"Parse embedding model provider name\"\"\"\n class_name = embedding_model.__class__.__name__\n\n if \"DashScope\" in class_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n elif \"OpenAI\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n elif \"Gemini\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value\n elif \"Ollama\" in class_name:\n return AgentScopeGenAiProviderName.OLLAMA.value\n else:\n model_name = getattr(embedding_model, \"model_name\", \"\")\n if \"text-embedding\" in model_name:\n if \"qwen\" in model_name or \"dashscope\" in model_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n else:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n return \"agentscope\"\n\n\ndef _get_tool_definitions(\n tools: list[dict[str, Any]] | None,\n tool_choice: str | None,\n) -> str | None:\n \"\"\"Extract and serialize tool definitions for tracing.\"\"\"\n if tools is None or not isinstance(tools, list) or len(tools) == 0:\n return None\n\n if tool_choice == \"none\":\n return None\n\n try:\n flat_tools = []\n for tool in tools:\n if not isinstance(tool, dict) or \"function\" not in tool:\n continue\n\n func_def = tool[\"function\"]\n flat_tool = {\n \"type\": tool.get(\"type\", \"function\"),\n \"name\": func_def.get(\"name\"),\n \"description\": func_def.get(\"description\"),\n \"parameters\": func_def.get(\"parameters\"),\n }\n flat_tool = {k: v for k, v in flat_tool.items() if v is not None}\n flat_tools.append(flat_tool)\n\n if flat_tools:\n return _serialize_to_str(flat_tools)\n return None\n\n except Exception:\n return None\n\n\ndef _to_serializable(obj: Any) -> Any:\n \"\"\"Convert an object to a JSON serializable type.\"\"\"\n if isinstance(obj, (str, int, bool, float, type(None))):\n return obj\n elif isinstance(obj, (list, tuple, set, frozenset)):\n return [_to_serializable(x) for x in obj]\n elif isinstance(obj, dict):\n return {str(key): _to_serializable(val) for (key, val) in obj.items()}\n elif isinstance(obj, (Msg, BaseModel)) or is_dataclass(obj):\n return repr(obj)\n elif inspect.isclass(obj) and issubclass(obj, BaseModel):\n return repr(obj)\n elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):\n return obj.isoformat()\n elif isinstance(obj, datetime.timedelta):\n return obj.total_seconds()\n elif isinstance(obj, enum.Enum):\n return _to_serializable(obj.value)\n else:\n return str(obj)\n\n\ndef _serialize_to_str(value: Any) -> str:\n \"\"\"Serialize value to JSON string\"\"\"\n try:\n return json.dumps(value, ensure_ascii=False)\n except TypeError:\n return json.dumps(_to_serializable(value), ensure_ascii=False)\n\n\ndef _convert_block_to_part(block: Dict[str, Any]) -> Dict[str, Any] | None:\n \"\"\"Convert content block to standardized part format.\"\"\"\n block_type = block.get(\"type\")\n\n if block_type == \"text\":\n return {\"type\": \"text\", \"content\": block.get(\"text\", \"\")}\n\n elif block_type == \"thinking\":\n return {\n \"type\": \"text\",\n \"content\": f\"[Thinking] {block.get('thinking', '')}\",\n }\n\n elif block_type == \"tool_use\":\n return {\n \"type\": \"tool_call\",\n \"id\": block.get(\"id\", \"\"),\n \"name\": block.get(\"name\", \"\"),\n \"arguments\": block.get(\"input\", {}),\n }\n\n elif block_type == \"tool_result\":\n output = block.get(\"output\", \"\")\n result = (\n _serialize_to_str(output)\n if isinstance(output, (list, dict))\n else str(output)\n )\n return {\n \"type\": \"tool_call_response\",\n \"id\": block.get(\"id\", \"\"),\n \"result\": result,\n }\n\n elif block_type in [\"image\", \"audio\", \"video\"]:\n source = block.get(\"source\", {})\n source_type = source.get(\"type\")\n media_type = source.get(\"media_type\")\n\n if source_type == \"url\":\n return {\n \"type\": \"uri\",\n \"uri\": source.get(\"url\", \"\"),\n \"modality\": block_type,\n \"mime_type\": media_type,\n }\n elif source_type == \"base64\":\n if not media_type:\n default_media_types = {\n \"image\": \"image/jpeg\",\n \"audio\": \"audio/wav\",\n \"video\": \"video/mp4\",\n }\n media_type = default_media_types.get(block_type, \"unknown\")\n return {\n \"type\": \"blob\",\n \"content\": source.get(\"data\", \"\"),\n \"media_type\": media_type,\n \"modality\": block_type,\n }\n\n return None\n\n\ndef _format_msg_to_parts(msg: Msg) -> dict[str, Any]:\n \"\"\"Convert Msg to standard format (parts structure)\"\"\"\n try:\n parts = []\n for block in msg.get_content_blocks():\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n formatted_msg = {\"role\": msg.role, \"parts\": parts}\n if msg.name:\n formatted_msg[\"name\"] = msg.name\n\n return formatted_msg\n\n except Exception as e:\n logger.debug(f\"Error formatting message: {e}\")\n return {\n \"role\": msg.role,\n \"parts\": [\n {\n \"type\": \"text\",\n \"content\": str(msg.content) if msg.content else \"\",\n }\n ],\n }\n\n\ndef create_llm_invocation(\n call_instance: ChatModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> LLMInvocation:\n \"\"\"Create LLM invocation from call context.\"\"\"\n provider_name = get_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n # Convert input messages\n input_messages = []\n messages = call_args[0] if call_args else call_kwargs.get(\"messages\")\n if messages:\n try:\n converted_messages = get_message_converter(provider_name)(messages)\n input_messages = convert_agentscope_messages_to_genai_format(\n converted_messages, provider_name=provider_name\n )\n except Exception as e:\n logger.warning(\n f\"Failed to convert input messages: {e}\", exc_info=True\n )\n\n invocation = LLMInvocation(\n request_model=request_model,\n provider=provider_name,\n input_messages=input_messages,\n )\n\n # Set optional request parameters if present\n if call_kwargs.get(\"max_tokens\"):\n invocation.max_tokens = call_kwargs[\"max_tokens\"]\n if call_kwargs.get(\"temperature\"):\n invocation.temperature = call_kwargs[\"temperature\"]\n if call_kwargs.get(\"top_p\"):\n invocation.top_p = call_kwargs[\"top_p\"]\n\n # Set tool definitions if applicable\n if not call_kwargs.get(\"structured_model\", False):\n tool_definitions = _get_tool_definitions(\n call_kwargs.get(\"tools\"), call_kwargs.get(\"tool_choice\")\n )\n if tool_definitions:\n invocation.attributes[\"gen_ai.request.tool_definitions\"] = (\n tool_definitions\n )\n\n return invocation\n\n\ndef create_embedding_invocation(\n call_instance: EmbeddingModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> EmbeddingInvocation:\n \"\"\"Create Embedding invocation from call context.\"\"\"\n provider_name = get_embedding_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n invocation = EmbeddingInvocation(\n request_model=request_model,\n provider=provider_name,\n )\n\n # Set encoding formats if present\n if call_kwargs.get(\"encoding_formats\"):\n invocation.encoding_formats = call_kwargs[\"encoding_formats\"]\n\n return invocation\n\n\ndef create_agent_invocation(\n reply_instance: AgentBase,\n reply_args: Tuple[Any, ...],\n reply_kwargs: Dict[str, Any],\n) -> InvokeAgentInvocation:\n \"\"\"Create Agent invocation from reply call context.\"\"\"\n # Get provider and model info\n provider_name = None\n request_model = None\n if hasattr(reply_instance, \"model\") and reply_instance.model:\n model = reply_instance.model\n request_model = getattr(model, \"model_name\", None)\n if isinstance(model, ChatModelBase):\n provider_name = get_provider_name(model)\n\n # Convert input messages\n input_messages = []\n msg = reply_args[0] if reply_args else reply_kwargs.get(\"msg\")\n if msg:\n try:\n if isinstance(msg, Msg):\n input_messages = convert_agentscope_messages_to_genai_format(\n [msg]\n )\n elif isinstance(msg, list):\n input_messages = convert_agentscope_messages_to_genai_format(\n msg\n )\n except Exception as e:\n logger.debug(f\"Error converting agent input messages: {e}\")\n\n invocation = InvokeAgentInvocation(\n provider=provider_name,\n agent_name=getattr(reply_instance, \"name\", \"unknown_agent\"),\n agent_id=getattr(reply_instance, \"id\", \"unknown\"),\n agent_description=inspect.getdoc(reply_instance.__class__)\n or \"No description available\",\n conversation_id=_config.run_id,\n request_model=request_model,\n input_messages=input_messages,\n )\n\n # Set system instruction if available\n if hasattr(reply_instance, \"sys_prompt\") and reply_instance.sys_prompt:\n sys_prompt = reply_instance.sys_prompt\n if isinstance(sys_prompt, str):\n invocation.system_instruction = [Text(content=sys_prompt)]\n elif isinstance(sys_prompt, list):\n invocation.system_instruction = sys_prompt\n\n return invocation\n\n\ndef get_chatmodel_output_messages(chat_response: Any) -> List[Dict[str, Any]]:\n \"\"\"Convert ChatResponse to OpenTelemetry standard output message format.\"\"\"\n try:\n if not isinstance(chat_response, ChatResponse):\n return []\n\n parts = []\n for block in chat_response.content:\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n if not parts:\n parts.append({\"type\": \"text\", \"content\": \"\"})\n\n return [\n {\n \"role\": \"assistant\",\n \"parts\": parts,\n \"finish_reason\": \"stop\",\n }\n ]\n\n except Exception as e:\n logger.warning(\n f\"Error processing ChatResponse to output messages: {e}\"\n )\n return [\n {\n \"role\": \"assistant\",\n \"parts\": [\n {\"type\": \"text\", \"content\": \"\"}\n ],\n \"finish_reason\": \"error\",\n }\n ]\n\n\ndef convert_agentscope_messages_to_genai_format(\n messages: Any,\n provider_name: Optional[str] = None,\n) -> List[InputMessage]:\n \"\"\"Convert AgentScope messages to opentelemetry-util-genai InputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires InputMessage objects.\n \"\"\"\n if not messages:\n return []\n\n if not isinstance(messages, list):\n messages = [messages]\n\n input_messages = []\n for msg in messages:\n if isinstance(msg, Msg):\n msg_dict = _format_msg_to_parts(msg)\n elif isinstance(msg, dict):\n msg_dict = msg\n else:\n continue\n\n role = msg_dict.get(\"role\", \"user\")\n parts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type == \"tool_call_response\":\n converted_parts.append(\n ToolCallResponse(\n id=part.get(\"id\"),\n response=part.get(\"result\", \"\"),\n )\n )\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type in (\"uri\", \"blob\"):\n converted_parts.append(part)\n else:\n # Keep other types as-is\n converted_parts.append(part)\n\n input_messages.append(InputMessage(role=role, parts=converted_parts))\n\n return input_messages\n\n\ndef convert_chatresponse_to_output_messages(\n chat_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert ChatResponse to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n output_dicts = get_chatmodel_output_messages(chat_response)\n if not output_dicts:\n return []\n\n output_messages = []\n for msg_dict in output_dicts:\n role = msg_dict.get(\"role\", \"assistant\")\n parts_dicts = msg_dict.get(\"parts\", [])\n finish_reason = msg_dict.get(\"finish_reason\", \"stop\")\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(part)\n\n output_messages.append(\n OutputMessage(\n role=role,\n parts=converted_parts,\n finish_reason=finish_reason,\n )\n )\n\n return output_messages\n\n\ndef convert_agent_response_to_output_messages(\n agent_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert agent Msg response to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n if not hasattr(agent_response, \"content\"):\n return []\n\n try:\n # Use _format_msg_to_parts to convert Msg to dict, then convert to OutputMessage\n msg_dict = _format_msg_to_parts(agent_response)\n parts_dicts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"reasoning\":\n converted_parts.append(\n Reasoning(content=part.get(\"content\", \"\"))\n )\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type in (\"uri\", \"blob\"):\n converted_parts.append(part)\n else:\n converted_parts.append(Text(content=str(part)))\n\n if not converted_parts:\n converted_parts.append(Text(content=\"\"))\n\n return [\n OutputMessage(\n role=\"assistant\",\n parts=converted_parts,\n finish_reason=\"stop\",\n )\n ]\n except Exception as e:\n logger.debug(f\"Failed to convert agent response: {e}\")\n return []\n"}, "68afc3c3efa238660673830d9a0f4e491fc7ab78": {"t": 1785758589.455682, "n": 18220, "h": "61e3fa037d847d9a", "c": "# -*- coding: utf-8 -*-\n\"\"\"Attributes processor for span attributes.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport enum\nimport inspect\nimport json\nimport logging\nfrom dataclasses import is_dataclass\nfrom enum import Enum\nfrom typing import Any, Dict, List, Optional, Tuple, Union\n\nfrom agentscope import _config\nfrom agentscope.agent import AgentBase\nfrom agentscope.embedding import EmbeddingModelBase\nfrom agentscope.message import Msg\nfrom agentscope.model import ChatModelBase, ChatResponse\nfrom pydantic import BaseModel\n\nfrom opentelemetry.semconv._incubating.attributes import (\n gen_ai_attributes as GenAIAttributes,\n)\nfrom opentelemetry.util.genai.extended_types import (\n EmbeddingInvocation,\n InvokeAgentInvocation,\n)\nfrom opentelemetry.util.genai.types import (\n InputMessage,\n LLMInvocation,\n OutputMessage,\n Text,\n ToolCall,\n ToolCallResponse,\n)\n\nfrom .message_converter import get_message_converter\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgentScopeGenAiProviderName(str, Enum):\n \"\"\"Extended provider names not in standard OpenTelemetry semantic conventions.\"\"\"\n\n OLLAMA = \"ollama\"\n DASHSCOPE = \"dashscope\"\n MOONSHOT = \"moonshot\"\n\n\n# Provider name mapping based on class names\n_PROVIDER_NAME_MAP = {\n \"openai\": GenAIAttributes.GenAiProviderNameValues.OPENAI.value,\n \"gemini\": GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value,\n \"anthropic\": GenAIAttributes.GenAiProviderNameValues.ANTHROPIC.value,\n \"dashscope\": AgentScopeGenAiProviderName.DASHSCOPE.value,\n \"ollama\": AgentScopeGenAiProviderName.OLLAMA.value,\n}\n\n# Base URL to provider mapping for OpenAI-compatible APIs\n_BASE_URL_PROVIDER_MAP = [\n (\"openai.com\", GenAIAttributes.GenAiProviderNameValues.OPENAI.value),\n (\n \"api.deepseek.com\",\n GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,\n ),\n (\"dashscope.aliyuncs.com\", AgentScopeGenAiProviderName.DASHSCOPE.value),\n]\n\n\ndef get_provider_name(chat_model: ChatModelBase) -> str:\n \"\"\"Parse chat model provider name\"\"\"\n classname = chat_model.__class__.__name__\n prefix = classname.removesuffix(\"ChatModel\").lower()\n\n # Special handling for DashScopeChatModel with base_http_api_url\n if (\n prefix == \"dashscope\"\n and hasattr(chat_model, \"base_http_api_url\")\n and chat_model.base_http_api_url\n ):\n base_url = chat_model.base_http_api_url\n for url_fragment, provider in _BASE_URL_PROVIDER_MAP:\n if url_fragment in base_url:\n return provider\n\n return _PROVIDER_NAME_MAP.get(prefix, \"unknown\")\n\n\ndef get_embedding_provider_name(embedding_model: EmbeddingModelBase) -> str:\n \"\"\"Parse embedding model provider name\"\"\"\n class_name = embedding_model.__class__.__name__\n\n if \"DashScope\" in class_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n elif \"OpenAI\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n elif \"Gemini\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value\n elif \"Ollama\" in class_name:\n return AgentScopeGenAiProviderName.OLLAMA.value\n else:\n model_name = getattr(embedding_model, \"model_name\", \"\")\n if \"text-embedding\" in model_name:\n if \"qwen\" in model_name or \"dashscope\" in model_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n else:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n return \"agentscope\"\n\n\ndef _get_tool_definitions(\n tools: list[dict[str, Any]] | None,\n tool_choice: str | None,\n) -> str | None:\n \"\"\"Extract and serialize tool definitions for tracing.\"\"\"\n if tools is None or not isinstance(tools, list) or len(tools) == 0:\n return None\n\n if tool_choice == \"none\":\n return None\n\n try:\n flat_tools = []\n for tool in tools:\n if not isinstance(tool, dict) or \"function\" not in tool:\n continue\n\n func_def = tool[\"function\"]\n flat_tool = {\n \"type\": tool.get(\"type\", \"function\"),\n \"name\": func_def.get(\"name\"),\n \"description\": func_def.get(\"description\"),\n \"parameters\": func_def.get(\"parameters\"),\n }\n flat_tool = {k: v for k, v in flat_tool.items() if v is not None}\n flat_tools.append(flat_tool)\n\n if flat_tools:\n return _serialize_to_str(flat_tools)\n return None\n\n except Exception:\n return None\n\n\ndef _to_serializable(obj: Any) -> Any:\n \"\"\"Convert an object to a JSON serializable type.\"\"\"\n if isinstance(obj, (str, int, bool, float, type(None))):\n return obj\n elif isinstance(obj, (list, tuple, set, frozenset)):\n return [_to_serializable(x) for x in obj]\n elif isinstance(obj, dict):\n return {str(key): _to_serializable(val) for (key, val) in obj.items()}\n elif isinstance(obj, (Msg, BaseModel)) or is_dataclass(obj):\n return repr(obj)\n elif inspect.isclass(obj) and issubclass(obj, BaseModel):\n return repr(obj)\n elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):\n return obj.isoformat()\n elif isinstance(obj, datetime.timedelta):\n return obj.total_seconds()\n elif isinstance(obj, enum.Enum):\n return _to_serializable(obj.value)\n else:\n return str(obj)\n\n\ndef _serialize_to_str(value: Any) -> str:\n \"\"\"Serialize value to JSON string\"\"\"\n try:\n return json.dumps(value, ensure_ascii=False)\n except TypeError:\n return json.dumps(_to_serializable(value), ensure_ascii=False)\n\n\ndef _convert_block_to_part(block: Dict[str, Any]) -> Dict[str, Any] | None:\n \"\"\"Convert content block to standardized part format.\"\"\"\n block_type = block.get(\"type\")\n\n if block_type == \"text\":\n return {\"type\": \"text\", \"content\": block.get(\"text\", \"\")}\n\n elif block_type == \"thinking\":\n return {\n \"type\": \"text\",\n \"content\": f\"[Thinking] {block.get('thinking', '')}\",\n }\n\n elif block_type == \"tool_use\":\n return {\n \"type\": \"tool_call\",\n \"id\": block.get(\"id\", \"\"),\n \"name\": block.get(\"name\", \"\"),\n \"arguments\": block.get(\"input\", {}),\n }\n\n elif block_type == \"tool_result\":\n output = block.get(\"output\", \"\")\n result = (\n _serialize_to_str(output)\n if isinstance(output, (list, dict))\n else str(output)\n )\n return {\n \"type\": \"tool_call_response\",\n \"id\": block.get(\"id\", \"\"),\n \"result\": result,\n }\n\n elif block_type == \"image\":\n source = block.get(\"source\", {})\n source_type = source.get(\"type\")\n if source_type == \"url\":\n return {\"type\": \"image\", \"url\": source.get(\"url\", \"\")}\n elif source_type == \"base64\":\n data = source.get(\"data\", \"\")\n media_type = source.get(\"media_type\", \"image/jpeg\")\n return {\"type\": \"image\", \"url\": f\"data:{media_type};base64,{data}\"}\n\n elif block_type == \"audio\":\n return {\"type\": \"audio\", \"source\": block.get(\"source\", {})}\n\n elif block_type == \"video\":\n return {\"type\": \"video\", \"source\": block.get(\"source\", {})}\n\n return None\n\n\ndef _format_msg_to_parts(msg: Msg) -> dict[str, Any]:\n \"\"\"Convert Msg to standard format (parts structure)\"\"\"\n try:\n parts = []\n for block in msg.get_content_blocks():\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n formatted_msg = {\"role\": msg.role, \"parts\": parts}\n if msg.name:\n formatted_msg[\"name\"] = msg.name\n\n return formatted_msg\n\n except Exception as e:\n logger.debug(f\"Error formatting message: {e}\")\n return {\n \"role\": msg.role,\n \"parts\": [\n {\n \"type\": \"text\",\n \"content\": str(msg.content) if msg.content else \"\",\n }\n ],\n }\n\n\ndef create_llm_invocation(\n call_instance: ChatModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> LLMInvocation:\n \"\"\"Create LLM invocation from call context.\"\"\"\n provider_name = get_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n # Convert input messages\n input_messages = []\n messages = call_args[0] if call_args else call_kwargs.get(\"messages\")\n if messages:\n try:\n converted_messages = get_message_converter(provider_name)(messages)\n input_messages = convert_agentscope_messages_to_genai_format(\n converted_messages, provider_name=provider_name\n )\n except Exception as e:\n logger.warning(\n f\"Failed to convert input messages: {e}\", exc_info=True\n )\n\n invocation = LLMInvocation(\n request_model=request_model,\n provider=provider_name,\n input_messages=input_messages,\n )\n\n # Set optional request parameters if present\n if call_kwargs.get(\"max_tokens\"):\n invocation.max_tokens = call_kwargs[\"max_tokens\"]\n if call_kwargs.get(\"temperature\"):\n invocation.temperature = call_kwargs[\"temperature\"]\n if call_kwargs.get(\"top_p\"):\n invocation.top_p = call_kwargs[\"top_p\"]\n\n # Set tool definitions if applicable\n if not call_kwargs.get(\"structured_model\", False):\n tool_definitions = _get_tool_definitions(\n call_kwargs.get(\"tools\"), call_kwargs.get(\"tool_choice\")\n )\n if tool_definitions:\n invocation.attributes[\"gen_ai.request.tool_definitions\"] = (\n tool_definitions\n )\n\n return invocation\n\n\ndef create_embedding_invocation(\n call_instance: EmbeddingModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> EmbeddingInvocation:\n \"\"\"Create Embedding invocation from call context.\"\"\"\n provider_name = get_embedding_provider_name(call_instance)\n request_model = getattr(call_instance, \"model_name\", \"unknown_model\")\n\n invocation = EmbeddingInvocation(\n request_model=request_model,\n provider=provider_name,\n )\n\n # Set encoding formats if present\n if call_kwargs.get(\"encoding_formats\"):\n invocation.encoding_formats = call_kwargs[\"encoding_formats\"]\n\n return invocation\n\n\ndef create_agent_invocation(\n reply_instance: AgentBase,\n reply_args: Tuple[Any, ...],\n reply_kwargs: Dict[str, Any],\n) -> InvokeAgentInvocation:\n \"\"\"Create Agent invocation from reply call context.\"\"\"\n # Get provider and model info\n provider_name = None\n request_model = None\n if hasattr(reply_instance, \"model\") and reply_instance.model:\n model = reply_instance.model\n request_model = getattr(model, \"model_name\", None)\n if isinstance(model, ChatModelBase):\n provider_name = get_provider_name(model)\n\n # Convert input messages\n input_messages = []\n msg = reply_args[0] if reply_args else reply_kwargs.get(\"msg\")\n if msg:\n try:\n if isinstance(msg, Msg):\n input_messages = convert_agentscope_messages_to_genai_format(\n [msg]\n )\n elif isinstance(msg, list):\n input_messages = convert_agentscope_messages_to_genai_format(\n msg\n )\n except Exception as e:\n logger.debug(f\"Error converting agent input messages: {e}\")\n\n invocation = InvokeAgentInvocation(\n provider=provider_name,\n agent_name=getattr(reply_instance, \"name\", \"unknown_agent\"),\n agent_id=getattr(reply_instance, \"id\", \"unknown\"),\n agent_description=inspect.getdoc(reply_instance.__class__)\n or \"No description available\",\n conversation_id=_config.run_id,\n request_model=request_model,\n input_messages=input_messages,\n )\n\n # Set system instruction if available\n if hasattr(reply_instance, \"sys_prompt\") and reply_instance.sys_prompt:\n sys_prompt = reply_instance.sys_prompt\n if isinstance(sys_prompt, str):\n invocation.system_instruction = [Text(content=sys_prompt)]\n elif isinstance(sys_prompt, list):\n invocation.system_instruction = sys_prompt\n\n return invocation\n\n\ndef get_chatmodel_output_messages(chat_response: Any) -> List[Dict[str, Any]]:\n \"\"\"Convert ChatResponse to OpenTelemetry standard output message format.\"\"\"\n try:\n if not isinstance(chat_response, ChatResponse):\n return []\n\n parts = []\n for block in chat_response.content:\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n if not parts:\n parts.append({\"type\": \"text\", \"content\": \"\"})\n\n return [\n {\n \"role\": \"assistant\",\n \"parts\": parts,\n \"finish_reason\": \"stop\",\n }\n ]\n\n except Exception as e:\n logger.warning(\n f\"Error processing ChatResponse to output messages: {e}\"\n )\n return [\n {\n \"role\": \"assistant\",\n \"parts\": [\n {\"type\": \"text\", \"content\": \"\"}\n ],\n \"finish_reason\": \"error\",\n }\n ]\n\n\ndef convert_agentscope_messages_to_genai_format(\n messages: Any,\n provider_name: Optional[str] = None,\n) -> List[InputMessage]:\n \"\"\"Convert AgentScope messages to opentelemetry-util-genai InputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires InputMessage objects.\n \"\"\"\n if not messages:\n return []\n\n if not isinstance(messages, list):\n messages = [messages]\n\n input_messages = []\n for msg in messages:\n if isinstance(msg, Msg):\n msg_dict = _format_msg_to_parts(msg)\n elif isinstance(msg, dict):\n msg_dict = msg\n else:\n continue\n\n role = msg_dict.get(\"role\", \"user\")\n parts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type == \"tool_call_response\":\n converted_parts.append(\n ToolCallResponse(\n id=part.get(\"id\"),\n response=part.get(\"result\", \"\"),\n )\n )\n else:\n # Keep other types as-is\n converted_parts.append(part)\n\n input_messages.append(InputMessage(role=role, parts=converted_parts))\n\n return input_messages\n\n\ndef convert_chatresponse_to_output_messages(\n chat_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert ChatResponse to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n output_dicts = get_chatmodel_output_messages(chat_response)\n if not output_dicts:\n return []\n\n output_messages = []\n for msg_dict in output_dicts:\n role = msg_dict.get(\"role\", \"assistant\")\n parts_dicts = msg_dict.get(\"parts\", [])\n finish_reason = msg_dict.get(\"finish_reason\", \"stop\")\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(part)\n\n output_messages.append(\n OutputMessage(\n role=role,\n parts=converted_parts,\n finish_reason=finish_reason,\n )\n )\n\n return output_messages\n\n\ndef convert_agent_response_to_output_messages(\n agent_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert agent Msg response to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n if not hasattr(agent_response, \"content\"):\n return []\n\n try:\n # Use _format_msg_to_parts to convert Msg to dict, then convert to OutputMessage\n msg_dict = _format_msg_to_parts(agent_response)\n parts_dicts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(Text(content=str(part)))\n\n if not converted_parts:\n converted_parts.append(Text(content=\"\"))\n\n return [\n OutputMessage(\n role=\"assistant\",\n parts=converted_parts,\n finish_reason=\"stop\",\n )\n ]\n except Exception as e:\n logger.debug(f\"Failed to convert agent response: {e}\")\n return []\n"}, "a73c05d3abf966ca71e01672712061f89085e965": {"t": 1785758590.8437877, "n": 18459, "h": "d295b5496b391277", "c": "# -*- coding: utf-8 -*-\n\"\"\"Attributes processor for span attributes.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport enum\nimport inspect\nimport json\nimport logging\nfrom dataclasses import is_dataclass\nfrom enum import Enum\nfrom typing import Any, Dict, List, Optional, Tuple\n\nfrom agentscope import _config\nfrom agentscope.agent import AgentBase\nfrom agentscope.embedding import EmbeddingModelBase\nfrom agentscope.message import Msg\nfrom agentscope.model import ChatModelBase, ChatResponse\nfrom pydantic import BaseModel\n\nfrom opentelemetry.semconv._incubating.attributes import (\n gen_ai_attributes as GenAIAttributes,\n)\nfrom opentelemetry.util.genai.types import (\n InputMessage,\n OutputMessage,\n Text,\n ToolCall,\n ToolCallResponse,\n)\n\nfrom .message_converter import get_message_converter\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgentScopeGenAiProviderName(str, Enum):\n \"\"\"Extended provider names not in standard OpenTelemetry semantic conventions.\"\"\"\n\n OLLAMA = \"ollama\"\n DASHSCOPE = \"dashscope\"\n MOONSHOT = \"moonshot\"\n\n\n# Provider name mapping based on class names\n_PROVIDER_NAME_MAP = {\n \"openai\": GenAIAttributes.GenAiProviderNameValues.OPENAI.value,\n \"gemini\": GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value,\n \"anthropic\": GenAIAttributes.GenAiProviderNameValues.ANTHROPIC.value,\n \"dashscope\": AgentScopeGenAiProviderName.DASHSCOPE.value,\n \"ollama\": AgentScopeGenAiProviderName.OLLAMA.value,\n}\n\n# Base URL to provider mapping for OpenAI-compatible APIs\n_BASE_URL_PROVIDER_MAP = [\n (\"openai.com\", GenAIAttributes.GenAiProviderNameValues.OPENAI.value),\n (\n \"api.deepseek.com\",\n GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,\n ),\n (\"dashscope.aliyuncs.com\", AgentScopeGenAiProviderName.DASHSCOPE.value),\n]\n\n\ndef get_provider_name(chat_model: ChatModelBase) -> str:\n \"\"\"Parse chat model provider name\"\"\"\n classname = chat_model.__class__.__name__\n prefix = classname.removesuffix(\"ChatModel\").lower()\n\n # Special handling for DashScopeChatModel with base_http_api_url\n if (\n prefix == \"dashscope\"\n and hasattr(chat_model, \"base_http_api_url\")\n and chat_model.base_http_api_url\n ):\n base_url = chat_model.base_http_api_url\n for url_fragment, provider in _BASE_URL_PROVIDER_MAP:\n if url_fragment in base_url:\n return provider\n\n return _PROVIDER_NAME_MAP.get(prefix, \"unknown\")\n\n\ndef get_embedding_provider_name(embedding_model: EmbeddingModelBase) -> str:\n \"\"\"Parse embedding model provider name\"\"\"\n class_name = embedding_model.__class__.__name__\n\n if \"DashScope\" in class_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n elif \"OpenAI\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n elif \"Gemini\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value\n elif \"Ollama\" in class_name:\n return AgentScopeGenAiProviderName.OLLAMA.value\n else:\n model_name = getattr(embedding_model, \"model_name\", \"\")\n if \"text-embedding\" in model_name:\n if \"qwen\" in model_name or \"dashscope\" in model_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n else:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n return \"agentscope\"\n\n\ndef _get_tool_definitions(\n tools: list[dict[str, Any]] | None,\n tool_choice: str | None,\n) -> str | None:\n \"\"\"Extract and serialize tool definitions for tracing.\"\"\"\n if tools is None or not isinstance(tools, list) or len(tools) == 0:\n return None\n\n if tool_choice == \"none\":\n return None\n\n try:\n flat_tools = []\n for tool in tools:\n if not isinstance(tool, dict) or \"function\" not in tool:\n continue\n\n func_def = tool[\"function\"]\n flat_tool = {\n \"type\": tool.get(\"type\", \"function\"),\n \"name\": func_def.get(\"name\"),\n \"description\": func_def.get(\"description\"),\n \"parameters\": func_def.get(\"parameters\"),\n }\n flat_tool = {k: v for k, v in flat_tool.items() if v is not None}\n flat_tools.append(flat_tool)\n\n if flat_tools:\n return _serialize_to_str(flat_tools)\n return None\n\n except Exception:\n return None\n\n\ndef _to_serializable(obj: Any) -> Any:\n \"\"\"Convert an object to a JSON serializable type.\"\"\"\n if isinstance(obj, (str, int, bool, float, type(None))):\n return obj\n elif isinstance(obj, (list, tuple, set, frozenset)):\n return [_to_serializable(x) for x in obj]\n elif isinstance(obj, dict):\n return {str(key): _to_serializable(val) for (key, val) in obj.items()}\n elif isinstance(obj, (Msg, BaseModel)) or is_dataclass(obj):\n return repr(obj)\n elif inspect.isclass(obj) and issubclass(obj, BaseModel):\n return repr(obj)\n elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):\n return obj.isoformat()\n elif isinstance(obj, datetime.timedelta):\n return obj.total_seconds()\n elif isinstance(obj, enum.Enum):\n return _to_serializable(obj.value)\n else:\n return str(obj)\n\n\ndef _serialize_to_str(value: Any) -> str:\n \"\"\"Serialize value to JSON string\"\"\"\n try:\n return json.dumps(value, ensure_ascii=False)\n except TypeError:\n return json.dumps(_to_serializable(value), ensure_ascii=False)\n\n\ndef _convert_block_to_part(block: Dict[str, Any]) -> Dict[str, Any] | None:\n \"\"\"Convert content block to standardized part format.\"\"\"\n block_type = block.get(\"type\")\n\n if block_type == \"text\":\n return {\"type\": \"text\", \"content\": block.get(\"text\", \"\")}\n\n elif block_type == \"thinking\":\n return {\n \"type\": \"text\",\n \"content\": f\"[Thinking] {block.get('thinking', '')}\",\n }\n\n elif block_type == \"tool_use\":\n return {\n \"type\": \"tool_call\",\n \"id\": block.get(\"id\", \"\"),\n \"name\": block.get(\"name\", \"\"),\n \"arguments\": block.get(\"input\", {}),\n }\n\n elif block_type == \"tool_result\":\n output = block.get(\"output\", \"\")\n result = (\n _serialize_to_str(output)\n if isinstance(output, (list, dict))\n else str(output)\n )\n return {\n \"type\": \"tool_call_response\",\n \"id\": block.get(\"id\", \"\"),\n \"result\": result,\n }\n\n elif block_type == \"image\":\n source = block.get(\"source\", {})\n source_type = source.get(\"type\")\n if source_type == \"url\":\n return {\"type\": \"image\", \"url\": source.get(\"url\", \"\")}\n elif source_type == \"base64\":\n data = source.get(\"data\", \"\")\n media_type = source.get(\"media_type\", \"image/jpeg\")\n return {\"type\": \"image\", \"url\": f\"data:{media_type};base64,{data}\"}\n\n elif block_type == \"audio\":\n return {\"type\": \"audio\", \"source\": block.get(\"source\", {})}\n\n elif block_type == \"video\":\n return {\"type\": \"video\", \"source\": block.get(\"source\", {})}\n\n return None\n\n\ndef _format_msg_to_parts(msg: Msg) -> dict[str, Any]:\n \"\"\"Convert Msg to standard format (parts structure)\"\"\"\n try:\n parts = []\n for block in msg.get_content_blocks():\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n formatted_msg = {\"role\": msg.role, \"parts\": parts}\n if msg.name:\n formatted_msg[\"name\"] = msg.name\n\n return formatted_msg\n\n except Exception as e:\n logger.debug(f\"Error formatting message: {e}\")\n return {\n \"role\": msg.role,\n \"parts\": [\n {\n \"type\": \"text\",\n \"content\": str(msg.content) if msg.content else \"\",\n }\n ],\n }\n\n\ndef extract_llm_attributes(\n call_instance: ChatModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> Dict[str, Any]:\n \"\"\"Extract LLM call attributes\"\"\"\n provider_name = get_provider_name(call_instance)\n\n messages = call_args[0] if call_args else call_kwargs.get(\"messages\")\n\n if messages:\n try:\n input_messages = get_message_converter(provider_name)(messages)\n except Exception as e:\n logger.warning(\n f\"Failed to convert input messages: {e}\", exc_info=True\n )\n input_messages = {\"args\": call_args, \"kwargs\": call_kwargs}\n else:\n logger.debug(\"No messages provided for LLM call\")\n input_messages = {\"args\": call_args, \"kwargs\": call_kwargs}\n\n tool_definitions = None\n if not call_kwargs.get(\"structured_model\", False):\n tool_definitions = _get_tool_definitions(\n call_kwargs.get(\"tools\"), call_kwargs.get(\"tool_choice\")\n )\n\n return {\n \"operation_name\": GenAIAttributes.GenAiOperationNameValues.CHAT.value,\n \"provider_name\": provider_name,\n \"request_model\": getattr(call_instance, \"model_name\", \"unknown_model\"),\n \"request_max_tokens\": call_kwargs.get(\"max_tokens\"),\n \"request_temperature\": call_kwargs.get(\"temperature\"),\n \"request_top_p\": call_kwargs.get(\"top_p\"),\n \"request_top_k\": call_kwargs.get(\"top_k\"),\n \"request_stop_sequences\": call_kwargs.get(\"stop_sequences\"),\n \"request_tool_definitions\": tool_definitions,\n \"input_messages\": _serialize_to_str(input_messages),\n }\n\n\ndef extract_embedding_attributes(\n call_instance: EmbeddingModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> Dict[str, Any]:\n \"\"\"Extract Embedding call attributes\"\"\"\n provider_name = get_embedding_provider_name(call_instance)\n\n text_for_embedding = call_args[0] if call_args else call_kwargs.get(\"text\")\n\n if text_for_embedding:\n input_message = []\n for text_item in text_for_embedding:\n input_message.append(\n {\n \"role\": \"user\",\n \"content\": [{\"type\": \"text\", \"text\": text_item}],\n }\n )\n input_messages = input_message\n else:\n logger.debug(\"No text provided for embedding call\")\n input_messages = {\"args\": call_args, \"kwargs\": call_kwargs}\n\n return {\n \"operation_name\": GenAIAttributes.GenAiOperationNameValues.EMBEDDINGS.value,\n \"provider_name\": provider_name,\n \"request_model\": getattr(call_instance, \"model_name\", \"unknown_model\"),\n \"request_encoding_formats\": call_kwargs.get(\"encoding_formats\"),\n \"input_messages\": _serialize_to_str(input_messages),\n }\n\n\ndef extract_agent_attributes(\n reply_instance: AgentBase,\n reply_args: Tuple[Any, ...],\n reply_kwargs: Dict[str, Any],\n) -> Dict[str, Any]:\n \"\"\"Extract Agent reply call attributes\"\"\"\n msg = reply_args[0] if reply_args else reply_kwargs.get(\"msg\")\n\n if msg:\n try:\n if isinstance(msg, Msg):\n input_messages = _format_msg_to_parts(msg)\n elif isinstance(msg, list):\n input_messages = [\n _format_msg_to_parts(m) for m in msg if isinstance(m, Msg)\n ]\n else:\n input_messages = []\n except Exception as e:\n logger.debug(f\"Error formatting agent messages: {e}\")\n input_messages = []\n else:\n logger.debug(\"No msg provided for agent reply\")\n input_messages = {\"args\": reply_args, \"kwargs\": reply_kwargs}\n\n request_model = None\n provider_name = None\n if hasattr(reply_instance, \"model\") and reply_instance.model:\n model = reply_instance.model\n # Only set request_model if model has a model_name attribute\n request_model = getattr(model, \"model_name\", None)\n if isinstance(model, ChatModelBase):\n provider_name = get_provider_name(model)\n\n return {\n \"operation_name\": GenAIAttributes.GenAiOperationNameValues.INVOKE_AGENT.value,\n \"agent_id\": getattr(reply_instance, \"id\", \"unknown\"),\n \"agent_name\": getattr(reply_instance, \"name\", \"unknown_agent\"),\n \"agent_description\": inspect.getdoc(reply_instance.__class__)\n or \"No description available\",\n \"system_instructions\": reply_instance.sys_prompt\n if hasattr(reply_instance, \"sys_prompt\")\n else None,\n \"request_model\": request_model,\n \"provider_name\": provider_name,\n \"conversation_id\": _config.run_id,\n \"input_messages\": _serialize_to_str(input_messages),\n }\n\n\ndef get_chatmodel_output_messages(chat_response: Any) -> List[Dict[str, Any]]:\n \"\"\"Convert ChatResponse to OpenTelemetry standard output message format.\"\"\"\n try:\n if not isinstance(chat_response, ChatResponse):\n return []\n\n parts = []\n for block in chat_response.content:\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n if not parts:\n parts.append({\"type\": \"text\", \"content\": \"\"})\n\n return [\n {\n \"role\": \"assistant\",\n \"parts\": parts,\n \"finish_reason\": \"stop\",\n }\n ]\n\n except Exception as e:\n logger.warning(\n f\"Error processing ChatResponse to output messages: {e}\"\n )\n return [\n {\n \"role\": \"assistant\",\n \"parts\": [\n {\"type\": \"text\", \"content\": \"\"}\n ],\n \"finish_reason\": \"error\",\n }\n ]\n\n\ndef convert_agentscope_messages_to_genai_format(\n messages: Any,\n provider_name: Optional[str] = None,\n) -> List[InputMessage]:\n \"\"\"Convert AgentScope messages to opentelemetry-util-genai InputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires InputMessage objects.\n \"\"\"\n if not messages:\n return []\n\n if not isinstance(messages, list):\n messages = [messages]\n\n input_messages = []\n for msg in messages:\n if isinstance(msg, Msg):\n msg_dict = _format_msg_to_parts(msg)\n elif isinstance(msg, dict):\n msg_dict = msg\n else:\n continue\n\n role = msg_dict.get(\"role\", \"user\")\n parts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type == \"tool_call_response\":\n converted_parts.append(\n ToolCallResponse(\n id=part.get(\"id\"),\n response=part.get(\"result\", \"\"),\n )\n )\n else:\n # Keep other types as-is\n converted_parts.append(part)\n\n input_messages.append(InputMessage(role=role, parts=converted_parts))\n\n return input_messages\n\n\ndef convert_chatresponse_to_output_messages(\n chat_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert ChatResponse to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n output_dicts = get_chatmodel_output_messages(chat_response)\n if not output_dicts:\n return []\n\n output_messages = []\n for msg_dict in output_dicts:\n role = msg_dict.get(\"role\", \"assistant\")\n parts_dicts = msg_dict.get(\"parts\", [])\n finish_reason = msg_dict.get(\"finish_reason\", \"stop\")\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(part)\n\n output_messages.append(\n OutputMessage(\n role=role,\n parts=converted_parts,\n finish_reason=finish_reason,\n )\n )\n\n return output_messages\n\n\ndef convert_agent_response_to_output_messages(\n agent_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert agent Msg response to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n if not hasattr(agent_response, \"content\"):\n return []\n\n try:\n # Use _format_msg_to_parts to convert Msg to dict, then convert to OutputMessage\n msg_dict = _format_msg_to_parts(agent_response)\n parts_dicts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(Text(content=str(part)))\n\n if not converted_parts:\n converted_parts.append(Text(content=\"\"))\n\n return [\n OutputMessage(\n role=\"assistant\",\n parts=converted_parts,\n finish_reason=\"stop\",\n )\n ]\n except Exception as e:\n logger.debug(f\"Failed to convert agent response: {e}\")\n return []\n"}, "8737f9105299b78439747b2669725df147a68025": {"t": 1785758592.2688181, "n": 18743, "h": "7f61c81b65d4bb69", "c": "# -*- coding: utf-8 -*-\n\"\"\"Attributes processor for span attributes.\"\"\"\n\nfrom __future__ import annotations\n\nimport datetime\nimport enum\nimport inspect\nimport json\nimport logging\nfrom dataclasses import is_dataclass\nfrom enum import Enum\nfrom typing import Any, Dict, List, Optional, Tuple\n\nfrom agentscope import _config\nfrom agentscope.agent import AgentBase\nfrom agentscope.embedding import EmbeddingModelBase\nfrom agentscope.message import Msg\nfrom agentscope.model import ChatModelBase, ChatResponse\nfrom pydantic import BaseModel\n\nfrom opentelemetry.semconv._incubating.attributes import (\n gen_ai_attributes as GenAIAttributes,\n)\nfrom opentelemetry.util.genai.types import (\n InputMessage,\n OutputMessage,\n Text,\n ToolCall,\n ToolCallResponse,\n)\n\nfrom .message_converter import get_message_converter\n\nlogger = logging.getLogger(__name__)\n\n\nclass AgentScopeGenAiProviderName(str, Enum):\n \"\"\"Extended provider names not in standard OpenTelemetry semantic conventions.\"\"\"\n\n OLLAMA = \"ollama\"\n DASHSCOPE = \"dashscope\"\n MOONSHOT = \"moonshot\"\n\n\n# Provider name mapping based on class names\n_PROVIDER_NAME_MAP = {\n \"openai\": GenAIAttributes.GenAiProviderNameValues.OPENAI.value,\n \"gemini\": GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value,\n \"anthropic\": GenAIAttributes.GenAiProviderNameValues.ANTHROPIC.value,\n \"dashscope\": AgentScopeGenAiProviderName.DASHSCOPE.value,\n \"ollama\": AgentScopeGenAiProviderName.OLLAMA.value,\n}\n\n# Base URL to provider mapping for OpenAI-compatible APIs\n_BASE_URL_PROVIDER_MAP = [\n (\"openai.com\", GenAIAttributes.GenAiProviderNameValues.OPENAI.value),\n (\n \"api.deepseek.com\",\n GenAIAttributes.GenAiProviderNameValues.DEEPSEEK.value,\n ),\n (\"dashscope.aliyuncs.com\", AgentScopeGenAiProviderName.DASHSCOPE.value),\n]\n\n\ndef get_provider_name(chat_model: ChatModelBase) -> str:\n \"\"\"Parse chat model provider name\"\"\"\n classname = chat_model.__class__.__name__\n prefix = classname.removesuffix(\"ChatModel\").lower()\n\n # Special handling for DashScopeChatModel with base_http_api_url\n if (\n prefix == \"dashscope\"\n and hasattr(chat_model, \"base_http_api_url\")\n and chat_model.base_http_api_url\n ):\n base_url = chat_model.base_http_api_url\n for url_fragment, provider in _BASE_URL_PROVIDER_MAP:\n if url_fragment in base_url:\n return provider\n\n return _PROVIDER_NAME_MAP.get(prefix, \"unknown\")\n\n\ndef get_embedding_provider_name(embedding_model: EmbeddingModelBase) -> str:\n \"\"\"Parse embedding model provider name\"\"\"\n class_name = embedding_model.__class__.__name__\n\n if \"DashScope\" in class_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n elif \"OpenAI\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n elif \"Gemini\" in class_name:\n return GenAIAttributes.GenAiProviderNameValues.GCP_GEMINI.value\n elif \"Ollama\" in class_name:\n return AgentScopeGenAiProviderName.OLLAMA.value\n else:\n model_name = getattr(embedding_model, \"model_name\", \"\")\n if \"text-embedding\" in model_name:\n if \"qwen\" in model_name or \"dashscope\" in model_name:\n return AgentScopeGenAiProviderName.DASHSCOPE.value\n else:\n return GenAIAttributes.GenAiProviderNameValues.OPENAI.value\n return \"agentscope\"\n\n\ndef _get_tool_definitions(\n tools: list[dict[str, Any]] | None,\n tool_choice: str | None,\n) -> str | None:\n \"\"\"Extract and serialize tool definitions for tracing.\"\"\"\n if tools is None or not isinstance(tools, list) or len(tools) == 0:\n return None\n\n if tool_choice == \"none\":\n return None\n\n try:\n flat_tools = []\n for tool in tools:\n if not isinstance(tool, dict) or \"function\" not in tool:\n continue\n\n func_def = tool[\"function\"]\n flat_tool = {\n \"type\": tool.get(\"type\", \"function\"),\n \"name\": func_def.get(\"name\"),\n \"description\": func_def.get(\"description\"),\n \"parameters\": func_def.get(\"parameters\"),\n }\n flat_tool = {k: v for k, v in flat_tool.items() if v is not None}\n flat_tools.append(flat_tool)\n\n if flat_tools:\n return _serialize_to_str(flat_tools)\n return None\n\n except Exception:\n return None\n\n\ndef _to_serializable(obj: Any) -> Any:\n \"\"\"Convert an object to a JSON serializable type.\"\"\"\n if isinstance(obj, (str, int, bool, float, type(None))):\n return obj\n elif isinstance(obj, (list, tuple, set, frozenset)):\n return [_to_serializable(x) for x in obj]\n elif isinstance(obj, dict):\n return {str(key): _to_serializable(val) for (key, val) in obj.items()}\n elif isinstance(obj, (Msg, BaseModel)) or is_dataclass(obj):\n return repr(obj)\n elif inspect.isclass(obj) and issubclass(obj, BaseModel):\n return repr(obj)\n elif isinstance(obj, (datetime.date, datetime.datetime, datetime.time)):\n return obj.isoformat()\n elif isinstance(obj, datetime.timedelta):\n return obj.total_seconds()\n elif isinstance(obj, enum.Enum):\n return _to_serializable(obj.value)\n else:\n return str(obj)\n\n\ndef _serialize_to_str(value: Any) -> str:\n \"\"\"Serialize value to JSON string\"\"\"\n try:\n return json.dumps(value, ensure_ascii=False)\n except TypeError:\n return json.dumps(_to_serializable(value), ensure_ascii=False)\n\n\ndef _convert_block_to_part(block: Dict[str, Any]) -> Dict[str, Any] | None:\n \"\"\"Convert content block to standardized part format.\"\"\"\n block_type = block.get(\"type\")\n\n if block_type == \"text\":\n return {\"type\": \"text\", \"content\": block.get(\"text\", \"\")}\n\n elif block_type == \"thinking\":\n return {\n \"type\": \"text\",\n \"content\": f\"[Thinking] {block.get('thinking', '')}\",\n }\n\n elif block_type == \"tool_use\":\n return {\n \"type\": \"tool_call\",\n \"id\": block.get(\"id\", \"\"),\n \"name\": block.get(\"name\", \"\"),\n \"arguments\": block.get(\"input\", {}),\n }\n\n elif block_type == \"tool_result\":\n output = block.get(\"output\", \"\")\n result = (\n _serialize_to_str(output)\n if isinstance(output, (list, dict))\n else str(output)\n )\n return {\n \"type\": \"tool_call_response\",\n \"id\": block.get(\"id\", \"\"),\n \"result\": result,\n }\n\n elif block_type == \"image\":\n source = block.get(\"source\", {})\n source_type = source.get(\"type\")\n if source_type == \"url\":\n return {\"type\": \"image\", \"url\": source.get(\"url\", \"\")}\n elif source_type == \"base64\":\n data = source.get(\"data\", \"\")\n media_type = source.get(\"media_type\", \"image/jpeg\")\n return {\"type\": \"image\", \"url\": f\"data:{media_type};base64,{data}\"}\n\n elif block_type == \"audio\":\n return {\"type\": \"audio\", \"source\": block.get(\"source\", {})}\n\n elif block_type == \"video\":\n return {\"type\": \"video\", \"source\": block.get(\"source\", {})}\n\n return None\n\n\ndef _format_msg_to_parts(msg: Msg) -> dict[str, Any]:\n \"\"\"Convert Msg to standard format (parts structure)\"\"\"\n try:\n parts = []\n for block in msg.get_content_blocks():\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n formatted_msg = {\"role\": msg.role, \"parts\": parts}\n if msg.name:\n formatted_msg[\"name\"] = msg.name\n\n return formatted_msg\n\n except Exception as e:\n logger.debug(f\"Error formatting message: {e}\")\n return {\n \"role\": msg.role,\n \"parts\": [\n {\n \"type\": \"text\",\n \"content\": str(msg.content) if msg.content else \"\",\n }\n ],\n }\n\n\ndef extract_llm_attributes(\n call_instance: ChatModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> Dict[str, Any]:\n \"\"\"Extract LLM call attributes\"\"\"\n provider_name = get_provider_name(call_instance)\n\n messages = call_args[0] if call_args else call_kwargs.get(\"messages\")\n\n if messages:\n try:\n input_messages = get_message_converter(provider_name)(messages)\n except Exception as e:\n logger.warning(\n f\"Failed to convert input messages: {e}\", exc_info=True\n )\n input_messages = {\"args\": call_args, \"kwargs\": call_kwargs}\n else:\n logger.debug(\"No messages provided for LLM call\")\n input_messages = {\"args\": call_args, \"kwargs\": call_kwargs}\n\n tool_definitions = None\n if not call_kwargs.get(\"structured_model\", False):\n tool_definitions = _get_tool_definitions(\n call_kwargs.get(\"tools\"), call_kwargs.get(\"tool_choice\")\n )\n\n return {\n \"operation_name\": GenAIAttributes.GenAiOperationNameValues.CHAT.value,\n \"provider_name\": provider_name,\n \"request_model\": getattr(call_instance, \"model_name\", \"unknown_model\"),\n \"request_max_tokens\": call_kwargs.get(\"max_tokens\"),\n \"request_temperature\": call_kwargs.get(\"temperature\"),\n \"request_top_p\": call_kwargs.get(\"top_p\"),\n \"request_top_k\": call_kwargs.get(\"top_k\"),\n \"request_stop_sequences\": call_kwargs.get(\"stop_sequences\"),\n \"request_tool_definitions\": tool_definitions,\n \"input_messages\": _serialize_to_str(input_messages),\n }\n\n\ndef extract_embedding_attributes(\n call_instance: EmbeddingModelBase,\n call_args: Tuple[Any, ...],\n call_kwargs: Dict[str, Any],\n) -> Dict[str, Any]:\n \"\"\"Extract Embedding call attributes\"\"\"\n provider_name = get_embedding_provider_name(call_instance)\n\n text_for_embedding = call_args[0] if call_args else call_kwargs.get(\"text\")\n\n if text_for_embedding:\n input_message = []\n for text_item in text_for_embedding:\n input_message.append(\n {\n \"role\": \"user\",\n \"content\": [{\"type\": \"text\", \"text\": text_item}],\n }\n )\n input_messages = input_message\n else:\n logger.debug(\"No text provided for embedding call\")\n input_messages = {\"args\": call_args, \"kwargs\": call_kwargs}\n\n return {\n \"operation_name\": GenAIAttributes.GenAiOperationNameValues.EMBEDDINGS.value,\n \"provider_name\": provider_name,\n \"request_model\": getattr(call_instance, \"model_name\", \"unknown_model\"),\n \"request_encoding_formats\": call_kwargs.get(\"encoding_formats\"),\n \"input_messages\": _serialize_to_str(input_messages),\n }\n\n\ndef extract_agent_attributes(\n reply_instance: AgentBase,\n reply_args: Tuple[Any, ...],\n reply_kwargs: Dict[str, Any],\n) -> Dict[str, Any]:\n \"\"\"Extract Agent reply call attributes\"\"\"\n msg = reply_args[0] if reply_args else reply_kwargs.get(\"msg\")\n\n if msg:\n try:\n if isinstance(msg, Msg):\n input_messages = _format_msg_to_parts(msg)\n elif isinstance(msg, list):\n input_messages = [\n _format_msg_to_parts(m) for m in msg if isinstance(m, Msg)\n ]\n else:\n input_messages = []\n except Exception as e:\n logger.debug(f\"Error formatting agent messages: {e}\")\n input_messages = []\n else:\n logger.debug(\"No msg provided for agent reply\")\n input_messages = {\"args\": reply_args, \"kwargs\": reply_kwargs}\n\n request_model = None\n provider_name = None\n if hasattr(reply_instance, \"model\") and reply_instance.model:\n model = reply_instance.model\n # Only set request_model if model has a model_name attribute\n request_model = getattr(model, \"model_name\", None)\n if isinstance(model, ChatModelBase):\n provider_name = get_provider_name(model)\n\n return {\n \"operation_name\": GenAIAttributes.GenAiOperationNameValues.INVOKE_AGENT.value,\n \"agent_id\": getattr(reply_instance, \"id\", \"unknown\"),\n \"agent_name\": getattr(reply_instance, \"name\", \"unknown_agent\"),\n \"agent_description\": inspect.getdoc(reply_instance.__class__)\n or \"No description available\",\n \"system_instructions\": reply_instance.sys_prompt\n if hasattr(reply_instance, \"sys_prompt\")\n else None,\n \"request_model\": request_model,\n \"provider_name\": provider_name,\n \"conversation_id\": _config.run_id,\n \"input_messages\": _serialize_to_str(input_messages),\n }\n\n\ndef get_chatmodel_output_messages(chat_response: Any) -> List[Dict[str, Any]]:\n \"\"\"Convert ChatResponse to OpenTelemetry standard output message format.\"\"\"\n try:\n if not isinstance(chat_response, ChatResponse):\n return []\n\n parts = []\n for block in chat_response.content:\n part = _convert_block_to_part(block)\n if part:\n parts.append(part)\n\n if not parts:\n parts.append({\"type\": \"text\", \"content\": \"\"})\n\n return [\n {\n \"role\": \"assistant\",\n \"parts\": parts,\n \"finish_reason\": \"stop\",\n }\n ]\n\n except Exception as e:\n logger.warning(\n f\"Error processing ChatResponse to output messages: {e}\"\n )\n return [\n {\n \"role\": \"assistant\",\n \"parts\": [\n {\"type\": \"text\", \"content\": \"\"}\n ],\n \"finish_reason\": \"error\",\n }\n ]\n\n\ndef convert_agentscope_messages_to_genai_format(\n messages: Any,\n provider_name: Optional[str] = None,\n) -> List[InputMessage]:\n \"\"\"Convert AgentScope messages to opentelemetry-util-genai InputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires InputMessage objects.\n \"\"\"\n if not messages:\n return []\n\n if not isinstance(messages, list):\n messages = [messages]\n\n input_messages = []\n for msg in messages:\n if isinstance(msg, Msg):\n msg_dict = _format_msg_to_parts(msg)\n elif isinstance(msg, dict):\n if provider_name:\n try:\n converted = get_message_converter(provider_name)([msg])\n msg_dict = converted[0] if converted else msg\n except Exception:\n msg_dict = msg\n else:\n msg_dict = msg\n else:\n continue\n\n role = msg_dict.get(\"role\", \"user\")\n parts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n elif part_type == \"tool_call_response\":\n converted_parts.append(\n ToolCallResponse(\n id=part.get(\"id\"),\n response=part.get(\"result\", \"\"),\n )\n )\n else:\n # Keep other types as-is\n converted_parts.append(part)\n\n input_messages.append(InputMessage(role=role, parts=converted_parts))\n\n return input_messages\n\n\ndef convert_chatresponse_to_output_messages(\n chat_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert ChatResponse to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n output_dicts = get_chatmodel_output_messages(chat_response)\n if not output_dicts:\n return []\n\n output_messages = []\n for msg_dict in output_dicts:\n role = msg_dict.get(\"role\", \"assistant\")\n parts_dicts = msg_dict.get(\"parts\", [])\n finish_reason = msg_dict.get(\"finish_reason\", \"stop\")\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(part)\n\n output_messages.append(\n OutputMessage(\n role=role,\n parts=converted_parts,\n finish_reason=finish_reason,\n )\n )\n\n return output_messages\n\n\ndef convert_agent_response_to_output_messages(\n agent_response: Any,\n) -> List[OutputMessage]:\n \"\"\"Convert agent Msg response to opentelemetry-util-genai OutputMessage format.\n\n This function is used by ExtendedTelemetryHandler which requires OutputMessage objects.\n \"\"\"\n if not hasattr(agent_response, \"content\"):\n return []\n\n try:\n # Use _format_msg_to_parts to convert Msg to dict, then convert to OutputMessage\n msg_dict = _format_msg_to_parts(agent_response)\n parts_dicts = msg_dict.get(\"parts\", [])\n\n converted_parts = []\n for part in parts_dicts:\n part_type = part.get(\"type\")\n if part_type == \"text\":\n converted_parts.append(Text(content=part.get(\"content\", \"\")))\n elif part_type == \"tool_call\":\n converted_parts.append(\n ToolCall(\n id=part.get(\"id\"),\n name=part.get(\"name\", \"\"),\n arguments=part.get(\"arguments\", {}),\n )\n )\n else:\n converted_parts.append(Text(content=str(part)))\n\n if not converted_parts:\n converted_parts.append(Text(content=\"\"))\n\n return [\n OutputMessage(\n role=\"assistant\",\n parts=converted_parts,\n finish_reason=\"stop\",\n )\n ]\n except Exception as e:\n logger.debug(f\"Failed to convert agent response: {e}\")\n return []\n"}, "45004380c53c019e8e8ee07a73d66997b00a8ba2": {"t": 1785758593.6540606, "n": 777, "h": "6380652f904c3509", "c": "import os\nfrom importlib.metadata import version\n\nfrom packaging import version as pkg_version\n\ntry:\n from pydantic import BaseModel\nexcept ImportError:\n BaseModel = None\n\n# 获取 AgentScope 版本\ntry:\n _AGENTSCOPE_VERSION = version(\"agentscope\")\nexcept Exception:\n _AGENTSCOPE_VERSION = \"0.0.0\"\n\n# 环境变量配置\nOTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = (\n \"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT\"\n)\n\n\ndef is_agentscope_v1():\n \"\"\"检查是否为 AgentScope v1.0.0 及以上版本\"\"\"\n return pkg_version.parse(_AGENTSCOPE_VERSION) >= pkg_version.parse(\"1.0.0\")\n\n\ndef is_content_enabled() -> bool:\n \"\"\"检查是否应该捕获消息内容\"\"\"\n return (\n os.getenv(\n OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, \"false\"\n ).lower()\n == \"true\"\n )\n"}, "fe887cf3445eb8fe548b7e3a1c5a20995d0e1dc8": {"t": 1785758595.067652, "n": 981, "h": "ac4d4a8925345687", "c": "import os\nimport json\nimport datetime\nimport enum\nimport inspect\nfrom importlib.metadata import version\nfrom packaging import version as pkg_version\nfrom dataclasses import is_dataclass\nfrom typing import Any, Dict, List, Optional, Union, Tuple, Iterable\n\nfrom opentelemetry.util.types import AttributeValue\n\ntry:\n from pydantic import BaseModel\nexcept ImportError:\n BaseModel = None\n\n# 获取 AgentScope 版本\ntry:\n import agentscope\n _AGENTSCOPE_VERSION = version(\"agentscope\")\nexcept Exception:\n _AGENTSCOPE_VERSION = \"0.0.0\"\n\n# 环境变量配置\nOTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT = (\n \"OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT\"\n)\n\n\ndef is_agentscope_v1():\n \"\"\"检查是否为 AgentScope v1.0.0 及以上版本\"\"\"\n return pkg_version.parse(_AGENTSCOPE_VERSION) >= pkg_version.parse(\"1.0.0\")\n\ndef is_content_enabled() -> bool:\n \"\"\"检查是否应该捕获消息内容\"\"\"\n return (\n os.getenv(OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT, \"false\").lower() == \"true\"\n )\n"}}}}