mirror of https://github.com/microsoft/autogen.git
Compare commits
13 Commits
723abfcf54
...
1130eb3eec
Author | SHA1 | Date |
---|---|---|
![]() |
1130eb3eec | |
![]() |
71a4eaedf9 | |
![]() |
95b1ed5d81 | |
![]() |
c75515990e | |
![]() |
3500170be1 | |
![]() |
c8bdd31936 | |
![]() |
6d5cb42a97 | |
![]() |
a93e463ad6 | |
![]() |
3bf46e031a | |
![]() |
65569f89a1 | |
![]() |
8699b27830 | |
![]() |
3425d7dc2c | |
![]() |
ec6f19c329 |
|
@ -90,6 +90,7 @@ body:
|
|||
multiple: false
|
||||
options:
|
||||
- "Python dev (main branch)"
|
||||
- "Python 0.5.2"
|
||||
- "Python 0.5.1"
|
||||
- "Python 0.4.9"
|
||||
- "Python 0.4.8"
|
||||
|
|
|
@ -33,7 +33,7 @@ jobs:
|
|||
[
|
||||
# For main use the workflow target
|
||||
{ ref: "${{github.ref}}", dest-dir: dev, uv-version: "0.5.13", sphinx-release-override: "dev" },
|
||||
{ ref: "python-v0.5.1", dest-dir: stable, uv-version: "0.5.13", sphinx-release-override: "stable" },
|
||||
{ ref: "python-v0.5.2", dest-dir: stable, uv-version: "0.5.13", sphinx-release-override: "stable" },
|
||||
{ ref: "v0.4.0.post1", dest-dir: "0.4.0", uv-version: "0.5.13", sphinx-release-override: "" },
|
||||
{ ref: "v0.4.1", dest-dir: "0.4.1", uv-version: "0.5.13", sphinx-release-override: "" },
|
||||
{ ref: "v0.4.2", dest-dir: "0.4.2", uv-version: "0.5.13", sphinx-release-override: "" },
|
||||
|
@ -45,6 +45,7 @@ jobs:
|
|||
{ ref: "python-v0.4.8", dest-dir: "0.4.8", uv-version: "0.5.13", sphinx-release-override: "" },
|
||||
{ ref: "python-v0.4.9-website", dest-dir: "0.4.9", uv-version: "0.5.13", sphinx-release-override: "" },
|
||||
{ ref: "python-v0.5.1", dest-dir: "0.5.1", uv-version: "0.5.13", sphinx-release-override: "" },
|
||||
{ ref: "python-v0.5.2", dest-dir: "0.5.2", uv-version: "0.5.13", sphinx-release-override: "" },
|
||||
]
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
|
|
@ -5,11 +5,16 @@
|
|||
"url": "/autogen/dev/"
|
||||
},
|
||||
{
|
||||
"name": "0.5.1 (stable)",
|
||||
"name": "0.5.2 (stable)",
|
||||
"version": "stable",
|
||||
"url": "/autogen/stable/",
|
||||
"preferred": true
|
||||
},
|
||||
{
|
||||
"name": "0.5.1",
|
||||
"version": "0.5.1",
|
||||
"url": "/autogen/0.5.1/"
|
||||
},
|
||||
{
|
||||
"name": "0.4.9",
|
||||
"version": "0.4.9",
|
||||
|
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||
|
||||
[project]
|
||||
name = "autogen-agentchat"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
license = {file = "LICENSE-CODE"}
|
||||
description = "AutoGen agents and teams library"
|
||||
readme = "README.md"
|
||||
|
@ -15,7 +15,7 @@ classifiers = [
|
|||
"Operating System :: OS Independent",
|
||||
]
|
||||
dependencies = [
|
||||
"autogen-core==0.5.1",
|
||||
"autogen-core==0.5.2",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
|
|
|
@ -46,6 +46,7 @@ from ..messages import (
|
|||
MemoryQueryEvent,
|
||||
ModelClientStreamingChunkEvent,
|
||||
StructuredMessage,
|
||||
StructuredMessageFactory,
|
||||
TextMessage,
|
||||
ThoughtEvent,
|
||||
ToolCallExecutionEvent,
|
||||
|
@ -74,6 +75,7 @@ class AssistantAgentConfig(BaseModel):
|
|||
reflect_on_tool_use: bool
|
||||
tool_call_summary_format: str
|
||||
metadata: Dict[str, str] | None = None
|
||||
structured_message_factory: ComponentModel | None = None
|
||||
|
||||
|
||||
class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
|
||||
|
@ -183,6 +185,7 @@ class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
|
|||
This will be used with the model client to generate structured output.
|
||||
If this is set, the agent will respond with a :class:`~autogen_agentchat.messages.StructuredMessage` instead of a :class:`~autogen_agentchat.messages.TextMessage`
|
||||
in the final response, unless `reflect_on_tool_use` is `False` and a tool call is made.
|
||||
format_string (str | None, optional): The format string used to create the content for a :class:`~autogen_agentchat.messages.StructuredMessage` response.
|
||||
tool_call_summary_format (str, optional): The format string used to create the content for a :class:`~autogen_agentchat.messages.ToolCallSummaryMessage` response.
|
||||
The format string is used to format the tool call summary for every tool call result.
|
||||
Defaults to "{result}".
|
||||
|
@ -635,6 +638,7 @@ class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
|
|||
reflect_on_tool_use: bool | None = None,
|
||||
tool_call_summary_format: str = "{result}",
|
||||
output_content_type: type[BaseModel] | None = None,
|
||||
format_string: str | None = None,
|
||||
memory: Sequence[Memory] | None = None,
|
||||
metadata: Dict[str, str] | None = None,
|
||||
):
|
||||
|
@ -643,6 +647,13 @@ class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
|
|||
self._model_client = model_client
|
||||
self._model_client_stream = model_client_stream
|
||||
self._output_content_type: type[BaseModel] | None = output_content_type
|
||||
self._format_string = format_string
|
||||
self._structured_message_factory: StructuredMessageFactory | None = None
|
||||
if output_content_type is not None:
|
||||
self._structured_message_factory = StructuredMessageFactory(
|
||||
input_model=output_content_type, format_string=format_string
|
||||
)
|
||||
|
||||
self._memory = None
|
||||
if memory is not None:
|
||||
if isinstance(memory, list):
|
||||
|
@ -771,6 +782,7 @@ class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
|
|||
reflect_on_tool_use = self._reflect_on_tool_use
|
||||
tool_call_summary_format = self._tool_call_summary_format
|
||||
output_content_type = self._output_content_type
|
||||
format_string = self._format_string
|
||||
|
||||
# STEP 1: Add new user/handoff messages to the model context
|
||||
await self._add_messages_to_context(
|
||||
|
@ -840,6 +852,7 @@ class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
|
|||
reflect_on_tool_use=reflect_on_tool_use,
|
||||
tool_call_summary_format=tool_call_summary_format,
|
||||
output_content_type=output_content_type,
|
||||
format_string=format_string,
|
||||
):
|
||||
yield output_event
|
||||
|
||||
|
@ -942,6 +955,7 @@ class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
|
|||
reflect_on_tool_use: bool,
|
||||
tool_call_summary_format: str,
|
||||
output_content_type: type[BaseModel] | None,
|
||||
format_string: str | None = None,
|
||||
) -> AsyncGenerator[BaseAgentEvent | BaseChatMessage | Response, None]:
|
||||
"""
|
||||
Handle final or partial responses from model_result, including tool calls, handoffs,
|
||||
|
@ -957,6 +971,7 @@ class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
|
|||
content=content,
|
||||
source=agent_name,
|
||||
models_usage=model_result.usage,
|
||||
format_string=format_string,
|
||||
),
|
||||
inner_messages=inner_messages,
|
||||
)
|
||||
|
@ -1277,9 +1292,6 @@ class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
|
|||
def _to_config(self) -> AssistantAgentConfig:
|
||||
"""Convert the assistant agent to a declarative config."""
|
||||
|
||||
if self._output_content_type:
|
||||
raise ValueError("AssistantAgent with output_content_type does not support declarative config.")
|
||||
|
||||
return AssistantAgentConfig(
|
||||
name=self.name,
|
||||
model_client=self._model_client.dump_component(),
|
||||
|
@ -1294,12 +1306,23 @@ class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
|
|||
model_client_stream=self._model_client_stream,
|
||||
reflect_on_tool_use=self._reflect_on_tool_use,
|
||||
tool_call_summary_format=self._tool_call_summary_format,
|
||||
structured_message_factory=self._structured_message_factory.dump_component()
|
||||
if self._structured_message_factory
|
||||
else None,
|
||||
metadata=self._metadata,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _from_config(cls, config: AssistantAgentConfig) -> Self:
|
||||
"""Create an assistant agent from a declarative config."""
|
||||
if config.structured_message_factory:
|
||||
structured_message_factory = StructuredMessageFactory.load_component(config.structured_message_factory)
|
||||
format_string = structured_message_factory.format_string
|
||||
output_content_type = structured_message_factory.ContentModel
|
||||
|
||||
else:
|
||||
format_string = None
|
||||
output_content_type = None
|
||||
|
||||
return cls(
|
||||
name=config.name,
|
||||
|
@ -1313,5 +1336,7 @@ class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
|
|||
model_client_stream=config.model_client_stream,
|
||||
reflect_on_tool_use=config.reflect_on_tool_use,
|
||||
tool_call_summary_format=config.tool_call_summary_format,
|
||||
output_content_type=output_content_type,
|
||||
format_string=format_string,
|
||||
metadata=config.metadata,
|
||||
)
|
||||
|
|
|
@ -5,15 +5,15 @@ class and includes specific fields relevant to the type of message being sent.
|
|||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Any, Dict, Generic, List, Literal, Mapping, TypeVar, Optional, Type
|
||||
from typing import Any, Dict, Generic, List, Literal, Mapping, Optional, Type, TypeVar
|
||||
|
||||
from autogen_core import FunctionCall, Image
|
||||
from autogen_core import Component, ComponentBase, FunctionCall, Image
|
||||
from autogen_core.memory import MemoryContent
|
||||
from autogen_core.models import FunctionExecutionResult, LLMMessage, RequestUsage, UserMessage
|
||||
from autogen_core.utils import schema_to_pydantic_model
|
||||
from pydantic import BaseModel, Field, computed_field
|
||||
from typing_extensions import Annotated, Self
|
||||
from autogen_core import Component, ComponentBase
|
||||
from autogen_core.utils import schema_to_pydantic_model
|
||||
|
||||
|
||||
class BaseMessage(BaseModel, ABC):
|
||||
"""Abstract base class for all message types in AgentChat.
|
||||
|
@ -180,6 +180,7 @@ class StructuredMessage(BaseChatMessage, Generic[StructuredContentType]):
|
|||
from pydantic import BaseModel
|
||||
from autogen_agentchat.messages import StructuredMessage
|
||||
|
||||
|
||||
class MyMessageContent(BaseModel):
|
||||
text: str
|
||||
number: int
|
||||
|
@ -199,6 +200,13 @@ class StructuredMessage(BaseChatMessage, Generic[StructuredContentType]):
|
|||
`Pydantic BaseModel <https://docs.pydantic.dev/latest/concepts/models/>`_."""
|
||||
|
||||
format_string: Optional[str] = None
|
||||
"""
|
||||
An optional format string to render the content into a human-readable format.
|
||||
The format string can use the fields of the content model as placeholders.
|
||||
For example, if the content model has a field `name`, you can use
|
||||
`{name}` in the format string to include the value of that field.
|
||||
The format string is used in the :meth:`to_text` method to create a
|
||||
human-readable representation of the message."""
|
||||
|
||||
@computed_field
|
||||
def type(self) -> str:
|
||||
|
@ -222,13 +230,16 @@ class StructuredMessage(BaseChatMessage, Generic[StructuredContentType]):
|
|||
source=self.source,
|
||||
)
|
||||
|
||||
|
||||
class StructureMessageConfig(BaseModel):
|
||||
"""The declarative configuration for the structured input."""
|
||||
json_schema: dict
|
||||
format_string: Optional[str] = None
|
||||
content_model_name: str
|
||||
|
||||
class StructuredMessageComponent(ComponentBase[StructureMessageConfig], Component[StructureMessageConfig]):
|
||||
json_schema: Dict[str, Any]
|
||||
format_string: Optional[str] = None
|
||||
content_model_name: str
|
||||
|
||||
|
||||
class StructuredMessageFactory(ComponentBase[StructureMessageConfig], Component[StructureMessageConfig]):
|
||||
"""
|
||||
A component that creates structured chat messages from Pydantic models or JSON schemas.
|
||||
|
||||
|
@ -243,28 +254,33 @@ class StructuredMessageComponent(ComponentBase[StructureMessageConfig], Componen
|
|||
.. code-block:: python
|
||||
|
||||
from pydantic import BaseModel
|
||||
from autogen_agentchat.messages import StructuredMessageComponent
|
||||
from autogen_agentchat.messages import StructuredMessageFactory
|
||||
|
||||
|
||||
class TestContent(BaseModel):
|
||||
field1: str
|
||||
field2: int
|
||||
|
||||
|
||||
format_string = "This is a string {field1} and this is an int {field2}"
|
||||
sm_component = StructuredMessageComponent(input_model=TestContent, format_string=format_string)
|
||||
sm_component = StructuredMessageFactory(input_model=TestContent, format_string=format_string)
|
||||
|
||||
message = sm_component.StructuredMessage(
|
||||
source="test_agent",
|
||||
content=TestContent(field1="Hello", field2=42),
|
||||
format_string=format_string
|
||||
source="test_agent", content=TestContent(field1="Hello", field2=42), format_string=format_string
|
||||
)
|
||||
|
||||
print(message.to_model_text()) # Output: This is a string Hello and this is an int 42
|
||||
print(message.to_model_text()) # Output: This is a string Hello and this is an int 42
|
||||
|
||||
config = sm_component._to_config()
|
||||
s_m_dyn = StructuredMessageComponent._from_config(config)
|
||||
message = s_m_dyn.StructuredMessage(source="test_agent", content=s_m_dyn.ContentModel(field1="dyn agent", field2=43), format_string=s_m_dyn.format_string)
|
||||
config = sm_component.dump_component()
|
||||
|
||||
s_m_dyn = StructuredMessageFactory.load_component(config)
|
||||
message = s_m_dyn.StructuredMessage(
|
||||
source="test_agent",
|
||||
content=s_m_dyn.ContentModel(field1="dyn agent", field2=43),
|
||||
format_string=s_m_dyn.format_string,
|
||||
)
|
||||
print(type(message)) # StructuredMessage[GeneratedModel]
|
||||
print(message.to_model_text()) # Output: This is a string dyn agent and this is an int 43
|
||||
print(message.to_model_text()) # Output: This is a string dyn agent and this is an int 43
|
||||
|
||||
Attributes:
|
||||
component_config_schema (StructureMessageConfig): Defines the configuration structure for this component.
|
||||
|
@ -282,35 +298,42 @@ class StructuredMessageComponent(ComponentBase[StructureMessageConfig], Componen
|
|||
"""
|
||||
|
||||
component_config_schema = StructureMessageConfig
|
||||
component_provider_override = "autogen_agentchat.messages.StructuredMessageComponent"
|
||||
component_provider_override = "autogen_agentchat.messages.StructuredMessageFactory"
|
||||
component_type = "structured_message"
|
||||
|
||||
def __init__(self, json_schema: Optional[str]=None, input_model: Optional[Type[BaseModel]] = None, format_string: Optional[str] = None, content_model_name: Optional[str] = None) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
json_schema: Optional[Dict[str, Any]] = None,
|
||||
input_model: Optional[Type[BaseModel]] = None,
|
||||
format_string: Optional[str] = None,
|
||||
content_model_name: Optional[str] = None,
|
||||
) -> None:
|
||||
self.format_string = format_string
|
||||
|
||||
if not json_schema and not input_model:
|
||||
raise ValueError("Either `input_json_schema` or `input_model` must be provided.")
|
||||
|
||||
if input_model:
|
||||
|
||||
if json_schema:
|
||||
self.ContentModel = schema_to_pydantic_model(
|
||||
json_schema, model_name=content_model_name or "GeneratedContentModel"
|
||||
)
|
||||
elif input_model:
|
||||
self.ContentModel = input_model
|
||||
else:
|
||||
self.ContentModel = schema_to_pydantic_model(json_schema, model_name=content_model_name or "GeneratedContentModel")
|
||||
|
||||
self.StructuredMessage = StructuredMessage[self.ContentModel]
|
||||
|
||||
raise ValueError("Either `json_schema` or `input_model` must be provided.")
|
||||
|
||||
self.StructuredMessage = StructuredMessage[self.ContentModel] # type: ignore[name-defined]
|
||||
|
||||
def _to_config(self) -> StructureMessageConfig:
|
||||
return StructureMessageConfig(
|
||||
json_schema=self.ContentModel.model_json_schema(),
|
||||
format_string=self.format_string,
|
||||
content_model_name=self.ContentModel.__name__
|
||||
content_model_name=self.ContentModel.__name__,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _from_config(cls, config: StructureMessageConfig) -> "StructuredMessageComponent":
|
||||
def _from_config(cls, config: StructureMessageConfig) -> "StructuredMessageFactory":
|
||||
return cls(
|
||||
json_schema=config.json_schema,
|
||||
format_string=config.format_string,
|
||||
content_model_name=config.content_model_name
|
||||
content_model_name=config.content_model_name,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ from ...messages import (
|
|||
MessageFactory,
|
||||
ModelClientStreamingChunkEvent,
|
||||
StopMessage,
|
||||
StructuredMessage,
|
||||
TextMessage,
|
||||
)
|
||||
from ...state import TeamState
|
||||
|
@ -68,6 +69,15 @@ class BaseGroupChat(Team, ABC, ComponentBase[BaseModel]):
|
|||
for message_type in custom_message_types:
|
||||
self._message_factory.register(message_type)
|
||||
|
||||
for agent in participants:
|
||||
for message_type in agent.produced_message_types:
|
||||
try:
|
||||
if issubclass(message_type, StructuredMessage):
|
||||
self._message_factory.register(message_type) # type: ignore[reportUnknownArgumentType]
|
||||
except TypeError:
|
||||
# Not a class or not a valid subclassable type (skip)
|
||||
pass
|
||||
|
||||
# The team ID is a UUID that is used to identify the team and its participants
|
||||
# in the agent runtime. It is used to create unique topic types for each participant.
|
||||
# Currently, team ID is binded to an object instance of the group chat class.
|
||||
|
|
|
@ -36,7 +36,7 @@ from autogen_core.models._model_client import ModelFamily
|
|||
from autogen_core.tools import BaseTool, FunctionTool
|
||||
from autogen_ext.models.openai import OpenAIChatCompletionClient
|
||||
from autogen_ext.models.replay import ReplayChatCompletionClient
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ValidationError
|
||||
from utils import FileLogHandler
|
||||
|
||||
logger = logging.getLogger(EVENT_LOGGER_NAME)
|
||||
|
@ -1104,3 +1104,103 @@ async def test_model_client_stream_with_tool_calls() -> None:
|
|||
elif isinstance(message, ModelClientStreamingChunkEvent):
|
||||
chunks.append(message.content)
|
||||
assert "".join(chunks) == "Example response 2 to task"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalid_structured_output_format() -> None:
|
||||
class AgentResponse(BaseModel):
|
||||
response: str
|
||||
status: str
|
||||
|
||||
model_client = ReplayChatCompletionClient(
|
||||
[
|
||||
CreateResult(
|
||||
finish_reason="stop",
|
||||
content='{"response": "Hello"}',
|
||||
usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
|
||||
cached=False,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
agent = AssistantAgent(
|
||||
name="assistant",
|
||||
model_client=model_client,
|
||||
output_content_type=AgentResponse,
|
||||
)
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
await agent.run()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_structured_message_factory_serialization() -> None:
|
||||
class AgentResponse(BaseModel):
|
||||
result: str
|
||||
status: str
|
||||
|
||||
model_client = ReplayChatCompletionClient(
|
||||
[
|
||||
CreateResult(
|
||||
finish_reason="stop",
|
||||
content=AgentResponse(result="All good", status="ok").model_dump_json(),
|
||||
usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
|
||||
cached=False,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
agent = AssistantAgent(
|
||||
name="structured_agent",
|
||||
model_client=model_client,
|
||||
output_content_type=AgentResponse,
|
||||
format_string="{result} - {status}",
|
||||
)
|
||||
|
||||
dumped = agent.dump_component()
|
||||
restored_agent = AssistantAgent.load_component(dumped)
|
||||
result = await restored_agent.run()
|
||||
|
||||
assert isinstance(result.messages[0], StructuredMessage)
|
||||
assert result.messages[0].content.result == "All good" # type: ignore[reportUnknownMemberType]
|
||||
assert result.messages[0].content.status == "ok" # type: ignore[reportUnknownMemberType]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_structured_message_format_string() -> None:
|
||||
class AgentResponse(BaseModel):
|
||||
field1: str
|
||||
field2: str
|
||||
|
||||
expected = AgentResponse(field1="foo", field2="bar")
|
||||
|
||||
model_client = ReplayChatCompletionClient(
|
||||
[
|
||||
CreateResult(
|
||||
finish_reason="stop",
|
||||
content=expected.model_dump_json(),
|
||||
usage=RequestUsage(prompt_tokens=10, completion_tokens=5),
|
||||
cached=False,
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
agent = AssistantAgent(
|
||||
name="formatted_agent",
|
||||
model_client=model_client,
|
||||
output_content_type=AgentResponse,
|
||||
format_string="{field1} - {field2}",
|
||||
)
|
||||
|
||||
result = await agent.run()
|
||||
|
||||
assert len(result.messages) == 1
|
||||
message = result.messages[0]
|
||||
|
||||
# Check that it's a StructuredMessage with the correct content model
|
||||
assert isinstance(message, StructuredMessage)
|
||||
assert isinstance(message.content, AgentResponse) # type: ignore[reportUnknownMemberType]
|
||||
assert message.content == expected
|
||||
|
||||
# Check that the format_string was applied correctly
|
||||
assert message.to_model_text() == "foo - bar"
|
||||
|
|
|
@ -1441,3 +1441,87 @@ async def test_declarative_groupchats_with_config(runtime: AgentRuntime | None)
|
|||
assert selector.dump_component().provider == "autogen_agentchat.teams.SelectorGroupChat"
|
||||
assert swarm.dump_component().provider == "autogen_agentchat.teams.Swarm"
|
||||
assert magentic.dump_component().provider == "autogen_agentchat.teams.MagenticOneGroupChat"
|
||||
|
||||
|
||||
class _StructuredContent(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class _StructuredAgent(BaseChatAgent):
|
||||
def __init__(self, name: str, description: str) -> None:
|
||||
super().__init__(name, description)
|
||||
self._message = _StructuredContent(message="Structured hello")
|
||||
|
||||
@property
|
||||
def produced_message_types(self) -> Sequence[type[BaseChatMessage]]:
|
||||
return (StructuredMessage[_StructuredContent],)
|
||||
|
||||
async def on_messages(self, messages: Sequence[BaseChatMessage], cancellation_token: CancellationToken) -> Response:
|
||||
return Response(
|
||||
chat_message=StructuredMessage[_StructuredContent](
|
||||
source=self.name,
|
||||
content=self._message,
|
||||
format_string="Structured says: {message}",
|
||||
)
|
||||
)
|
||||
|
||||
async def on_reset(self, cancellation_token: CancellationToken) -> None:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_message_type_auto_registration(runtime: AgentRuntime | None) -> None:
|
||||
agent1 = _StructuredAgent("structured", description="emits structured messages")
|
||||
agent2 = _EchoAgent("echo", description="echoes input")
|
||||
|
||||
team = RoundRobinGroupChat(participants=[agent1, agent2], max_turns=2, runtime=runtime)
|
||||
|
||||
result = await team.run(task="Say something structured")
|
||||
|
||||
assert len(result.messages) == 3
|
||||
assert isinstance(result.messages[0], TextMessage)
|
||||
assert isinstance(result.messages[1], StructuredMessage)
|
||||
assert isinstance(result.messages[2], TextMessage)
|
||||
assert result.messages[1].to_text() == "Structured says: Structured hello"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_structured_message_state_roundtrip(runtime: AgentRuntime | None) -> None:
|
||||
agent1 = _StructuredAgent("structured", description="sends structured")
|
||||
agent2 = _EchoAgent("echo", description="echoes")
|
||||
|
||||
team1 = RoundRobinGroupChat(
|
||||
participants=[agent1, agent2],
|
||||
termination_condition=MaxMessageTermination(2),
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
await team1.run(task="Say something structured")
|
||||
state1 = await team1.save_state()
|
||||
|
||||
# Recreate team without needing custom_message_types
|
||||
agent3 = _StructuredAgent("structured", description="sends structured")
|
||||
agent4 = _EchoAgent("echo", description="echoes")
|
||||
team2 = RoundRobinGroupChat(
|
||||
participants=[agent3, agent4],
|
||||
termination_condition=MaxMessageTermination(2),
|
||||
runtime=runtime,
|
||||
)
|
||||
|
||||
await team2.load_state(state1)
|
||||
state2 = await team2.save_state()
|
||||
|
||||
# Assert full state equality
|
||||
assert state1 == state2
|
||||
|
||||
# Assert message thread content match
|
||||
manager1 = await team1._runtime.try_get_underlying_agent_instance( # pyright: ignore
|
||||
AgentId(f"{team1._group_chat_manager_name}_{team1._team_id}", team1._team_id), # pyright: ignore
|
||||
RoundRobinGroupChatManager,
|
||||
)
|
||||
manager2 = await team2._runtime.try_get_underlying_agent_instance( # pyright: ignore
|
||||
AgentId(f"{team2._group_chat_manager_name}_{team2._team_id}", team2._team_id), # pyright: ignore
|
||||
RoundRobinGroupChatManager,
|
||||
)
|
||||
|
||||
assert manager1._message_thread == manager2._message_thread # pyright: ignore
|
||||
|
|
|
@ -11,7 +11,7 @@ from autogen_agentchat.messages import (
|
|||
MultiModalMessage,
|
||||
StopMessage,
|
||||
StructuredMessage,
|
||||
StructuredMessageComponent,
|
||||
StructuredMessageFactory,
|
||||
TextMessage,
|
||||
ToolCallExecutionEvent,
|
||||
ToolCallRequestEvent,
|
||||
|
@ -52,18 +52,21 @@ def test_structured_message() -> None:
|
|||
assert dumped_message["content"]["field2"] == 42
|
||||
assert dumped_message["type"] == "StructuredMessage[TestContent]"
|
||||
|
||||
|
||||
def test_structured_message_component() -> None:
|
||||
# Create a structured message with the test contentformat_string="this is a string {field1} and this is an int {field2}"
|
||||
format_string="this is a string {field1} and this is an int {field2}"
|
||||
s_m = StructuredMessageComponent(input_model=TestContent, format_string=format_string)
|
||||
config = s_m._to_config()
|
||||
s_m_dyn = StructuredMessageComponent._from_config(config)
|
||||
message = s_m_dyn.StructuredMessage(source="test_agent", content=s_m_dyn.ContentModel(field1="test", field2=42), format_string=s_m_dyn.format_string)
|
||||
format_string = "this is a string {field1} and this is an int {field2}"
|
||||
s_m = StructuredMessageFactory(input_model=TestContent, format_string=format_string)
|
||||
config = s_m.dump_component()
|
||||
s_m_dyn = StructuredMessageFactory.load_component(config)
|
||||
message = s_m_dyn.StructuredMessage(
|
||||
source="test_agent", content=s_m_dyn.ContentModel(field1="test", field2=42), format_string=s_m_dyn.format_string
|
||||
)
|
||||
|
||||
assert isinstance(message.content, s_m_dyn.ContentModel)
|
||||
assert not isinstance(message.content, TestContent)
|
||||
assert message.content.field1 == "test"
|
||||
assert message.content.field2 == 42
|
||||
assert message.content.field1 == "test" # type: ignore[attr-defined]
|
||||
assert message.content.field2 == 42 # type: ignore[attr-defined]
|
||||
|
||||
dumped_message = message.model_dump()
|
||||
assert dumped_message["source"] == "test_agent"
|
||||
|
@ -71,6 +74,7 @@ def test_structured_message_component() -> None:
|
|||
assert dumped_message["content"]["field2"] == 42
|
||||
assert message.to_model_text() == format_string.format(field1="test", field2=42)
|
||||
|
||||
|
||||
def test_message_factory() -> None:
|
||||
factory = MessageFactory()
|
||||
|
||||
|
@ -128,6 +132,22 @@ def test_message_factory() -> None:
|
|||
assert structured_message.content.field2 == 42
|
||||
assert structured_message.type == "StructuredMessage[TestContent]" # type: ignore[comparison-overlap]
|
||||
|
||||
sm_factory = StructuredMessageFactory(input_model=TestContent, format_string=None, content_model_name="TestContent")
|
||||
config = sm_factory.dump_component()
|
||||
config.config["content_model_name"] = "DynamicTestContent"
|
||||
sm_factory_dynamic = StructuredMessageFactory.load_component(config)
|
||||
|
||||
factory.register(sm_factory_dynamic.StructuredMessage)
|
||||
msg = sm_factory_dynamic.StructuredMessage(
|
||||
content=sm_factory_dynamic.ContentModel(field1="static", field2=123), source="static_agent"
|
||||
)
|
||||
restored = factory.create(msg.dump())
|
||||
assert isinstance(restored, StructuredMessage)
|
||||
assert isinstance(restored.content, sm_factory_dynamic.ContentModel) # type: ignore[reportUnkownMemberType]
|
||||
assert restored.source == "static_agent"
|
||||
assert restored.content.field1 == "static" # type: ignore[attr-defined]
|
||||
assert restored.content.field2 == 123 # type: ignore[attr-defined]
|
||||
|
||||
|
||||
class TestContainer(BaseModel):
|
||||
chat_messages: List[ChatMessage]
|
||||
|
|
|
@ -38,7 +38,6 @@
|
|||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"from pydantic import BaseModel\n",
|
||||
"from typing import List, Optional\n",
|
||||
"\n",
|
||||
"from autogen_core import AgentId, MessageContext, RoutedAgent, SingleThreadedAgentRuntime, message_handler\n",
|
||||
|
@ -57,7 +56,8 @@
|
|||
"from llama_index.embeddings.openai import OpenAIEmbedding\n",
|
||||
"from llama_index.llms.azure_openai import AzureOpenAI\n",
|
||||
"from llama_index.llms.openai import OpenAI\n",
|
||||
"from llama_index.tools.wikipedia import WikipediaToolSpec"
|
||||
"from llama_index.tools.wikipedia import WikipediaToolSpec\n",
|
||||
"from pydantic import BaseModel"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -291,7 +291,7 @@
|
|||
"A --- B\n",
|
||||
"| |\n",
|
||||
"| |\n",
|
||||
"C --- D\n",
|
||||
"D --- C\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Each solver agent is connected to two other solver agents. \n",
|
||||
|
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||
|
||||
[project]
|
||||
name = "autogen-core"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
license = {file = "LICENSE-CODE"}
|
||||
description = "Foundational interfaces and agent runtime implementation for AutoGen"
|
||||
readme = "README.md"
|
||||
|
@ -69,7 +69,7 @@ dev = [
|
|||
"pygments",
|
||||
"sphinxext-rediraffe",
|
||||
|
||||
"autogen_ext==0.5.1",
|
||||
"autogen_ext==0.5.2",
|
||||
|
||||
# Documentation tooling
|
||||
"diskcache",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import datetime
|
||||
from typing import Annotated, Any, Dict, ForwardRef, List, Literal, Optional, Type, Union
|
||||
from ipaddress import IPv4Address, IPv6Address
|
||||
from typing import Annotated, Any, Dict, ForwardRef, List, Literal, Optional, Type, Union, cast
|
||||
|
||||
from pydantic import (
|
||||
UUID1,
|
||||
|
@ -10,7 +11,6 @@ from pydantic import (
|
|||
BaseModel,
|
||||
EmailStr,
|
||||
Field,
|
||||
IPvAnyAddress,
|
||||
conbytes,
|
||||
confloat,
|
||||
conint,
|
||||
|
@ -18,6 +18,7 @@ from pydantic import (
|
|||
constr,
|
||||
create_model,
|
||||
)
|
||||
from pydantic.fields import FieldInfo
|
||||
|
||||
|
||||
class SchemaConversionError(Exception):
|
||||
|
@ -44,14 +45,14 @@ class UnsupportedKeywordError(SchemaConversionError):
|
|||
pass
|
||||
|
||||
|
||||
TYPE_MAPPING: Dict[str, Any] = {
|
||||
TYPE_MAPPING: Dict[str, Type[Any]] = {
|
||||
"string": str,
|
||||
"integer": int,
|
||||
"boolean": bool,
|
||||
"number": float,
|
||||
"array": List,
|
||||
"object": dict,
|
||||
"null": None,
|
||||
"null": type(None),
|
||||
}
|
||||
|
||||
FORMAT_MAPPING: Dict[str, Any] = {
|
||||
|
@ -64,10 +65,10 @@ FORMAT_MAPPING: Dict[str, Any] = {
|
|||
"email": EmailStr,
|
||||
"uri": AnyUrl,
|
||||
"hostname": constr(strict=True),
|
||||
"ipv4": IPvAnyAddress,
|
||||
"ipv6": IPvAnyAddress,
|
||||
"ipv4-network": IPvAnyAddress,
|
||||
"ipv6-network": IPvAnyAddress,
|
||||
"ipv4": IPv4Address,
|
||||
"ipv6": IPv6Address,
|
||||
"ipv4-network": IPv4Address,
|
||||
"ipv6-network": IPv6Address,
|
||||
"date-time": datetime.datetime,
|
||||
"date": datetime.date,
|
||||
"time": datetime.time,
|
||||
|
@ -84,13 +85,28 @@ FORMAT_MAPPING: Dict[str, Any] = {
|
|||
}
|
||||
|
||||
|
||||
def _make_field(
|
||||
default: Any,
|
||||
*,
|
||||
title: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
) -> Any:
|
||||
"""Construct a Pydantic Field with proper typing."""
|
||||
field_kwargs: Dict[str, Any] = {}
|
||||
if title is not None:
|
||||
field_kwargs["title"] = title
|
||||
if description is not None:
|
||||
field_kwargs["description"] = description
|
||||
return Field(default, **field_kwargs)
|
||||
|
||||
|
||||
class _JSONSchemaToPydantic:
|
||||
def __init__(self):
|
||||
self._model_cache = {}
|
||||
def __init__(self) -> None:
|
||||
self._model_cache: Dict[str, Optional[Union[Type[BaseModel], ForwardRef]]] = {}
|
||||
|
||||
def _resolve_ref(self, ref: str, schema: Dict[str, Any]) -> Dict[str, Any]:
|
||||
ref_key = ref.split("/")[-1]
|
||||
definitions = schema.get("$defs", {})
|
||||
definitions = cast(dict[str, dict[str, Any]], schema.get("$defs", {}))
|
||||
|
||||
if ref_key not in definitions:
|
||||
raise ReferenceNotFoundError(
|
||||
|
@ -110,7 +126,7 @@ class _JSONSchemaToPydantic:
|
|||
|
||||
return self._model_cache[ref_name]
|
||||
|
||||
def _process_definitions(self, root_schema: Dict[str, Any]):
|
||||
def _process_definitions(self, root_schema: Dict[str, Any]) -> None:
|
||||
if "$defs" in root_schema:
|
||||
for model_name in root_schema["$defs"]:
|
||||
if model_name not in self._model_cache:
|
||||
|
@ -132,7 +148,7 @@ class _JSONSchemaToPydantic:
|
|||
schema = {**resolved, **{k: v for k, v in schema.items() if k != "$ref"}}
|
||||
|
||||
if "allOf" in schema:
|
||||
merged = {"type": "object", "properties": {}, "required": []}
|
||||
merged: Dict[str, Any] = {"type": "object", "properties": {}, "required": []}
|
||||
for s in schema["allOf"]:
|
||||
part = self._resolve_ref(s["$ref"], root_schema) if "$ref" in s else s
|
||||
merged["properties"].update(part.get("properties", {}))
|
||||
|
@ -146,7 +162,7 @@ class _JSONSchemaToPydantic:
|
|||
return self._json_schema_to_model(schema, model_name, root_schema)
|
||||
|
||||
def _resolve_union_types(self, schemas: List[Dict[str, Any]]) -> List[Any]:
|
||||
types = []
|
||||
types: List[Any] = []
|
||||
for s in schemas:
|
||||
if "$ref" in s:
|
||||
types.append(self.get_ref(s["$ref"].split("/")[-1]))
|
||||
|
@ -167,7 +183,7 @@ class _JSONSchemaToPydantic:
|
|||
)
|
||||
|
||||
base_type = TYPE_MAPPING[json_type]
|
||||
constraints = {}
|
||||
constraints: Dict[str, Any] = {}
|
||||
|
||||
if json_type == "string":
|
||||
if "minLength" in value:
|
||||
|
@ -219,7 +235,7 @@ class _JSONSchemaToPydantic:
|
|||
)
|
||||
item_type = TYPE_MAPPING[item_type_name]
|
||||
|
||||
base_type = conlist(item_type, **constraints) if constraints else List[item_type]
|
||||
base_type = conlist(item_type, **constraints) if constraints else List[item_type] # type: ignore[valid-type]
|
||||
|
||||
if "format" in value:
|
||||
format_type = FORMAT_MAPPING.get(value["format"])
|
||||
|
@ -237,7 +253,7 @@ class _JSONSchemaToPydantic:
|
|||
self, schema: Dict[str, Any], model_name: str, root_schema: Dict[str, Any]
|
||||
) -> Type[BaseModel]:
|
||||
if "allOf" in schema:
|
||||
merged = {"type": "object", "properties": {}, "required": []}
|
||||
merged: Dict[str, Any] = {"type": "object", "properties": {}, "required": []}
|
||||
for s in schema["allOf"]:
|
||||
part = self._resolve_ref(s["$ref"], root_schema) if "$ref" in s else s
|
||||
merged["properties"].update(part.get("properties", {}))
|
||||
|
@ -248,7 +264,7 @@ class _JSONSchemaToPydantic:
|
|||
merged["required"] = list(set(merged["required"]))
|
||||
schema = merged
|
||||
|
||||
fields = {}
|
||||
fields: Dict[str, tuple[Any, FieldInfo]] = {}
|
||||
required_fields = set(schema.get("required", []))
|
||||
|
||||
for key, value in schema.get("properties", {}).items():
|
||||
|
@ -299,9 +315,16 @@ class _JSONSchemaToPydantic:
|
|||
if "description" in value:
|
||||
field_args["description"] = value["description"]
|
||||
|
||||
fields[key] = (field_type, Field(**field_args))
|
||||
fields[key] = (
|
||||
field_type,
|
||||
_make_field(
|
||||
default_value if not is_required else ...,
|
||||
title=value.get("title"),
|
||||
description=value.get("description"),
|
||||
),
|
||||
)
|
||||
|
||||
model = create_model(model_name, **fields)
|
||||
model: Type[BaseModel] = create_model(model_name, **cast(dict[str, Any], fields))
|
||||
model.model_rebuild()
|
||||
return model
|
||||
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
from typing import Any, Dict, List, Literal, Optional
|
||||
import types
|
||||
from typing import Any, Dict, List, Literal, Optional, Type, get_args, get_origin
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
from autogen_core.utils._json_to_pydantic import (
|
||||
FORMAT_MAPPING,
|
||||
TYPE_MAPPING,
|
||||
FormatNotSupportedError,
|
||||
ReferenceNotFoundError,
|
||||
UnsupportedKeywordError,
|
||||
_JSONSchemaToPydantic,
|
||||
_JSONSchemaToPydantic, # pyright: ignore[reportPrivateUsage]
|
||||
)
|
||||
from pydantic import BaseModel, EmailStr, Field, ValidationError
|
||||
|
||||
|
@ -44,31 +47,31 @@ class ComplexModel(BaseModel):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def converter():
|
||||
def converter() -> _JSONSchemaToPydantic:
|
||||
"""Fixture to create a fresh instance of JSONSchemaToPydantic for every test."""
|
||||
return _JSONSchemaToPydantic()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_json_schema():
|
||||
def sample_json_schema() -> Dict[str, Any]:
|
||||
"""Fixture that returns a JSON schema dynamically using model_json_schema()."""
|
||||
return User.model_json_schema()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_json_schema_recursive():
|
||||
def sample_json_schema_recursive() -> Dict[str, Any]:
|
||||
"""Fixture that returns a self-referencing JSON schema."""
|
||||
return Employee.model_json_schema()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_json_schema_nested():
|
||||
def sample_json_schema_nested() -> Dict[str, Any]:
|
||||
"""Fixture that returns a nested schema with arrays of objects."""
|
||||
return Department.model_json_schema()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_json_schema_complex():
|
||||
def sample_json_schema_complex() -> Dict[str, Any]:
|
||||
"""Fixture that returns a complex schema with multiple structures."""
|
||||
return ComplexModel.model_json_schema()
|
||||
|
||||
|
@ -82,7 +85,13 @@ def sample_json_schema_complex():
|
|||
(sample_json_schema_complex, "ComplexModel", ["user", "extra_info", "sub_items"]),
|
||||
],
|
||||
)
|
||||
def test_json_schema_to_pydantic(converter, schema_fixture, model_name, expected_fields, request):
|
||||
def test_json_schema_to_pydantic(
|
||||
converter: _JSONSchemaToPydantic,
|
||||
schema_fixture: Any,
|
||||
model_name: str,
|
||||
expected_fields: List[str],
|
||||
request: Any,
|
||||
) -> None:
|
||||
"""Test conversion of JSON Schema to Pydantic model using the class instance."""
|
||||
schema = request.getfixturevalue(schema_fixture.__name__)
|
||||
Model = converter.json_schema_to_pydantic(schema, model_name)
|
||||
|
@ -155,7 +164,13 @@ def test_json_schema_to_pydantic(converter, schema_fixture, model_name, expected
|
|||
),
|
||||
],
|
||||
)
|
||||
def test_valid_data_model(converter, schema_fixture, model_name, valid_data, request):
|
||||
def test_valid_data_model(
|
||||
converter: _JSONSchemaToPydantic,
|
||||
schema_fixture: Any,
|
||||
model_name: str,
|
||||
valid_data: Dict[str, Any],
|
||||
request: Any,
|
||||
) -> None:
|
||||
"""Test that valid data is accepted by the generated model."""
|
||||
schema = request.getfixturevalue(schema_fixture.__name__)
|
||||
Model = converter.json_schema_to_pydantic(schema, model_name)
|
||||
|
@ -230,7 +245,13 @@ def test_valid_data_model(converter, schema_fixture, model_name, valid_data, req
|
|||
),
|
||||
],
|
||||
)
|
||||
def test_invalid_data_model(converter, schema_fixture, model_name, invalid_data, request):
|
||||
def test_invalid_data_model(
|
||||
converter: _JSONSchemaToPydantic,
|
||||
schema_fixture: Any,
|
||||
model_name: str,
|
||||
invalid_data: Dict[str, Any],
|
||||
request: Any,
|
||||
) -> None:
|
||||
"""Test that invalid data raises ValidationError."""
|
||||
schema = request.getfixturevalue(schema_fixture.__name__)
|
||||
Model = converter.json_schema_to_pydantic(schema, model_name)
|
||||
|
@ -258,19 +279,19 @@ class NestedListModel(BaseModel):
|
|||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_json_schema_list_dict():
|
||||
def sample_json_schema_list_dict() -> Dict[str, Any]:
|
||||
"""Fixture for `List[Dict[str, Any]]`"""
|
||||
return ListDictModel.model_json_schema()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_json_schema_dict_list():
|
||||
def sample_json_schema_dict_list() -> Dict[str, Any]:
|
||||
"""Fixture for `Dict[str, List[Any]]`"""
|
||||
return DictListModel.model_json_schema()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_json_schema_nested_list():
|
||||
def sample_json_schema_nested_list() -> Dict[str, Any]:
|
||||
"""Fixture for `List[List[str]]`"""
|
||||
return NestedListModel.model_json_schema()
|
||||
|
||||
|
@ -283,7 +304,13 @@ def sample_json_schema_nested_list():
|
|||
(sample_json_schema_nested_list, "NestedListModel", ["matrix"]),
|
||||
],
|
||||
)
|
||||
def test_json_schema_to_pydantic_nested(converter, schema_fixture, model_name, expected_fields, request):
|
||||
def test_json_schema_to_pydantic_nested(
|
||||
converter: _JSONSchemaToPydantic,
|
||||
schema_fixture: Any,
|
||||
model_name: str,
|
||||
expected_fields: list[str],
|
||||
request: Any,
|
||||
) -> None:
|
||||
"""Test conversion of JSON Schema to Pydantic model using the class instance."""
|
||||
schema = request.getfixturevalue(schema_fixture.__name__)
|
||||
Model = converter.json_schema_to_pydantic(schema, model_name)
|
||||
|
@ -324,7 +351,13 @@ def test_json_schema_to_pydantic_nested(converter, schema_fixture, model_name, e
|
|||
),
|
||||
],
|
||||
)
|
||||
def test_valid_data_model_nested(converter, schema_fixture, model_name, valid_data, request):
|
||||
def test_valid_data_model_nested(
|
||||
converter: _JSONSchemaToPydantic,
|
||||
schema_fixture: Any,
|
||||
model_name: str,
|
||||
valid_data: Dict[str, Any],
|
||||
request: Any,
|
||||
) -> None:
|
||||
"""Test that valid data is accepted by the generated model."""
|
||||
schema = request.getfixturevalue(schema_fixture.__name__)
|
||||
Model = converter.json_schema_to_pydantic(schema, model_name)
|
||||
|
@ -364,7 +397,13 @@ def test_valid_data_model_nested(converter, schema_fixture, model_name, valid_da
|
|||
),
|
||||
],
|
||||
)
|
||||
def test_invalid_data_model_nested(converter, schema_fixture, model_name, invalid_data, request):
|
||||
def test_invalid_data_model_nested(
|
||||
converter: _JSONSchemaToPydantic,
|
||||
schema_fixture: Any,
|
||||
model_name: str,
|
||||
invalid_data: Dict[str, Any],
|
||||
request: Any,
|
||||
) -> None:
|
||||
"""Test that invalid data raises ValidationError."""
|
||||
schema = request.getfixturevalue(schema_fixture.__name__)
|
||||
Model = converter.json_schema_to_pydantic(schema, model_name)
|
||||
|
@ -373,25 +412,25 @@ def test_invalid_data_model_nested(converter, schema_fixture, model_name, invali
|
|||
Model(**invalid_data)
|
||||
|
||||
|
||||
def test_reference_not_found(converter):
|
||||
def test_reference_not_found(converter: _JSONSchemaToPydantic) -> None:
|
||||
schema = {"type": "object", "properties": {"manager": {"$ref": "#/$defs/MissingRef"}}}
|
||||
with pytest.raises(ReferenceNotFoundError):
|
||||
converter.json_schema_to_pydantic(schema, "MissingRefModel")
|
||||
|
||||
|
||||
def test_format_not_supported(converter):
|
||||
def test_format_not_supported(converter: _JSONSchemaToPydantic) -> None:
|
||||
schema = {"type": "object", "properties": {"custom_field": {"type": "string", "format": "unsupported-format"}}}
|
||||
with pytest.raises(FormatNotSupportedError):
|
||||
converter.json_schema_to_pydantic(schema, "UnsupportedFormatModel")
|
||||
|
||||
|
||||
def test_unsupported_keyword(converter):
|
||||
def test_unsupported_keyword(converter: _JSONSchemaToPydantic) -> None:
|
||||
schema = {"type": "object", "properties": {"broken_field": {"title": "Missing type"}}}
|
||||
with pytest.raises(UnsupportedKeywordError):
|
||||
converter.json_schema_to_pydantic(schema, "MissingTypeModel")
|
||||
|
||||
|
||||
def test_enum_field_schema():
|
||||
def test_enum_field_schema() -> None:
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -401,18 +440,24 @@ def test_enum_field_schema():
|
|||
"required": ["status"],
|
||||
}
|
||||
|
||||
converter = _JSONSchemaToPydantic()
|
||||
converter: _JSONSchemaToPydantic = _JSONSchemaToPydantic()
|
||||
Model = converter.json_schema_to_pydantic(schema, "Task")
|
||||
|
||||
assert Model.model_fields["status"].annotation == Literal["pending", "approved", "rejected"]
|
||||
assert Model.model_fields["priority"].annotation == Optional[Literal[1, 2, 3]]
|
||||
status_ann = Model.model_fields["status"].annotation
|
||||
assert get_origin(status_ann) is Literal
|
||||
assert set(get_args(status_ann)) == {"pending", "approved", "rejected"}
|
||||
|
||||
priority_ann = Model.model_fields["priority"].annotation
|
||||
args = get_args(priority_ann)
|
||||
assert type(None) in args
|
||||
assert Literal[1, 2, 3] in args
|
||||
|
||||
instance = Model(status="approved", priority=2)
|
||||
assert instance.status == "approved"
|
||||
assert instance.priority == 2
|
||||
assert instance.status == "approved" # type: ignore[attr-defined]
|
||||
assert instance.priority == 2 # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def test_metadata_title_description(converter):
|
||||
def test_metadata_title_description(converter: _JSONSchemaToPydantic) -> None:
|
||||
schema = {
|
||||
"title": "CustomerProfile",
|
||||
"description": "A profile containing personal and contact info",
|
||||
|
@ -437,7 +482,7 @@ def test_metadata_title_description(converter):
|
|||
"required": ["first_name"],
|
||||
}
|
||||
|
||||
Model = converter.json_schema_to_pydantic(schema, "CustomerProfile")
|
||||
Model: Type[BaseModel] = converter.json_schema_to_pydantic(schema, "CustomerProfile")
|
||||
generated_schema = Model.model_json_schema()
|
||||
|
||||
assert generated_schema["title"] == "CustomerProfile"
|
||||
|
@ -460,7 +505,7 @@ def test_metadata_title_description(converter):
|
|||
assert email["description"] == "Primary email"
|
||||
|
||||
|
||||
def test_oneof_with_discriminator(converter):
|
||||
def test_oneof_with_discriminator(converter: _JSONSchemaToPydantic) -> None:
|
||||
schema = {
|
||||
"title": "PetWrapper",
|
||||
"type": "object",
|
||||
|
@ -491,11 +536,11 @@ def test_oneof_with_discriminator(converter):
|
|||
|
||||
# Instantiate with a Cat
|
||||
cat = Model(pet={"pet_type": "cat", "hunting_skill": "expert"})
|
||||
assert cat.pet.pet_type == "cat"
|
||||
assert cat.pet.pet_type == "cat" # type: ignore[attr-defined]
|
||||
|
||||
# Instantiate with a Dog
|
||||
dog = Model(pet={"pet_type": "dog", "pack_size": 4})
|
||||
assert dog.pet.pet_type == "dog"
|
||||
assert dog.pet.pet_type == "dog" # type: ignore[attr-defined]
|
||||
|
||||
# Check round-trip schema includes discriminator
|
||||
model_schema = Model.model_json_schema()
|
||||
|
@ -503,7 +548,7 @@ def test_oneof_with_discriminator(converter):
|
|||
assert model_schema["properties"]["pet"]["discriminator"]["propertyName"] == "pet_type"
|
||||
|
||||
|
||||
def test_allof_merging_with_refs(converter):
|
||||
def test_allof_merging_with_refs(converter: _JSONSchemaToPydantic) -> None:
|
||||
schema = {
|
||||
"title": "EmployeeWithDepartment",
|
||||
"allOf": [{"$ref": "#/$defs/Employee"}, {"$ref": "#/$defs/Department"}],
|
||||
|
@ -525,15 +570,15 @@ def test_allof_merging_with_refs(converter):
|
|||
|
||||
Model = converter.json_schema_to_pydantic(schema, "EmployeeWithDepartment")
|
||||
instance = Model(id="123", name="Alice", department="Engineering")
|
||||
assert instance.id == "123"
|
||||
assert instance.name == "Alice"
|
||||
assert instance.department == "Engineering"
|
||||
assert instance.id == "123" # type: ignore[attr-defined]
|
||||
assert instance.name == "Alice" # type: ignore[attr-defined]
|
||||
assert instance.department == "Engineering" # type: ignore[attr-defined]
|
||||
|
||||
dumped = instance.model_dump()
|
||||
assert dumped == {"id": "123", "name": "Alice", "department": "Engineering"}
|
||||
|
||||
|
||||
def test_nested_allof_merging(converter):
|
||||
def test_nested_allof_merging(converter: _JSONSchemaToPydantic) -> None:
|
||||
schema = {
|
||||
"title": "ContainerModel",
|
||||
"type": "object",
|
||||
|
@ -565,8 +610,8 @@ def test_nested_allof_merging(converter):
|
|||
Model = converter.json_schema_to_pydantic(schema, "ContainerModel")
|
||||
instance = Model(nested={"data": {"base_field": "abc", "extra": "xyz"}})
|
||||
|
||||
assert instance.nested.data.base_field == "abc"
|
||||
assert instance.nested.data.extra == "xyz"
|
||||
assert instance.nested.data.base_field == "abc" # type: ignore[attr-defined]
|
||||
assert instance.nested.data.extra == "xyz" # type: ignore[attr-defined]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -620,12 +665,15 @@ def test_nested_allof_merging(converter):
|
|||
),
|
||||
],
|
||||
)
|
||||
def test_field_constraints(schema, field_name, valid_values, invalid_values):
|
||||
def test_field_constraints(
|
||||
schema: Dict[str, Any],
|
||||
field_name: str,
|
||||
valid_values: List[Any],
|
||||
invalid_values: List[Any],
|
||||
) -> None:
|
||||
converter = _JSONSchemaToPydantic()
|
||||
Model = converter.json_schema_to_pydantic(schema, "ConstraintModel")
|
||||
|
||||
import json
|
||||
|
||||
for value in valid_values:
|
||||
instance = Model(**{field_name: value})
|
||||
assert getattr(instance, field_name) == value
|
||||
|
@ -650,7 +698,60 @@ def test_field_constraints(schema, field_name, valid_values, invalid_values):
|
|||
},
|
||||
],
|
||||
)
|
||||
def test_unknown_type_raises(schema):
|
||||
def test_unknown_type_raises(schema: Dict[str, Any]) -> None:
|
||||
converter = _JSONSchemaToPydantic()
|
||||
with pytest.raises(UnsupportedKeywordError):
|
||||
converter.json_schema_to_pydantic(schema, "UnknownTypeModel")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("json_type, expected_type", list(TYPE_MAPPING.items()))
|
||||
def test_basic_type_mapping(json_type: str, expected_type: type) -> None:
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {"field": {"type": json_type}},
|
||||
"required": ["field"],
|
||||
}
|
||||
converter = _JSONSchemaToPydantic()
|
||||
Model = converter.json_schema_to_pydantic(schema, f"{json_type.capitalize()}Model")
|
||||
|
||||
assert "field" in Model.__annotations__
|
||||
field_type = Model.__annotations__["field"]
|
||||
|
||||
# For array/object/null we check the outer type only
|
||||
if json_type == "null":
|
||||
assert field_type is type(None)
|
||||
elif json_type == "array":
|
||||
assert getattr(field_type, "__origin__", None) is list
|
||||
elif json_type == "object":
|
||||
assert field_type in (dict, Dict) or getattr(field_type, "__origin__", None) in (dict, Dict)
|
||||
|
||||
else:
|
||||
assert field_type == expected_type
|
||||
|
||||
|
||||
@pytest.mark.parametrize("format_name, expected_type", list(FORMAT_MAPPING.items()))
|
||||
def test_format_mapping(format_name: str, expected_type: Any) -> None:
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {"field": {"type": "string", "format": format_name}},
|
||||
"required": ["field"],
|
||||
}
|
||||
converter = _JSONSchemaToPydantic()
|
||||
Model = converter.json_schema_to_pydantic(schema, f"{format_name.capitalize()}Model")
|
||||
|
||||
assert "field" in Model.__annotations__
|
||||
field_type = Model.__annotations__["field"]
|
||||
if isinstance(expected_type, types.FunctionType): # if it's a constrained constructor (e.g., conint)
|
||||
assert callable(field_type)
|
||||
else:
|
||||
assert field_type == expected_type
|
||||
|
||||
|
||||
def test_unknown_format_raises() -> None:
|
||||
schema = {
|
||||
"type": "object",
|
||||
"properties": {"bad_field": {"type": "string", "format": "definitely-not-a-format"}},
|
||||
}
|
||||
converter = _JSONSchemaToPydantic()
|
||||
with pytest.raises(FormatNotSupportedError):
|
||||
converter.json_schema_to_pydantic(schema, "UnknownFormatModel")
|
||||
|
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||
|
||||
[project]
|
||||
name = "autogen-ext"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
license = {file = "LICENSE-CODE"}
|
||||
description = "AutoGen extensions library"
|
||||
readme = "README.md"
|
||||
|
@ -15,7 +15,7 @@ classifiers = [
|
|||
"Operating System :: OS Independent",
|
||||
]
|
||||
dependencies = [
|
||||
"autogen-core==0.5.1",
|
||||
"autogen-core==0.5.2",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
|
@ -31,7 +31,7 @@ docker = ["docker~=7.0", "asyncio_atexit>=1.0.1"]
|
|||
ollama = ["ollama>=0.4.7", "tiktoken>=0.8.0"]
|
||||
openai = ["openai>=1.66.5", "tiktoken>=0.8.0", "aiofiles"]
|
||||
file-surfer = [
|
||||
"autogen-agentchat==0.5.1",
|
||||
"autogen-agentchat==0.5.2",
|
||||
"magika>=0.6.1rc2",
|
||||
"markitdown[all]~=0.1.0a3",
|
||||
]
|
||||
|
@ -43,21 +43,21 @@ llama-cpp = [
|
|||
graphrag = ["graphrag>=1.0.1"]
|
||||
chromadb = ["chromadb>=1.0.0"]
|
||||
web-surfer = [
|
||||
"autogen-agentchat==0.5.1",
|
||||
"autogen-agentchat==0.5.2",
|
||||
"playwright>=1.48.0",
|
||||
"pillow>=11.0.0",
|
||||
"magika>=0.6.1rc2",
|
||||
"markitdown[all]~=0.1.0a3",
|
||||
]
|
||||
magentic-one = [
|
||||
"autogen-agentchat==0.5.1",
|
||||
"autogen-agentchat==0.5.2",
|
||||
"magika>=0.6.1rc2",
|
||||
"markitdown[all]~=0.1.0a3",
|
||||
"playwright>=1.48.0",
|
||||
"pillow>=11.0.0",
|
||||
]
|
||||
video-surfer = [
|
||||
"autogen-agentchat==0.5.1",
|
||||
"autogen-agentchat==0.5.2",
|
||||
"opencv-python>=4.5",
|
||||
"ffmpeg-python",
|
||||
"openai-whisper",
|
||||
|
@ -137,7 +137,7 @@ rich = ["rich>=13.9.4"]
|
|||
|
||||
mcp = [
|
||||
"mcp>=1.6.0",
|
||||
"json-schema-to-pydantic>=0.2.3"
|
||||
"json-schema-to-pydantic>=0.2.4"
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from .memory_controller import MemoryController, MemoryControllerConfig
|
||||
from ._memory_bank import MemoryBankConfig
|
||||
from .memory_controller import MemoryController, MemoryControllerConfig
|
||||
|
||||
__all__ = ["MemoryController", "MemoryControllerConfig", "MemoryBankConfig"]
|
||||
|
|
|
@ -32,9 +32,9 @@ dependencies = [
|
|||
"loguru",
|
||||
"pyyaml",
|
||||
"html2text",
|
||||
"autogen-core>=0.4.9.2,<0.5",
|
||||
"autogen-agentchat>=0.4.9.2,<0.5",
|
||||
"autogen-ext[magentic-one, openai, azure]>=0.4.2,<0.5",
|
||||
"autogen-core>=0.4.9.2,<0.6",
|
||||
"autogen-agentchat>=0.4.9.2,<0.6",
|
||||
"autogen-ext[magentic-one, openai, azure]>=0.4.2,<0.6",
|
||||
"anthropic",
|
||||
]
|
||||
optional-dependencies = {web = ["fastapi", "uvicorn"], database = ["psycopg"]}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
version = 1
|
||||
revision = 1
|
||||
requires-python = ">=3.10, <3.13"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.12.4' and sys_platform == 'darwin'",
|
||||
|
@ -90,7 +91,6 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "agbench"
|
||||
version = "0.0.1a1"
|
||||
source = { editable = "packages/agbench" }
|
||||
dependencies = [
|
||||
{ name = "azure-identity" },
|
||||
|
@ -452,7 +452,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "autogen-agentchat"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = { editable = "packages/autogen-agentchat" }
|
||||
dependencies = [
|
||||
{ name = "autogen-core" },
|
||||
|
@ -463,7 +463,7 @@ requires-dist = [{ name = "autogen-core", editable = "packages/autogen-core" }]
|
|||
|
||||
[[package]]
|
||||
name = "autogen-core"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = { editable = "packages/autogen-core" }
|
||||
dependencies = [
|
||||
{ name = "jsonref" },
|
||||
|
@ -582,7 +582,7 @@ dev = [
|
|||
|
||||
[[package]]
|
||||
name = "autogen-ext"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = { editable = "packages/autogen-ext" }
|
||||
dependencies = [
|
||||
{ name = "autogen-core" },
|
||||
|
@ -745,7 +745,7 @@ requires-dist = [
|
|||
{ name = "httpx", marker = "extra == 'http-tool'", specifier = ">=0.27.0" },
|
||||
{ name = "ipykernel", marker = "extra == 'jupyter-executor'", specifier = ">=6.29.5" },
|
||||
{ name = "json-schema-to-pydantic", marker = "extra == 'http-tool'", specifier = ">=0.2.0" },
|
||||
{ name = "json-schema-to-pydantic", marker = "extra == 'mcp'", specifier = ">=0.2.3" },
|
||||
{ name = "json-schema-to-pydantic", marker = "extra == 'mcp'", specifier = ">=0.2.4" },
|
||||
{ name = "langchain-core", marker = "extra == 'langchain'", specifier = "~=0.3.3" },
|
||||
{ name = "llama-cpp-python", marker = "extra == 'llama-cpp'", specifier = ">=0.3.8" },
|
||||
{ name = "magika", marker = "extra == 'file-surfer'", specifier = ">=0.6.1rc2" },
|
||||
|
@ -780,6 +780,7 @@ requires-dist = [
|
|||
{ name = "tiktoken", marker = "extra == 'ollama'", specifier = ">=0.8.0" },
|
||||
{ name = "tiktoken", marker = "extra == 'openai'", specifier = ">=0.8.0" },
|
||||
]
|
||||
provides-extras = ["anthropic", "langchain", "azure", "docker", "ollama", "openai", "file-surfer", "llama-cpp", "graphrag", "chromadb", "web-surfer", "magentic-one", "video-surfer", "diskcache", "redis", "grpc", "jupyter-executor", "task-centric-memory", "semantic-kernel-core", "gemini", "semantic-kernel-google", "semantic-kernel-hugging-face", "semantic-kernel-mistralai", "semantic-kernel-ollama", "semantic-kernel-onnx", "semantic-kernel-anthropic", "semantic-kernel-pandas", "semantic-kernel-aws", "semantic-kernel-dapr", "http-tool", "semantic-kernel-all", "rich", "mcp"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
|
@ -808,7 +809,6 @@ requires-dist = [
|
|||
|
||||
[[package]]
|
||||
name = "autogenstudio"
|
||||
version = "0.4.2"
|
||||
source = { editable = "packages/autogen-studio" }
|
||||
dependencies = [
|
||||
{ name = "aiofiles" },
|
||||
|
@ -862,6 +862,7 @@ requires-dist = [
|
|||
{ name = "uvicorn", marker = "extra == 'web'" },
|
||||
{ name = "websockets" },
|
||||
]
|
||||
provides-extras = ["web", "database"]
|
||||
|
||||
[[package]]
|
||||
name = "autograd"
|
||||
|
@ -3044,14 +3045,14 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "json-schema-to-pydantic"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/8d/da0e791baf63a957ff67e0706d59386b72ab87858e616b6fcfc9b58cd910/json_schema_to_pydantic-0.2.3.tar.gz", hash = "sha256:c76db1f6001996895328e7aa174aae201d85d1f5e79d592c272ea03c8586e453", size = 35305 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0e/5a/82ce52917b4b021e739dc02384bb3257b5ddd04e40211eacdc32c88bdda5/json_schema_to_pydantic-0.2.4.tar.gz", hash = "sha256:c24060aa7694ae7be0465ce11339a6d1cc8a72cd8f4378c889d19722fa7da1ee", size = 37816 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4a/55/81bbfbc806aab8dc4a21ad1c9c7fd61f94f2b4076ea64f1730a0368831a2/json_schema_to_pydantic-0.2.3-py3-none-any.whl", hash = "sha256:fe0c04357aa8d27ad5a46e54c2d6a8f35ca6c10b36e76a95c39827e38397f427", size = 11699 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2e/86/35135e8e4b1da50e6e8ed2afcacce589e576f3460c892d5e616390a4eb71/json_schema_to_pydantic-0.2.4-py3-none-any.whl", hash = "sha256:5c46675df0ab2685d92ed805da38348a34488654cb95ceb1a564dda23dcc3a89", size = 11940 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4690,7 +4691,6 @@ name = "nvidia-cublas-cu12"
|
|||
version = "12.4.5.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/7f/7fbae15a3982dc9595e49ce0f19332423b260045d0a6afe93cdbe2f1f624/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0f8aa1706812e00b9f19dfe0cdb3999b092ccb8ca168c0db5b8ea712456fd9b3", size = 363333771 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ae/71/1c91302526c45ab494c23f61c7a84aa568b8c1f9d196efa5993957faf906/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b", size = 363438805 },
|
||||
]
|
||||
|
||||
|
@ -4699,7 +4699,6 @@ name = "nvidia-cuda-cupti-cu12"
|
|||
version = "12.4.127"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/93/b5/9fb3d00386d3361b03874246190dfec7b206fd74e6e287b26a8fcb359d95/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:79279b35cf6f91da114182a5ce1864997fd52294a87a16179ce275773799458a", size = 12354556 },
|
||||
{ url = "https://files.pythonhosted.org/packages/67/42/f4f60238e8194a3106d06a058d494b18e006c10bb2b915655bd9f6ea4cb1/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb", size = 13813957 },
|
||||
]
|
||||
|
||||
|
@ -4708,7 +4707,6 @@ name = "nvidia-cuda-nvrtc-cu12"
|
|||
version = "12.4.127"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/77/aa/083b01c427e963ad0b314040565ea396f914349914c298556484f799e61b/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0eedf14185e04b76aa05b1fea04133e59f465b6f960c0cbf4e37c3cb6b0ea198", size = 24133372 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2c/14/91ae57cd4db3f9ef7aa99f4019cfa8d54cb4caa7e00975df6467e9725a9f/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338", size = 24640306 },
|
||||
]
|
||||
|
||||
|
@ -4717,7 +4715,6 @@ name = "nvidia-cuda-runtime-cu12"
|
|||
version = "12.4.127"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/aa/b656d755f474e2084971e9a297def515938d56b466ab39624012070cb773/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:961fe0e2e716a2a1d967aab7caee97512f71767f852f67432d572e36cb3a11f3", size = 894177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ea/27/1795d86fe88ef397885f2e580ac37628ed058a92ed2c39dc8eac3adf0619/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5", size = 883737 },
|
||||
]
|
||||
|
||||
|
@ -4740,7 +4737,6 @@ dependencies = [
|
|||
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/8a/0e728f749baca3fbeffad762738276e5df60851958be7783af121a7221e7/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:5dad8008fc7f92f5ddfa2101430917ce2ffacd86824914c82e28990ad7f00399", size = 211422548 },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117 },
|
||||
]
|
||||
|
||||
|
@ -4749,7 +4745,6 @@ name = "nvidia-curand-cu12"
|
|||
version = "10.3.5.147"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/80/9c/a79180e4d70995fdf030c6946991d0171555c6edf95c265c6b2bf7011112/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1f173f09e3e3c76ab084aba0de819c49e56614feae5c12f69883f4ae9bb5fad9", size = 56314811 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b", size = 56305206 },
|
||||
]
|
||||
|
||||
|
@ -4763,7 +4758,6 @@ dependencies = [
|
|||
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/6b/a5c33cf16af09166845345275c34ad2190944bcc6026797a39f8e0a282e0/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:d338f155f174f90724bbde3758b7ac375a70ce8e706d70b018dd3375545fc84e", size = 127634111 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057 },
|
||||
]
|
||||
|
||||
|
@ -4775,7 +4769,6 @@ dependencies = [
|
|||
{ name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux')" },
|
||||
]
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/96/a9/c0d2f83a53d40a4a41be14cea6a0bf9e668ffcf8b004bd65633f433050c0/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9d32f62896231ebe0480efd8a7f702e143c98cfaa0e8a76df3386c1ba2b54df3", size = 207381987 },
|
||||
{ url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763 },
|
||||
]
|
||||
|
||||
|
@ -4792,7 +4785,6 @@ name = "nvidia-nvjitlink-cu12"
|
|||
version = "12.4.127"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/02/45/239d52c05074898a80a900f49b1615d81c07fceadd5ad6c4f86a987c0bc4/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:4abe7fef64914ccfa909bc2ba39739670ecc9e820c83ccc7a6ed414122599b83", size = 20552510 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ff/ff/847841bacfbefc97a00036e0fce5a0f086b640756dc38caea5e1bb002655/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57", size = 21066810 },
|
||||
]
|
||||
|
||||
|
@ -4801,7 +4793,6 @@ name = "nvidia-nvtx-cu12"
|
|||
version = "12.4.127"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/06/39/471f581edbb7804b39e8063d92fc8305bdc7a80ae5c07dbe6ea5c50d14a5/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7959ad635db13edf4fc65c06a6e9f9e55fc2f92596db928d169c0bb031e88ef3", size = 100417 },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144 },
|
||||
]
|
||||
|
||||
|
|
Loading…
Reference in New Issue