diff --git a/python/packages/autogen-core/src/autogen_core/_runtime_impl_helpers.py b/python/packages/autogen-core/src/autogen_core/_runtime_impl_helpers.py index 3c025f0e1..31bfc0412 100644 --- a/python/packages/autogen-core/src/autogen_core/_runtime_impl_helpers.py +++ b/python/packages/autogen-core/src/autogen_core/_runtime_impl_helpers.py @@ -1,5 +1,5 @@ from collections import defaultdict -from typing import Awaitable, Callable, DefaultDict, List, Set, Sequence +from typing import Awaitable, Callable, DefaultDict, List, Sequence, Set from ._agent import Agent from ._agent_id import AgentId diff --git a/python/packages/autogen-studio/autogenstudio/gallery/builder.py b/python/packages/autogen-studio/autogenstudio/gallery/builder.py index 6582481ae..9c2b8c9d0 100644 --- a/python/packages/autogen-studio/autogenstudio/gallery/builder.py +++ b/python/packages/autogen-studio/autogenstudio/gallery/builder.py @@ -183,6 +183,7 @@ def create_default_gallery() -> Gallery: model_client=base_model, tools=[tools.calculator_tool], ) + builder.add_agent( calc_assistant.dump_component(), description="An agent that provides assistance with ability to use tools." ) @@ -200,10 +201,25 @@ def create_default_gallery() -> Gallery: calc_team = RoundRobinGroupChat(participants=[calc_assistant], termination_condition=calc_or_term) builder.add_team( calc_team.dump_component(), - label="Default Team", + label="RoundRobin Team", description="A single AssistantAgent (with a calculator tool) in a RoundRobinGroupChat team. ", ) + critic_agent = AssistantAgent( + name="critic_agent", + system_message="You are a helpful assistant. Critique the assistant's output and suggest improvements.", + description="an agent that critiques and improves the assistant's output", + model_client=base_model, + ) + selector_default_team = SelectorGroupChat( + participants=[calc_assistant, critic_agent], termination_condition=calc_or_term, model_client=base_model + ) + builder.add_team( + selector_default_team.dump_component(), + label="Selector Team", + description="A team with 2 agents - an AssistantAgent (with a calculator tool) and a CriticAgent in a SelectorGroupChat team.", + ) + # Create web surfer agent websurfer_agent = MultimodalWebSurfer( name="websurfer_agent", diff --git a/python/packages/autogen-studio/autogenstudio/web/app.py b/python/packages/autogen-studio/autogenstudio/web/app.py index a9fbb0fcc..4deccdd90 100644 --- a/python/packages/autogen-studio/autogenstudio/web/app.py +++ b/python/packages/autogen-studio/autogenstudio/web/app.py @@ -13,7 +13,7 @@ from ..version import VERSION from .config import settings from .deps import cleanup_managers, init_managers from .initialization import AppInitializer -from .routes import runs, sessions, teams, ws +from .routes import runs, sessions, teams, validation, ws # Initialize application app_file_path = os.path.dirname(os.path.abspath(__file__)) @@ -107,6 +107,12 @@ api.include_router( responses={404: {"description": "Not found"}}, ) +api.include_router( + validation.router, + prefix="/validate", + tags=["validation"], + responses={404: {"description": "Not found"}}, +) # Version endpoint diff --git a/python/packages/autogen-studio/autogenstudio/web/routes/validation.py b/python/packages/autogen-studio/autogenstudio/web/routes/validation.py new file mode 100644 index 000000000..d03266eac --- /dev/null +++ b/python/packages/autogen-studio/autogenstudio/web/routes/validation.py @@ -0,0 +1,174 @@ +# api/routes/validation.py +import importlib +from typing import Any, Dict, List, Optional + +from autogen_core import ComponentModel, is_component_class +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel + +router = APIRouter() + + +class ValidationRequest(BaseModel): + component: Dict[str, Any] + + +class ValidationError(BaseModel): + field: str + error: str + suggestion: Optional[str] = None + + +class ValidationResponse(BaseModel): + is_valid: bool + errors: List[ValidationError] = [] + warnings: List[ValidationError] = [] + + +class ValidationService: + @staticmethod + def validate_provider(provider: str) -> Optional[ValidationError]: + """Validate that the provider exists and can be imported""" + try: + if provider in ["azure_openai_chat_completion_client", "AzureOpenAIChatCompletionClient"]: + provider = "autogen_ext.models.openai.AzureOpenAIChatCompletionClient" + elif provider in ["openai_chat_completion_client", "OpenAIChatCompletionClient"]: + provider = "autogen_ext.models.openai.OpenAIChatCompletionClient" + + module_path, class_name = provider.rsplit(".", maxsplit=1) + module = importlib.import_module(module_path) + component_class = getattr(module, class_name) + + if not is_component_class(component_class): + return ValidationError( + field="provider", + error=f"Class {provider} is not a valid component class", + suggestion="Ensure the class inherits from Component and implements required methods", + ) + return None + except ImportError: + return ValidationError( + field="provider", + error=f"Could not import provider {provider}", + suggestion="Check that the provider module is installed and the path is correct", + ) + except Exception as e: + return ValidationError( + field="provider", + error=f"Error validating provider: {str(e)}", + suggestion="Check the provider string format and class implementation", + ) + + @staticmethod + def validate_component_type(component: Dict[str, Any]) -> Optional[ValidationError]: + """Validate the component type""" + if "component_type" not in component: + return ValidationError( + field="component_type", + error="Component type is missing", + suggestion="Add a component_type field to the component configuration", + ) + return None + + @staticmethod + def validate_config_schema(component: Dict[str, Any]) -> List[ValidationError]: + """Validate the component configuration against its schema""" + errors = [] + try: + # Convert to ComponentModel for initial validation + model = ComponentModel(**component) + + # Get the component class + provider = model.provider + module_path, class_name = provider.rsplit(".", maxsplit=1) + module = importlib.import_module(module_path) + component_class = getattr(module, class_name) + + # Validate against component's schema + if hasattr(component_class, "component_config_schema"): + try: + component_class.component_config_schema.model_validate(model.config) + except Exception as e: + errors.append( + ValidationError( + field="config", + error=f"Config validation failed: {str(e)}", + suggestion="Check that the config matches the component's schema", + ) + ) + else: + errors.append( + ValidationError( + field="config", + error="Component class missing config schema", + suggestion="Implement component_config_schema in the component class", + ) + ) + except Exception as e: + errors.append( + ValidationError( + field="config", + error=f"Schema validation error: {str(e)}", + suggestion="Check the component configuration format", + ) + ) + return errors + + @staticmethod + def validate_instantiation(component: Dict[str, Any]) -> Optional[ValidationError]: + """Validate that the component can be instantiated""" + try: + model = ComponentModel(**component) + # Attempt to load the component + module_path, class_name = model.provider.rsplit(".", maxsplit=1) + module = importlib.import_module(module_path) + component_class = getattr(module, class_name) + component_class.load_component(model) + return None + except Exception as e: + return ValidationError( + field="instantiation", + error=f"Failed to instantiate component: {str(e)}", + suggestion="Check that the component can be properly instantiated with the given config", + ) + + @classmethod + def validate(cls, component: Dict[str, Any]) -> ValidationResponse: + """Validate a component configuration""" + errors = [] + warnings = [] + + # Check provider + if provider_error := cls.validate_provider(component.get("provider", "")): + errors.append(provider_error) + + # Check component type + if type_error := cls.validate_component_type(component): + errors.append(type_error) + + # Validate schema + schema_errors = cls.validate_config_schema(component) + errors.extend(schema_errors) + + # Only attempt instantiation if no errors so far + if not errors: + if inst_error := cls.validate_instantiation(component): + errors.append(inst_error) + + # Check for version warnings + if "version" not in component: + warnings.append( + ValidationError( + field="version", + error="Component version not specified", + suggestion="Consider adding a version to ensure compatibility", + ) + ) + + return ValidationResponse(is_valid=len(errors) == 0, errors=errors, warnings=warnings) + + +@router.post("/") +async def validate_component(request: ValidationRequest) -> ValidationResponse: + """Validate a component configuration""" + return ValidationService.validate(request.component) diff --git a/python/packages/autogen-studio/frontend/src/components/views/atoms.tsx b/python/packages/autogen-studio/frontend/src/components/views/atoms.tsx index c6e81476f..ba98d17ee 100644 --- a/python/packages/autogen-studio/frontend/src/components/views/atoms.tsx +++ b/python/packages/autogen-studio/frontend/src/components/views/atoms.tsx @@ -59,7 +59,7 @@ export const LoadingDots = ({ size = 8 }) => { export const TruncatableText = memo( ({ - content, + content = "", isJson = false, className = "", jsonThreshold = 1000, diff --git a/python/packages/autogen-studio/frontend/src/components/views/gallery/default_gallery.json b/python/packages/autogen-studio/frontend/src/components/views/gallery/default_gallery.json index e95142b52..ac2a1dbbe 100644 --- a/python/packages/autogen-studio/frontend/src/components/views/gallery/default_gallery.json +++ b/python/packages/autogen-studio/frontend/src/components/views/gallery/default_gallery.json @@ -4,8 +4,8 @@ "url": null, "metadata": { "author": "AutoGen Team", - "created_at": "2025-02-09T09:43:30.164372", - "updated_at": "2025-02-09T09:43:30.486369", + "created_at": "2025-02-11T18:37:53.922275", + "updated_at": "2025-02-11T18:37:54.268540", "version": "1.0.0", "description": "A default gallery containing basic components for human-in-loop conversations", "tags": ["human-in-loop", "assistant", "web agents"], @@ -22,7 +22,7 @@ "version": 1, "component_version": 1, "description": "A single AssistantAgent (with a calculator tool) in a RoundRobinGroupChat team. ", - "label": "Default Team", + "label": "RoundRobin Team", "config": { "participants": [ { @@ -116,6 +116,158 @@ } } }, + { + "provider": "autogen_agentchat.teams.SelectorGroupChat", + "component_type": "team", + "version": 1, + "component_version": 1, + "description": "A team with 2 agents - an AssistantAgent (with a calculator tool) and a CriticAgent in a SelectorGroupChat team.", + "label": "Selector Team", + "config": { + "participants": [ + { + "provider": "autogen_agentchat.agents.AssistantAgent", + "component_type": "agent", + "version": 1, + "component_version": 1, + "description": "An agent that provides assistance with tool use.", + "label": "AssistantAgent", + "config": { + "name": "assistant_agent", + "model_client": { + "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", + "component_type": "model", + "version": 1, + "component_version": 1, + "description": "Chat completion client for OpenAI hosted models.", + "label": "OpenAIChatCompletionClient", + "config": { + "model": "gpt-4o-mini" + } + }, + "tools": [ + { + "provider": "autogen_core.tools.FunctionTool", + "component_type": "tool", + "version": 1, + "component_version": 1, + "description": "Create custom tools by wrapping standard Python functions.", + "label": "FunctionTool", + "config": { + "source_code": "def calculator(a: float, b: float, operator: str) -> str:\n try:\n if operator == \"+\":\n return str(a + b)\n elif operator == \"-\":\n return str(a - b)\n elif operator == \"*\":\n return str(a * b)\n elif operator == \"/\":\n if b == 0:\n return \"Error: Division by zero\"\n return str(a / b)\n else:\n return \"Error: Invalid operator. Please use +, -, *, or /\"\n except Exception as e:\n return f\"Error: {str(e)}\"\n", + "name": "calculator", + "description": "A simple calculator that performs basic arithmetic operations", + "global_imports": [], + "has_cancellation_support": false + } + } + ], + "handoffs": [], + "model_context": { + "provider": "autogen_core.model_context.UnboundedChatCompletionContext", + "component_type": "chat_completion_context", + "version": 1, + "component_version": 1, + "description": "An unbounded chat completion context that keeps a view of the all the messages.", + "label": "UnboundedChatCompletionContext", + "config": {} + }, + "description": "An agent that provides assistance with ability to use tools.", + "system_message": "You are a helpful assistant. Solve tasks carefully. When done, say TERMINATE.", + "model_client_stream": false, + "reflect_on_tool_use": false, + "tool_call_summary_format": "{result}" + } + }, + { + "provider": "autogen_agentchat.agents.AssistantAgent", + "component_type": "agent", + "version": 1, + "component_version": 1, + "description": "An agent that provides assistance with tool use.", + "label": "AssistantAgent", + "config": { + "name": "critic_agent", + "model_client": { + "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", + "component_type": "model", + "version": 1, + "component_version": 1, + "description": "Chat completion client for OpenAI hosted models.", + "label": "OpenAIChatCompletionClient", + "config": { + "model": "gpt-4o-mini" + } + }, + "tools": [], + "handoffs": [], + "model_context": { + "provider": "autogen_core.model_context.UnboundedChatCompletionContext", + "component_type": "chat_completion_context", + "version": 1, + "component_version": 1, + "description": "An unbounded chat completion context that keeps a view of the all the messages.", + "label": "UnboundedChatCompletionContext", + "config": {} + }, + "description": "an agent that critiques and improves the assistant's output", + "system_message": "You are a helpful assistant. Critique the assistant's output and suggest improvements.", + "model_client_stream": false, + "reflect_on_tool_use": false, + "tool_call_summary_format": "{result}" + } + } + ], + "model_client": { + "provider": "autogen_ext.models.openai.OpenAIChatCompletionClient", + "component_type": "model", + "version": 1, + "component_version": 1, + "description": "Chat completion client for OpenAI hosted models.", + "label": "OpenAIChatCompletionClient", + "config": { + "model": "gpt-4o-mini" + } + }, + "termination_condition": { + "provider": "autogen_agentchat.base.OrTerminationCondition", + "component_type": "termination", + "version": 1, + "component_version": 1, + "label": "OrTerminationCondition", + "config": { + "conditions": [ + { + "provider": "autogen_agentchat.conditions.TextMentionTermination", + "component_type": "termination", + "version": 1, + "component_version": 1, + "description": "Terminate the conversation if a specific text is mentioned.", + "label": "TextMentionTermination", + "config": { + "text": "TERMINATE" + } + }, + { + "provider": "autogen_agentchat.conditions.MaxMessageTermination", + "component_type": "termination", + "version": 1, + "component_version": 1, + "description": "Terminate the conversation after a maximum number of messages have been exchanged.", + "label": "MaxMessageTermination", + "config": { + "max_messages": 10, + "include_agent_event": false + } + } + ] + } + }, + "selector_prompt": "You are in a role play game. The following roles are available:\n{roles}.\nRead the following conversation. Then select the next role from {participants} to play. Only return the role.\n\n{history}\n\nRead the above conversation. Then select the next role from {participants} to play. Only return the role.\n", + "allow_repeated_speaker": false, + "max_selector_attempts": 3 + } + }, { "provider": "autogen_agentchat.teams.SelectorGroupChat", "component_type": "team", diff --git a/python/packages/autogen-studio/frontend/src/components/views/gallery/store.tsx b/python/packages/autogen-studio/frontend/src/components/views/gallery/store.tsx index 6e35f26a6..604d4e57e 100644 --- a/python/packages/autogen-studio/frontend/src/components/views/gallery/store.tsx +++ b/python/packages/autogen-studio/frontend/src/components/views/gallery/store.tsx @@ -151,7 +151,7 @@ export const useGalleryStore = create()( }, }), { - name: "gallery-storage-v6", + name: "gallery-storage-v7", } ) ); diff --git a/python/packages/autogen-studio/frontend/src/components/views/playground/chat/rendermessage.tsx b/python/packages/autogen-studio/frontend/src/components/views/playground/chat/rendermessage.tsx index ec1eedbaa..2041a1a1a 100644 --- a/python/packages/autogen-studio/frontend/src/components/views/playground/chat/rendermessage.tsx +++ b/python/packages/autogen-studio/frontend/src/components/views/playground/chat/rendermessage.tsx @@ -63,7 +63,7 @@ const RenderToolCall: React.FC<{ content: FunctionCall[] }> = ({ content }) => ( Calling {call.name} tool with arguments diff --git a/python/packages/autogen-studio/frontend/src/components/views/playground/chat/runview.tsx b/python/packages/autogen-studio/frontend/src/components/views/playground/chat/runview.tsx index 658fbd74e..96ffcefeb 100644 --- a/python/packages/autogen-studio/frontend/src/components/views/playground/chat/runview.tsx +++ b/python/packages/autogen-studio/frontend/src/components/views/playground/chat/runview.tsx @@ -60,7 +60,6 @@ const RunView: React.FC = ({ const showLLMEvents = useSettingsStore( (state) => state.playground.showLLMEvents ); - console.log("showLLMEvents", showLLMEvents); const visibleMessages = useMemo(() => { if (showLLMEvents) { diff --git a/python/packages/autogen-studio/frontend/src/components/views/team/api.ts b/python/packages/autogen-studio/frontend/src/components/views/team/api.ts index 8a29e874b..65ff98b28 100644 --- a/python/packages/autogen-studio/frontend/src/components/views/team/api.ts +++ b/python/packages/autogen-studio/frontend/src/components/views/team/api.ts @@ -1,6 +1,18 @@ -import { Team, AgentConfig } from "../../types/datamodel"; +import { Team, Component, ComponentConfig } from "../../types/datamodel"; import { getServerUrl } from "../../utils"; +interface ValidationError { + field: string; + error: string; + suggestion?: string; +} + +export interface ValidationResponse { + is_valid: boolean; + errors: ValidationError[]; + warnings: ValidationError[]; +} + export class TeamAPI { private getBaseUrl(): string { return getServerUrl(); @@ -77,51 +89,41 @@ export class TeamAPI { if (!data.status) throw new Error(data.message || "Failed to link agent to team"); } +} - async linkAgentWithSequence( - teamId: number, - agentId: number, - sequenceId: number - ): Promise { - const response = await fetch( - `${this.getBaseUrl()}/teams/${teamId}/agents/${agentId}/${sequenceId}`, - { - method: "POST", - headers: this.getHeaders(), - } - ); - const data = await response.json(); - if (!data.status) - throw new Error( - data.message || "Failed to link agent to team with sequence" - ); +// move validationapi to its own class + +export class ValidationAPI { + private getBaseUrl(): string { + return getServerUrl(); } - async unlinkAgent(teamId: number, agentId: number): Promise { - const response = await fetch( - `${this.getBaseUrl()}/teams/${teamId}/agents/${agentId}`, - { - method: "DELETE", - headers: this.getHeaders(), - } - ); - const data = await response.json(); - if (!data.status) - throw new Error(data.message || "Failed to unlink agent from team"); + private getHeaders(): HeadersInit { + return { + "Content-Type": "application/json", + }; } - async getTeamAgents(teamId: number): Promise { - const response = await fetch( - `${this.getBaseUrl()}/teams/${teamId}/agents`, - { - headers: this.getHeaders(), - } - ); + async validateComponent( + component: Component + ): Promise { + const response = await fetch(`${this.getBaseUrl()}/validate`, { + method: "POST", + headers: this.getHeaders(), + body: JSON.stringify({ + component: component, + }), + }); + const data = await response.json(); - if (!data.status) - throw new Error(data.message || "Failed to fetch team agents"); - return data.data; + if (!response.ok) { + throw new Error(data.message || "Failed to validate component"); + } + + return data; } } +export const validationAPI = new ValidationAPI(); + export const teamAPI = new TeamAPI(); diff --git a/python/packages/autogen-studio/frontend/src/components/views/team/builder/builder.tsx b/python/packages/autogen-studio/frontend/src/components/views/team/builder/builder.tsx index bbaf1bbef..62f8c27ca 100644 --- a/python/packages/autogen-studio/frontend/src/components/views/team/builder/builder.tsx +++ b/python/packages/autogen-studio/frontend/src/components/views/team/builder/builder.tsx @@ -27,7 +27,16 @@ import { } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; import { Button, Layout, message, Modal, Switch, Tooltip } from "antd"; -import { Cable, Code2, Download, PlayCircle, Save } from "lucide-react"; +import { + Cable, + CheckCircle, + CircleX, + Code2, + Download, + ListCheck, + PlayCircle, + Save, +} from "lucide-react"; import { useTeamBuilderStore } from "./store"; import { ComponentLibrary } from "./library"; import { ComponentTypes, Team, Session } from "../../../types/datamodel"; @@ -43,6 +52,9 @@ import debounce from "lodash.debounce"; import { appContext } from "../../../../hooks/provider"; import { sessionAPI } from "../../playground/api"; import TestDrawer from "./testdrawer"; +import { teamAPI, validationAPI, ValidationResponse } from "../api"; +import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; +import { ValidationErrors } from "./validationerrors"; const { Sider, Content } = Layout; interface DragItemData { @@ -76,6 +88,10 @@ export const TeamBuilder: React.FC = ({ const [activeDragItem, setActiveDragItem] = useState( null ); + const [validationResults, setValidationResults] = + useState(null); + + const [validationLoading, setValidationLoading] = useState(false); const [testDrawerVisible, setTestDrawerVisible] = useState(false); @@ -145,6 +161,12 @@ export const TeamBuilder: React.FC = ({ setNodes(initialNodes); setEdges(initialEdges); } + handleValidate(); + + return () => { + console.log("cleanup component"); + setValidationResults(null); + }; }, [team, setNodes, setEdges]); // Handle JSON changes @@ -167,9 +189,32 @@ export const TeamBuilder: React.FC = ({ useEffect(() => { return () => { handleJsonChange.cancel(); + setValidationResults(null); }; }, [handleJsonChange]); + const handleValidate = useCallback(async () => { + const component = syncToJson(); + if (!component) { + throw new Error("Unable to generate valid configuration"); + } + + try { + setValidationLoading(true); + const validationResult = await validationAPI.validateComponent(component); + + setValidationResults(validationResult); + // if (validationResult.is_valid) { + // messageApi.success("Validation successful"); + // } + } catch (error) { + console.error("Validation error:", error); + messageApi.error("Validation failed"); + } finally { + setValidationLoading(false); + } + }, [syncToJson]); + // Handle save const handleSave = useCallback(async () => { try { @@ -291,6 +336,8 @@ export const TeamBuilder: React.FC = ({ setTestDrawerVisible(false); }; + const teamValidated = validationResults && validationResults.is_valid; + const onDragStart = (item: DragItem) => { // We can add any drag start logic here if needed }; @@ -303,6 +350,7 @@ export const TeamBuilder: React.FC = ({ return (
{contextHolder} +
= ({
/> - {isJsonMode ? ( - "JSON " - ) : ( - <> - Visual builder{" "} - {/* - {" "} - experimental{" "} - */} - - )}{" "} - mode{" "} - - {" "} - (experimental) - + {isJsonMode ? "View JSON" : <>Visual Builder}{" "}
+
- - - + {validationResults && !validationResults.is_valid && ( +
+ {" "} + +
+ )}
+ > +
+ } + className="p-1.5 hover:bg-primary/10 rounded-md text-primary/75 hover:text-primary disabled:opacity-50 disabled:cursor-not-allowed" + onClick={handleValidate} + /> + + + + + >((props) => { /> */}
- {component.config.model_client && ( + {component.config?.model_client && (
- {component.config.model_client.config.model} + {component.config?.model_client.config?.model}
)} void; +} + +const ValidationErrorView: React.FC = ({ + validation, + onClose, +}) => ( +
+
e.stopPropagation()} + > + + + + +
+
+ +

Validation Issues

+

+ {validation.errors.length} errors • {validation.warnings.length}{" "} + warnings +

+
+ + {/* Errors Section */} + {validation.errors.length > 0 && ( +
+

Errors

+ {validation.errors.map((error, idx) => ( +
+
+ +
+
+ {error.field} +
+
{error.error}
+ {error.suggestion && ( +
+ Suggestion: {error.suggestion} +
+ )} +
+
+
+ ))} +
+ )} + + {/* Warnings Section */} + {validation.warnings.length > 0 && ( +
+

Warnings

+ {validation.warnings.map((warning, idx) => ( +
+
+ +
+
+ {warning.field} +
+
{warning.error}
+ {warning.suggestion && ( +
+ Suggestion: {warning.suggestion} +
+ )} +
+
+
+ ))} +
+ )} +
+
+
+); + +interface ValidationErrorsProps { + validation: ValidationResponse; +} + +export const ValidationErrors: React.FC = ({ + validation, +}) => { + const [showFullView, setShowFullView] = React.useState(false); + + return ( + <> +
setShowFullView(true)} + > + + + {validation.errors.length} errors • {validation.warnings.length}{" "} + warnings + + +
+ + {showFullView && ( + setShowFullView(false)} + /> + )} + + ); +}; diff --git a/python/packages/autogen-test-utils/src/autogen_test_utils/__init__.py b/python/packages/autogen-test-utils/src/autogen_test_utils/__init__.py index 0dfb1ece4..d3e5af883 100644 --- a/python/packages/autogen-test-utils/src/autogen_test_utils/__init__.py +++ b/python/packages/autogen-test-utils/src/autogen_test_utils/__init__.py @@ -1,5 +1,6 @@ from __future__ import annotations +from asyncio import Event from dataclasses import dataclass from typing import Any @@ -16,8 +17,6 @@ from autogen_core import ( ) from pydantic import BaseModel -from asyncio import Event - @dataclass class MessageType: ...