Nathan Brake
Refactor agent type handling to use AgentFramework enum. Remove agent_type from YAML configurations and update telemetry processing to accommodate the new framework structure. Enhance smolagents configuration with file saving capabilities. (#43)
a9fb876 unverified
raw
history blame
4.12 kB
from typing import Any, Dict, List
import json
from any_agent import AgentFramework
from surf_spot_finder.evaluation.telemetry import TelemetryProcessor
class OpenAITelemetryProcessor(TelemetryProcessor):
"""Processor for OpenAI agent telemetry data."""
def _get_agent_framework(self) -> AgentFramework:
return AgentFramework.OPENAI
def extract_hypothesis_answer(self, trace: List[Dict[str, Any]]) -> str:
for span in reversed(trace):
# Looking for the final response that has the summary answer
if (
"attributes" in span
and span.get("attributes", {}).get("openinference.span.kind") == "LLM"
):
output_key = (
"llm.output_messages.0.message.contents.0.message_content.text"
)
if output_key in span["attributes"]:
return span["attributes"][output_key]
raise ValueError("No agent final answer found in trace")
def _extract_telemetry_data(self, telemetry: List[Dict[str, Any]]) -> list:
"""Extract LLM calls and tool calls from OpenAI telemetry."""
calls = []
for span in telemetry:
if "attributes" not in span:
continue
attributes = span.get("attributes", {})
span_kind = attributes.get("openinference.span.kind", "")
# Collect LLM interactions - look for direct message content first
if span_kind == "LLM":
# Initialize the LLM info dictionary
span_info = {}
# Try to get input message
input_key = "llm.input_messages.1.message.content" # User message is usually at index 1
if input_key in attributes:
span_info["input"] = attributes[input_key]
# Try to get output message directly
output_content = None
# Try in multiple possible locations
for key in [
"llm.output_messages.0.message.content",
"llm.output_messages.0.message.contents.0.message_content.text",
]:
if key in attributes:
output_content = attributes[key]
break
# If we found direct output content, use it
if output_content:
span_info["output"] = output_content
calls.append(span_info)
elif span_kind == "TOOL":
tool_name = attributes.get("tool.name", "Unknown tool")
tool_output = attributes.get("output.value", "")
span_info = {
"tool_name": tool_name,
"input": attributes.get("input.value", ""),
"output": tool_output,
# Can't add status yet because it isn't being set by openinference
# "status": span.get("status", {}).get("status_code"),
}
span_info["input"] = json.loads(span_info["input"])
calls.append(span_info)
return calls
# Backward compatibility functions that use the new class structure
def extract_hypothesis_answer(
trace: List[Dict[str, Any]], agent_framework: AgentFramework
) -> str:
"""Extract the hypothesis agent final answer from the trace"""
processor = TelemetryProcessor.create(agent_framework)
return processor.extract_hypothesis_answer(trace)
def parse_generic_key_value_string(text: str) -> Dict[str, str]:
"""
Parse a string that has items of a dict with key-value pairs separated by '='.
Only splits on '=' signs, handling quoted strings properly.
"""
return TelemetryProcessor.parse_generic_key_value_string(text)
def extract_evidence(
telemetry: List[Dict[str, Any]], agent_framework: AgentFramework
) -> str:
"""Extract relevant telemetry evidence based on the agent type."""
processor = TelemetryProcessor.create(agent_framework)
return processor.extract_evidence(telemetry)