mirror of
https://github.com/microsoft/autogen.git
synced 2025-10-03 12:08:08 +00:00
Doc: multi-agent design pattern (#329)
* Doc: multi-agent design pattern * Fix warnings. * mypy * fix type * chore: Remove unused import and checkpoint code in langgraph_agent.py
This commit is contained in:
parent
8b13d59b59
commit
1531a448ad
86
python/docs/drawio/coder-reviewer.drawio
Normal file
86
python/docs/drawio/coder-reviewer.drawio
Normal file
@ -0,0 +1,86 @@
|
||||
<mxfile host="app.diagrams.net" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36 Edg/127.0.0.0" version="24.7.6">
|
||||
<diagram name="Page-1" id="kM63aGWDAVgwnXhMnwsJ">
|
||||
<mxGraphModel dx="1773" dy="1145" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-6" value="approved=False" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="360" y="290" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="rwyUPL19n1b9p3f1DsXw-8" target="rwyUPL19n1b9p3f1DsXw-10">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-8" value="CoderAgent:<div>handle_writing_task</div>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
|
||||
<mxGeometry x="160" y="200" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="rwyUPL19n1b9p3f1DsXw-10" target="rwyUPL19n1b9p3f1DsXw-15">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-10" value="ReviewerAgent:<div>handle_review_task</div>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
|
||||
<mxGeometry x="305" y="200" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="1" source="rwyUPL19n1b9p3f1DsXw-15" target="rwyUPL19n1b9p3f1DsXw-8">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<Array as="points">
|
||||
<mxPoint x="540" y="320" />
|
||||
<mxPoint x="220" y="320" />
|
||||
</Array>
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-20" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="rwyUPL19n1b9p3f1DsXw-15" target="rwyUPL19n1b9p3f1DsXw-21">
|
||||
<mxGeometry relative="1" as="geometry">
|
||||
<mxPoint x="630" y="230" as="targetPoint" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-15" value="CoderAgent:<div>handle_review_result</div>" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
|
||||
<mxGeometry x="450" y="200" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-18" value="approved=True" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="580" y="245" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-21" value="Application:<div>Receive Result</div>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="620" y="215" width="90" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="rwyUPL19n1b9p3f1DsXw-22" target="rwyUPL19n1b9p3f1DsXw-8">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-22" value="Application:<div>Send Task</div>" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="80" y="215" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-27" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.25;exitDx=0;exitDy=0;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" edge="1" parent="1" source="rwyUPL19n1b9p3f1DsXw-25" target="rwyUPL19n1b9p3f1DsXw-26">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-35" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="1" source="rwyUPL19n1b9p3f1DsXw-25" target="rwyUPL19n1b9p3f1DsXw-33">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-25" value="CoderAgent" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#ffe6cc;strokeColor=#d79b00;" vertex="1" parent="1">
|
||||
<mxGeometry x="260" y="620" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-28" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.75;exitDx=0;exitDy=0;entryX=1;entryY=0.75;entryDx=0;entryDy=0;" edge="1" parent="1" source="rwyUPL19n1b9p3f1DsXw-26" target="rwyUPL19n1b9p3f1DsXw-25">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-26" value="ReviewerAgent" style="rounded=1;whiteSpace=wrap;html=1;fillColor=#d5e8d4;strokeColor=#82b366;" vertex="1" parent="1">
|
||||
<mxGeometry x="480" y="620" width="120" height="60" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-29" value="CodeReviewTask" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="395" y="600" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-30" value="CodeReviewResult" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="395" y="677" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-32" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.25;entryDx=0;entryDy=0;" edge="1" parent="1" source="rwyUPL19n1b9p3f1DsXw-31" target="rwyUPL19n1b9p3f1DsXw-25">
|
||||
<mxGeometry relative="1" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-31" value="CodeWritingTask" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="100" y="620" width="100" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-33" value="CodeWritingResult" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="90" y="650" width="110" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="rwyUPL19n1b9p3f1DsXw-36" value="approved=True" style="text;html=1;align=center;verticalAlign=middle;whiteSpace=wrap;rounded=0;" vertex="1" parent="1">
|
||||
<mxGeometry x="220" y="677" width="60" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
</root>
|
||||
</mxGraphModel>
|
||||
</diagram>
|
||||
</mxfile>
|
@ -43,6 +43,8 @@ nb_execution_mode = "off"
|
||||
nb_execution_raise_on_error = True
|
||||
nb_execution_timeout = 60
|
||||
|
||||
myst_heading_anchors = 5
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
|
@ -39,7 +39,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import asyncio\n",
|
||||
"import os\n",
|
||||
"from dataclasses import dataclass\n",
|
||||
"from typing import Any, Callable, List, Literal\n",
|
||||
@ -51,7 +50,6 @@
|
||||
"from langchain_core.messages import HumanMessage, SystemMessage\n",
|
||||
"from langchain_core.tools import tool # pyright: ignore\n",
|
||||
"from langchain_openai import ChatOpenAI, AzureChatOpenAI\n",
|
||||
"from langgraph.checkpoint import MemorySaver\n",
|
||||
"from langgraph.graph import END, MessagesState, StateGraph\n",
|
||||
"from langgraph.prebuilt import ToolNode"
|
||||
]
|
||||
@ -105,7 +103,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@ -157,14 +155,11 @@
|
||||
" # This means that after `tools` is called, `agent` node is called next.\n",
|
||||
" self._workflow.add_edge(\"tools\", \"agent\")\n",
|
||||
"\n",
|
||||
" # Initialize memory to persist state between graph runs\n",
|
||||
" self._checkpointer = MemorySaver()\n",
|
||||
"\n",
|
||||
" # Finally, we compile it!\n",
|
||||
" # This compiles it into a LangChain Runnable,\n",
|
||||
" # meaning you can use it as you would any other runnable.\n",
|
||||
" # Note that we're (optionally) passing the memory when compiling the graph\n",
|
||||
" self._app = self._workflow.compile(checkpointer=self._checkpointer)\n",
|
||||
" self._app = self._workflow.compile()\n",
|
||||
"\n",
|
||||
" @message_handler\n",
|
||||
" async def handle_user_message(self, message: Message, cancellation_token: CancellationToken) -> Message:\n",
|
||||
@ -195,7 +190,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@ -231,7 +226,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
@ -247,7 +242,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"execution_count": 14,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@ -272,7 +267,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 61 KiB |
@ -13,16 +13,529 @@
|
||||
"multi-agent systems out-performing single agent systems at complex tasks\n",
|
||||
"like software development.\n",
|
||||
"\n",
|
||||
"You can implement any multi-agent design pattern using AGNext agents, which\n",
|
||||
"communicate with each other using messages through the agent runtime.\n",
|
||||
"See [samples](https://github.com/microsoft/agnext/tree/main/python/samples#pattern-examples)\n",
|
||||
"for how to implement patterns like reflection and group chat.\n"
|
||||
"A multi-agent design pattern is a structure that emerges from message protocols:\n",
|
||||
"it describes how agents interact with each other to solve problems.\n",
|
||||
"For example, the [tool-equiped agent](./tools.ipynb#tool-equipped-agent) in\n",
|
||||
"the previous section employs a design pattern called ReAct,\n",
|
||||
"which involves an agent interacting with tools.\n",
|
||||
"\n",
|
||||
"You can implement any multi-agent design pattern using AGNext agents.\n",
|
||||
"In this section, we use the reflection pattern as an example."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Reflection\n",
|
||||
"\n",
|
||||
"Reflection is a design pattern where an LLM generation is followed by a reflection,\n",
|
||||
"which in itself is another LLM generation conditioned on the output of the first one.\n",
|
||||
"For example, given a task to write code, the first LLM can generate a code snippet,\n",
|
||||
"and the second LLM can generate a critique of the code snippet.\n",
|
||||
"\n",
|
||||
"In the context of AGNext and agents, reflection can be implemented as a pair\n",
|
||||
"of agents, where the first agent generates a message and the second agent\n",
|
||||
"generates a response to the message. The two agents continue to interact\n",
|
||||
"until they reach a stopping condition, such as a maximum number of iterations\n",
|
||||
"or an approval from the second agent."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Let's implement a simple reflection design pattern using AGNext agents.\n",
|
||||
"There will be two agents: a coder agent and a reviewer agent, the coder agent\n",
|
||||
"will generate a code snippet, and the reviewer agent will generate a critique\n",
|
||||
"of the code snippet.\n",
|
||||
"\n",
|
||||
"### Message Protocol\n",
|
||||
"\n",
|
||||
"Before we define the agents, we need to first define the message protocol for the agents."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from dataclasses import dataclass\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class CodeWritingTask:\n",
|
||||
" task: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class CodeWritingResult:\n",
|
||||
" task: str\n",
|
||||
" code: str\n",
|
||||
" review: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class CodeReviewTask:\n",
|
||||
" session_id: str\n",
|
||||
" code_writing_task: str\n",
|
||||
" code_writing_scratchpad: str\n",
|
||||
" code: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class CodeReviewResult:\n",
|
||||
" review: str\n",
|
||||
" session_id: str\n",
|
||||
" approved: bool"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The above set of messages defines the protocol for our example reflection design pattern:\n",
|
||||
"- The application sends a `CodeWritingTask` message to the coder agent\n",
|
||||
"- The coder agent generates a `CodeReviewTask` message, which is sent to the reviewer agent\n",
|
||||
"- The reviewer agent generates a `CodeReviewResult` message, which is sent back to the coder agent\n",
|
||||
"- Depending on the `CodeReview` message, if the code is approved, the coder agent sends a `CodeWritingResult` message\n",
|
||||
"back to the application, otherwise, the coder agent sends another `CodeWritingTask` message to the reviewer agent,\n",
|
||||
"and the process continues.\n",
|
||||
"\n",
|
||||
"We can visualize the message protocol using a data flow diagram:\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"### Agents\n",
|
||||
"\n",
|
||||
"Now, let's define the agents for the reflection design pattern."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import json\n",
|
||||
"import re\n",
|
||||
"import uuid\n",
|
||||
"from typing import Dict, List, Union\n",
|
||||
"\n",
|
||||
"from agnext.components import TypeRoutedAgent, message_handler\n",
|
||||
"from agnext.components.models import (\n",
|
||||
" AssistantMessage,\n",
|
||||
" ChatCompletionClient,\n",
|
||||
" LLMMessage,\n",
|
||||
" SystemMessage,\n",
|
||||
" UserMessage,\n",
|
||||
")\n",
|
||||
"from agnext.core import CancellationToken"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"We use the [Broadcast Communication](./message-and-communication.ipynb#broadcast-communication) API\n",
|
||||
"to implement the design pattern. The agents implements the pub/sub model.\n",
|
||||
"The coder agent subscribes to the `CodeWritingTask` and `CodeReviewResult` messages,\n",
|
||||
"and publishes the `CodeReviewTask` and `CodeWritingResult` messages."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class CoderAgent(TypeRoutedAgent):\n",
|
||||
" \"\"\"An agent that performs code writing tasks.\"\"\"\n",
|
||||
"\n",
|
||||
" def __init__(self, model_client: ChatCompletionClient) -> None:\n",
|
||||
" super().__init__(\"A code writing agent.\")\n",
|
||||
" self._system_messages = [\n",
|
||||
" SystemMessage(\n",
|
||||
" content=\"\"\"You are a proficient coder. You write code to solve problems.\n",
|
||||
"Work with the reviewer to improve your code.\n",
|
||||
"Always put all finished code in a single Markdown code block.\n",
|
||||
"For example:\n",
|
||||
"```python\n",
|
||||
"def hello_world():\n",
|
||||
" print(\"Hello, World!\")\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Respond using the following format:\n",
|
||||
"\n",
|
||||
"Thoughts: <Your comments>\n",
|
||||
"Code: <Your code>\n",
|
||||
"\"\"\",\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" self._model_client = model_client\n",
|
||||
" self._session_memory: Dict[str, List[CodeWritingTask | CodeReviewTask | CodeReviewResult]] = {}\n",
|
||||
"\n",
|
||||
" @message_handler\n",
|
||||
" async def handle_code_writing_task(self, message: CodeWritingTask, cancellation_token: CancellationToken) -> None:\n",
|
||||
" # Store the messages in a temporary memory for this request only.\n",
|
||||
" session_id = str(uuid.uuid4())\n",
|
||||
" self._session_memory.setdefault(session_id, []).append(message)\n",
|
||||
" # Generate a response using the chat completion API.\n",
|
||||
" response = await self._model_client.create(\n",
|
||||
" self._system_messages + [UserMessage(content=message.task, source=self.metadata[\"name\"])],\n",
|
||||
" cancellation_token=cancellation_token,\n",
|
||||
" )\n",
|
||||
" assert isinstance(response.content, str)\n",
|
||||
" # Extract the code block from the response.\n",
|
||||
" code_block = self._extract_code_block(response.content)\n",
|
||||
" if code_block is None:\n",
|
||||
" raise ValueError(\"Code block not found.\")\n",
|
||||
" # Create a code review task.\n",
|
||||
" code_review_task = CodeReviewTask(\n",
|
||||
" session_id=session_id,\n",
|
||||
" code_writing_task=message.task,\n",
|
||||
" code_writing_scratchpad=response.content,\n",
|
||||
" code=code_block,\n",
|
||||
" )\n",
|
||||
" # Store the code review task in the session memory.\n",
|
||||
" self._session_memory[session_id].append(code_review_task)\n",
|
||||
" # Publish a code review task.\n",
|
||||
" await self.publish_message(code_review_task)\n",
|
||||
"\n",
|
||||
" @message_handler\n",
|
||||
" async def handle_code_review_result(self, message: CodeReviewResult, cancellation_token: CancellationToken) -> None:\n",
|
||||
" # Store the review result in the session memory.\n",
|
||||
" self._session_memory[message.session_id].append(message)\n",
|
||||
" # Obtain the request from previous messages.\n",
|
||||
" review_request = next(\n",
|
||||
" m for m in reversed(self._session_memory[message.session_id]) if isinstance(m, CodeReviewTask)\n",
|
||||
" )\n",
|
||||
" assert review_request is not None\n",
|
||||
" # Check if the code is approved.\n",
|
||||
" if message.approved:\n",
|
||||
" # Publish the code writing result.\n",
|
||||
" await self.publish_message(\n",
|
||||
" CodeWritingResult(\n",
|
||||
" code=review_request.code,\n",
|
||||
" task=review_request.code_writing_task,\n",
|
||||
" review=message.review,\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
" print(\"Code Writing Result:\")\n",
|
||||
" print(\"-\" * 80)\n",
|
||||
" print(f\"Task:\\n{review_request.code_writing_task}\")\n",
|
||||
" print(\"-\" * 80)\n",
|
||||
" print(f\"Code:\\n{review_request.code}\")\n",
|
||||
" print(\"-\" * 80)\n",
|
||||
" print(f\"Review:\\n{message.review}\")\n",
|
||||
" print(\"-\" * 80)\n",
|
||||
" else:\n",
|
||||
" # Create a list of LLM messages to send to the model.\n",
|
||||
" messages: List[LLMMessage] = [*self._system_messages]\n",
|
||||
" for m in self._session_memory[message.session_id]:\n",
|
||||
" if isinstance(m, CodeReviewResult):\n",
|
||||
" messages.append(UserMessage(content=m.review, source=\"Reviewer\"))\n",
|
||||
" elif isinstance(m, CodeReviewTask):\n",
|
||||
" messages.append(AssistantMessage(content=m.code_writing_scratchpad, source=\"Coder\"))\n",
|
||||
" elif isinstance(m, CodeWritingTask):\n",
|
||||
" messages.append(UserMessage(content=m.task, source=\"User\"))\n",
|
||||
" else:\n",
|
||||
" raise ValueError(f\"Unexpected message type: {m}\")\n",
|
||||
" # Generate a revision using the chat completion API.\n",
|
||||
" response = await self._model_client.create(messages, cancellation_token=cancellation_token)\n",
|
||||
" assert isinstance(response.content, str)\n",
|
||||
" # Extract the code block from the response.\n",
|
||||
" code_block = self._extract_code_block(response.content)\n",
|
||||
" if code_block is None:\n",
|
||||
" raise ValueError(\"Code block not found.\")\n",
|
||||
" # Create a new code review task.\n",
|
||||
" code_review_task = CodeReviewTask(\n",
|
||||
" session_id=message.session_id,\n",
|
||||
" code_writing_task=review_request.code_writing_task,\n",
|
||||
" code_writing_scratchpad=response.content,\n",
|
||||
" code=code_block,\n",
|
||||
" )\n",
|
||||
" # Store the code review task in the session memory.\n",
|
||||
" self._session_memory[message.session_id].append(code_review_task)\n",
|
||||
" # Publish a new code review task.\n",
|
||||
" await self.publish_message(code_review_task)\n",
|
||||
"\n",
|
||||
" def _extract_code_block(self, markdown_text: str) -> Union[str, None]:\n",
|
||||
" pattern = r\"```(\\w+)\\n(.*?)\\n```\"\n",
|
||||
" # Search for the pattern in the markdown text\n",
|
||||
" match = re.search(pattern, markdown_text, re.DOTALL)\n",
|
||||
" # Extract the language and code block if a match is found\n",
|
||||
" if match:\n",
|
||||
" return match.group(2)\n",
|
||||
" return None"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"A few things to note about `CoderAgent`:\n",
|
||||
"- It uses chain-of-thought prompting in its system message.\n",
|
||||
"- It stores message histories for different `CodeWritingTask` in a dictionary,\n",
|
||||
"so each task has its own history.\n",
|
||||
"- When making an LLM inference request using its model client, it transforms\n",
|
||||
"the message history into a list of {py:class}`agnext.components.models.LLMMessage` objects\n",
|
||||
"to pass to the model client.\n",
|
||||
"\n",
|
||||
"The reviewer agent subscribes to the `CodeReviewTask` message and publishes the `CodeReviewResult` message."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class ReviewerAgent(TypeRoutedAgent):\n",
|
||||
" \"\"\"An agent that performs code review tasks.\"\"\"\n",
|
||||
"\n",
|
||||
" def __init__(self, model_client: ChatCompletionClient) -> None:\n",
|
||||
" super().__init__(\"A code reviewer agent.\")\n",
|
||||
" self._system_messages = [\n",
|
||||
" SystemMessage(\n",
|
||||
" content=\"\"\"You are a code reviewer. You focus on correctness, efficiency and safety of the code.\n",
|
||||
"Respond using the following JSON format:\n",
|
||||
"{\n",
|
||||
" \"correctness\": \"<Your comments>\",\n",
|
||||
" \"efficiency\": \"<Your comments>\",\n",
|
||||
" \"safety\": \"<Your comments>\",\n",
|
||||
" \"approval\": \"<APPROVE or REVISE>\",\n",
|
||||
" \"suggested_changes\": \"<Your comments>\"\n",
|
||||
"}\n",
|
||||
"\"\"\",\n",
|
||||
" )\n",
|
||||
" ]\n",
|
||||
" self._session_memory: Dict[str, List[CodeReviewTask | CodeReviewResult]] = {}\n",
|
||||
" self._model_client = model_client\n",
|
||||
"\n",
|
||||
" @message_handler\n",
|
||||
" async def handle_code_review_task(self, message: CodeReviewTask, cancellation_token: CancellationToken) -> None:\n",
|
||||
" # Format the prompt for the code review.\n",
|
||||
" previous_feedback = \"\"\n",
|
||||
" if message.session_id in self._session_memory:\n",
|
||||
" previous_feedback = self._session_memory[message.session_id][-1].review\n",
|
||||
" # Store the messages in a temporary memory for this request only.\n",
|
||||
" self._session_memory.setdefault(message.session_id, []).append(message)\n",
|
||||
" prompt = f\"\"\"The problem statement is: {message.code_writing_task}\n",
|
||||
"The code is:\n",
|
||||
"```\n",
|
||||
"{message.code}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Previous feedback:\n",
|
||||
"{previous_feedback}\n",
|
||||
"\n",
|
||||
"Please review the code. If previous feedback was provided, see if it was addressed.\n",
|
||||
"\"\"\"\n",
|
||||
" # Generate a response using the chat completion API.\n",
|
||||
" response = await self._model_client.create(\n",
|
||||
" self._system_messages + [UserMessage(content=prompt, source=self.metadata[\"name\"])],\n",
|
||||
" cancellation_token=cancellation_token,\n",
|
||||
" json_output=True,\n",
|
||||
" )\n",
|
||||
" assert isinstance(response.content, str)\n",
|
||||
" # TODO: use structured generation library e.g. guidance to ensure the response is in the expected format.\n",
|
||||
" # Parse the response JSON.\n",
|
||||
" review = json.loads(response.content)\n",
|
||||
" # Construct the review text.\n",
|
||||
" review_text = \"Code review:\\n\" + \"\\n\".join([f\"{k}: {v}\" for k, v in review.items()])\n",
|
||||
" approved = review[\"approval\"].lower().strip() == \"approve\"\n",
|
||||
" result = CodeReviewResult(\n",
|
||||
" review=review_text,\n",
|
||||
" session_id=message.session_id,\n",
|
||||
" approved=approved,\n",
|
||||
" )\n",
|
||||
" # Store the review result in the session memory.\n",
|
||||
" self._session_memory[message.session_id].append(result)\n",
|
||||
" # Publish the review result.\n",
|
||||
" await self.publish_message(result)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The `ReviewerAgent` uses JSON-mode when making an LLM inference request, and\n",
|
||||
"also uses chain-of-thought prompting in its system message."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Logging\n",
|
||||
"\n",
|
||||
"Turn on logging to see the messages exchanged between the agents."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import logging\n",
|
||||
"\n",
|
||||
"logging.basicConfig(level=logging.WARNING)\n",
|
||||
"logging.getLogger(\"agnext\").setLevel(logging.DEBUG)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Running the Design Pattern\n",
|
||||
"\n",
|
||||
"Let's test the design pattern with a coding task."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"INFO:agnext:Publishing message of type CodeWritingTask to all subscribers: {'task': 'Write a function to find the sum of all even numbers in a list.'}\n",
|
||||
"INFO:agnext:Calling message handler for CoderAgent with message type CodeWritingTask published by Unknown\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 101, \"completion_tokens\": 101, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewTask to all subscribers: {'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'code_writing_task': 'Write a function to find the sum of all even numbers in a list.', 'code_writing_scratchpad': 'Thoughts: To find the sum of all even numbers in a list, I will iterate through the list and check if each number is even. If it is, I will add it to a cumulative sum. Finally, I will return this sum. This approach is straightforward and efficient.\\n\\nCode:\\n```python\\ndef sum_of_evens(numbers):\\n total = 0\\n for number in numbers:\\n if number % 2 == 0:\\n total += number\\n return total\\n```', 'code': 'def sum_of_evens(numbers):\\n total = 0\\n for number in numbers:\\n if number % 2 == 0:\\n total += number\\n return total'}\n",
|
||||
"INFO:agnext:Calling message handler for ReviewerAgent with message type CodeReviewTask published by CoderAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 176, \"completion_tokens\": 191, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewResult to all subscribers: {'review': \"Code review:\\ncorrectness: The function correctly sums all even numbers in the list as intended. There are no logical errors in the implementation.\\nefficiency: The implementation has a time complexity of O(n), where n is the number of elements in the list. This is optimal for this problem since each element has to be examined. However, using a generator expression with the sum function could potentially improve readability.\\nsafety: The function currently does not handle cases where the input 'numbers' is None or not a list. Adding input validation could enhance the safety of the function to prevent runtime errors.\\napproval: REVISE\\nsuggested_changes: Consider adding input validation to check if 'numbers' is a list. Also, you could improve readability and potentially performance by using a generator expression like 'return sum(number for number in numbers if number % 2 == 0)'.\", 'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'approved': False}\n",
|
||||
"INFO:agnext:Calling message handler for CoderAgent with message type CodeReviewResult published by ReviewerAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 389, \"completion_tokens\": 97, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewTask to all subscribers: {'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'code_writing_task': 'Write a function to find the sum of all even numbers in a list.', 'code_writing_scratchpad': 'Thoughts: I appreciate the reviewer\\'s feedback on improving the function\\'s safety and readability. I will incorporate input validation to ensure the input is a list, and I will use a generator expression to simplify the summation of even numbers. \\n\\nCode:\\n```python\\ndef sum_of_evens(numbers):\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n return sum(number for number in numbers if number % 2 == 0)\\n```', 'code': 'def sum_of_evens(numbers):\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n return sum(number for number in numbers if number % 2 == 0)'}\n",
|
||||
"INFO:agnext:Calling message handler for ReviewerAgent with message type CodeReviewTask published by CoderAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 357, \"completion_tokens\": 211, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewResult to all subscribers: {'review': \"Code review:\\ncorrectness: The function correctly calculates the sum of all even numbers from the provided list. The logic appears sound and there are no errors in the implementation.\\nefficiency: The time complexity remains O(n), which is optimal for the task at hand since all numbers must be examined to determine if they are even. While using a generator expression improves readability, it does not enhance performance significantly over a list comprehension for this particular case.\\nsafety: The function still raises a ValueError if the input is not a list, which is good. However, it would be beneficial to explicitly handle cases where 'numbers' is None to prevent potential runtime errors. An additional check would enhance its robustness.\\napproval: REVISE\\nsuggested_changes: Consider adding a check at the beginning of the function to handle cases where 'numbers' is None and return a meaningful error or handle it gracefully. An example could be to raise a TypeError or to explicitly check for None before continuing.\", 'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'approved': False}\n",
|
||||
"INFO:agnext:Calling message handler for CoderAgent with message type CodeReviewResult published by ReviewerAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 695, \"completion_tokens\": 123, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewTask to all subscribers: {'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'code_writing_task': 'Write a function to find the sum of all even numbers in a list.', 'code_writing_scratchpad': 'Thoughts: I appreciate the continued feedback for enhancing the robustness of the function. I will add an explicit check for `None` at the beginning of the function to raise a `TypeError` if the input is `None`. This will ensure the function is more resilient against unexpected input.\\n\\nCode:\\n```python\\ndef sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError(\"Input cannot be None.\")\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n return sum(number for number in numbers if number % 2 == 0)\\n```', 'code': 'def sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError(\"Input cannot be None.\")\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n return sum(number for number in numbers if number % 2 == 0)'}\n",
|
||||
"INFO:agnext:Calling message handler for ReviewerAgent with message type CodeReviewTask published by CoderAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 394, \"completion_tokens\": 218, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewResult to all subscribers: {'review': \"Code review:\\ncorrectness: The function correctly computes the sum of all even numbers from the provided list. It raises appropriate errors for incorrect input types, which is a good practice.\\nefficiency: The time complexity of O(n) is optimal for this problem since all elements must be checked to determine if they are even. The use of a generator expression is efficient in terms of memory usage.\\nsafety: The function includes checks for None and enforces input types, which is commendable. However, you should note that if an element within the list is not an integer, it might cause a runtime error when performing the modulus operation. A check to ensure all elements are integers (or can be handled appropriately) would improve safety.\\napproval: REVISE\\nsuggested_changes: Consider adding a check to ensure all elements within the 'numbers' list are integers. If any element is not an integer, raise a ValueError with a suitable message. This will prevent potential runtime errors due to invalid operations on unsupported types.\", 'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'approved': False}\n",
|
||||
"INFO:agnext:Calling message handler for CoderAgent with message type CodeReviewResult published by ReviewerAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 1034, \"completion_tokens\": 160, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewTask to all subscribers: {'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'code_writing_task': 'Write a function to find the sum of all even numbers in a list.', 'code_writing_scratchpad': 'Thoughts: Thank you for the constructive feedback. I will add a check to ensure that all elements in the list are integers before attempting to perform the modulus operation. If any element is not an integer, I will raise a `ValueError` with an appropriate message. This will make the function more robust and prevent runtime errors.\\n\\nCode:\\n```python\\ndef sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError(\"Input cannot be None.\")\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n if not all(isinstance(number, int) for number in numbers):\\n raise ValueError(\"All elements in the list must be integers.\")\\n return sum(number for number in numbers if number % 2 == 0)\\n```', 'code': 'def sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError(\"Input cannot be None.\")\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n if not all(isinstance(number, int) for number in numbers):\\n raise ValueError(\"All elements in the list must be integers.\")\\n return sum(number for number in numbers if number % 2 == 0)'}\n",
|
||||
"INFO:agnext:Calling message handler for ReviewerAgent with message type CodeReviewTask published by CoderAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 430, \"completion_tokens\": 249, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewResult to all subscribers: {'review': \"Code review:\\ncorrectness: The function correctly computes the sum of all even numbers from the provided list and raises appropriate errors for incorrect input types, which is a good practice. The checks for None, list type, and integer types are implemented effectively.\\nefficiency: The time complexity of O(n) is optimal, since checking every number in the list is necessary, and the use of a generator expression for summing is both efficient in terms of time and memory.\\nsafety: The function includes checks for None and enforces input types. However, while it checks if all elements are integers, it presumes that this check is sufficient to avoid runtime errors. If the list were to contain non-integer types that can be tested against 'number % 2', the modulus operation could still lead to runtime errors.\\napproval: REVISE\\nsuggested_changes: Consider implementing a specific check to ensure all elements are integers prior to performing the summation, which could raise a ValueError. This could prevent runtime errors if non-integer types somehow bypass the initial check. You could enhance safety by using a try-except block to catch any unexpected types during the modulus operation.\", 'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'approved': False}\n",
|
||||
"INFO:agnext:Calling message handler for CoderAgent with message type CodeReviewResult published by ReviewerAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 1441, \"completion_tokens\": 198, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewTask to all subscribers: {'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'code_writing_task': 'Write a function to find the sum of all even numbers in a list.', 'code_writing_scratchpad': 'Thoughts: Thank you for the thorough code review and highlighting the potential issues. I will enhance safety by implementing a try-except block around the modulus operation to catch any errors related to non-integer types during the sum computation. This will ensure that the function handles unexpected types gracefully and avoids runtime errors.\\n\\nCode:\\n```python\\ndef sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError(\"Input cannot be None.\")\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n if not all(isinstance(number, int) for number in numbers):\\n raise ValueError(\"All elements in the list must be integers.\")\\n \\n total = 0\\n for number in numbers:\\n try:\\n if number % 2 == 0:\\n total += number\\n except TypeError:\\n raise ValueError(f\"Invalid element found: {number}. All elements must be integers.\")\\n \\n return total\\n```', 'code': 'def sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError(\"Input cannot be None.\")\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n if not all(isinstance(number, int) for number in numbers):\\n raise ValueError(\"All elements in the list must be integers.\")\\n \\n total = 0\\n for number in numbers:\\n try:\\n if number % 2 == 0:\\n total += number\\n except TypeError:\\n raise ValueError(f\"Invalid element found: {number}. All elements must be integers.\")\\n \\n return total'}\n",
|
||||
"INFO:agnext:Calling message handler for ReviewerAgent with message type CodeReviewTask published by CoderAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 504, \"completion_tokens\": 181, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewResult to all subscribers: {'review': \"Code review:\\ncorrectness: The function still correctly computes the sum of all even numbers and raises appropriate errors for incorrect input types, which maintains a good level of correctness.\\nefficiency: The efficiency remains satisfactory with a time complexity of O(n). The use of a for loop is appropriate for iterating through the list.\\nsafety: While the previous feedback regarding safety was noted, the function doesn't fully mitigate potential runtime errors during the modulus operation. It's still possible for non-integer types to cause issues, which should be handled more effectively.\\napproval: REVISE\\nsuggested_changes: Consider using a try-except block that encapsulates the entire loop or implement an additional step where you filter or validate the elements before processing to ensure that only integers are considered. This will help avoid unexpected runtime errors while performing operations on list elements.\", 'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'approved': False}\n",
|
||||
"INFO:agnext:Calling message handler for CoderAgent with message type CodeReviewResult published by ReviewerAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 1818, \"completion_tokens\": 186, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewTask to all subscribers: {'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'code_writing_task': 'Write a function to find the sum of all even numbers in a list.', 'code_writing_scratchpad': 'Thoughts: Thank you for your patience and for highlighting the remaining safety concern. To address the issue of non-integer types causing potential runtime errors, I will refactor the code to filter the input list before attempting any operations. This way, we can ensure that only integers are processed, thus reducing the risk of unexpected errors.\\n\\nCode:\\n```python\\ndef sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError(\"Input cannot be None.\")\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n \\n # Filter the list to ensure only integers are processed.\\n integers = [number for number in numbers if isinstance(number, int)]\\n \\n if len(integers) != len(numbers):\\n raise ValueError(\"All elements in the list must be integers.\")\\n \\n return sum(number for number in integers if number % 2 == 0)\\n```', 'code': 'def sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError(\"Input cannot be None.\")\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n \\n # Filter the list to ensure only integers are processed.\\n integers = [number for number in numbers if isinstance(number, int)]\\n \\n if len(integers) != len(numbers):\\n raise ValueError(\"All elements in the list must be integers.\")\\n \\n return sum(number for number in integers if number % 2 == 0)'}\n",
|
||||
"INFO:agnext:Calling message handler for ReviewerAgent with message type CodeReviewTask published by CoderAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 419, \"completion_tokens\": 198, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewResult to all subscribers: {'review': \"Code review:\\ncorrectness: The function still correctly computes the sum of all even numbers and raises appropriate errors for incorrect input types, which maintains a good level of correctness.\\nefficiency: The efficiency remains satisfactory with a time complexity of O(n). The use of list comprehensions for filtering is both clear and efficient for this type of operation.\\nsafety: The function raises errors appropriately for None and non-list inputs, but it doesn't handle scenarios where non-integer types may still exist in the list before the summation. There's still a risk of runtime errors during the modulus operation if non-integers are present.\\napproval: REVISE\\nsuggested_changes: To improve safety, consider filtering the list more robustly by checking types before performing operations. You could also implement a try-except block around the summation operation to catch potential runtime errors, although filtering out non-integers beforehand is a better solution.\", 'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'approved': False}\n",
|
||||
"INFO:agnext:Calling message handler for CoderAgent with message type CodeReviewResult published by ReviewerAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 2200, \"completion_tokens\": 187, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewTask to all subscribers: {'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'code_writing_task': 'Write a function to find the sum of all even numbers in a list.', 'code_writing_scratchpad': 'Thoughts: Thank you for your insightful feedback. To make the function more robust against non-integer types, I will use a filtering process to ensure that only integers are included in the summation. Additionally, I will provide a clear error message if non-integer types slip through. This should eliminate runtime errors related to the modulus operation.\\n\\nCode:\\n```python\\ndef sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError(\"Input cannot be None.\")\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n\\n # Filtering to ensure only integers are included\\n integers = []\\n for number in numbers:\\n if isinstance(number, int):\\n integers.append(number)\\n else:\\n raise ValueError(f\"Invalid element found: {number}. All elements must be integers.\")\\n\\n return sum(number for number in integers if number % 2 == 0)\\n```', 'code': 'def sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError(\"Input cannot be None.\")\\n if not isinstance(numbers, list):\\n raise ValueError(\"Input must be a list.\")\\n\\n # Filtering to ensure only integers are included\\n integers = []\\n for number in numbers:\\n if isinstance(number, int):\\n integers.append(number)\\n else:\\n raise ValueError(f\"Invalid element found: {number}. All elements must be integers.\")\\n\\n return sum(number for number in integers if number % 2 == 0)'}\n",
|
||||
"INFO:agnext:Calling message handler for ReviewerAgent with message type CodeReviewTask published by CoderAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 435, \"completion_tokens\": 319, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewResult to all subscribers: {'review': \"Code review:\\ncorrectness: The function correctly handles None and non-list inputs by raising appropriate exceptions. It also accurately computes the sum of even integers in the list, which maintains correctness.\\nefficiency: The time complexity remains O(n) due to the single pass needed to filter integers and another pass for summation. However, it can be slightly optimized by combining both filtering and summation in a single comprehension, which reduces the overall pass through the list.\\nsafety: The function raises errors for invalid input types, but it does not account for cases where non-integer types might appear in the list prior to the sum calculation. This could lead to runtime errors if non-integers are inadvertently included. The error handling can be improved by ensuring that all elements are integers before performing the modulus operation.\\napproval: REVISE\\nsuggested_changes: Consider using a single loop with a generator expression to both filter and sum the even integers in one pass. This would enhance both efficiency and safety by eliminating the risk of runtime errors when performing operations on non-integer values. You can adjust the code as follows:\\n\\n```python\\ndef sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError('Input cannot be None.')\\n if not isinstance(numbers, list):\\n raise ValueError('Input must be a list.')\\n\\n return sum(number for number in numbers if isinstance(number, int) and number % 2 == 0)\\n```\", 'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'approved': False}\n",
|
||||
"INFO:agnext:Calling message handler for CoderAgent with message type CodeReviewResult published by ReviewerAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 2690, \"completion_tokens\": 130, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewTask to all subscribers: {'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'code_writing_task': 'Write a function to find the sum of all even numbers in a list.', 'code_writing_scratchpad': \"Thoughts: I appreciate the thorough review and suggestions. By combining filtering and summation into a single iteration using a generator expression, we can enhance both efficiency and safety. This ensures that only integers are considered for summation, which minimizes the risk of runtime errors. I will implement this change.\\n\\nCode:\\n```python\\ndef sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError('Input cannot be None.')\\n if not isinstance(numbers, list):\\n raise ValueError('Input must be a list.')\\n\\n return sum(number for number in numbers if isinstance(number, int) and number % 2 == 0)\\n```\", 'code': \"def sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError('Input cannot be None.')\\n if not isinstance(numbers, list):\\n raise ValueError('Input must be a list.')\\n\\n return sum(number for number in numbers if isinstance(number, int) and number % 2 == 0)\"}\n",
|
||||
"INFO:agnext:Calling message handler for ReviewerAgent with message type CodeReviewTask published by CoderAgent\n",
|
||||
"INFO:agnext.events:{\"prompt_tokens\": 495, \"completion_tokens\": 253, \"type\": \"LLMCall\"}\n",
|
||||
"INFO:agnext:Publishing message of type CodeReviewResult to all subscribers: {'review': \"Code review:\\ncorrectness: The function correctly implements the requirement of finding the sum of even numbers and raises appropriate exceptions for invalid input. It maintains accuracy in terms of results as it only sums integers that are even.\\nefficiency: While the implementation uses a generator expression to sum even integers efficiently, the previous feedback suggested combining filtering and summation into a single pass. The current approach still iterates through the list only once due to the nature of the generator expression, so it is efficient in its current form, but the suggestion for optimization is valid.\\nsafety: The function checks for None and ensures the input is a list, which increases safety. However, the filter in the generator ensures that only integers are considered for the modulus operation, preventing runtime errors from non-integer types. This aspect has been addressed adequately since the generator will exclude non-integer values.\\napproval: APPROVE\\nsuggested_changes: No further changes are necessary, but a small improvement could be adding a check to handle the situation where the list may contain only non-integers, returning 0 in such cases. Additionally, consider enhancing the documentation with a brief description of the function's behavior on edge cases.\", 'session_id': '633c4a66-2f94-472f-b4a2-09f77c164c17', 'approved': True}\n",
|
||||
"INFO:agnext:Calling message handler for CoderAgent with message type CodeReviewResult published by ReviewerAgent\n",
|
||||
"INFO:agnext:Publishing message of type CodeWritingResult to all subscribers: {'task': 'Write a function to find the sum of all even numbers in a list.', 'code': \"def sum_of_evens(numbers):\\n if numbers is None:\\n raise TypeError('Input cannot be None.')\\n if not isinstance(numbers, list):\\n raise ValueError('Input must be a list.')\\n\\n return sum(number for number in numbers if isinstance(number, int) and number % 2 == 0)\", 'review': \"Code review:\\ncorrectness: The function correctly implements the requirement of finding the sum of even numbers and raises appropriate exceptions for invalid input. It maintains accuracy in terms of results as it only sums integers that are even.\\nefficiency: While the implementation uses a generator expression to sum even integers efficiently, the previous feedback suggested combining filtering and summation into a single pass. The current approach still iterates through the list only once due to the nature of the generator expression, so it is efficient in its current form, but the suggestion for optimization is valid.\\nsafety: The function checks for None and ensures the input is a list, which increases safety. However, the filter in the generator ensures that only integers are considered for the modulus operation, preventing runtime errors from non-integer types. This aspect has been addressed adequately since the generator will exclude non-integer values.\\napproval: APPROVE\\nsuggested_changes: No further changes are necessary, but a small improvement could be adding a check to handle the situation where the list may contain only non-integers, returning 0 in such cases. Additionally, consider enhancing the documentation with a brief description of the function's behavior on edge cases.\"}\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Code Writing Result:\n",
|
||||
"--------------------------------------------------------------------------------\n",
|
||||
"Task:\n",
|
||||
"Write a function to find the sum of all even numbers in a list.\n",
|
||||
"--------------------------------------------------------------------------------\n",
|
||||
"Code:\n",
|
||||
"def sum_of_evens(numbers):\n",
|
||||
" if numbers is None:\n",
|
||||
" raise TypeError('Input cannot be None.')\n",
|
||||
" if not isinstance(numbers, list):\n",
|
||||
" raise ValueError('Input must be a list.')\n",
|
||||
"\n",
|
||||
" return sum(number for number in numbers if isinstance(number, int) and number % 2 == 0)\n",
|
||||
"--------------------------------------------------------------------------------\n",
|
||||
"Review:\n",
|
||||
"Code review:\n",
|
||||
"correctness: The function correctly implements the requirement of finding the sum of even numbers and raises appropriate exceptions for invalid input. It maintains accuracy in terms of results as it only sums integers that are even.\n",
|
||||
"efficiency: While the implementation uses a generator expression to sum even integers efficiently, the previous feedback suggested combining filtering and summation into a single pass. The current approach still iterates through the list only once due to the nature of the generator expression, so it is efficient in its current form, but the suggestion for optimization is valid.\n",
|
||||
"safety: The function checks for None and ensures the input is a list, which increases safety. However, the filter in the generator ensures that only integers are considered for the modulus operation, preventing runtime errors from non-integer types. This aspect has been addressed adequately since the generator will exclude non-integer values.\n",
|
||||
"approval: APPROVE\n",
|
||||
"suggested_changes: No further changes are necessary, but a small improvement could be adding a check to handle the situation where the list may contain only non-integers, returning 0 in such cases. Additionally, consider enhancing the documentation with a brief description of the function's behavior on edge cases.\n",
|
||||
"--------------------------------------------------------------------------------\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from agnext.application import SingleThreadedAgentRuntime\n",
|
||||
"from agnext.components.models import OpenAIChatCompletionClient\n",
|
||||
"\n",
|
||||
"runtime = SingleThreadedAgentRuntime()\n",
|
||||
"await runtime.register(\n",
|
||||
" \"ReviewerAgent\",\n",
|
||||
" lambda: ReviewerAgent(model_client=OpenAIChatCompletionClient(model=\"gpt-4o-mini\")),\n",
|
||||
")\n",
|
||||
"await runtime.register(\n",
|
||||
" \"CoderAgent\",\n",
|
||||
" lambda: CoderAgent(model_client=OpenAIChatCompletionClient(model=\"gpt-4o-mini\")),\n",
|
||||
")\n",
|
||||
"run_context = runtime.start()\n",
|
||||
"await runtime.publish_message(\n",
|
||||
" message=CodeWritingTask(\n",
|
||||
" task=\"Write a function to find the sum of all even numbers in a list.\"\n",
|
||||
" ),\n",
|
||||
" namespace=\"default\",\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Keep processing messages until idle.\n",
|
||||
"await run_context.stop_when_idle()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"The log messages show the interaction between the coder and reviewer agents.\n",
|
||||
"The final output shows the code snippet generated by the coder agent and the critique generated by the reviewer agent."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "agnext",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
@ -14,7 +14,6 @@ from agnext.core import CancellationToken
|
||||
from langchain_core.messages import HumanMessage, SystemMessage
|
||||
from langchain_core.tools import tool # pyright: ignore
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.checkpoint import MemorySaver
|
||||
from langgraph.graph import END, MessagesState, StateGraph
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
@ -83,14 +82,11 @@ class LangGraphToolUseAgent(TypeRoutedAgent):
|
||||
# This means that after `tools` is called, `agent` node is called next.
|
||||
self._workflow.add_edge("tools", "agent")
|
||||
|
||||
# Initialize memory to persist state between graph runs
|
||||
self._checkpointer = MemorySaver()
|
||||
|
||||
# Finally, we compile it!
|
||||
# This compiles it into a LangChain Runnable,
|
||||
# meaning you can use it as you would any other runnable.
|
||||
# Note that we're (optionally) passing the memory when compiling the graph
|
||||
self._app = self._workflow.compile(checkpointer=self._checkpointer)
|
||||
self._app = self._workflow.compile()
|
||||
|
||||
@message_handler
|
||||
async def handle_user_message(self, message: Message, cancellation_token: CancellationToken) -> Message:
|
||||
|
Loading…
x
Reference in New Issue
Block a user