Skip to content
Closed
Original file line number Diff line number Diff line change
Expand Up @@ -452,10 +452,7 @@ def _create_llm_span(
)

_set_span_attribute(span, GenAIAttributes.GEN_AI_SYSTEM, vendor)
_set_span_attribute(span, SpanAttributes.LLM_REQUEST_TYPE, request_type.value)
_set_span_attribute(
span, GenAIAttributes.GEN_AI_OPERATION_NAME, GenAICustomOperationName.LLM_REQUEST.value
)
_set_span_attribute(span, GenAIAttributes.GEN_AI_OPERATION_NAME, request_type.value)

# we already have an LLM span by this point,
# so skip any downstream instrumentation from here
Expand Down Expand Up @@ -732,11 +729,11 @@ def on_llm_end(
span, GenAIAttributes.GEN_AI_USAGE_OUTPUT_TOKENS, completion_tokens
)
_set_span_attribute(
span, SpanAttributes.LLM_USAGE_TOTAL_TOKENS, total_tokens
span, SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS, total_tokens
)

# Record token usage metrics
vendor = span.attributes.get(GenAIAttributes.GEN_AI_SYSTEM, "Langchain")
vendor = span.attributes.get(GenAIAttributes.GEN_AI_SYSTEM, "langchain")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if prompt_tokens > 0:
self.token_histogram.record(
prompt_tokens,
Expand Down Expand Up @@ -768,7 +765,7 @@ def on_llm_end(

# Record duration before ending span
duration = time.time() - self.spans[run_id].start_time
vendor = span.attributes.get(GenAIAttributes.GEN_AI_SYSTEM, "Langchain")
vendor = span.attributes.get(GenAIAttributes.GEN_AI_SYSTEM, "langchain")
self.duration_histogram.record(
duration,
attributes={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,23 +97,19 @@ def set_request_params(span, kwargs, span_holder: SpanHolder):
_set_span_attribute(span, GenAIAttributes.GEN_AI_REQUEST_TOP_P, params.get("top_p"))

tools = kwargs.get("invocation_params", {}).get("tools", [])
for i, tool in enumerate(tools):
tool_function = tool.get("function", tool)
_set_span_attribute(
span,
f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{i}.name",
tool_function.get("name"),
)
_set_span_attribute(
span,
f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{i}.description",
tool_function.get("description"),
)
_set_span_attribute(
span,
f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{i}.parameters",
json.dumps(tool_function.get("parameters", tool.get("input_schema"))),
)
if tools:
tool_defs = []
for tool in tools:
tool_function = tool.get("function", tool)
tool_def = {
"name": tool_function.get("name"),
"description": tool_function.get("description"),
}
params = tool_function.get("parameters") or tool.get("input_schema")
if params is not None:
tool_def["parameters"] = params
tool_defs.append(tool_def)
span.set_attribute(GenAIAttributes.GEN_AI_TOOL_DEFINITIONS, json.dumps(tool_defs))


def set_llm_request(
Expand All @@ -126,17 +122,8 @@ def set_llm_request(
set_request_params(span, kwargs, span_holder)

if should_send_prompts():
for i, msg in enumerate(prompts):
_set_span_attribute(
span,
f"{GenAIAttributes.GEN_AI_PROMPT}.{i}.role",
"user",
)
_set_span_attribute(
span,
f"{GenAIAttributes.GEN_AI_PROMPT}.{i}.content",
msg,
)
messages = [{"role": "user", "content": msg} for msg in prompts]
span.set_attribute(GenAIAttributes.GEN_AI_INPUT_MESSAGES, json.dumps(messages))


def set_chat_request(
Expand All @@ -148,81 +135,62 @@ def set_chat_request(
) -> None:
set_request_params(span, serialized.get("kwargs", {}), span_holder)

functions = kwargs.get("invocation_params", {}).get("functions", [])
if functions:
tool_defs = [
{
"name": f.get("name"),
"description": f.get("description"),
"parameters": f.get("parameters"),
}
for f in functions
]
span.set_attribute(GenAIAttributes.GEN_AI_TOOL_DEFINITIONS, json.dumps(tool_defs))

if should_send_prompts():
for i, function in enumerate(
kwargs.get("invocation_params", {}).get("functions", [])
):
prefix = f"{SpanAttributes.LLM_REQUEST_FUNCTIONS}.{i}"

_set_span_attribute(span, f"{prefix}.name", function.get("name"))
_set_span_attribute(
span, f"{prefix}.description", function.get("description")
)
_set_span_attribute(
span, f"{prefix}.parameters", json.dumps(function.get("parameters"))
)

i = 0
input_messages = []
for message in messages:
for msg in message:
_set_span_attribute(
span,
f"{GenAIAttributes.GEN_AI_PROMPT}.{i}.role",
_message_type_to_role(msg.type),
)
msg_obj = {"role": _message_type_to_role(msg.type)}

tool_calls = (
msg.tool_calls
if hasattr(msg, "tool_calls")
else msg.additional_kwargs.get("tool_calls")
)

if tool_calls:
_set_chat_tool_calls(
span, f"{GenAIAttributes.GEN_AI_PROMPT}.{i}", tool_calls
)
msg_obj["tool_calls"] = _build_tool_calls_list(tool_calls)

# Always set content if it exists, regardless of tool_calls presence
content = (
msg.content
if isinstance(msg.content, str)
else json.dumps(msg.content, cls=CallbackFilteredJSONEncoder)
)
_set_span_attribute(
span,
f"{GenAIAttributes.GEN_AI_PROMPT}.{i}.content",
content,
)
if content:
msg_obj["content"] = content

if msg.type == "tool" and hasattr(msg, "tool_call_id"):
_set_span_attribute(
span,
f"{GenAIAttributes.GEN_AI_PROMPT}.{i}.tool_call_id",
msg.tool_call_id,
)
msg_obj["tool_call_id"] = msg.tool_call_id

i += 1
input_messages.append(msg_obj)

if input_messages:
span.set_attribute(GenAIAttributes.GEN_AI_INPUT_MESSAGES, json.dumps(input_messages))


def set_chat_response(span: Span, response: LLMResult) -> None:
if not should_send_prompts():
return

i = 0
output_messages = []
for generations in response.generations:
for generation in generations:
prefix = f"{GenAIAttributes.GEN_AI_COMPLETION}.{i}"

if hasattr(generation, "message") and generation.message and hasattr(generation.message, "type"):
role = _message_type_to_role(generation.message.type)
else:
# For non-chat completions (Generation objects), default to assistant
role = "assistant"

_set_span_attribute(
span,
f"{prefix}.role",
role,
)
msg_obj = {"role": role}

# Try to get content from various sources
content = None
Expand All @@ -235,38 +203,19 @@ def set_chat_response(span: Span, response: LLMResult) -> None:
content = json.dumps(generation.message.content, cls=CallbackFilteredJSONEncoder)

if content:
_set_span_attribute(
span,
f"{prefix}.content",
content,
)
msg_obj["content"] = content

# Set finish reason if available
if generation.generation_info and generation.generation_info.get("finish_reason"):
_set_span_attribute(
span,
f"{prefix}.finish_reason",
generation.generation_info.get("finish_reason"),
)
msg_obj["finish_reason"] = generation.generation_info.get("finish_reason")

# Handle tool calls and function calls
if hasattr(generation, "message") and generation.message:
# Handle legacy function_call format (single function call)
if generation.message.additional_kwargs.get("function_call"):
_set_span_attribute(
span,
f"{prefix}.tool_calls.0.name",
generation.message.additional_kwargs.get("function_call").get(
"name"
),
)
_set_span_attribute(
span,
f"{prefix}.tool_calls.0.arguments",
generation.message.additional_kwargs.get("function_call").get(
"arguments"
),
)
fc = generation.message.additional_kwargs.get("function_call")
msg_obj["role"] = "assistant"
msg_obj["tool_calls"] = [{"name": fc.get("name"), "arguments": fc.get("arguments")}]

# Handle new tool_calls format (multiple tool calls)
tool_calls = (
Expand All @@ -275,13 +224,13 @@ def set_chat_response(span: Span, response: LLMResult) -> None:
else generation.message.additional_kwargs.get("tool_calls")
)
if tool_calls and isinstance(tool_calls, list):
_set_span_attribute(
span,
f"{prefix}.role",
"assistant",
)
_set_chat_tool_calls(span, prefix, tool_calls)
i += 1
msg_obj["role"] = "assistant"
msg_obj["tool_calls"] = _build_tool_calls_list(tool_calls)

output_messages.append(msg_obj)

if output_messages:
span.set_attribute(GenAIAttributes.GEN_AI_OUTPUT_MESSAGES, json.dumps(output_messages))


def set_chat_response_usage(
Expand Down Expand Up @@ -325,9 +274,8 @@ def set_chat_response_usage(
"input_token_details", {}
)
cache_read_tokens += input_token_details.get("cache_read", 0)
except Exception as e:
except Exception:
# If there's any issue processing usage metadata, continue without it
print(f"DEBUG: Error processing usage metadata: {e}")
pass

if (
Expand All @@ -348,16 +296,16 @@ def set_chat_response_usage(
)
_set_span_attribute(
span,
SpanAttributes.LLM_USAGE_TOTAL_TOKENS,
SpanAttributes.GEN_AI_USAGE_TOTAL_TOKENS,
total_tokens,
)
_set_span_attribute(
span,
SpanAttributes.LLM_USAGE_CACHE_READ_INPUT_TOKENS,
SpanAttributes.GEN_AI_USAGE_CACHE_READ_INPUT_TOKENS,
cache_read_tokens,
)
if record_token_usage:
vendor = span.attributes.get(GenAIAttributes.GEN_AI_SYSTEM, "Langchain")
vendor = span.attributes.get(GenAIAttributes.GEN_AI_SYSTEM, "langchain")

if input_tokens > 0:
token_histogram.record(
Expand Down Expand Up @@ -397,11 +345,9 @@ def _extract_model_name_from_association_metadata(metadata: Optional[dict[str, A
return "unknown"


def _set_chat_tool_calls(
span: Span, prefix: str, tool_calls: list[dict[str, Any]]
) -> None:
for idx, tool_call in enumerate(tool_calls):
tool_call_prefix = f"{prefix}.tool_calls.{idx}"
def _build_tool_calls_list(tool_calls: list[dict[str, Any]]) -> list[dict[str, Any]]:
result = []
for tool_call in tool_calls:
tool_call_dict = dict(tool_call)
tool_id = tool_call_dict.get("id")
tool_name = tool_call_dict.get(
Expand All @@ -411,14 +357,12 @@ def _set_chat_tool_calls(
"args", tool_call_dict.get("function", {}).get("arguments")
)

_set_span_attribute(span, f"{tool_call_prefix}.id", tool_id)
_set_span_attribute(
span,
f"{tool_call_prefix}.name",
tool_name,
)
_set_span_attribute(
span,
f"{tool_call_prefix}.arguments",
json.dumps(tool_args, cls=CallbackFilteredJSONEncoder),
)
call_obj = {}
if tool_id:
call_obj["id"] = tool_id
if tool_name:
call_obj["name"] = tool_name
if tool_args is not None:
call_obj["arguments"] = json.dumps(tool_args, cls=CallbackFilteredJSONEncoder)
result.append(call_obj)
return result
Loading
Loading