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:
@@ -20,13 +20,8 @@ from agent_framework import (
|
||||
ChatOptions,
|
||||
ChatResponse,
|
||||
ChatResponseUpdate,
|
||||
Contents,
|
||||
DataContent,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
Content,
|
||||
Role,
|
||||
TextContent,
|
||||
TextReasoningContent,
|
||||
ToolProtocol,
|
||||
UsageDetails,
|
||||
get_logger,
|
||||
@@ -452,31 +447,31 @@ class OllamaChatClient(BaseChatClient[TOllamaChatOptions], Generic[TOllamaChatOp
|
||||
return [OllamaMessage(role="system", content=message.text)]
|
||||
|
||||
def _format_user_message(self, message: ChatMessage) -> list[OllamaMessage]:
|
||||
if not any(isinstance(c, (DataContent, TextContent)) for c in message.contents) and not message.text:
|
||||
if not any(c.type in {"text", "data"} for c in message.contents) and not message.text:
|
||||
raise ServiceInvalidRequestError(
|
||||
"Ollama connector currently only supports user messages with TextContent or DataContent."
|
||||
)
|
||||
|
||||
if not any(isinstance(c, DataContent) for c in message.contents):
|
||||
if not any(c.type == "data" for c in message.contents):
|
||||
return [OllamaMessage(role="user", content=message.text)]
|
||||
|
||||
user_message = OllamaMessage(role="user", content=message.text)
|
||||
data_contents = [c for c in message.contents if isinstance(c, DataContent)]
|
||||
data_contents = [c for c in message.contents if c.type == "data"]
|
||||
if data_contents:
|
||||
if not any(c.has_top_level_media_type("image") for c in data_contents):
|
||||
raise ServiceInvalidRequestError("Only image data content is supported for user messages in Ollama.")
|
||||
# Ollama expects base64 strings without prefix
|
||||
user_message["images"] = [c.uri.split(",")[1] for c in data_contents]
|
||||
user_message["images"] = [c.uri.split(",")[1] for c in data_contents if c.uri]
|
||||
return [user_message]
|
||||
|
||||
def _format_assistant_message(self, message: ChatMessage) -> list[OllamaMessage]:
|
||||
text_content = message.text
|
||||
# Ollama shouldn't have encrypted reasoning, so we just process text.
|
||||
reasoning_contents = "".join((c.text or "") for c in message.contents if isinstance(c, TextReasoningContent))
|
||||
reasoning_contents = "".join((c.text or "") for c in message.contents if c.type == "text_reasoning")
|
||||
|
||||
assistant_message = OllamaMessage(role="assistant", content=text_content, thinking=reasoning_contents)
|
||||
|
||||
tool_calls = [item for item in message.contents if isinstance(item, FunctionCallContent)]
|
||||
tool_calls = [item for item in message.contents if item.type == "function_call"]
|
||||
if tool_calls:
|
||||
assistant_message["tool_calls"] = [
|
||||
{
|
||||
@@ -497,15 +492,15 @@ class OllamaChatClient(BaseChatClient[TOllamaChatOptions], Generic[TOllamaChatOp
|
||||
return [
|
||||
OllamaMessage(role="tool", content=str(item.result), tool_name=item.call_id)
|
||||
for item in message.contents
|
||||
if isinstance(item, FunctionResultContent)
|
||||
if item.type == "function_result"
|
||||
]
|
||||
|
||||
def _parse_contents_from_ollama(self, response: OllamaChatResponse) -> list[Contents]:
|
||||
contents: list[Contents] = []
|
||||
def _parse_contents_from_ollama(self, response: OllamaChatResponse) -> list[Content]:
|
||||
contents: list[Content] = []
|
||||
if response.message.thinking:
|
||||
contents.append(TextReasoningContent(text=response.message.thinking))
|
||||
contents.append(Content.from_text_reasoning(text=response.message.thinking))
|
||||
if response.message.content:
|
||||
contents.append(TextContent(text=response.message.content))
|
||||
contents.append(Content.from_text(text=response.message.content))
|
||||
if response.message.tool_calls:
|
||||
tool_calls = self._parse_tool_calls_from_ollama(response.message.tool_calls)
|
||||
contents.extend(tool_calls)
|
||||
@@ -533,10 +528,10 @@ class OllamaChatClient(BaseChatClient[TOllamaChatOptions], Generic[TOllamaChatOp
|
||||
),
|
||||
)
|
||||
|
||||
def _parse_tool_calls_from_ollama(self, tool_calls: Sequence[OllamaMessage.ToolCall]) -> list[Contents]:
|
||||
resp: list[Contents] = []
|
||||
def _parse_tool_calls_from_ollama(self, tool_calls: Sequence[OllamaMessage.ToolCall]) -> list[Content]:
|
||||
resp: list[Content] = []
|
||||
for tool in tool_calls:
|
||||
fcc = FunctionCallContent(
|
||||
fcc = Content.from_function_call(
|
||||
call_id=tool.function.name, # Use name of function as call ID since Ollama doesn't provide a call ID
|
||||
name=tool.function.name,
|
||||
arguments=tool.function.arguments if isinstance(tool.function.arguments, dict) else "",
|
||||
|
||||
@@ -9,13 +9,8 @@ from agent_framework import (
|
||||
BaseChatClient,
|
||||
ChatMessage,
|
||||
ChatResponseUpdate,
|
||||
DataContent,
|
||||
FunctionCallContent,
|
||||
FunctionResultContent,
|
||||
Content,
|
||||
HostedWebSearchTool,
|
||||
TextContent,
|
||||
TextReasoningContent,
|
||||
UriContent,
|
||||
ai_function,
|
||||
chat_middleware,
|
||||
)
|
||||
@@ -231,7 +226,7 @@ async def test_cmc_reasoning(
|
||||
ollama_client = OllamaChatClient()
|
||||
result = await ollama_client.get_response(messages=chat_history)
|
||||
|
||||
reasoning = "".join(c.text for c in result.messages.pop().contents if isinstance(c, TextReasoningContent))
|
||||
reasoning = "".join(c.text for c in result.messages.pop().contents if c.type == "text_reasoning")
|
||||
assert reasoning == "test"
|
||||
|
||||
|
||||
@@ -286,7 +281,7 @@ async def test_cmc_streaming_reasoning(
|
||||
result = ollama_client.get_streaming_response(messages=chat_history)
|
||||
|
||||
async for chunk in result:
|
||||
reasoning = "".join(c.text for c in chunk.contents if isinstance(c, TextReasoningContent))
|
||||
reasoning = "".join(c.text for c in chunk.contents if c.type == "text_reasoning")
|
||||
assert reasoning == "test"
|
||||
|
||||
|
||||
@@ -333,14 +328,14 @@ async def test_cmc_streaming_with_tool_call(
|
||||
chunks.append(chunk)
|
||||
|
||||
# Check parsed Toolcalls
|
||||
assert isinstance(chunks[0].contents[0], FunctionCallContent)
|
||||
assert chunks[0].contents[0].type == "function_call"
|
||||
tool_call = chunks[0].contents[0]
|
||||
assert tool_call.name == "hello_world"
|
||||
assert tool_call.arguments == {"arg1": "value1"}
|
||||
assert isinstance(chunks[1].contents[0], FunctionResultContent)
|
||||
assert chunks[1].contents[0].type == "function_result"
|
||||
tool_result = chunks[1].contents[0]
|
||||
assert tool_result.result == "Hello World"
|
||||
assert isinstance(chunks[2].contents[0], TextContent)
|
||||
assert chunks[2].contents[0].type == "text"
|
||||
text_result = chunks[2].contents[0]
|
||||
assert text_result.text == "test"
|
||||
|
||||
@@ -378,7 +373,7 @@ async def test_cmc_with_data_content_type(
|
||||
mock_chat.return_value = mock_chat_completion_response
|
||||
chat_history.append(
|
||||
ChatMessage(
|
||||
contents=[DataContent(uri="data:image/png;base64,xyz", media_type="image/png")],
|
||||
contents=[Content.from_uri(uri="data:image/png;base64,xyz", media_type="image/png")],
|
||||
role="user",
|
||||
)
|
||||
)
|
||||
@@ -401,7 +396,7 @@ async def test_cmc_with_invalid_data_content_media_type(
|
||||
# Remote Uris are not supported by Ollama client
|
||||
chat_history.append(
|
||||
ChatMessage(
|
||||
contents=[DataContent(uri="data:audio/mp3;base64,xyz", media_type="audio/mp3")],
|
||||
contents=[Content.from_uri(uri="data:audio/mp3;base64,xyz", media_type="audio/mp3")],
|
||||
role="user",
|
||||
)
|
||||
)
|
||||
@@ -424,7 +419,7 @@ async def test_cmc_with_invalid_content_type(
|
||||
# Remote Uris are not supported by Ollama client
|
||||
chat_history.append(
|
||||
ChatMessage(
|
||||
contents=[UriContent(uri="http://example.com/image.png", media_type="image/png")],
|
||||
contents=[Content.from_uri(uri="http://example.com/image.png", media_type="image/png")],
|
||||
role="user",
|
||||
)
|
||||
)
|
||||
@@ -444,7 +439,7 @@ async def test_cmc_integration_with_tool_call(
|
||||
result = await ollama_client.get_response(messages=chat_history, options={"tools": [hello_world]})
|
||||
|
||||
assert "hello" in result.text.lower() and "world" in result.text.lower()
|
||||
assert isinstance(result.messages[-2].contents[0], FunctionResultContent)
|
||||
assert result.messages[-2].contents[0].type == "function_result"
|
||||
tool_result = result.messages[-2].contents[0]
|
||||
assert tool_result.result == "Hello World"
|
||||
|
||||
@@ -478,10 +473,10 @@ async def test_cmc_streaming_integration_with_tool_call(
|
||||
|
||||
for c in chunks:
|
||||
if len(c.contents) > 0:
|
||||
if isinstance(c.contents[0], FunctionResultContent):
|
||||
if c.contents[0].type == "function_result":
|
||||
tool_result = c.contents[0]
|
||||
assert tool_result.result == "Hello World"
|
||||
if isinstance(c.contents[0], FunctionCallContent):
|
||||
if c.contents[0].type == "function_call":
|
||||
tool_call = c.contents[0]
|
||||
assert tool_call.name == "hello_world"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user