Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AgentChat component serialization #4439

Open
Tracked by #4006
victordibia opened this issue Dec 1, 2024 · 0 comments
Open
Tracked by #4006

AgentChat component serialization #4439

victordibia opened this issue Dec 1, 2024 · 0 comments
Labels
needs-design A design needs to be created and agreed upo proj-agentchat
Milestone

Comments

@victordibia
Copy link
Collaborator

victordibia commented Dec 1, 2024

What

Currently components in AgentChat dont have a built-in serialization story.
This issue is meant to help discuss and architect a setup where all components can be easily serialized to some declarative format and runtime object instantiated.

model_client = OpenAIClient(...)
model_client_spec = model_client.to_dict()

agent_a = AssistantAgent(...)
agent_a_spec = agent_a.to_dict()  # agent_a.to_yaml() ... 

# should be possible to instantiate a new instance of the component from spec
new_agent_a = AssistantAgent.from_dict(agent_a_spec)


team_a = RoundRobinGroupChat(participants=[agent_b], mode)
team_a_spec = team_a.to_dict()
team_a_spec_yaml = team_a.to_yaml('configs/team_a.yaml') 

termination = Termination(...)
termination_spec = 

How

  • Each top level concept (for lack of a better word) in AgentChat should be treated like a “component” (AGS has the idea of a component factory) - Model, Tool , Agent, Team , Memory , ….
  • Each component should implement some serialization methods
    • .to_dict method that specifies how it should be serialized to some declarative specification. .. returns a dict that can be json serialized.
      • Also implements a to_json and and to_yaml that uses to_dict underneath
    • .from_dict method that can take the declarative spec above and instantiate a new version of the class

Rough sketch:

from typing import Dict, Any, List, Optional
from dataclasses import dataclass

class RoundRobinGroupChat(BaseGroupChat):
    def to_dict(self) -> Dict[str, Any]:
        """Convert team to dictionary spec."""
        return {
            "provider": "autogen_agentchat.teams.RoundRobinGroupChat",
            "component_type": "team",
            "version": "1.0.0",
            "description": "A round-robin group chat implementation where participants take turns in sequence",
            "config": {
                "participants": [p.to_dict() for p in self._participants],
                "termination_condition": self._termination_condition.to_dict() if self._termination_condition else None,
                "max_turns": self._max_turns
            }
        }

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'RoundRobinGroupChat':
        """Create team from dictionary spec."""
        # Validate component type
        if data.get("component_type") != "team":
            raise ValueError(f"Invalid component_type. Expected 'team', got '{data.get('component_type')}'")
            
        # Validate provider
        expected_provider = "autogen_agentchat.teams.RoundRobinGroupChat"
        if data.get("provider") != expected_provider:
            raise ValueError(f"Invalid provider. Expected '{expected_provider}', got '{data.get('provider')}'")

        # Get configuration
        config = data.get("config", {})
        if not config:
            raise ValueError("Missing required 'config' field")

        # Convert nested components
        participants = [ChatAgent.from_dict(p) for p in config.get("participants", [])]
        termination = (TerminationCondition.from_dict(config["termination_condition"]) 
                      if config.get("termination_condition") else None)
        
        return cls(
            participants=participants,
            termination_condition=termination,
            max_turns=config.get("max_turns")
        )

    def to_yaml(self, file_path: str = None) -> Optional[str]:
        """Convert to YAML and optionally save to file."""
        import yaml
        yaml_str = yaml.dump(self.to_dict(), sort_keys=False)
        
        if file_path:
            with open(file_path, 'w') as f:
                f.write(yaml_str)
            return None
            
        return yaml_str

    @classmethod
    def from_yaml(cls, yaml_str: str) -> 'RoundRobinGroupChat':
        """Create instance from YAML string."""
        import yaml
        data = yaml.safe_load(yaml_str)
        return cls.from_dict(data)

What Fields Should the Spec Have?

some ideas for required fields inclide

  • provider: The class used to instantiate the runtime object e.g., autogen_agentchat.teams.RoundRobinGroupChat
  • component_type: shorthand for some high-level/semantic type of the component e.g., "model", team, agent, termination etc.
  • version: useful for compatibility across changes
  • description: optional field to describe what this specific instance of the object is. Extremely useful to consumers during sharing/reuse.
  • config: the actual data structure to be used ...

Benefits

  • Native serialization of component specs
  • Consistency across all applications using the component spec
  • Responsibility for correctly instantiating and serializing a component is solely held by the developer of the component. As it rightfully should be (apps like AGS should do the bare minimum )
  • The process for creating component specifications is not both consistent and simplified for all applications.
  • The developer experience is greatly improved - easy switching between declarative and python code (a common ask).
    • Create your agentchat team in python code, prototype,
    • Team.dict() .. save to team.yaml or team.json for distribution/deployment/debugging in AGS (with a single command)

How to run team_a in AutoGen studio?

autogenstudio ui --config-dir=configs

Note that this is only focused on serialization - how to return a runtime instance of a component from a declaritive spec and how to obtain that declarative spec from an instance. Now the ability to load/save runtime state is a separate task tracked in #4100 .

Notes

  • Should ChatAgent enforce a protocol for the new methods? to_dict etc
  • Do we get any benefits from basing components on pydantic classes wrt to serialization?
  • Any general conventions we want to support for all ? AGS component factory and example specs could be helpful here.

Related to #3624, #4388
Thoughts welcome @husseinmozannar @ekzhu @afourney @gagb @jackgerrits

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
needs-design A design needs to be created and agreed upo proj-agentchat
Projects
None yet
Development

No branches or pull requests

1 participant