Skip to content

Commit c2a43e8

Browse files
authored
Declarative BaseChat Agents (#5055)
* v1, make assistant agent declarative * make head tail context declarative * update and formatting * update assistant, format updates * make websurfer declarative * update formatting * move declarative docs to advanced section * remove tools until implemented * minor updates to termination conditions * update docs
1 parent 1f22a7b commit c2a43e8

File tree

17 files changed

+524
-144
lines changed

17 files changed

+524
-144
lines changed

python/packages/autogen-agentchat/src/autogen_agentchat/agents/_assistant_agent.py

+58-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
Sequence,
1414
)
1515

16-
from autogen_core import CancellationToken, FunctionCall
16+
from autogen_core import CancellationToken, Component, ComponentModel, FunctionCall
1717
from autogen_core.memory import Memory
1818
from autogen_core.model_context import (
1919
ChatCompletionContext,
@@ -28,6 +28,8 @@
2828
UserMessage,
2929
)
3030
from autogen_core.tools import FunctionTool, Tool
31+
from pydantic import BaseModel
32+
from typing_extensions import Self
3133

3234
from .. import EVENT_LOGGER_NAME
3335
from ..base import Handoff as HandoffBase
@@ -49,7 +51,21 @@
4951
event_logger = logging.getLogger(EVENT_LOGGER_NAME)
5052

5153

52-
class AssistantAgent(BaseChatAgent):
54+
class AssistantAgentConfig(BaseModel):
55+
"""The declarative configuration for the assistant agent."""
56+
57+
name: str
58+
model_client: ComponentModel
59+
# tools: List[Any] | None = None # TBD
60+
handoffs: List[HandoffBase | str] | None = None
61+
model_context: ComponentModel | None = None
62+
description: str
63+
system_message: str | None = None
64+
reflect_on_tool_use: bool
65+
tool_call_summary_format: str
66+
67+
68+
class AssistantAgent(BaseChatAgent, Component[AssistantAgentConfig]):
5369
"""An agent that provides assistance with tool use.
5470
5571
The :meth:`on_messages` returns a :class:`~autogen_agentchat.base.Response`
@@ -229,6 +245,9 @@ async def main() -> None:
229245
See `o1 beta limitations <https://platform.openai.com/docs/guides/reasoning#beta-limitations>`_ for more details.
230246
"""
231247

