Files
wehub-resource-sync b957a53def
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:21:23 +08:00

1058 lines
36 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
import re
from typing import TYPE_CHECKING, Any, cast
from azure.ai.agents.models import (
MessageDeltaImageFileContent,
MessageDeltaImageFileContentObject,
MessageDeltaTextContent,
MessageDeltaTextFileCitationAnnotation,
MessageDeltaTextFilePathAnnotation,
MessageDeltaTextUrlCitationAnnotation,
MessageImageFileContent,
MessageTextContent,
MessageTextFileCitationAnnotation,
MessageTextFilePathAnnotation,
MessageTextUrlCitationAnnotation,
RequiredFunctionToolCall,
RequiredMcpToolCall,
RunStep,
RunStepAzureAISearchToolCall,
RunStepBingCustomSearchToolCall,
RunStepBingGroundingToolCall,
RunStepDeepResearchToolCall,
RunStepDeltaCodeInterpreterImageOutput,
RunStepDeltaCodeInterpreterLogOutput,
RunStepDeltaCodeInterpreterToolCall,
RunStepDeltaFileSearchToolCall,
RunStepDeltaFunctionToolCall,
RunStepFileSearchToolCall,
RunStepFunctionToolCall,
RunStepMcpToolCall,
RunStepOpenAPIToolCall,
ThreadMessage,
ThreadRun,
)
from semantic_kernel.contents.annotation_content import AnnotationContent, CitationType
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.file_reference_content import FileReferenceContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.contents.function_result_content import FunctionResultContent
from semantic_kernel.contents.image_content import ImageContent
from semantic_kernel.contents.streaming_annotation_content import StreamingAnnotationContent
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
from semantic_kernel.contents.streaming_file_reference_content import StreamingFileReferenceContent
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
from semantic_kernel.contents.text_content import TextContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.utils.feature_stage_decorator import experimental
if TYPE_CHECKING:
from azure.ai.agents.models import (
MessageDeltaChunk,
RunStepDeltaToolCallObject,
)
_URL_PATTERN = re.compile(r"https?://[^\s\]\)]+", re.IGNORECASE)
THREAD_MESSAGE_ID = "thread_message_id"
"""
The methods in this file are used with Azure AI Agent
related code. They are used to invoke, create chat messages,
or generate message content.
"""
@experimental
def get_message_contents(message: "ChatMessageContent") -> list[dict[str, Any]]:
"""Get the message contents.
Args:
message: The message.
"""
contents: list[dict[str, Any]] = []
for content in message.items:
match content:
case TextContent():
# Make sure text is a string
final_text = content.text
if not isinstance(final_text, str):
if isinstance(final_text, (list, tuple)):
final_text = " ".join(map(str, final_text))
else:
final_text = str(final_text)
contents.append({"type": "text", "text": final_text})
case ImageContent():
if content.uri:
contents.append(content.to_dict())
case FileReferenceContent():
contents.append({
"type": "image_file",
"image_file": {"file_id": content.file_id},
})
case FunctionResultContent():
final_result = content.result
match final_result:
case str():
contents.append({"type": "text", "text": final_result})
case list() | tuple():
contents.append({"type": "text", "text": " ".join(map(str, final_result))})
case _:
contents.append({"type": "text", "text": str(final_result)})
return contents
@experimental
def generate_message_content(
assistant_name: str, message: "ThreadMessage", completed_step: "RunStep | None" = None
) -> ChatMessageContent:
"""Generate message content."""
role = AuthorRole(message.role)
metadata = (
{
"created_at": completed_step.created_at,
"message_id": message.id, # message needs to be defined in context
"thread_message_id": message.id, # Add `thread_message_id` to avoid breaking the existing `message_id` key
"step_id": completed_step.id,
"run_id": completed_step.run_id,
"thread_id": completed_step.thread_id,
"agent_id": completed_step.agent_id,
"usage": completed_step.usage,
}
if completed_step is not None
else None
)
content: ChatMessageContent = ChatMessageContent(role=role, name=assistant_name, metadata=metadata) # type: ignore
messages: list[MessageImageFileContent | MessageTextContent] = cast(
list[MessageImageFileContent | MessageTextContent], message.content or []
)
for item_content in messages:
if item_content.type == "text":
content.items.append(
TextContent(
text=item_content.text.value,
)
)
for annotation in item_content.text.annotations:
content.items.append(generate_annotation_content(annotation)) # type: ignore
elif item_content.type == "image_file":
content.items.append(
FileReferenceContent(
file_id=item_content.image_file.file_id,
)
)
return content
@experimental
def generate_streaming_message_content(
assistant_name: str,
message_delta_event: "MessageDeltaChunk",
thread_msg_id: str | None = None,
) -> StreamingChatMessageContent:
"""Generate streaming message content from a MessageDeltaEvent."""
delta = message_delta_event.delta
# Determine the role
role = AuthorRole(delta.role) if delta.role is not None else AuthorRole("assistant")
items: list[StreamingTextContent | StreamingAnnotationContent | StreamingFileReferenceContent] = []
delta_chunks: list[MessageDeltaImageFileContent | MessageDeltaTextContent] = cast(
list[MessageDeltaImageFileContent | MessageDeltaTextContent], delta.content or []
)
for delta_block in delta_chunks:
if delta_block.type == "text":
if delta_block.text and delta_block.text.value: # Ensure text is not None
text_value = delta_block.text.value
items.append(
StreamingTextContent(
text=text_value,
choice_index=delta_block.index,
)
)
# Process annotations if any
if delta_block.text.annotations:
for annotation in delta_block.text.annotations or []:
if isinstance(
annotation,
(
MessageDeltaTextFileCitationAnnotation,
MessageDeltaTextFilePathAnnotation,
MessageDeltaTextUrlCitationAnnotation,
),
):
items.append(generate_streaming_annotation_content(annotation))
elif delta_block.type == "image_file":
assert isinstance(delta_block, MessageDeltaImageFileContent) # nosec
if delta_block.image_file and isinstance(delta_block.image_file, MessageDeltaImageFileContentObject):
file_id = delta_block.image_file.file_id
items.append(
StreamingFileReferenceContent(
file_id=file_id,
)
)
metadata: dict[str, Any] | None = None
if thread_msg_id:
metadata = {THREAD_MESSAGE_ID: thread_msg_id}
return StreamingChatMessageContent(role=role, name=assistant_name, items=items, choice_index=0, metadata=metadata) # type: ignore
@experimental
def get_function_call_contents(
run: "ThreadRun", function_steps: dict[str, FunctionCallContent]
) -> list[FunctionCallContent]:
"""Extract function call contents from the run.
Args:
run: The run.
function_steps: The function steps
Returns:
The list of function call contents.
"""
function_call_contents: list[FunctionCallContent] = []
required_action = getattr(run, "required_action", None)
submit_tool_outputs = getattr(required_action, "submit_tool_outputs", None)
if not submit_tool_outputs or not hasattr(submit_tool_outputs, "tool_calls"):
return function_call_contents
tool_calls = getattr(submit_tool_outputs, "tool_calls", [])
if not isinstance(tool_calls, (list, tuple)):
return function_call_contents
for tool_call in tool_calls:
if not isinstance(tool_call, RequiredFunctionToolCall):
continue
fcc = FunctionCallContent(
id=tool_call.id,
index=getattr(tool_call, "index", None),
name=tool_call.function.name,
arguments=tool_call.function.arguments,
)
function_call_contents.append(fcc)
function_steps[tool_call.id] = fcc
return function_call_contents
@experimental
def generate_function_call_content(agent_name: str, fccs: list[FunctionCallContent]) -> ChatMessageContent:
"""Generate function call content.
Args:
agent_name: The agent name.
fccs: The function call contents.
Returns:
ChatMessageContent: The chat message content containing the function call content as the items.
"""
return ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name, items=fccs) # type: ignore
@experimental
def generate_function_call_streaming_content(
agent_name: str,
fccs: list[FunctionCallContent],
) -> StreamingChatMessageContent:
"""Generate function call content.
Args:
agent_name: The agent name.
fccs: The function call contents.
Returns:
StreamingChatMessageContent: The chat message content containing the function call content as the items.
"""
return StreamingChatMessageContent(role=AuthorRole.ASSISTANT, choice_index=0, name=agent_name, items=fccs) # type: ignore
@experimental
def generate_function_result_content(
agent_name: str, function_step: FunctionCallContent, tool_call: "RunStepFunctionToolCall"
) -> ChatMessageContent:
"""Generate function result content."""
function_call_content: ChatMessageContent = ChatMessageContent(role=AuthorRole.TOOL, name=agent_name) # type: ignore
function_call_content.items.append(
FunctionResultContent(
function_name=function_step.function_name,
plugin_name=function_step.plugin_name,
id=function_step.id,
result=tool_call.function.get("output"), # type: ignore
)
)
return function_call_content
@experimental
def generate_bing_grounding_content(
agent_name: str, bing_tool_call: "RunStepBingGroundingToolCall | RunStepBingCustomSearchToolCall"
) -> ChatMessageContent:
"""Generate function result content related to a Bing Grounding Tool or Bing Custom Search Tool."""
message_content: ChatMessageContent = ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name) # type: ignore
# Extract tool details based on the specific tool type
if isinstance(bing_tool_call, RunStepBingGroundingToolCall):
tool_details = bing_tool_call.bing_grounding
elif isinstance(bing_tool_call, RunStepBingCustomSearchToolCall):
tool_details = bing_tool_call.bing_custom_search
else:
# This should never happen with proper typing, but provides safety
raise TypeError(f"Unsupported Bing tool call type: {type(bing_tool_call)}")
message_content.items.append(
FunctionCallContent(
id=bing_tool_call.id,
name=bing_tool_call.type,
function_name=bing_tool_call.type,
arguments=tool_details,
)
)
return message_content
@experimental
def generate_azure_ai_search_content(
agent_name: str, azure_ai_search_tool_call: "RunStepAzureAISearchToolCall"
) -> ChatMessageContent | None:
"""Generate function result content related to an Azure AI Search Tool."""
items: list[FunctionCallContent | FunctionResultContent] = []
# Azure AI Search tool call contains both tool call input and output
arguments = azure_ai_search_tool_call.azure_ai_search.get("input")
if arguments:
items.append(
FunctionCallContent(
id=azure_ai_search_tool_call.id,
name=azure_ai_search_tool_call.type,
function_name=azure_ai_search_tool_call.type,
arguments=arguments,
inner_content=azure_ai_search_tool_call,
)
)
result = azure_ai_search_tool_call.azure_ai_search.get("output")
if result:
items.append(
FunctionResultContent(
function_name=azure_ai_search_tool_call.type,
id=azure_ai_search_tool_call.id,
result=result,
inner_content=azure_ai_search_tool_call,
)
)
return ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name, items=items) if items else None # type: ignore
@experimental
def generate_file_search_content(
agent_name: str, file_search_tool_call: "RunStepFileSearchToolCall"
) -> ChatMessageContent:
"""Generate function result content related to an Azure AI Search Tool."""
message_content: ChatMessageContent = ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name) # type: ignore
# Azure AI Search tool call contains both tool call input and output
message_content.items.append(
FunctionCallContent(
id=file_search_tool_call.id,
name=file_search_tool_call.type,
function_name=file_search_tool_call.type,
arguments=file_search_tool_call.file_search.get("ranking_options", None),
)
)
message_content.items.append(
FunctionResultContent(
function_name=file_search_tool_call.type,
id=file_search_tool_call.id,
result=file_search_tool_call.file_search.get("results", None),
)
)
return message_content
@experimental
def generate_deep_research_content(
agent_name: str, deep_research_tool_call: "RunStepDeepResearchToolCall"
) -> ChatMessageContent:
"""Generate content for a Deep Research tool call.
Emits both the tool call (input) and the tool result (output). If URLs are present
in the output text, a simple "Citations" section with unique URLs is appended as text.
Args:
agent_name: The agent name.
deep_research_tool_call: The deep research tool call details.
Returns:
ChatMessageContent summarizing the deep research call and result.
"""
items: list[FunctionCallContent | FunctionResultContent | TextContent] = []
details = deep_research_tool_call.deep_research
# Function call (input)
items.append(
FunctionCallContent(
id=deep_research_tool_call.id,
name=deep_research_tool_call.type,
function_name=deep_research_tool_call.type,
arguments={"input": getattr(details, "input", None)},
inner_content=deep_research_tool_call,
)
)
# Function result (output)
output_text = getattr(details, "output", None)
if output_text:
items.append(
FunctionResultContent(
function_name=deep_research_tool_call.type,
id=deep_research_tool_call.id,
result=output_text,
inner_content=deep_research_tool_call,
)
)
# Optional: Append a simple citations section from any URLs in the output
urls = _extract_unique_urls(str(output_text))
if urls:
citations_lines = ["## Citations"] + [f"{i + 1}. [{u}]({u})" for i, u in enumerate(urls)]
items.append(TextContent(text="\n\n" + "\n".join(citations_lines)))
return ChatMessageContent(role=AuthorRole.ASSISTANT, name=agent_name, items=items) # type: ignore
def _extract_unique_urls(text: str) -> list[str]:
"""Extract unique HTTP/HTTPS URLs from text in order of appearance."""
seen: set[str] = set()
ordered: list[str] = []
for match in _URL_PATTERN.finditer(text or ""):
url = match.group(0)
if url not in seen:
seen.add(url)
ordered.append(url)
return ordered
@experimental
def generate_openapi_content(agent_name: str, openapi_tool_call: RunStepOpenAPIToolCall) -> ChatMessageContent:
"""Generate ChatMessageContent for a non-streaming OpenAPI tool call."""
tool_id = openapi_tool_call.get("id")
tool_type = openapi_tool_call.get("type", "openapi")
function: dict[str, Any] = openapi_tool_call.get("function", {})
items: list[FunctionCallContent | FunctionResultContent] = []
arguments = function.get("arguments")
if arguments:
items.append(
FunctionCallContent(
id=tool_id,
name=tool_type,
function_name=function.get("name"),
arguments=arguments,
)
)
output = function.get("output")
if output:
items.append(
FunctionResultContent(
function_name=function.get("name"),
id=tool_id,
name=tool_type,
result=output,
)
)
return ChatMessageContent(
role=AuthorRole.ASSISTANT,
items=items, # type: ignore
name=agent_name,
)
@experimental
def generate_code_interpreter_content(agent_name: str, code: str) -> "ChatMessageContent":
"""Generate code interpreter content.
Args:
agent_name: The agent name.
code: The code.
Returns:
ChatMessageContent: The chat message content.
"""
return ChatMessageContent(
role=AuthorRole.ASSISTANT,
content=code,
name=agent_name,
metadata={"code": True},
)
@experimental
def generate_streaming_function_content(
agent_name: str, step_details: "RunStepDeltaToolCallObject"
) -> "StreamingChatMessageContent | None":
"""Generate streaming function content.
Args:
agent_name: The agent name.
step_details: The function step.
Returns:
StreamingChatMessageContent: The chat message content.
"""
if not step_details.tool_calls:
return None
items: list[FunctionCallContent] = []
tool_calls: list[
RunStepDeltaCodeInterpreterToolCall | RunStepDeltaFileSearchToolCall | RunStepDeltaFunctionToolCall
] = cast(
list[RunStepDeltaCodeInterpreterToolCall | RunStepDeltaFileSearchToolCall | RunStepDeltaFunctionToolCall],
step_details.tool_calls or [],
)
for tool in tool_calls:
if tool.type == "function" and tool.function:
items.append(
FunctionCallContent(
id=tool.id,
index=getattr(tool, "index", None),
name=tool.function.name,
arguments=tool.function.arguments,
)
)
return (
StreamingChatMessageContent(
role=AuthorRole.ASSISTANT,
name=agent_name,
items=items, # type: ignore
choice_index=0,
)
if len(items) > 0
else None
)
@experimental
def generate_streaming_bing_grounding_content(
agent_name: str, step_details: "RunStepDeltaToolCallObject"
) -> StreamingChatMessageContent | None:
"""Generate StreamingChatMessageContent for Bing Grounding and Bing Custom Search tool calls."""
if not step_details.tool_calls:
return None
items: list[FunctionCallContent] = []
for index, tool in enumerate(step_details.tool_calls):
if tool.type not in ("bing_grounding", "bing_custom_search"):
continue
# Extract tool details based on the specific tool type
if tool.type == "bing_grounding":
tool_details = tool.get("bing_grounding", {})
elif tool.type == "bing_custom_search":
tool_details = tool.get("bing_custom_search", {})
else:
continue
request_url = tool_details.get("requesturl", None)
response_metadata = tool_details.get("response_metadata", None)
if not request_url and not response_metadata:
continue
items.append(
FunctionCallContent(
id=tool.id,
index=index,
name=tool.type,
function_name=tool.type,
arguments=tool_details,
)
)
if not items:
return None
return StreamingChatMessageContent(
role=AuthorRole.ASSISTANT,
name=agent_name,
choice_index=0,
items=items, # type: ignore
)
@experimental
def generate_streaming_azure_ai_search_content(
agent_name: str, step_details: "RunStepDeltaToolCallObject"
) -> StreamingChatMessageContent | None:
"""Generate function result content related to a Bing Grounding Tool."""
if not step_details.tool_calls:
return None
items: list[FunctionCallContent | FunctionResultContent] = []
for index, tool in enumerate(step_details.tool_calls):
if tool.type == "azure_ai_search":
azure_ai_search_tool = cast(RunStepAzureAISearchToolCall, tool)
azure_ai_search_dict: dict = azure_ai_search_tool.get("azure_ai_search", None)
arguments = azure_ai_search_dict.get("input", {}) if azure_ai_search_dict else None
if arguments:
items.append(
FunctionCallContent(
id=azure_ai_search_tool.id,
index=index,
name=azure_ai_search_tool.type,
function_name=azure_ai_search_tool.type,
arguments=arguments,
inner_content=azure_ai_search_tool,
)
)
result = azure_ai_search_dict.get("output", {}) if azure_ai_search_dict else None
if result:
items.append(
FunctionResultContent(
function_name=azure_ai_search_tool.type,
id=azure_ai_search_tool.id,
result=result,
inner_content=azure_ai_search_tool,
)
)
return (
StreamingChatMessageContent(
role=AuthorRole.ASSISTANT,
name=agent_name,
choice_index=0,
items=items, # type: ignore
)
if items
else None
) # type: ignore
@experimental
def generate_streaming_deep_research_content(
agent_name: str, step_details: "RunStepDeltaToolCallObject"
) -> StreamingChatMessageContent | None:
"""Generate streaming content related to a Deep Research Tool.
Emits FunctionCallContent for the input and FunctionResultContent for the output
as they appear in streamed tool call deltas.
"""
if not step_details.tool_calls:
return None
items: list[FunctionCallContent | FunctionResultContent] = []
for index, tool in enumerate(step_details.tool_calls):
if tool.type == "deep_research":
deep_research_dict: dict = tool.get("deep_research", {})
arguments = {"input": deep_research_dict.get("input")}
if any(v is not None for v in arguments.values()):
items.append(
FunctionCallContent(
id=tool.get("id"),
index=index,
name=tool.type,
function_name=tool.type,
arguments=arguments,
)
)
result = deep_research_dict.get("output")
if result is not None:
items.append(
FunctionResultContent(
function_name=tool.type,
id=tool.get("id"),
result=result,
)
)
return (
StreamingChatMessageContent(
role=AuthorRole.ASSISTANT,
name=agent_name,
choice_index=0,
items=items, # type: ignore
)
if items
else None
)
@experimental
def generate_streaming_file_search_content(
agent_name: str, step_details: "RunStepDeltaToolCallObject"
) -> StreamingChatMessageContent | None:
"""Generate function result content related to a File Search Tool."""
if not step_details.tool_calls:
return None
items: list[FunctionCallContent | FunctionResultContent] = []
for index, tool in enumerate(step_details.tool_calls):
if tool.type == "file_search":
file_search_tool = cast(RunStepFileSearchToolCall, tool)
arguments = getattr(file_search_tool, "file_search", None)
results: list[Any] = []
if arguments is not None:
results = arguments.pop("results", None)
items.append(
FunctionCallContent(
id=file_search_tool.id,
index=index,
name=file_search_tool.type,
function_name=file_search_tool.type,
arguments=arguments,
)
)
items.append(
FunctionResultContent(
function_name=file_search_tool.type,
id=file_search_tool.id,
name=file_search_tool.type,
result=results,
)
)
return StreamingChatMessageContent(
role=AuthorRole.ASSISTANT,
name=agent_name,
choice_index=0,
items=items, # type: ignore
)
@experimental
def generate_streaming_openapi_content(
agent_name: str,
step_details: "RunStepDeltaToolCallObject",
) -> "StreamingChatMessageContent | None":
"""Generate OpenAPI content for streaming function/tool call messages."""
if not getattr(step_details, "tool_calls", None):
return None
items: list[FunctionCallContent | FunctionResultContent] = [] # type: ignore
for index, tool in enumerate(step_details.tool_calls or []):
if tool.get("type") != "openapi":
continue
func: dict[str, Any] = tool.get("function")
tool_id = tool.get("id")
arguments = func.get("arguments") if func else None
if arguments:
items.append(
FunctionCallContent(
id=tool_id,
index=index,
name="openapi",
function_name=func.get("name") if func else None,
arguments=arguments,
)
)
output = func.get("output") if func else None
if output:
items.append(
FunctionResultContent(
function_name=func.get("name") if func else None,
id=tool_id,
name="openapi",
result=output,
)
)
if not items:
return None
return StreamingChatMessageContent(
role=AuthorRole.ASSISTANT,
name=agent_name,
choice_index=0,
items=items, # type: ignore
)
@experimental
def generate_streaming_code_interpreter_content(
agent_name: str, step_details: "RunStepDeltaToolCallObject"
) -> "StreamingChatMessageContent | None":
"""Generate code interpreter content.
Args:
agent_name: The agent name.
step_details: The current step details.
Returns:
StreamingChatMessageContent: The chat message content.
"""
items: list[StreamingTextContent | StreamingFileReferenceContent] = []
if not step_details.tool_calls:
return None
metadata: dict[str, bool] = {}
for index, tool in enumerate(step_details.tool_calls):
if isinstance(tool, RunStepDeltaCodeInterpreterToolCall):
code_interpreter_tool_call = tool.code_interpreter
if code_interpreter_tool_call is None:
continue
if code_interpreter_tool_call.input:
items.append(
StreamingTextContent(
choice_index=index,
text=code_interpreter_tool_call.input,
)
)
metadata["code"] = True
if code_interpreter_tool_call.outputs:
for output in code_interpreter_tool_call.outputs:
if (
isinstance(output, RunStepDeltaCodeInterpreterImageOutput)
and output.image is not None
and output.image.file_id
):
items.append(
StreamingFileReferenceContent(
file_id=output.image.file_id,
)
)
if isinstance(output, RunStepDeltaCodeInterpreterLogOutput) and output.logs:
items.append(
StreamingTextContent(
choice_index=index,
text=output.logs,
)
)
return (
StreamingChatMessageContent(
role=AuthorRole.ASSISTANT,
name=agent_name,
items=items, # type: ignore
choice_index=0,
metadata=metadata if metadata else None,
)
if len(items) > 0
else None
)
@experimental
def generate_annotation_content(
annotation: MessageTextFilePathAnnotation | MessageTextFileCitationAnnotation | MessageTextUrlCitationAnnotation,
) -> AnnotationContent:
"""Generate annotation content with safe attribute access."""
file_id = None
url = None
title = None
citation_type = None
if isinstance(annotation, MessageTextFilePathAnnotation) and annotation.file_path:
file_id = annotation.file_path.file_id
citation_type = CitationType.FILE_PATH
elif isinstance(annotation, MessageTextFileCitationAnnotation) and annotation.file_citation:
file_id = annotation.file_citation.file_id
citation_type = CitationType.FILE_CITATION
elif isinstance(annotation, MessageTextUrlCitationAnnotation) and annotation.url_citation:
url = annotation.url_citation.url
title = annotation.url_citation.title
citation_type = CitationType.URL_CITATION
return AnnotationContent(
file_id=file_id,
quote=getattr(annotation, "text", None),
start_index=getattr(annotation, "start_index", None),
end_index=getattr(annotation, "end_index", None),
url=url,
title=title,
citation_type=citation_type,
)
@experimental
def generate_streaming_annotation_content(
annotation: MessageDeltaTextFilePathAnnotation
| MessageDeltaTextFileCitationAnnotation
| MessageDeltaTextUrlCitationAnnotation,
) -> StreamingAnnotationContent:
"""Generate streaming annotation content with defensive checks."""
file_id = None
url = None
quote = None
title = None
citation_type = None
if isinstance(annotation, MessageDeltaTextFilePathAnnotation) and annotation.file_path:
file_id = annotation.file_path.file_id
quote = getattr(annotation, "text", None)
citation_type = CitationType.FILE_PATH
elif isinstance(annotation, MessageDeltaTextFileCitationAnnotation) and annotation.file_citation:
file_id = annotation.file_citation.file_id
quote = getattr(annotation, "text", None)
citation_type = CitationType.FILE_CITATION
elif isinstance(annotation, MessageDeltaTextUrlCitationAnnotation) and annotation.url_citation:
url = annotation.url_citation.url
title = annotation.url_citation.title
quote = annotation.get("text", None)
citation_type = CitationType.URL_CITATION
return StreamingAnnotationContent(
file_id=file_id,
quote=quote,
start_index=getattr(annotation, "start_index", None),
end_index=getattr(annotation, "end_index", None),
url=url,
title=title,
citation_type=citation_type,
)
@experimental
def generate_mcp_content(agent_name: str, mcp_tool_call: RunStepMcpToolCall) -> ChatMessageContent:
"""Generate MCP tool content.
Args:
agent_name: The name of the agent.
mcp_tool_call: The MCP tool call.
Returns:
The generated content.
"""
mcp_result = FunctionResultContent(
function_name=mcp_tool_call.name,
id=mcp_tool_call.id,
result=mcp_tool_call.output,
)
return ChatMessageContent(
role=AuthorRole.ASSISTANT,
name=agent_name,
items=[mcp_result],
inner_content=mcp_tool_call, # type: ignore
)
@experimental
def generate_mcp_call_content(agent_name: str, mcp_tool_calls: list[RequiredMcpToolCall]) -> ChatMessageContent:
"""Generate MCP tool call content.
Args:
agent_name: The name of the agent.
mcp_tool_calls: The MCP tool calls.
Returns:
The generated content.
"""
content_items: list[FunctionCallContent] = []
for mcp_call in mcp_tool_calls:
content_items.append(
FunctionCallContent(
id=mcp_call.id,
name=mcp_call.name,
function_name=mcp_call.name,
arguments=mcp_call.arguments,
server_label=mcp_call.server_label,
)
)
return ChatMessageContent(
role=AuthorRole.ASSISTANT,
name=agent_name,
items=content_items, # type: ignore
)
@experimental
def generate_streaming_mcp_call_content(
agent_name: str, mcp_tool_calls: list["RequiredMcpToolCall"]
) -> "StreamingChatMessageContent | None":
"""Generate streaming MCP content.
Args:
agent_name: The name of the agent.
mcp_tool_calls: The mcp tool call details.
Returns:
The generated streaming content.
"""
items: list[FunctionCallContent] = []
for index, tool in enumerate(mcp_tool_calls or []):
if isinstance(tool, RequiredMcpToolCall):
items.append(
FunctionCallContent(
id=tool.id,
index=index,
name=tool.name,
function_name=tool.name,
arguments=tool.arguments,
server_label=tool.server_label,
)
)
return (
StreamingChatMessageContent(
role=AuthorRole.ASSISTANT,
name=agent_name,
items=items, # type: ignore
choice_index=0,
)
if items
else None
)
@experimental
def generate_streaming_mcp_content(
agent_name: str, step_details: "RunStepDeltaToolCallObject"
) -> StreamingChatMessageContent | None:
"""Generate MCP tool content.
Args:
agent_name: The name of the agent.
step_details: The steps details with mcp tool call.
Returns:
The generated content.
"""
if not step_details.tool_calls:
return None
items: list[FunctionResultContent] = []
for _, tool in enumerate(step_details.tool_calls):
if tool.type == "mcp":
mcp_tool_call = cast(RunStepMcpToolCall, tool)
if not mcp_tool_call.get("output"):
continue
mcp_result = FunctionResultContent(
function_name=mcp_tool_call.get("name"),
id=mcp_tool_call.get("id"),
result=mcp_tool_call.get("output"),
)
items.append(mcp_result)
return (
StreamingChatMessageContent(
role=AuthorRole.ASSISTANT,
name=agent_name,
items=items, # type: ignore
inner_content=mcp_tool_call, # type: ignore
choice_index=0,
)
if items
else None
) # type: ignore