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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 39 additions & 31 deletions src/runner/templates/agents/node/manual/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -45,46 +45,50 @@ function trimMessages(messages, maxSize = 10000) {
}

function buildSentryMessages(messages) {
const sentryMessages = [];
const legacyMessages = [];
let systemInstructions = null;

for (const msg of messages) {
const { role, content } = msg;

if (role === "system") {
systemInstructions = typeof content === "string" ? content : JSON.stringify(content);
if (msg.role === "system") {
systemInstructions =
typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
}
}

const parts = [];
if (typeof content === "string") {
parts.push({ type: "text", text: content });
} else if (Array.isArray(content)) {
for (const part of content) {
if (part.type === "text") {
parts.push({ type: "text", text: part.text });
} else if (part.type === "image") {
parts.push({ type: "text", text: "[Blob substitute]" });
}
}
}
const lastMessage = messages[messages.length - 1];
if (!lastMessage) {
return { sentryMessages: [], legacyMessages: [], systemInstructions };
}

sentryMessages.push({
role,
content: typeof content === "string" ? content : "[multimodal]",
parts,
});

if (Array.isArray(content)) {
const redacted = content.map((part) =>
part.type === "image" ? { type: "text", text: "[Blob substitute]" } : part
);
legacyMessages.push({ role, content: redacted });
} else {
legacyMessages.push({ role, content });
const { role, content } = lastMessage;

const parts = [];
if (typeof content === "string") {
parts.push({ type: "text", text: content });
} else if (Array.isArray(content)) {
for (const part of content) {
if (part.type === "text") {
parts.push({ type: "text", text: part.text });
} else if (part.type === "image") {
parts.push({ type: "text", text: "[Blob substitute]" });
}
}
}

const sentryMessages = [{
role,
content: typeof content === "string" ? content : "[multimodal]",
parts,
}];

const legacyMessages = [Array.isArray(content)
? {
role,
content: content.map((part) =>
part.type === "image" ? { type: "text", text: "[Blob substitute]" } : part,
),
}
: { role, content }];

return { sentryMessages, legacyMessages, systemInstructions };
}

Expand Down Expand Up @@ -150,6 +154,10 @@ const TOOLS = {{ agent.tools | dump }};
agentSpan.setAttribute("gen_ai.usage.output_tokens", outputTokens{{ loop.index }});
agentSpan.setAttribute("gen_ai.usage.output_tokens.reasoning", Math.max(0, Math.floor(outputTokens{{ loop.index }} / 4)));
agentSpan.setAttribute("gen_ai.usage.total_tokens", totalTokens{{ loop.index }});
agentSpan.setAttribute("gen_ai.input.messages", otelJson{{ loop.index }});
if (system{{ loop.index }} !== null) {
agentSpan.setAttribute("gen_ai.system_instructions", system{{ loop.index }});
}

// Chat span (child of agent)
const chatDesc{{ loop.index }} = `chat ${model{{ loop.index }}}`;
Expand Down
72 changes: 38 additions & 34 deletions src/runner/templates/agents/python/manual/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -48,46 +48,47 @@ def trim_messages(messages, max_size=10000):


def build_sentry_messages(messages):
"""Build gen_ai.input.messages and gen_ai.request.messages from raw messages."""
sentry_messages = []
legacy_messages = []
"""Build gen_ai.input.messages and gen_ai.request.messages from the last raw message."""
system_instructions = None

for msg in messages:
role = msg["role"]
content = msg.get("content", "")

if role == "system":
if msg["role"] == "system":
system_instructions = content if isinstance(content, str) else json.dumps(content)

# New OTel format with parts
parts = []
if isinstance(content, str):
parts.append({"type": "text", "text": content})
elif isinstance(content, list):
for part in content:
if part.get("type") == "text":
parts.append({"type": "text", "text": part["text"]})
elif part.get("type") == "image":
parts.append({"type": "text", "text": "[Blob substitute]"})

sentry_messages.append({
"role": role,
"content": content if isinstance(content, str) else "[multimodal]",
"parts": parts,
})

# Legacy format with binary redaction
if isinstance(content, list):
redacted = []
for part in content:
if part.get("type") == "text":
redacted.append(part)
elif part.get("type") == "image":
redacted.append({"type": "text", "text": "[Blob substitute]"})
legacy_messages.append({"role": role, "content": redacted})
else:
legacy_messages.append({"role": role, "content": content})
if not messages:
return [], [], system_instructions

last_message = messages[-1]
role = last_message["role"]
content = last_message.get("content", "")

parts = []
if isinstance(content, str):
parts.append({"type": "text", "text": content})
elif isinstance(content, list):
for part in content:
if part.get("type") == "text":
parts.append({"type": "text", "text": part["text"]})
elif part.get("type") == "image":
parts.append({"type": "text", "text": "[Blob substitute]"})

sentry_messages = [{
"role": role,
"content": content if isinstance(content, str) else "[multimodal]",
"parts": parts,
}]

if isinstance(content, list):
redacted = []
for part in content:
if part.get("type") == "text":
redacted.append(part)
elif part.get("type") == "image":
redacted.append({"type": "text", "text": "[Blob substitute]"})
legacy_messages = [{"role": role, "content": redacted}]
else:
legacy_messages = [{"role": role, "content": content}]

return sentry_messages, legacy_messages, system_instructions

Expand Down Expand Up @@ -151,6 +152,9 @@ TOOLS = {{ agent.tools | dump }}
agent_span.set_data("gen_ai.usage.output_tokens", output_tokens)
agent_span.set_data("gen_ai.usage.output_tokens.reasoning", max(0, output_tokens // 4))
agent_span.set_data("gen_ai.usage.total_tokens", total_tokens)
agent_span.set_data("gen_ai.input.messages", otel_messages_json)
if system_instructions is not None:
agent_span.set_data("gen_ai.system_instructions", system_instructions)

# Chat span (child of agent)
chat_desc = f"chat {model}"
Expand Down
68 changes: 35 additions & 33 deletions src/runner/templates/llm/node/manual/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -45,48 +45,50 @@ function trimMessages(messages, maxSize = 10000) {
}

function buildSentryMessages(messages) {
const sentryMessages = [];
const legacyMessages = [];
let systemInstructions = null;

for (const msg of messages) {
const { role, content } = msg;

if (role === "system") {
systemInstructions = typeof content === "string" ? content : JSON.stringify(content);
if (msg.role === "system") {
systemInstructions =
typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
}
}

// New OTel format with parts
const parts = [];
if (typeof content === "string") {
parts.push({ type: "text", text: content });
} else if (Array.isArray(content)) {
for (const part of content) {
if (part.type === "text") {
parts.push({ type: "text", text: part.text });
} else if (part.type === "image") {
parts.push({ type: "text", text: "[Blob substitute]" });
}
}
}
const lastMessage = messages[messages.length - 1];
if (!lastMessage) {
return { sentryMessages: [], legacyMessages: [], systemInstructions };
}

sentryMessages.push({
role,
content: typeof content === "string" ? content : "[multimodal]",
parts,
});

// Legacy format with binary redaction
if (Array.isArray(content)) {
const redacted = content.map((part) =>
part.type === "image" ? { type: "text", text: "[Blob substitute]" } : part
);
legacyMessages.push({ role, content: redacted });
} else {
legacyMessages.push({ role, content });
const { role, content } = lastMessage;

const parts = [];
if (typeof content === "string") {
parts.push({ type: "text", text: content });
} else if (Array.isArray(content)) {
for (const part of content) {
if (part.type === "text") {
parts.push({ type: "text", text: part.text });
} else if (part.type === "image") {
parts.push({ type: "text", text: "[Blob substitute]" });
}
}
}

const sentryMessages = [{
role,
content: typeof content === "string" ? content : "[multimodal]",
parts,
}];

const legacyMessages = [Array.isArray(content)
? {
role,
content: content.map((part) =>
part.type === "image" ? { type: "text", text: "[Blob substitute]" } : part,
),
}
: { role, content }];

return { sentryMessages, legacyMessages, systemInstructions };
}
{% endblock %}
Expand Down
69 changes: 35 additions & 34 deletions src/runner/templates/llm/python/manual/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -44,46 +44,47 @@ def trim_messages(messages, max_size=10000):


def build_sentry_messages(messages):
"""Build gen_ai.input.messages and gen_ai.request.messages from raw messages."""
sentry_messages = []
legacy_messages = []
"""Build gen_ai.input.messages and gen_ai.request.messages from the last raw message."""
system_instructions = None

for msg in messages:
role = msg["role"]
content = msg.get("content", "")

if role == "system":
if msg["role"] == "system":
system_instructions = content if isinstance(content, str) else json.dumps(content)

# New OTel format with parts
parts = []
if isinstance(content, str):
parts.append({"type": "text", "text": content})
elif isinstance(content, list):
for part in content:
if part.get("type") == "text":
parts.append({"type": "text", "text": part["text"]})
elif part.get("type") == "image":
parts.append({"type": "text", "text": "[Blob substitute]"})

sentry_messages.append({
"role": role,
"content": content if isinstance(content, str) else "[multimodal]",
"parts": parts,
})

# Legacy format with binary redaction
if isinstance(content, list):
redacted = []
for part in content:
if part.get("type") == "text":
redacted.append(part)
elif part.get("type") == "image":
redacted.append({"type": "text", "text": "[Blob substitute]"})
legacy_messages.append({"role": role, "content": redacted})
else:
legacy_messages.append({"role": role, "content": content})
if not messages:
return [], [], system_instructions

last_message = messages[-1]
role = last_message["role"]
content = last_message.get("content", "")

parts = []
if isinstance(content, str):
parts.append({"type": "text", "text": content})
elif isinstance(content, list):
for part in content:
if part.get("type") == "text":
parts.append({"type": "text", "text": part["text"]})
elif part.get("type") == "image":
parts.append({"type": "text", "text": "[Blob substitute]"})

sentry_messages = [{
"role": role,
"content": content if isinstance(content, str) else "[multimodal]",
"parts": parts,
}]

if isinstance(content, list):
redacted = []
for part in content:
if part.get("type") == "text":
redacted.append(part)
elif part.get("type") == "image":
redacted.append({"type": "text", "text": "[Blob substitute]"})
legacy_messages = [{"role": role, "content": redacted}]
else:
legacy_messages = [{"role": role, "content": content}]

return sentry_messages, legacy_messages, system_instructions
{% endblock %}
Expand Down
Loading
Loading