248+
component_config_schema = AssistantAgentConfig
249+
component_provider_override = "autogen_agentchat.agents.AssistantAgent"
250+
232251
def __init__(
233252
self,
234253
name: str,
@@ -462,3 +481,40 @@ async def load_state(self, state: Mapping[str, Any]) -> None:
462481
assistant_agent_state = AssistantAgentState.model_validate(state)
463482
# Load the model context state.
464483
await self._model_context.load_state(assistant_agent_state.llm_context)
484+
485+
def _to_config(self) -> AssistantAgentConfig:
486+
"""Convert the assistant agent to a declarative config."""
487+
488+
# raise an error if tools is not empty until it is implemented
489+
# TBD : Implement serializing tools and remove this check.
490+
if self._tools and len(self._tools) > 0:
491+
raise NotImplementedError("Serializing tools is not implemented yet.")
492+
493+
return AssistantAgentConfig(
494+
name=self.name,
495+
model_client=self._model_client.dump_component(),
496+
# tools=[], # TBD
497+
handoffs=list(self._handoffs.values()),
498+
model_context=self._model_context.dump_component(),
499+
description=self.description,
500+
system_message=self._system_messages[0].content
501+
if self._system_messages and isinstance(self._system_messages[0].content, str)
502+
else None,
503+
reflect_on_tool_use=self._reflect_on_tool_use,
504+
tool_call_summary_format=self._tool_call_summary_format,
505+
)
506+
507+
@classmethod
508+
def _from_config(cls, config: AssistantAgentConfig) -> Self:
509+
"""Create an assistant agent from a declarative config."""
510+
return cls(
511+
name=config.name,
512+
model_client=ChatCompletionClient.load_component(config.model_client),
513+
# tools=[], # TBD
514+
handoffs=config.handoffs,
515+
model_context=None,
516+
description=config.description,
517+
system_message=config.system_message,
518+
reflect_on_tool_use=config.reflect_on_tool_use,
519+
tool_call_summary_format=config.tool_call_summary_format,
520+
)

python/packages/autogen-agentchat/src/autogen_agentchat/agents/_base_chat_agent.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
from abc import ABC, abstractmethod
22
from typing import Any, AsyncGenerator, List, Mapping, Sequence
33

4-
from autogen_core import CancellationToken
4+
from autogen_core import CancellationToken, ComponentBase
5+
from pydantic import BaseModel
56

67
from ..base import ChatAgent, Response, TaskResult
78
from ..messages import (
@@ -13,7 +14,7 @@
1314
from ..state import BaseState
1415

1516

16-
class BaseChatAgent(ChatAgent, ABC):
17+
class BaseChatAgent(ChatAgent, ABC, ComponentBase[BaseModel]):
1718
"""Base class for a chat agent.
1819
1920
This abstract class provides a base implementation for a :class:`ChatAgent`.
@@ -35,6 +36,8 @@ class BaseChatAgent(ChatAgent, ABC):
3536
This design principle must be followed when creating a new agent.
3637
"""
3738

39+
component_type = "agent"
40+
3841
def __init__(self, name: str, description: str) -> None:
3942
self._name = name
4043
if self._name.isidentifier() is False:

python/packages/autogen-agentchat/src/autogen_agentchat/agents/_user_proxy_agent.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
from inspect import iscoroutinefunction
66
from typing import Any, AsyncGenerator, Awaitable, Callable, ClassVar, Generator, Optional, Sequence, Union, cast
77

8-
from autogen_core import CancellationToken
8+
from autogen_core import CancellationToken, Component
9+
from pydantic import BaseModel
10+
from typing_extensions import Self
911

1012
from ..base import Response
1113
from ..messages import AgentEvent, ChatMessage, HandoffMessage, TextMessage, UserInputRequestedEvent
@@ -24,7 +26,15 @@ async def cancellable_input(prompt: str, cancellation_token: Optional[Cancellati
2426
return await task
2527

2628

27-
class UserProxyAgent(BaseChatAgent):
29+
class UserProxyAgentConfig(BaseModel):
30+
"""Declarative configuration for the UserProxyAgent."""
31+
32+
name: str
33+
description: str = "A human user"
34+
input_func: str | None = None
35+
36+
37+
class UserProxyAgent(BaseChatAgent, Component[UserProxyAgentConfig]):
2838
"""An agent that can represent a human user through an input function.
2939
3040
This agent can be used to represent a human user in a chat system by providing a custom input function.
@@ -109,6 +119,10 @@ async def cancellable_user_agent():
109119
print(f"BaseException: {e}")
110120
"""
111121

122+
component_type = "agent"
123+
component_provider_override = "autogen_agentchat.agents.UserProxyAgent"
124+
component_config_schema = UserProxyAgentConfig
125+
112126
class InputRequestContext:
113127
def __init__(self) -> None:
114128
raise RuntimeError(
@@ -218,3 +232,11 @@ async def on_messages_stream(
218232
async def on_reset(self, cancellation_token: Optional[CancellationToken] = None) -> None:
219233
"""Reset agent state."""
220234
pass
235+
236+
def _to_config(self) -> UserProxyAgentConfig:
237+
# TODO: Add ability to serialie input_func
238+
return UserProxyAgentConfig(name=self.name, description=self.description, input_func=None)
239+
240+
@classmethod
241+
def _from_config(cls, config: UserProxyAgentConfig) -> Self:
242+
return cls(name=config.name, description=config.description, input_func=None)

python/packages/autogen-agentchat/src/autogen_agentchat/base/_termination.py

-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ async def main() -> None:
4848
"""
4949

5050
component_type = "termination"
51-
# component_config_schema = BaseModel # type: ignore
5251

5352
@property
5453
@abstractmethod

python/packages/autogen-agentchat/src/autogen_agentchat/conditions/_terminations.py

-8
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ class StopMessageTerminationConfig(BaseModel):
1616
class StopMessageTermination(TerminationCondition, Component[StopMessageTerminationConfig]):
1717
"""Terminate the conversation if a StopMessage is received."""
1818

19-
component_type = "termination"
2019
component_config_schema = StopMessageTerminationConfig
2120
component_provider_override = "autogen_agentchat.conditions.StopMessageTermination"
2221

@@ -58,7 +57,6 @@ class MaxMessageTermination(TerminationCondition, Component[MaxMessageTerminatio
5857
max_messages: The maximum number of messages allowed in the conversation.
5958
"""
6059

61-
component_type = "termination"
6260
component_config_schema = MaxMessageTerminationConfig
6361
component_provider_override = "autogen_agentchat.conditions.MaxMessageTermination"
6462

@@ -104,7 +102,6 @@ class TextMentionTermination(TerminationCondition, Component[TextMentionTerminat
104102
text: The text to look for in the messages.
105103
"""
106104

107-
component_type = "termination"
108105
component_config_schema = TextMentionTerminationConfig
109106
component_provider_override = "autogen_agentchat.conditions.TextMentionTermination"
110107

@@ -159,7 +156,6 @@ class TokenUsageTermination(TerminationCondition, Component[TokenUsageTerminatio
159156
ValueError: If none of max_total_token, max_prompt_token, or max_completion_token is provided.
160157
"""
161158

162-
component_type = "termination"
163159
component_config_schema = TokenUsageTerminationConfig
164160
component_provider_override = "autogen_agentchat.conditions.TokenUsageTermination"
165161

@@ -234,7 +230,6 @@ class HandoffTermination(TerminationCondition, Component[HandoffTerminationConfi
234230
target (str): The target of the handoff message.
235231
"""
236232

237-
component_type = "termination"
238233
component_config_schema = HandoffTerminationConfig
239234
component_provider_override = "autogen_agentchat.conditions.HandoffTermination"
240235

@@ -279,7 +274,6 @@ class TimeoutTermination(TerminationCondition, Component[TimeoutTerminationConfi
279274
timeout_seconds: The maximum duration in seconds before terminating the conversation.
280275
"""
281276

282-
component_type = "termination"
283277
component_config_schema = TimeoutTerminationConfig
284278
component_provider_override = "autogen_agentchat.conditions.TimeoutTermination"
285279

@@ -339,7 +333,6 @@ class ExternalTermination(TerminationCondition, Component[ExternalTerminationCon
339333
340334
"""
341335

342-
component_type = "termination"
343336
component_config_schema = ExternalTerminationConfig
344337
component_provider_override = "autogen_agentchat.conditions.ExternalTermination"
345338

@@ -389,7 +382,6 @@ class SourceMatchTermination(TerminationCondition, Component[SourceMatchTerminat
389382
TerminatedException: If the termination condition has already been reached.
390383
"""
391384

392-
component_type = "termination"
393385
component_config_schema = SourceMatchTerminationConfig
394386
component_provider_override = "autogen_agentchat.conditions.SourceMatchTermination"
395387

python/packages/autogen-agentchat/tests/test_assistant_agent.py

+48
Original file line numberDiff line numberDiff line change
@@ -592,3 +592,51 @@ class BadMemory:
592592

593593
assert not isinstance(BadMemory(), Memory)
594594
assert isinstance(ListMemory(), Memory)
595+
596+
597+
@pytest.mark.asyncio
598+
async def test_assistant_agent_declarative(monkeypatch: pytest.MonkeyPatch) -> None:
599+
model = "gpt-4o-2024-05-13"
600+
chat_completions = [
601+
ChatCompletion(
602+
id="id1",
603+
choices=[
604+
Choice(
605+
finish_reason="stop",
606+
index=0,
607+
message=ChatCompletionMessage(content="Response to message 3", role="assistant"),
608+
)
609+
],
610+
created=0,
611+
model=model,
612+
object="chat.completion",
613+
usage=CompletionUsage(prompt_tokens=10, completion_tokens=5, total_tokens=15),
614+
),
615+
]
616+
mock = _MockChatCompletion(chat_completions)
617+
monkeypatch.setattr(AsyncCompletions, "create", mock.mock_create)
618+
model_context = BufferedChatCompletionContext(buffer_size=2)
619+
agent = AssistantAgent(
620+
"test_agent",
621+
model_client=OpenAIChatCompletionClient(model=model, api_key=""),
622+
model_context=model_context,
623+
)
624+
625+
agent_config = agent.dump_component()
626+
assert agent_config.provider == "autogen_agentchat.agents.AssistantAgent"
627+
628+
agent2 = AssistantAgent.load_component(agent_config)
629+
assert agent2.name == agent.name
630+
631+
agent3 = AssistantAgent(
632+
"test_agent",
633+
model_client=OpenAIChatCompletionClient(model=model, api_key=""),
634+
model_context=model_context,
635+
tools=[
636+
_pass_function,
637+
_fail_function,
638+
FunctionTool(_echo_function, description="Echo"),
639+
],
640+
)
641+
with pytest.raises(NotImplementedError):
642+
agent3.dump_component()

python/packages/autogen-agentchat/tests/test_declarative_components.py

+37
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@
1111
TokenUsageTermination,
1212
)
1313
from autogen_core import ComponentLoader, ComponentModel
14+
from autogen_core.model_context import (
15+
BufferedChatCompletionContext,
16+
HeadAndTailChatCompletionContext,
17+
UnboundedChatCompletionContext,
18+
)
1419

1520

1621
@pytest.mark.asyncio
@@ -92,3 +97,35 @@ async def test_termination_declarative() -> None:
9297
# Test loading complex composition
9398
loaded_composite = ComponentLoader.load_component(composite_config)
9499
assert isinstance(loaded_composite, AndTerminationCondition)
100+
101+
102+
@pytest.mark.asyncio
103+
async def test_chat_completion_context_declarative() -> None:
104+
unbounded_context = UnboundedChatCompletionContext()
105+
buffered_context = BufferedChatCompletionContext(buffer_size=5)
106+
head_tail_context = HeadAndTailChatCompletionContext(head_size=3, tail_size=2)
107+
108+
# Test serialization
109+
unbounded_config = unbounded_context.dump_component()
110+
assert unbounded_config.provider == "autogen_core.model_context.UnboundedChatCompletionContext"
111+
112+
buffered_config = buffered_context.dump_component()
113+
assert buffered_config.provider == "autogen_core.model_context.BufferedChatCompletionContext"
114+
assert buffered_config.config["buffer_size"] == 5
115+
116+
head_tail_config = head_tail_context.dump_component()
117+
assert head_tail_config.provider == "autogen_core.model_context.HeadAndTailChatCompletionContext"
118+
assert head_tail_config.config["head_size"] == 3
119+
assert head_tail_config.config["tail_size"] == 2
120+
121+
# Test deserialization
122+
loaded_unbounded = ComponentLoader.load_component(unbounded_config, UnboundedChatCompletionContext)
123+
assert isinstance(loaded_unbounded, UnboundedChatCompletionContext)
124+
125+
loaded_buffered = ComponentLoader.load_component(buffered_config, BufferedChatCompletionContext)
126+
127+
assert isinstance(loaded_buffered, BufferedChatCompletionContext)
128+
129+
loaded_head_tail = ComponentLoader.load_component(head_tail_config, HeadAndTailChatCompletionContext)
130+
131+
assert isinstance(loaded_head_tail, HeadAndTailChatCompletionContext)

python/packages/autogen-core/docs/src/user-guide/agentchat-user-guide/index.md

+15-2
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,18 @@ Sample code and use cases
6666

6767
How to migrate from AutoGen 0.2.x to 0.4.x.
6868
:::
69+
70+
:::{grid-item-card} {fas}`save;pst-color-primary` Serialize Components
71+
:link: ./serialize-components.html
72+
73+
Serialize and deserialize components
74+
:::
75+
76+
:::{grid-item-card} {fas}`brain;pst-color-primary` Memory
77+
:link: ./memory.html
78+
79+
Add memory capabilities to your agents
80+
:::
6981
::::
7082

7183
```{toctree}
@@ -91,8 +103,7 @@ tutorial/human-in-the-loop
91103
tutorial/termination
92104
tutorial/custom-agents
93105
tutorial/state
94-
tutorial/declarative
95-
tutorial/memory
106+
96107
```
97108

98109
```{toctree}
@@ -103,6 +114,8 @@ tutorial/memory
103114
selector-group-chat
104115
swarm
105116
magentic-one
117+
memory
118+
serialize-components
106119
```
107120

108121
```{toctree}

0 commit comments

Comments
 (0)