mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [Breaking] Simplified Content types to a single class with classmethod constructors. (#3252)
* ported Content to a new model * fixed linting * fixes * fixed data format handling * fix for 3.10 mypy * fix * fix int test
This commit is contained in:
@@ -7,31 +7,19 @@ from typing import Any, ClassVar, Final, Generic, Literal, TypedDict
|
||||
from agent_framework import (
|
||||
AGENT_FRAMEWORK_USER_AGENT,
|
||||
AIFunction,
|
||||
Annotations,
|
||||
Annotation,
|
||||
BaseChatClient,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
CitationAnnotation,
|
||||
CodeInterpreterToolCallContent,
|
||||
CodeInterpreterToolResultContent,
|
||||
Contents,
|
||||
ErrorContent,
|
||||
Content,
|
||||
FinishReason,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
HostedCodeInterpreterTool,
|
||||
HostedFileContent,
|
||||
HostedMCPTool,
|
||||
HostedWebSearchTool,
|
||||
MCPServerToolCallContent,
|
||||
MCPServerToolResultContent,
|
||||
Role,
|
||||
TextContent,
|
||||
TextReasoningContent,
|
||||
TextSpanRegion,
|
||||
UsageContent,
|
||||
UsageDetails,
|
||||
get_logger,
|
||||
prepare_function_call_results,
|
||||
@@ -486,7 +474,7 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
a_content.append({
|
||||
"type": "image",
|
||||
"source": {
|
||||
"data": content.get_data_bytes_as_str(),
|
||||
"data": content.get_data_bytes_as_str(), # type: ignore[attr-defined]
|
||||
"media_type": content.media_type,
|
||||
"type": "base64",
|
||||
},
|
||||
@@ -653,9 +641,9 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
"""
|
||||
match event.type:
|
||||
case "message_start":
|
||||
usage_details: list[UsageContent] = []
|
||||
usage_details: list[Content] = []
|
||||
if event.message.usage and (details := self._parse_usage_from_anthropic(event.message.usage)):
|
||||
usage_details.append(UsageContent(details=details))
|
||||
usage_details.append(Content.from_usage(usage_details=details))
|
||||
|
||||
return ChatResponseUpdate(
|
||||
response_id=event.message.id,
|
||||
@@ -672,7 +660,7 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
case "message_delta":
|
||||
usage = self._parse_usage_from_anthropic(event.usage)
|
||||
return ChatResponseUpdate(
|
||||
contents=[UsageContent(details=usage, raw_representation=event.usage)] if usage else [],
|
||||
contents=[Content.from_usage(usage_details=usage, raw_representation=event.usage)] if usage else [],
|
||||
finish_reason=FINISH_REASON_MAP.get(event.delta.stop_reason) if event.delta.stop_reason else None,
|
||||
raw_representation=event,
|
||||
)
|
||||
@@ -702,24 +690,24 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
return None
|
||||
usage_details = UsageDetails(output_token_count=usage.output_tokens)
|
||||
if usage.input_tokens is not None:
|
||||
usage_details.input_token_count = usage.input_tokens
|
||||
usage_details["input_token_count"] = usage.input_tokens
|
||||
if usage.cache_creation_input_tokens is not None:
|
||||
usage_details.additional_counts["anthropic.cache_creation_input_tokens"] = usage.cache_creation_input_tokens
|
||||
usage_details["anthropic.cache_creation_input_tokens"] = usage.cache_creation_input_tokens # type: ignore[typeddict-unknown-key]
|
||||
if usage.cache_read_input_tokens is not None:
|
||||
usage_details.additional_counts["anthropic.cache_read_input_tokens"] = usage.cache_read_input_tokens
|
||||
usage_details["anthropic.cache_read_input_tokens"] = usage.cache_read_input_tokens # type: ignore[typeddict-unknown-key]
|
||||
return usage_details
|
||||
|
||||
def _parse_contents_from_anthropic(
|
||||
self,
|
||||
content: Sequence[BetaContentBlock | BetaRawContentBlockDelta | BetaTextBlock],
|
||||
) -> list[Contents]:
|
||||
) -> list[Content]:
|
||||
"""Parse contents from the Anthropic message."""
|
||||
contents: list[Contents] = []
|
||||
contents: list[Content] = []
|
||||
for content_block in content:
|
||||
match content_block.type:
|
||||
case "text" | "text_delta":
|
||||
contents.append(
|
||||
TextContent(
|
||||
Content.from_text(
|
||||
text=content_block.text,
|
||||
raw_representation=content_block,
|
||||
annotations=self._parse_citations_from_anthropic(content_block),
|
||||
@@ -729,7 +717,7 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
self._last_call_id_name = (content_block.id, content_block.name)
|
||||
if content_block.type == "mcp_tool_use":
|
||||
contents.append(
|
||||
MCPServerToolCallContent(
|
||||
Content.from_mcp_server_tool_call(
|
||||
call_id=content_block.id,
|
||||
tool_name=content_block.name,
|
||||
server_name=None,
|
||||
@@ -739,10 +727,10 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
)
|
||||
elif "code_execution" in (content_block.name or ""):
|
||||
contents.append(
|
||||
CodeInterpreterToolCallContent(
|
||||
Content.from_code_interpreter_tool_call(
|
||||
call_id=content_block.id,
|
||||
inputs=[
|
||||
TextContent(
|
||||
Content.from_text(
|
||||
text=str(content_block.input),
|
||||
raw_representation=content_block,
|
||||
)
|
||||
@@ -752,7 +740,7 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
)
|
||||
else:
|
||||
contents.append(
|
||||
FunctionCallContent(
|
||||
Content.from_function_call(
|
||||
call_id=content_block.id,
|
||||
name=content_block.name,
|
||||
arguments=content_block.input,
|
||||
@@ -760,14 +748,14 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
)
|
||||
)
|
||||
case "mcp_tool_result":
|
||||
call_id, name = self._last_call_id_name or (None, None)
|
||||
parsed_output: list[Contents] | None = None
|
||||
call_id, _ = self._last_call_id_name or (None, None)
|
||||
parsed_output: list[Content] | None = None
|
||||
if content_block.content:
|
||||
if isinstance(content_block.content, list):
|
||||
parsed_output = self._parse_contents_from_anthropic(content_block.content)
|
||||
elif isinstance(content_block.content, (str, bytes)):
|
||||
parsed_output = [
|
||||
TextContent(
|
||||
Content.from_text(
|
||||
text=str(content_block.content),
|
||||
raw_representation=content_block,
|
||||
)
|
||||
@@ -775,28 +763,27 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
else:
|
||||
parsed_output = self._parse_contents_from_anthropic([content_block.content])
|
||||
contents.append(
|
||||
MCPServerToolResultContent(
|
||||
Content.from_mcp_server_tool_result(
|
||||
call_id=content_block.tool_use_id,
|
||||
output=parsed_output,
|
||||
raw_representation=content_block,
|
||||
)
|
||||
)
|
||||
case "web_search_tool_result" | "web_fetch_tool_result":
|
||||
call_id, name = self._last_call_id_name or (None, None)
|
||||
call_id, _ = self._last_call_id_name or (None, None)
|
||||
contents.append(
|
||||
FunctionResultContent(
|
||||
Content.from_function_result(
|
||||
call_id=content_block.tool_use_id,
|
||||
name=name if name and call_id == content_block.tool_use_id else "web_tool",
|
||||
result=content_block.content,
|
||||
raw_representation=content_block,
|
||||
)
|
||||
)
|
||||
case "code_execution_tool_result":
|
||||
code_outputs: list[Contents] = []
|
||||
code_outputs: list[Content] = []
|
||||
if content_block.content:
|
||||
if isinstance(content_block.content, BetaCodeExecutionToolResultError):
|
||||
code_outputs.append(
|
||||
ErrorContent(
|
||||
Content.from_error(
|
||||
message=content_block.content.error_code,
|
||||
raw_representation=content_block.content,
|
||||
)
|
||||
@@ -804,41 +791,41 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
else:
|
||||
if content_block.content.stdout:
|
||||
code_outputs.append(
|
||||
TextContent(
|
||||
Content.from_text(
|
||||
text=content_block.content.stdout,
|
||||
raw_representation=content_block.content,
|
||||
)
|
||||
)
|
||||
if content_block.content.stderr:
|
||||
code_outputs.append(
|
||||
ErrorContent(
|
||||
Content.from_error(
|
||||
message=content_block.content.stderr,
|
||||
raw_representation=content_block.content,
|
||||
)
|
||||
)
|
||||
for code_file_content in content_block.content.content:
|
||||
code_outputs.append(
|
||||
HostedFileContent(
|
||||
Content.from_hosted_file(
|
||||
file_id=code_file_content.file_id,
|
||||
raw_representation=code_file_content,
|
||||
)
|
||||
)
|
||||
contents.append(
|
||||
CodeInterpreterToolResultContent(
|
||||
Content.from_code_interpreter_tool_result(
|
||||
call_id=content_block.tool_use_id,
|
||||
raw_representation=content_block,
|
||||
outputs=code_outputs,
|
||||
)
|
||||
)
|
||||
case "bash_code_execution_tool_result":
|
||||
bash_outputs: list[Contents] = []
|
||||
bash_outputs: list[Content] = []
|
||||
if content_block.content:
|
||||
if isinstance(
|
||||
content_block.content,
|
||||
BetaBashCodeExecutionToolResultError,
|
||||
):
|
||||
bash_outputs.append(
|
||||
ErrorContent(
|
||||
Content.from_error(
|
||||
message=content_block.content.error_code,
|
||||
raw_representation=content_block.content,
|
||||
)
|
||||
@@ -846,39 +833,38 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
else:
|
||||
if content_block.content.stdout:
|
||||
bash_outputs.append(
|
||||
TextContent(
|
||||
Content.from_text(
|
||||
text=content_block.content.stdout,
|
||||
raw_representation=content_block.content,
|
||||
)
|
||||
)
|
||||
if content_block.content.stderr:
|
||||
bash_outputs.append(
|
||||
ErrorContent(
|
||||
Content.from_error(
|
||||
message=content_block.content.stderr,
|
||||
raw_representation=content_block.content,
|
||||
)
|
||||
)
|
||||
for bash_file_content in content_block.content.content:
|
||||
contents.append(
|
||||
HostedFileContent(
|
||||
Content.from_hosted_file(
|
||||
file_id=bash_file_content.file_id,
|
||||
raw_representation=bash_file_content,
|
||||
)
|
||||
)
|
||||
contents.append(
|
||||
FunctionResultContent(
|
||||
Content.from_function_result(
|
||||
call_id=content_block.tool_use_id,
|
||||
name=content_block.type,
|
||||
result=bash_outputs,
|
||||
raw_representation=content_block,
|
||||
)
|
||||
)
|
||||
case "text_editor_code_execution_tool_result":
|
||||
text_editor_outputs: list[Contents] = []
|
||||
text_editor_outputs: list[Content] = []
|
||||
match content_block.content.type:
|
||||
case "text_editor_code_execution_tool_result_error":
|
||||
text_editor_outputs.append(
|
||||
ErrorContent(
|
||||
Content.from_error(
|
||||
message=content_block.content.error_code
|
||||
and getattr(content_block.content, "error_message", ""),
|
||||
raw_representation=content_block.content,
|
||||
@@ -887,10 +873,12 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
case "text_editor_code_execution_view_result":
|
||||
annotations = (
|
||||
[
|
||||
CitationAnnotation(
|
||||
Annotation(
|
||||
type="citation",
|
||||
raw_representation=content_block.content,
|
||||
annotated_regions=[
|
||||
TextSpanRegion(
|
||||
type="text_span",
|
||||
start_index=content_block.content.start_line,
|
||||
end_index=content_block.content.start_line
|
||||
+ (content_block.content.num_lines or 0),
|
||||
@@ -903,7 +891,7 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
else None
|
||||
)
|
||||
text_editor_outputs.append(
|
||||
TextContent(
|
||||
Content.from_text(
|
||||
text=content_block.content.content,
|
||||
annotations=annotations,
|
||||
raw_representation=content_block.content,
|
||||
@@ -911,10 +899,12 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
)
|
||||
case "text_editor_code_execution_str_replace_result":
|
||||
old_annotation = (
|
||||
CitationAnnotation(
|
||||
Annotation(
|
||||
type="citation",
|
||||
raw_representation=content_block.content,
|
||||
annotated_regions=[
|
||||
TextSpanRegion(
|
||||
type="text_span",
|
||||
start_index=content_block.content.old_start or 0,
|
||||
end_index=(
|
||||
(content_block.content.old_start or 0)
|
||||
@@ -928,13 +918,15 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
else None
|
||||
)
|
||||
new_annotation = (
|
||||
CitationAnnotation(
|
||||
Annotation(
|
||||
type="citation",
|
||||
raw_representation=content_block.content,
|
||||
snippet="\n".join(content_block.content.lines)
|
||||
snippet="\n".join(content_block.content.lines) # type: ignore[typeddict-item]
|
||||
if content_block.content.lines
|
||||
else None,
|
||||
annotated_regions=[
|
||||
TextSpanRegion(
|
||||
type="text_span",
|
||||
start_index=content_block.content.new_start or 0,
|
||||
end_index=(
|
||||
(content_block.content.new_start or 0)
|
||||
@@ -950,7 +942,7 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
annotations = [ann for ann in [old_annotation, new_annotation] if ann is not None]
|
||||
|
||||
text_editor_outputs.append(
|
||||
TextContent(
|
||||
Content.from_text(
|
||||
text=(
|
||||
"\n".join(content_block.content.lines) if content_block.content.lines else ""
|
||||
),
|
||||
@@ -960,15 +952,14 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
)
|
||||
case "text_editor_code_execution_create_result":
|
||||
text_editor_outputs.append(
|
||||
TextContent(
|
||||
Content.from_text(
|
||||
text=f"File update: {content_block.content.is_file_update}",
|
||||
raw_representation=content_block.content,
|
||||
)
|
||||
)
|
||||
contents.append(
|
||||
FunctionResultContent(
|
||||
Content.from_function_result(
|
||||
call_id=content_block.tool_use_id,
|
||||
name=content_block.type,
|
||||
result=text_editor_outputs,
|
||||
raw_representation=content_block,
|
||||
)
|
||||
@@ -981,7 +972,7 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
# This matches OpenAI's behavior where streaming chunks have name="".
|
||||
call_id, _ = self._last_call_id_name if self._last_call_id_name else ("", "")
|
||||
contents.append(
|
||||
FunctionCallContent(
|
||||
Content.from_function_call(
|
||||
call_id=call_id,
|
||||
name="",
|
||||
arguments=content_block.partial_json,
|
||||
@@ -990,7 +981,7 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
)
|
||||
case "thinking" | "thinking_delta":
|
||||
contents.append(
|
||||
TextReasoningContent(
|
||||
Content.from_text_reasoning(
|
||||
text=content_block.thinking,
|
||||
raw_representation=content_block,
|
||||
)
|
||||
@@ -1001,65 +992,65 @@ class AnthropicClient(BaseChatClient[TAnthropicOptions], Generic[TAnthropicOptio
|
||||
|
||||
def _parse_citations_from_anthropic(
|
||||
self, content_block: BetaContentBlock | BetaRawContentBlockDelta | BetaTextBlock
|
||||
) -> list[Annotations] | None:
|
||||
content_citations = getattr(content_block, "citations", None)
|
||||
if not content_citations:
|
||||
) -> list[Annotation] | None:
|
||||
content_blocks = getattr(content_block, "citations", None)
|
||||
if not content_blocks:
|
||||
return None
|
||||
annotations: list[Annotations] = []
|
||||
for citation in content_citations:
|
||||
cit = CitationAnnotation(raw_representation=citation)
|
||||
annotations: list[Annotation] = []
|
||||
for citation in content_blocks:
|
||||
cit = Annotation(type="citation", raw_representation=citation)
|
||||
match citation.type:
|
||||
case "char_location":
|
||||
cit.title = citation.title
|
||||
cit.snippet = citation.cited_text
|
||||
cit["title"] = citation.title
|
||||
cit["snippet"] = citation.cited_text
|
||||
if citation.file_id:
|
||||
cit.file_id = citation.file_id
|
||||
if not cit.annotated_regions:
|
||||
cit.annotated_regions = []
|
||||
cit.annotated_regions.append(
|
||||
cit["file_id"] = citation.file_id
|
||||
cit.setdefault("annotated_regions", [])
|
||||
cit["annotated_regions"].append( # type: ignore[attr-defined]
|
||||
TextSpanRegion(
|
||||
type="text_span",
|
||||
start_index=citation.start_char_index,
|
||||
end_index=citation.end_char_index,
|
||||
)
|
||||
)
|
||||
case "page_location":
|
||||
cit.title = citation.document_title
|
||||
cit.snippet = citation.cited_text
|
||||
cit["title"] = citation.document_title
|
||||
cit["snippet"] = citation.cited_text
|
||||
if citation.file_id:
|
||||
cit.file_id = citation.file_id
|
||||
if not cit.annotated_regions:
|
||||
cit.annotated_regions = []
|
||||
cit.annotated_regions.append(
|
||||
cit["file_id"] = citation.file_id
|
||||
cit.setdefault("annotated_regions", [])
|
||||
cit["annotated_regions"].append( # type: ignore[attr-defined]
|
||||
TextSpanRegion(
|
||||
type="text_span",
|
||||
start_index=citation.start_page_number,
|
||||
end_index=citation.end_page_number,
|
||||
)
|
||||
)
|
||||
case "content_block_location":
|
||||
cit.title = citation.document_title
|
||||
cit.snippet = citation.cited_text
|
||||
cit["title"] = citation.document_title
|
||||
cit["snippet"] = citation.cited_text
|
||||
if citation.file_id:
|
||||
cit.file_id = citation.file_id
|
||||
if not cit.annotated_regions:
|
||||
cit.annotated_regions = []
|
||||
cit.annotated_regions.append(
|
||||
cit["file_id"] = citation.file_id
|
||||
cit.setdefault("annotated_regions", [])
|
||||
cit["annotated_regions"].append( # type: ignore[attr-defined]
|
||||
TextSpanRegion(
|
||||
type="text_span",
|
||||
start_index=citation.start_block_index,
|
||||
end_index=citation.end_block_index,
|
||||
)
|
||||
)
|
||||
case "web_search_result_location":
|
||||
cit.title = citation.title
|
||||
cit.snippet = citation.cited_text
|
||||
cit.url = citation.url
|
||||
cit["title"] = citation.title
|
||||
cit["snippet"] = citation.cited_text
|
||||
cit["url"] = citation.url
|
||||
case "search_result_location":
|
||||
cit.title = citation.title
|
||||
cit.snippet = citation.cited_text
|
||||
cit.url = citation.source
|
||||
if not cit.annotated_regions:
|
||||
cit.annotated_regions = []
|
||||
cit.annotated_regions.append(
|
||||
cit["title"] = citation.title
|
||||
cit["snippet"] = citation.cited_text
|
||||
cit["url"] = citation.source
|
||||
cit.setdefault("annotated_regions", [])
|
||||
cit["annotated_regions"].append( # type: ignore[attr-defined]
|
||||
TextSpanRegion(
|
||||
type="text_span",
|
||||
start_index=citation.start_block_index,
|
||||
end_index=citation.end_block_index,
|
||||
)
|
||||
|
||||
@@ -10,16 +10,12 @@ from agent_framework import (
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatResponseUpdate,
|
||||
DataContent,
|
||||
Content,
|
||||
FinishReason,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
HostedCodeInterpreterTool,
|
||||
HostedMCPTool,
|
||||
HostedWebSearchTool,
|
||||
Role,
|
||||
TextContent,
|
||||
TextReasoningContent,
|
||||
ai_function,
|
||||
)
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
@@ -170,7 +166,7 @@ def test_prepare_message_for_anthropic_function_call(mock_anthropic_client: Magi
|
||||
message = ChatMessage(
|
||||
role=Role.ASSISTANT,
|
||||
contents=[
|
||||
FunctionCallContent(
|
||||
Content.from_function_call(
|
||||
call_id="call_123",
|
||||
name="get_weather",
|
||||
arguments={"location": "San Francisco"},
|
||||
@@ -194,9 +190,8 @@ def test_prepare_message_for_anthropic_function_result(mock_anthropic_client: Ma
|
||||
message = ChatMessage(
|
||||
role=Role.TOOL,
|
||||
contents=[
|
||||
FunctionResultContent(
|
||||
Content.from_function_result(
|
||||
call_id="call_123",
|
||||
name="get_weather",
|
||||
result="Sunny, 72°F",
|
||||
)
|
||||
],
|
||||
@@ -219,7 +214,7 @@ def test_prepare_message_for_anthropic_text_reasoning(mock_anthropic_client: Mag
|
||||
chat_client = create_test_anthropic_client(mock_anthropic_client)
|
||||
message = ChatMessage(
|
||||
role=Role.ASSISTANT,
|
||||
contents=[TextReasoningContent(text="Let me think about this...")],
|
||||
contents=[Content.from_text_reasoning(text="Let me think about this...")],
|
||||
)
|
||||
|
||||
result = chat_client._prepare_message_for_anthropic(message)
|
||||
@@ -507,12 +502,12 @@ def test_process_message_basic(mock_anthropic_client: MagicMock) -> None:
|
||||
assert len(response.messages) == 1
|
||||
assert response.messages[0].role == Role.ASSISTANT
|
||||
assert len(response.messages[0].contents) == 1
|
||||
assert isinstance(response.messages[0].contents[0], TextContent)
|
||||
assert response.messages[0].contents[0].type == "text"
|
||||
assert response.messages[0].contents[0].text == "Hello there!"
|
||||
assert response.finish_reason == FinishReason.STOP
|
||||
assert response.usage_details is not None
|
||||
assert response.usage_details.input_token_count == 10
|
||||
assert response.usage_details.output_token_count == 5
|
||||
assert response.usage_details["input_token_count"] == 10
|
||||
assert response.usage_details["output_token_count"] == 5
|
||||
|
||||
|
||||
def test_process_message_with_tool_use(mock_anthropic_client: MagicMock) -> None:
|
||||
@@ -536,7 +531,7 @@ def test_process_message_with_tool_use(mock_anthropic_client: MagicMock) -> None
|
||||
response = chat_client._process_message(mock_message)
|
||||
|
||||
assert len(response.messages[0].contents) == 1
|
||||
assert isinstance(response.messages[0].contents[0], FunctionCallContent)
|
||||
assert response.messages[0].contents[0].type == "function_call"
|
||||
assert response.messages[0].contents[0].call_id == "call_123"
|
||||
assert response.messages[0].contents[0].name == "get_weather"
|
||||
assert response.finish_reason == FinishReason.TOOL_CALLS
|
||||
@@ -550,8 +545,8 @@ def test_parse_usage_from_anthropic_basic(mock_anthropic_client: MagicMock) -> N
|
||||
result = chat_client._parse_usage_from_anthropic(usage)
|
||||
|
||||
assert result is not None
|
||||
assert result.input_token_count == 10
|
||||
assert result.output_token_count == 5
|
||||
assert result["input_token_count"] == 10
|
||||
assert result["output_token_count"] == 5
|
||||
|
||||
|
||||
def test_parse_usage_from_anthropic_none(mock_anthropic_client: MagicMock) -> None:
|
||||
@@ -571,7 +566,7 @@ def test_parse_contents_from_anthropic_text(mock_anthropic_client: MagicMock) ->
|
||||
result = chat_client._parse_contents_from_anthropic(content)
|
||||
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], TextContent)
|
||||
assert result[0].type == "text"
|
||||
assert result[0].text == "Hello!"
|
||||
|
||||
|
||||
@@ -590,7 +585,7 @@ def test_parse_contents_from_anthropic_tool_use(mock_anthropic_client: MagicMock
|
||||
result = chat_client._parse_contents_from_anthropic(content)
|
||||
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], FunctionCallContent)
|
||||
assert result[0].type == "function_call"
|
||||
assert result[0].call_id == "call_123"
|
||||
assert result[0].name == "get_weather"
|
||||
|
||||
@@ -613,7 +608,7 @@ def test_parse_contents_from_anthropic_input_json_delta_no_duplicate_name(mock_a
|
||||
|
||||
result = chat_client._parse_contents_from_anthropic([tool_use_content])
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], FunctionCallContent)
|
||||
assert result[0].type == "function_call"
|
||||
assert result[0].call_id == "call_123"
|
||||
assert result[0].name == "get_weather" # Initial event has name
|
||||
|
||||
@@ -624,7 +619,7 @@ def test_parse_contents_from_anthropic_input_json_delta_no_duplicate_name(mock_a
|
||||
|
||||
result = chat_client._parse_contents_from_anthropic([delta_content_1])
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], FunctionCallContent)
|
||||
assert result[0].type == "function_call"
|
||||
assert result[0].call_id == "call_123"
|
||||
assert result[0].name == "" # Delta events should have empty name
|
||||
assert result[0].arguments == '{"location":'
|
||||
@@ -636,7 +631,7 @@ def test_parse_contents_from_anthropic_input_json_delta_no_duplicate_name(mock_a
|
||||
|
||||
result = chat_client._parse_contents_from_anthropic([delta_content_2])
|
||||
assert len(result) == 1
|
||||
assert isinstance(result[0], FunctionCallContent)
|
||||
assert result[0].type == "function_call"
|
||||
assert result[0].call_id == "call_123"
|
||||
assert result[0].name == "" # Still empty name for subsequent deltas
|
||||
assert result[0].arguments == '"San Francisco"}'
|
||||
@@ -771,9 +766,7 @@ async def test_anthropic_client_integration_function_calling() -> None:
|
||||
|
||||
assert response is not None
|
||||
# Should contain function call
|
||||
has_function_call = any(
|
||||
isinstance(content, FunctionCallContent) for msg in response.messages for content in msg.contents
|
||||
)
|
||||
has_function_call = any(content.type == "function_call" for msg in response.messages for content in msg.contents)
|
||||
assert has_function_call
|
||||
|
||||
|
||||
@@ -872,8 +865,8 @@ async def test_anthropic_client_integration_images() -> None:
|
||||
ChatMessage(
|
||||
role=Role.USER,
|
||||
contents=[
|
||||
TextContent(text="Describe this image"),
|
||||
DataContent(media_type="image/jpeg", data=image_bytes),
|
||||
Content.from_text(text="Describe this image"),
|
||||
Content.from_data(media_type="image/jpeg", data=image_bytes),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user