From fcf14ee9e9295b0a134895817dab5c65b027cbf7 Mon Sep 17 00:00:00 2001
From: Eric Zhu
Date: Fri, 1 Mar 2024 13:50:58 -0800
Subject: [PATCH 01/13] Remove default trigger value for register_nested_chats
(#1833)
* remove default trigger value for register_nested_chats
* update notebook and test
---
autogen/agentchat/conversable_agent.py | 4 ++--
notebook/agentchat_nestedchat.ipynb | 1 +
test/agentchat/test_nested.py | 1 +
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py
index 6439c2099..d952fc35e 100644
--- a/autogen/agentchat/conversable_agent.py
+++ b/autogen/agentchat/conversable_agent.py
@@ -362,7 +362,7 @@ class ConversableAgent(LLMAgent):
def register_nested_chats(
self,
chat_queue: List[Dict[str, Any]],
- trigger: Union[Type[Agent], str, Agent, Callable[[Agent], bool], List] = [Agent, None],
+ trigger: Union[Type[Agent], str, Agent, Callable[[Agent], bool], List],
reply_func_from_nested_chats: Union[str, Callable] = "summary_from_nested_chats",
position: int = 2,
**kwargs,
@@ -370,7 +370,7 @@ class ConversableAgent(LLMAgent):
"""Register a nested chat reply function.
Args:
chat_queue (list): a list of chat objects to be initiated.
- trigger (Agent class, str, Agent instance, callable, or list): Default to [Agent, None]. Ref to `register_reply` for details.
+ trigger (Agent class, str, Agent instance, callable, or list): refer to `register_reply` for details.
reply_func_from_nested_chats (Callable, str): the reply function for the nested chat.
The function takes a chat_queue for nested chat, recipient agent, a list of messages, a sender agent and a config as input and returns a reply message.
Default to "summary_from_nested_chats", which corresponds to a built-in reply function that get summary from the nested chat_queue.
diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb
index c4a25871e..f9a7f78ef 100644
--- a/notebook/agentchat_nestedchat.ipynb
+++ b/notebook/agentchat_nestedchat.ipynb
@@ -800,6 +800,7 @@
"]\n",
"assistant_1.register_nested_chats(\n",
" nested_chat_queue,\n",
+ " trigger=user,\n",
")\n",
"# user.initiate_chat(assistant, message=tasks[0], max_turns=1)\n",
"\n",
diff --git a/test/agentchat/test_nested.py b/test/agentchat/test_nested.py
index 7addbe2c1..a04fd0093 100755
--- a/test/agentchat/test_nested.py
+++ b/test/agentchat/test_nested.py
@@ -112,6 +112,7 @@ def test_nested():
]
assistant.register_nested_chats(
nested_chat_queue,
+ trigger=user,
)
user.initiate_chats([{"recipient": assistant, "message": tasks[0]}, {"recipient": assistant, "message": tasks[1]}])
From 97923eeed848d843b2ad130ae6ffd77f4950512a Mon Sep 17 00:00:00 2001
From: Jack Gerrits
Date: Sat, 2 Mar 2024 01:24:56 -0500
Subject: [PATCH 02/13] Split apart ecosystem page, update sidebar, other
website tweaks (#1812)
* Split apart ecosystem page, update sidebar
* fix docs 404
* fix redirect and link
---------
Co-authored-by: Eric Zhu
---
website/docs/Ecosystem.md | 29 ------------------
website/docs/{FAQ.md => FAQ.mdx} | 22 ++-----------
.../{ => ecosystem}/img/ecosystem-fabric.png | Bin
.../{ => ecosystem}/img/ecosystem-memgpt.png | Bin
.../{ => ecosystem}/img/ecosystem-ollama.png | Bin
website/docs/ecosystem/memgpt.md | 7 +++++
website/docs/ecosystem/microsoft-fabric.md | 7 +++++
website/docs/ecosystem/ollama.md | 7 +++++
website/docs/installation/Installation.mdx | 2 +-
website/docusaurus.config.js | 25 ++++++++-------
website/sidebars.js | 1 +
11 files changed, 38 insertions(+), 62 deletions(-)
delete mode 100644 website/docs/Ecosystem.md
rename website/docs/{FAQ.md => FAQ.mdx} (87%)
rename website/docs/{ => ecosystem}/img/ecosystem-fabric.png (100%)
rename website/docs/{ => ecosystem}/img/ecosystem-memgpt.png (100%)
rename website/docs/{ => ecosystem}/img/ecosystem-ollama.png (100%)
create mode 100644 website/docs/ecosystem/memgpt.md
create mode 100644 website/docs/ecosystem/microsoft-fabric.md
create mode 100644 website/docs/ecosystem/ollama.md
diff --git a/website/docs/Ecosystem.md b/website/docs/Ecosystem.md
deleted file mode 100644
index 88e3236dd..000000000
--- a/website/docs/Ecosystem.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# Ecosystem
-This page lists libraries that have integrations with Autogen for LLM applications using multiple agents in alphabetical order. Including your own integration to this list is highly encouraged. Simply open a pull request with a few lines of text, see the dropdown below for more information.
-
-
-## MemGPT + AutoGen
-
-
-
-
-MemGPT enables LLMs to manage their own memory and overcome limited context windows. You can use MemGPT to create perpetual chatbots that learn about you and modify their own personalities over time. You can connect MemGPT to your own local filesystems and databases, as well as connect MemGPT to your own tools and APIs. The MemGPT + AutoGen integration allows you to equip any AutoGen agent with MemGPT capabilities.
-
-- [MemGPT + AutoGen Documentation with Code Examples](https://memgpt.readme.io/docs/autogen)
-
-
-## Microsoft Fabric + AutoGen
-
-
-
-[Microsoft Fabric](https://learn.microsoft.com/en-us/fabric/get-started/microsoft-fabric-overview) is an all-in-one analytics solution for enterprises that covers everything from data movement to data science, Real-Time Analytics, and business intelligence. It offers a comprehensive suite of services, including data lake, data engineering, and data integration, all in one place. In this notenook, we give a simple example for using AutoGen in Microsoft Fabric.
-
-- [Microsoft Fabric + AutoGen Code Examples](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_microsoft_fabric.ipynb)
-
-## Ollama + AutoGen
-
-
-
-[Ollama](https://ollama.com/) allows the users to run open-source large language models, such as Llama 2, locally. Ollama bundles model weights, configuration, and data into a single package, defined by a Modelfile. It optimizes setup and configuration details, including GPU usage.
-
-- [Ollama + AutoGen instruction](https://ollama.ai/blog/openai-compatibility)
diff --git a/website/docs/FAQ.md b/website/docs/FAQ.mdx
similarity index 87%
rename from website/docs/FAQ.md
rename to website/docs/FAQ.mdx
index 9d6df09e0..ee9266917 100644
--- a/website/docs/FAQ.md
+++ b/website/docs/FAQ.mdx
@@ -1,24 +1,8 @@
+import TOCInline from '@theme/TOCInline';
+
# Frequently Asked Questions
-- [Install the correct package - `pyautogen`](#install-the-correct-package---pyautogen)
-- [Set your API endpoints](#set-your-api-endpoints)
- - [Use the constructed configuration list in agents](#use-the-constructed-configuration-list-in-agents)
- - [Unexpected keyword argument 'base_url'](#unexpected-keyword-argument-base_url)
- - [How does an agent decide which model to pick out of the list?](#how-does-an-agent-decide-which-model-to-pick-out-of-the-list)
- - [Can I use non-OpenAI models?](#can-i-use-non-openai-models)
-- [Handle Rate Limit Error and Timeout Error](#handle-rate-limit-error-and-timeout-error)
-- [How to continue a finished conversation](#how-to-continue-a-finished-conversation)
-- [How do we decide what LLM is used for each agent? How many agents can be used? How do we decide how many agents in the group?](#how-do-we-decide-what-llm-is-used-for-each-agent-how-many-agents-can-be-used-how-do-we-decide-how-many-agents-in-the-group)
-- [Why is code not saved as file?](#why-is-code-not-saved-as-file)
-- [Code execution](#code-execution)
- - [Enable Python 3 docker image](#enable-python-3-docker-image)
- - [Agents keep thanking each other when using `gpt-3.5-turbo`](#agents-keep-thanking-each-other-when-using-gpt-35-turbo)
-- [ChromaDB fails in codespaces because of old version of sqlite3](#chromadb-fails-in-codespaces-because-of-old-version-of-sqlite3)
-- [How to register a reply function](#how-to-register-a-reply-function)
-- [How to get last message?](#how-to-get-last-message)
-- [How to get each agent message?](#how-to-get-each-agent-message)
-- [When using autogen docker, is it always necessary to reinstall modules?](#when-using-autogen-docker-is-it-always-necessary-to-reinstall-modules)
-- [Agents are throwing due to docker not running, how can I resolve this?](#agents-are-throwing-due-to-docker-not-running-how-can-i-resolve-this)
+
## Install the correct package - `pyautogen`
diff --git a/website/docs/img/ecosystem-fabric.png b/website/docs/ecosystem/img/ecosystem-fabric.png
similarity index 100%
rename from website/docs/img/ecosystem-fabric.png
rename to website/docs/ecosystem/img/ecosystem-fabric.png
diff --git a/website/docs/img/ecosystem-memgpt.png b/website/docs/ecosystem/img/ecosystem-memgpt.png
similarity index 100%
rename from website/docs/img/ecosystem-memgpt.png
rename to website/docs/ecosystem/img/ecosystem-memgpt.png
diff --git a/website/docs/img/ecosystem-ollama.png b/website/docs/ecosystem/img/ecosystem-ollama.png
similarity index 100%
rename from website/docs/img/ecosystem-ollama.png
rename to website/docs/ecosystem/img/ecosystem-ollama.png
diff --git a/website/docs/ecosystem/memgpt.md b/website/docs/ecosystem/memgpt.md
new file mode 100644
index 000000000..076b0c1d9
--- /dev/null
+++ b/website/docs/ecosystem/memgpt.md
@@ -0,0 +1,7 @@
+# MemGPT
+
+
+
+MemGPT enables LLMs to manage their own memory and overcome limited context windows. You can use MemGPT to create perpetual chatbots that learn about you and modify their own personalities over time. You can connect MemGPT to your own local filesystems and databases, as well as connect MemGPT to your own tools and APIs. The MemGPT + AutoGen integration allows you to equip any AutoGen agent with MemGPT capabilities.
+
+- [MemGPT + AutoGen Documentation with Code Examples](https://memgpt.readme.io/docs/autogen)
diff --git a/website/docs/ecosystem/microsoft-fabric.md b/website/docs/ecosystem/microsoft-fabric.md
new file mode 100644
index 000000000..d0593a4a7
--- /dev/null
+++ b/website/docs/ecosystem/microsoft-fabric.md
@@ -0,0 +1,7 @@
+# Microsoft Fabric
+
+
+
+[Microsoft Fabric](https://learn.microsoft.com/en-us/fabric/get-started/microsoft-fabric-overview) is an all-in-one analytics solution for enterprises that covers everything from data movement to data science, Real-Time Analytics, and business intelligence. It offers a comprehensive suite of services, including data lake, data engineering, and data integration, all in one place. In this notenook, we give a simple example for using AutoGen in Microsoft Fabric.
+
+- [Microsoft Fabric + AutoGen Code Examples](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_microsoft_fabric.ipynb)
diff --git a/website/docs/ecosystem/ollama.md b/website/docs/ecosystem/ollama.md
new file mode 100644
index 000000000..f2ffaa81e
--- /dev/null
+++ b/website/docs/ecosystem/ollama.md
@@ -0,0 +1,7 @@
+# Ollama
+
+
+
+[Ollama](https://ollama.com/) allows the users to run open-source large language models, such as Llama 2, locally. Ollama bundles model weights, configuration, and data into a single package, defined by a Modelfile. It optimizes setup and configuration details, including GPU usage.
+
+- [Ollama + AutoGen instruction](https://ollama.ai/blog/openai-compatibility)
diff --git a/website/docs/installation/Installation.mdx b/website/docs/installation/Installation.mdx
index b4bce027d..3421b7504 100644
--- a/website/docs/installation/Installation.mdx
+++ b/website/docs/installation/Installation.mdx
@@ -81,7 +81,7 @@ pip install pyautogen
## Code execution with Docker (default)
-Even if you install AutoGen locally, we highly recommend using Docker for [code execution](FAQ.md#code-execution).
+Even if you install AutoGen locally, we highly recommend using Docker for [code execution](FAQ.mdx#code-execution).
The default behaviour for code-execution agents is for code execution to be performed in a docker container.
diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
index 4a6579229..c4a7d95a3 100644
--- a/website/docusaurus.config.js
+++ b/website/docusaurus.config.js
@@ -50,7 +50,7 @@ module.exports = {
type: "doc",
docId: "reference/agentchat/conversable_agent",
position: "left",
- label: "SDK",
+ label: "API",
},
{ to: "blog", label: "Blog", position: "left" },
{
@@ -76,18 +76,9 @@ module.exports = {
// label: "Notebooks",
// },
{
- label: "Resources",
- type: "dropdown",
- items: [
- {
- type: "doc",
- docId: "Ecosystem",
- },
- {
- type: "doc",
- docId: "Gallery",
- },
- ],
+ type: "doc",
+ position: "left",
+ docId: "Gallery",
},
{
label: "Other Languages",
@@ -195,6 +186,14 @@ module.exports = {
to: "/docs/llm_configuration/",
from: ["/docs/llm_endpoint_configuration/"],
},
+ {
+ to: "/docs/ecosystem/memgpt/",
+ from: ["/docs/Ecosystem"],
+ },
+ {
+ to: "/docs/Getting-Started",
+ from: ["/docs/"],
+ },
],
},
]
diff --git a/website/sidebars.js b/website/sidebars.js
index 5b1b9d689..70f07465b 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -26,6 +26,7 @@
{'Use Cases': [{type: 'autogenerated', dirName: 'Use-Cases'}]},
'Contribute',
'Research',
+ {'Ecosystem': [{type: 'autogenerated', dirName: 'ecosystem'}]},
'Migration-Guide'
],
// pydoc-markdown auto-generated markdowns from docstrings
From d60464374db0152f3141d8ec111f19a4e8ade7e9 Mon Sep 17 00:00:00 2001
From: Jack Gerrits
Date: Sat, 2 Mar 2024 09:27:11 -0500
Subject: [PATCH 03/13] Upgrade Quarto and use notebook metadata for
frontmatter (#1836)
* Update process_notebook to use metadata instead of a yaml comment
* upgrade quarto and version check in tool
* formatting
* address comments
---
.devcontainer/Dockerfile | 8 +-
.devcontainer/dev/Dockerfile | 8 +-
.github/workflows/deploy-website.yml | 12 +-
notebook/agentchat_RetrieveChat.ipynb | 14 +-
...at_auto_feedback_from_code_execution.ipynb | 13 +-
notebook/agentchat_function_call_async.ipynb | 10 +-
notebook/agentchat_groupchat.ipynb | 10 +-
notebook/agentchat_groupchat_RAG.ipynb | 18 +-
...tchat_groupchat_finite_state_machine.ipynb | 14 +-
notebook/agentchat_logging.ipynb | 15 +-
.../agentchat_multi_task_async_chats.ipynb | 12 +-
notebook/agentchat_multi_task_chats.ipynb | 12 +-
notebook/agentchat_nestedchat.ipynb | 10 +-
notebook/agentchat_society_of_mind.ipynb | 10 +-
notebook/agentchats_sequential_chats.ipynb | 22 +-
notebook/contributing.md | 44 +++-
website/docs/llm_configuration.ipynb | 4 -
website/process_notebooks.py | 216 ++++++++----------
18 files changed, 208 insertions(+), 244 deletions(-)
mode change 100644 => 100755 website/process_notebooks.py
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 0e24c6cd5..4431ee58b 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -14,13 +14,13 @@ RUN apt-get update \
&& apt-get -y install --no-install-recommends build-essential npm \
&& apt-get autoremove -y \
&& apt-get clean -y \
- && wget https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-amd64.deb \
- && dpkg -i quarto-1.4.549-linux-amd64.deb \
- && rm -rf /var/lib/apt/lists/* quarto-1.4.549-linux-amd64.deb
+ && wget https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-amd64.deb \
+ && dpkg -i quarto-1.5.23-linux-amd64.deb \
+ && rm -rf /var/lib/apt/lists/* quarto-1.5.23-linux-amd64.deb
ENV DEBIAN_FRONTEND=dialog
# For docs
RUN npm install --global yarn
RUN pip install pydoc-markdown
RUN pip install pyyaml
-RUN pip install colored
+RUN pip install colored
diff --git a/.devcontainer/dev/Dockerfile b/.devcontainer/dev/Dockerfile
index 482157437..4749e41ba 100644
--- a/.devcontainer/dev/Dockerfile
+++ b/.devcontainer/dev/Dockerfile
@@ -33,12 +33,12 @@ RUN cd website
RUN yarn install --frozen-lockfile --ignore-engines
RUN arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) && \
- wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-${arch}.tar.gz && \
+ wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-${arch}.tar.gz && \
mkdir -p /home/autogen/quarto/ && \
- tar -xzf quarto-1.4.549-linux-${arch}.tar.gz --directory /home/autogen/quarto/ && \
- rm quarto-1.4.549-linux-${arch}.tar.gz
+ tar -xzf quarto-1.5.23-linux-${arch}.tar.gz --directory /home/autogen/quarto/ && \
+ rm quarto-1.5.23-linux-${arch}.tar.gz
-ENV PATH="${PATH}:/home/autogen/quarto/quarto-1.4.549/bin/"
+ENV PATH="${PATH}:/home/autogen/quarto/quarto-1.5.23/bin/"
# Exposes the Yarn port for Docusaurus
EXPOSE 3000
diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml
index c9c7deede..ae866ffca 100644
--- a/.github/workflows/deploy-website.yml
+++ b/.github/workflows/deploy-website.yml
@@ -44,9 +44,9 @@ jobs:
- name: quarto install
working-directory: ${{ runner.temp }}
run: |
- wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-amd64.tar.gz
- tar -xzf quarto-1.4.549-linux-amd64.tar.gz
- echo "$(pwd)/quarto-1.4.549/bin/" >> $GITHUB_PATH
+ wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-amd64.tar.gz
+ tar -xzf quarto-1.5.23-linux-amd64.tar.gz
+ echo "$(pwd)/quarto-1.5.23/bin/" >> $GITHUB_PATH
- name: quarto run
run: |
quarto render .
@@ -90,9 +90,9 @@ jobs:
- name: quarto install
working-directory: ${{ runner.temp }}
run: |
- wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-amd64.tar.gz
- tar -xzf quarto-1.4.549-linux-amd64.tar.gz
- echo "$(pwd)/quarto-1.4.549/bin/" >> $GITHUB_PATH
+ wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-amd64.tar.gz
+ tar -xzf quarto-1.5.23-linux-amd64.tar.gz
+ echo "$(pwd)/quarto-1.5.23/bin/" >> $GITHUB_PATH
- name: quarto run
run: |
quarto render .
diff --git a/notebook/agentchat_RetrieveChat.ipynb b/notebook/agentchat_RetrieveChat.ipynb
index 6c965d0e7..92620a55b 100644
--- a/notebook/agentchat_RetrieveChat.ipynb
+++ b/notebook/agentchat_RetrieveChat.ipynb
@@ -5,12 +5,6 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "\n",
- "\n",
"# Using RetrieveChat for Retrieve Augmented Code Generation and Question Answering\n",
"\n",
"AutoGen offers conversable agents powered by LLM, tool or human, which can be used to perform tasks collectively via automated chat. This framework allows tool use and human participation through multi-agent conversation.\n",
@@ -3020,6 +3014,10 @@
}
],
"metadata": {
+ "front_matter": {
+ "tags": ["RAG"],
+ "description": "Explore the use of AutoGen's RetrieveChat for tasks like code generation from docstrings, answering complex questions with human feedback, and exploiting features like Update Context, custom prompts, and few-shot learning."
+ },
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
@@ -3036,8 +3034,8 @@
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
-},
-"test_skip": "Requires interactive usage"
+ },
+ "skip_test": "Requires interactive usage"
},
"nbformat": 4,
"nbformat_minor": 4
diff --git a/notebook/agentchat_auto_feedback_from_code_execution.ipynb b/notebook/agentchat_auto_feedback_from_code_execution.ipynb
index 8f232975b..1598c66f8 100644
--- a/notebook/agentchat_auto_feedback_from_code_execution.ipynb
+++ b/notebook/agentchat_auto_feedback_from_code_execution.ipynb
@@ -8,12 +8,6 @@
}
},
"source": [
- "\n",
- "\n",
"# Task Solving with Code Generation, Execution and Debugging\n",
"\n",
"AutoGen offers conversable LLM agents, which can be used to solve various tasks with human or automatic feedback, including tasks that require using tools via code.\n",
@@ -1098,6 +1092,13 @@
}
],
"metadata": {
+ "front_matter": {
+ "tags": [
+ "code generation",
+ "debugging"
+ ],
+ "description": "Use conversable language learning model agents to solve tasks and provide automatic feedback through a comprehensive example of writing, executing, and debugging Python code to compare stock price changes."
+ },
"kernelspec": {
"display_name": "Python 3",
"language": "python",
diff --git a/notebook/agentchat_function_call_async.ipynb b/notebook/agentchat_function_call_async.ipynb
index e7da183f5..b30357259 100644
--- a/notebook/agentchat_function_call_async.ipynb
+++ b/notebook/agentchat_function_call_async.ipynb
@@ -5,12 +5,6 @@
"id": "ae1f50ec",
"metadata": {},
"source": [
- "\n",
- "\n",
"# Task Solving with Provided Tools as Functions (Asynchronous Function Calls)\n"
]
},
@@ -366,6 +360,10 @@
}
],
"metadata": {
+ "front_matter": {
+ "tags": ["code generation", "function call", "async"],
+ "description": "Learn how to implement both synchronous and asynchronous function calls using AssistantAgent and UserProxyAgent in AutoGen, with examples of their application in individual and group chat settings for task execution with language models."
+ },
"kernelspec": {
"display_name": "flaml_dev",
"language": "python",
diff --git a/notebook/agentchat_groupchat.ipynb b/notebook/agentchat_groupchat.ipynb
index 058b687b3..bb45ade83 100644
--- a/notebook/agentchat_groupchat.ipynb
+++ b/notebook/agentchat_groupchat.ipynb
@@ -5,12 +5,6 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "\n",
- "\n",
"# Group Chat\n",
"\n",
"AutoGen offers conversable agents powered by LLM, tool or human, which can be used to perform tasks collectively via automated chat. This framework allows tool use and human participation through multi-agent conversation.\n",
@@ -223,6 +217,10 @@
}
],
"metadata": {
+ "front_matter": {
+ "tags": ["orchestration", "group chat"],
+ "description": "Explore the utilization of large language models in automated group chat scenarios, where agents perform tasks collectively, demonstrating how they can be configured, interact with each other, and retrieve specific information from external resources."
+ },
"kernelspec": {
"display_name": "flaml",
"language": "python",
diff --git a/notebook/agentchat_groupchat_RAG.ipynb b/notebook/agentchat_groupchat_RAG.ipynb
index 6e06a0ec2..50d35f5a9 100644
--- a/notebook/agentchat_groupchat_RAG.ipynb
+++ b/notebook/agentchat_groupchat_RAG.ipynb
@@ -1,17 +1,5 @@
{
"cells": [
- {
- "attachments": {},
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- ""
- ]
- },
{
"attachments": {},
"cell_type": "markdown",
@@ -1120,6 +1108,10 @@
}
],
"metadata": {
+ "front_matter": {
+ "tags": ["group chat", "orchestration", "RAG"],
+ "description": "Implement and manage a multi-agent chat system using AutoGen, where AI assistants retrieve information, generate code, and interact collaboratively to solve complex tasks, especially in areas not covered by their training data."
+ },
"kernelspec": {
"display_name": "flaml",
"language": "python",
@@ -1135,7 +1127,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.13"
+ "version": "3.11.7"
}
},
"nbformat": 4,
diff --git a/notebook/agentchat_groupchat_finite_state_machine.ipynb b/notebook/agentchat_groupchat_finite_state_machine.ipynb
index 3a05af280..e24bb5bbb 100644
--- a/notebook/agentchat_groupchat_finite_state_machine.ipynb
+++ b/notebook/agentchat_groupchat_finite_state_machine.ipynb
@@ -5,13 +5,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "\n",
- "\n",
- "# FSM - User can input speaker transition contraints.\n",
+ "# FSM - User can input speaker transition constraints\n",
"\n",
"AutoGen offers conversable agents powered by LLM, tool, or human, which can be used to perform tasks collectively via automated chat. This framework allows tool use and human participation through multi-agent conversation.\n",
"Please find documentation about this feature [here](https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat).\n",
@@ -597,6 +591,12 @@
}
],
"metadata": {
+ "front_matter": {
+ "description": "Explore the demonstration of the Finite State Machine implementation, which allows the user to input speaker transition constraints.",
+ "tags": [
+ "orchestration"
+ ]
+ },
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
diff --git a/notebook/agentchat_logging.ipynb b/notebook/agentchat_logging.ipynb
index 5d8a824e9..c06a8fd74 100644
--- a/notebook/agentchat_logging.ipynb
+++ b/notebook/agentchat_logging.ipynb
@@ -4,12 +4,6 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "\n",
- "\n",
"# Runtime Logging with AutoGen \n",
"\n",
"AutoGen offers utilities to log data for debugging and performance analysis. This notebook demonstrates how to use them. \n",
@@ -294,6 +288,13 @@
}
],
"metadata": {
+ "front_matter": {
+ "description": "Provide capabilities of runtime logging for debugging and performance analysis.",
+ "tags": [
+ "logging",
+ "debugging"
+ ]
+ },
"kernelspec": {
"display_name": "autog",
"language": "python",
@@ -309,7 +310,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.13"
+ "version": "3.11.7"
}
},
"nbformat": 4,
diff --git a/notebook/agentchat_multi_task_async_chats.ipynb b/notebook/agentchat_multi_task_async_chats.ipynb
index 16b46ab16..bc7d35dab 100644
--- a/notebook/agentchat_multi_task_async_chats.ipynb
+++ b/notebook/agentchat_multi_task_async_chats.ipynb
@@ -9,12 +9,6 @@
}
},
"source": [
- "\n",
- "\n",
"# Solving Multiple Tasks in a Sequence of Async Chats\n",
"\n",
"This notebook showcases how to use the new chat interface of conversational agents in AutoGen: a_initiate_chats, to conduct a series of tasks. Similar to \"notebook/agentchat_microsoft_fabric.ipynb\", this new interface allows one to pass multiple tasks and their corresponding dedicated agents and execute concurrently. Depending on the prerequisite task(s), the tasks will be solved concurrently, with the summaries from prerequisite task(s) provided to subsequent tasks as context, if the `summary_method` argument is specified.\n",
@@ -1484,6 +1478,10 @@
}
],
"metadata": {
+ "front_matter": {
+ "tags": ["sequential chat"],
+ "description": "Use conversational agents to solve a set of tasks with a sequence of async chats."
+ },
"kernelspec": {
"display_name": "Python 3",
"language": "python",
@@ -1499,7 +1497,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.8"
+ "version": "3.11.7"
},
"vscode": {
"interpreter": {
diff --git a/notebook/agentchat_multi_task_chats.ipynb b/notebook/agentchat_multi_task_chats.ipynb
index ab65f6e0f..3f10bb072 100644
--- a/notebook/agentchat_multi_task_chats.ipynb
+++ b/notebook/agentchat_multi_task_chats.ipynb
@@ -9,12 +9,6 @@
}
},
"source": [
- "\n",
- "\n",
"# Solving Multiple Tasks in a Sequence of Chats\n",
"\n",
"This notebook showcases how to use the new chat interface of conversational agents in AutoGen: initiate_chats, to conduct a series of tasks. This new interface allows one to pass multiple tasks and their corresponding dedicated agents. Once initiate_chats is invoked, the tasks will be solved sequentially, with the summaries from previous tasks provided to subsequent tasks as context, if the `summary_method` argument is specified.\n",
@@ -1536,6 +1530,12 @@
}
],
"metadata": {
+ "front_matter": {
+ "description": "Use conversational agents to solve a set of tasks with a sequence of chats.",
+ "tags": [
+ "sequential chat"
+ ]
+ },
"kernelspec": {
"display_name": "Python 3",
"language": "python",
diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb
index f9a7f78ef..03a8afa8d 100644
--- a/notebook/agentchat_nestedchat.ipynb
+++ b/notebook/agentchat_nestedchat.ipynb
@@ -9,12 +9,6 @@
}
},
"source": [
- "\n",
- "\n",
"# Solving Complex Tasks with Nested Chats\n",
"\n",
"This notebook shows how you can leverage \"nested chats\" to solve complex task with AutoGen. Nested chats allow AutoGen agents to use other agents as their inner monologue to accomplish tasks. This abstraction is powerful as it allows you to compose agents in rich ways. This notebook shows how you can nest a pretty complex sequence of chats among _inner_ agents inside an _outer_ agent.\n",
@@ -814,6 +808,10 @@
}
],
"metadata": {
+ "front_matter": {
+ "tags": ["nested chat"],
+ "description": "Solve complex tasks with one or more sequence chats nested as inner monologue."
+ },
"kernelspec": {
"display_name": "Python 3",
"language": "python",
diff --git a/notebook/agentchat_society_of_mind.ipynb b/notebook/agentchat_society_of_mind.ipynb
index b395c1433..be9ef5521 100644
--- a/notebook/agentchat_society_of_mind.ipynb
+++ b/notebook/agentchat_society_of_mind.ipynb
@@ -5,12 +5,6 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "\n",
- "\n",
"# SocietyOfMindAgent\n",
"\n",
"This notebook demonstrates the SocietyOfMindAgent, which runs a group chat as an internal monologue, but appears to the external world as a single agent. This confers three distinct advantages:\n",
@@ -362,6 +356,10 @@
}
],
"metadata": {
+ "front_matter": {
+ "tags": ["orchestration"],
+ "description": "Explore the demonstration of the SocietyOfMindAgent in the AutoGen library, which runs a group chat as an internal monologue, but appears to the external world as a single agent, offering a structured way to manage complex interactions among multiple agents and handle issues such as extracting responses from complex dialogues and dealing with context window constraints."
+ },
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
diff --git a/notebook/agentchats_sequential_chats.ipynb b/notebook/agentchats_sequential_chats.ipynb
index 22a3eafa9..9b5904050 100644
--- a/notebook/agentchats_sequential_chats.ipynb
+++ b/notebook/agentchats_sequential_chats.ipynb
@@ -1,16 +1,5 @@
{
"cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- ""
- ]
- },
{
"attachments": {},
"cell_type": "markdown",
@@ -20,11 +9,6 @@
}
},
"source": [
- "\n",
"# Solving Multiple Tasks in a Sequence of Chats\n",
"\n",
"This notebook showcases how to use the new chat interface `autogen.initiate_chats` to solve a set of tasks with a sequence of chats. \n",
@@ -837,6 +821,12 @@
}
],
"metadata": {
+ "front_matter": {
+ "description": "Use AutoGen to solve a set of tasks with a sequence of chats.",
+ "tags": [
+ "sequential chats"
+ ]
+ },
"kernelspec": {
"display_name": "Python 3",
"language": "python",
diff --git a/notebook/contributing.md b/notebook/contributing.md
index 4234bce6d..c717939b2 100644
--- a/notebook/contributing.md
+++ b/notebook/contributing.md
@@ -2,16 +2,23 @@
## How to get a notebook displayed on the website
-Ensure the first cell is markdown and before absolutely anything else include the following yaml within a comment.
-
-```markdown
-
+In the notebook metadata set the `tags` and `description` `front_matter` properties. For example:
+
+```json
+{
+ "...": "...",
+ "metadata": {
+ "...": "...",
+ "front_matter": {
+ "tags": ["code generation", "debugging"],
+ "description": "Use conversable language learning model agents to solve tasks and provide automatic feedback through a comprehensive example of writing, executing, and debugging Python code to compare stock price changes."
+ }
+ }
+}
```
+**Note**: Notebook metadata can be edited by opening the notebook in a text editor (Or "Open With..." -> "Text Editor" in VSCode)
+
The `tags` field is a list of tags that will be used to categorize the notebook. The `description` field is a brief description of the notebook.
## Best practices for authoring notebooks
@@ -102,9 +109,26 @@ If a notebook needs to be skipped then add to the notebook metadata:
{
"...": "...",
"metadata": {
- "test_skip": "REASON"
+ "skip_test": "REASON"
}
}
```
-Note: Notebook metadata can be edited by opening the notebook in a text editor (Or "Open With..." -> "Text Editor" in VSCode)
+## Metadata fields
+
+All possible metadata fields are as follows:
+```json
+{
+ "...": "...",
+ "metadata": {
+ "...": "...",
+ "front_matter": {
+ "tags": "List[str] - List of tags to categorize the notebook",
+ "description": "str - Brief description of the notebook",
+ },
+ "skip_test": "str - Reason for skipping the test. If present, the notebook will be skipped during testing",
+ "skip_render": "str - Reason for skipping rendering the notebook. If present, the notebook will be left out of the website.",
+ "extra_files_to_copy": "List[str] - List of files to copy to the website. The paths are relative to the notebook directory",
+ }
+}
+```
diff --git a/website/docs/llm_configuration.ipynb b/website/docs/llm_configuration.ipynb
index 5691f1d79..e7fad3cf3 100644
--- a/website/docs/llm_configuration.ipynb
+++ b/website/docs/llm_configuration.ipynb
@@ -4,10 +4,6 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "---\n",
- "custom_edit_url: https://github.com/microsoft/autogen/edit/main/website/docs/llm_configuration.ipynb\n",
- "---\n",
- "\n",
"# LLM Configuration\n",
"\n",
"In AutoGen, agents use LLMs as key components to understand and react. To configure an agent's access to LLMs, you can specify an `llm_config` argument in its constructor. For example, the following snippet shows a configuration that uses `gpt-4`:"
diff --git a/website/process_notebooks.py b/website/process_notebooks.py
old mode 100644
new mode 100755
index 835a69a35..8a2875c22
--- a/website/process_notebooks.py
+++ b/website/process_notebooks.py
@@ -15,9 +15,10 @@ import typing
import concurrent.futures
import os
-from typing import Optional, Tuple, Union
+from typing import Any, Dict, Optional, Tuple, Union
from dataclasses import dataclass
+
from multiprocessing import current_process
try:
@@ -26,6 +27,7 @@ except ImportError:
print("pyyaml not found.\n\nPlease install pyyaml:\n\tpip install pyyaml\n")
sys.exit(1)
+
try:
import nbclient
from nbclient.client import (
@@ -65,7 +67,12 @@ class Result:
def check_quarto_bin(quarto_bin: str = "quarto") -> None:
"""Check if quarto is installed."""
try:
- subprocess.check_output([quarto_bin, "--version"])
+ version = subprocess.check_output([quarto_bin, "--version"], text=True).strip()
+ version = tuple(map(int, version.split(".")))
+ if version < (1, 5, 23):
+ print("Quarto version is too old. Please upgrade to 1.5.23 or later.")
+ sys.exit(1)
+
except FileNotFoundError:
print("Quarto is not installed. Please install it from https://quarto.org")
sys.exit(1)
@@ -76,32 +83,9 @@ def notebooks_target_dir(website_directory: Path) -> Path:
return website_directory / "docs" / "notebooks"
-def extract_yaml_from_notebook(notebook: Path) -> typing.Optional[typing.Dict]:
- with open(notebook, "r", encoding="utf-8") as f:
- content = f.read()
-
- json_content = json.loads(content)
- first_cell = json_content["cells"][0]
-
- # must exists on lines on their own
- if first_cell["cell_type"] != "markdown":
- return None
-
- lines = first_cell["source"]
- if "" not in lines:
- return None
-
- closing_arrow_idx = lines.index("-->")
-
- front_matter_lines = lines[1:closing_arrow_idx]
- front_matter = yaml.safe_load("\n".join(front_matter_lines))
- return front_matter
+def load_metadata(notebook: Path) -> typing.Dict:
+ content = json.load(notebook.open())
+ return content["metadata"]
def skip_reason_or_none_if_ok(notebook: Path) -> typing.Optional[str]:
@@ -125,29 +109,17 @@ def skip_reason_or_none_if_ok(notebook: Path) -> typing.Optional[str]:
first_cell = json_content["cells"][0]
# must exists on lines on their own
- if first_cell["cell_type"] != "markdown":
- return "first cell is not markdown"
-
- lines = first_cell["source"]
- if "" not in lines:
- return "no closing --> found, or it is not on a line on its own"
+ metadata = load_metadata(notebook)
- try:
- front_matter = extract_yaml_from_notebook(notebook)
- except yaml.YAMLError as e:
- return colored(f"Failed to parse front matter in {notebook.name}: {e}", "red")
+ if "front_matter" not in metadata:
+ return "front matter missing from notebook metadata ⚠️"
- # Should not be none at this point as we have already done the same checks as in extract_yaml_from_notebook
- assert front_matter is not None, f"Front matter is None for {notebook.name}"
-
- if "skip" in front_matter and front_matter["skip"] is True:
- return "skip is set to true"
+ front_matter = metadata["front_matter"]
if "tags" not in front_matter:
return "tags is not in front matter"
@@ -171,16 +143,20 @@ def process_notebook(src_notebook: Path, website_dir: Path, notebook_dir: Path,
in_notebook_dir = "notebook" in src_notebook.parts
+ metadata = load_metadata(src_notebook)
+ if "skip_render" in metadata:
+ return fmt_skip(src_notebook, "skip_render is in notebook metadata")
+
if in_notebook_dir:
- relative_notebook = src_notebook.relative_to(notebook_dir)
+ relative_notebook = src_notebook.resolve().relative_to(notebook_dir.resolve())
dest_dir = notebooks_target_dir(website_directory=website_dir)
- target_mdx_file = dest_dir / relative_notebook.with_suffix(".mdx")
+ target_file = dest_dir / relative_notebook.with_suffix(".mdx")
intermediate_notebook = dest_dir / relative_notebook
# If the intermediate_notebook already exists, check if it is newer than the source file
- if target_mdx_file.exists():
- if target_mdx_file.stat().st_mtime > src_notebook.stat().st_mtime:
- return colored(f"Skipping {src_notebook.name}, as target file is newer", "blue")
+ if target_file.exists():
+ if target_file.stat().st_mtime > src_notebook.stat().st_mtime:
+ return fmt_skip(src_notebook, f"target file ({target_file.name}) is newer ☑️")
if dry_run:
return colored(f"Would process {src_notebook.name}", "green")
@@ -191,11 +167,8 @@ def process_notebook(src_notebook: Path, website_dir: Path, notebook_dir: Path,
# Check if another file has to be copied too
# Solely added for the purpose of agent_library_example.json
- front_matter = extract_yaml_from_notebook(src_notebook)
- # Should not be none at this point as we have already done the same checks as in extract_yaml_from_notebook
- assert front_matter is not None, f"Front matter is None for {src_notebook.name}"
- if "extra_files_to_copy" in front_matter:
- for file in front_matter["extra_files_to_copy"]:
+ if "extra_files_to_copy" in metadata:
+ for file in metadata["extra_files_to_copy"]:
shutil.copy(src_notebook.parent / file, dest_dir / file)
# Capture output
@@ -203,28 +176,19 @@ def process_notebook(src_notebook: Path, website_dir: Path, notebook_dir: Path,
[quarto_bin, "render", intermediate_notebook], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
if result.returncode != 0:
- return (
- colored(f"Failed to render {intermediate_notebook}", "red")
- + f"\n{result.stderr}"
- + f"\n{result.stdout}"
+ return fmt_error(
+ src_notebook, f"Failed to render {src_notebook}\n\nstderr:\n{result.stderr}\nstdout:\n{result.stdout}"
)
# Unlink intermediate files
intermediate_notebook.unlink()
-
- if "extra_files_to_copy" in front_matter:
- for file in front_matter["extra_files_to_copy"]:
- (dest_dir / file).unlink()
-
- # Post process the file
- post_process_mdx(target_mdx_file)
else:
- target_mdx_file = src_notebook.with_suffix(".mdx")
+ target_file = src_notebook.with_suffix(".mdx")
# If the intermediate_notebook already exists, check if it is newer than the source file
- if target_mdx_file.exists():
- if target_mdx_file.stat().st_mtime > src_notebook.stat().st_mtime:
- return colored(f"Skipping {src_notebook.name}, as target file is newer", "blue")
+ if target_file.exists():
+ if target_file.stat().st_mtime > src_notebook.stat().st_mtime:
+ return fmt_skip(src_notebook, f"target file ({target_file.name}) is newer ☑️")
if dry_run:
return colored(f"Would process {src_notebook.name}", "green")
@@ -233,9 +197,17 @@ def process_notebook(src_notebook: Path, website_dir: Path, notebook_dir: Path,
[quarto_bin, "render", src_notebook], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
)
if result.returncode != 0:
- return colored(f"Failed to render {src_notebook}", "red") + f"\n{result.stderr}" + f"\n{result.stdout}"
+ return fmt_error(
+ src_notebook, f"Failed to render {src_notebook}\n\nstderr:\n{result.stderr}\nstdout:\n{result.stdout}"
+ )
+
+ front_matter = {}
+ if "front_matter" in metadata:
+ front_matter = metadata["front_matter"]
- return colored(f"Processed {src_notebook.name}", "green")
+ post_process_mdx(target_file, front_matter)
+
+ return fmt_ok(src_notebook)
# Notebook execution based on nbmake: https://github.com/treebeardtech/nbmakes
@@ -258,21 +230,14 @@ NB_VERSION = 4
def test_notebook(notebook_path: Path, timeout: int = 300) -> Tuple[Path, Optional[Union[NotebookError, NotebookSkip]]]:
nb = nbformat.read(str(notebook_path), NB_VERSION)
- allow_errors = False
- if "execution" in nb.metadata:
- if "timeout" in nb.metadata.execution:
- timeout = nb.metadata.execution.timeout
- if "allow_errors" in nb.metadata.execution:
- allow_errors = nb.metadata.execution.allow_errors
-
- if "test_skip" in nb.metadata:
- return notebook_path, NotebookSkip(reason=nb.metadata.test_skip)
+ if "skip_test" in nb.metadata:
+ return notebook_path, NotebookSkip(reason=nb.metadata.skip_test)
try:
c = NotebookClient(
nb,
timeout=timeout,
- allow_errors=allow_errors,
+ allow_errors=False,
record_timing=True,
)
os.environ["PYDEVD_DISABLE_FILE_VALIDATION"] = "1"
@@ -327,24 +292,19 @@ def get_error_info(nb: NotebookNode) -> Optional[NotebookError]:
# rendered_notebook is the final mdx file
-def post_process_mdx(rendered_mdx: Path) -> None:
+def post_process_mdx(rendered_mdx: Path, front_matter: Dict) -> None:
notebook_name = f"{rendered_mdx.stem}.ipynb"
with open(rendered_mdx, "r", encoding="utf-8") as f:
content = f.read()
- # Check for existence of "export const quartoRawHtml", this indicates there was a front matter line in the file
- if "export const quartoRawHtml" not in content:
- raise ValueError(f"File {rendered_mdx} does not contain 'export const quartoRawHtml'")
-
- # Extract the text between
- front_matter = content.split("")[0]
- # Strip empty lines before and after
- front_matter = "\n".join([line for line in front_matter.split("\n") if line.strip() != ""])
+ # If there is front matter in the mdx file, we need to remove it
+ if content.startswith("---"):
+ front_matter_end = content.find("---", 3)
+ front_matter = yaml.safe_load(content[4:front_matter_end])
+ content = content[front_matter_end + 3 :]
- # add file path
- front_matter += f"\nsource_notebook: /notebook/{notebook_name}"
- # Custom edit url
- front_matter += f"\ncustom_edit_url: https://github.com/microsoft/autogen/edit/main/notebook/{notebook_name}"
+ front_matter["source_notebook"] = f"/notebook/{notebook_name}"
+ front_matter["custom_edit_url"] = f"https://github.com/microsoft/autogen/edit/main/notebook/{notebook_name}"
# inject in content directly after the markdown title the word done
# Find the end of the line with the title
@@ -352,8 +312,11 @@ def post_process_mdx(rendered_mdx: Path) -> None:
# Extract page title
title = content[content.find("#") + 1 : content.find("\n", content.find("#"))].strip()
+ # If there is a { in the title we trim off the { and everything after it
+ if "{" in title:
+ title = title[: title.find("{")].strip()
- front_matter += f"\ntitle: {title}"
+ front_matter["title"] = title
github_link = f"https://github.com/microsoft/autogen/blob/main/notebook/{notebook_name}"
content = (
@@ -374,12 +337,15 @@ def post_process_mdx(rendered_mdx: Path) -> None:
+ content[title_end:]
)
+ # Dump front_matter to ysaml
+ front_matter = yaml.dump(front_matter, default_flow_style=False)
+
# Rewrite the content as
# ---
# front_matter
# ---
# content
- new_content = f"---\n{front_matter}\n---\n{content}"
+ new_content = f"---\n{front_matter}---\n{content}"
with open(rendered_mdx, "w", encoding="utf-8") as f:
f.write(new_content)
@@ -395,6 +361,18 @@ def collect_notebooks(notebook_directory: Path, website_directory: Path) -> typi
return notebooks
+def fmt_skip(notebook: Path, reason: str) -> None:
+ return f"{colored('[Skip]', 'yellow')} {colored(notebook.name, 'blue')}: {reason}"
+
+
+def fmt_ok(notebook: Path) -> None:
+ return f"{colored('[OK]', 'green')} {colored(notebook.name, 'blue')} ✅"
+
+
+def fmt_error(notebook: Path, error: NotebookError) -> None:
+ return f"{colored('[Error]', 'red')} {colored(notebook.name, 'blue')}: {error.error_name} - {error.error_value}"
+
+
def start_thread_to_terminate_when_parent_process_dies(ppid: int):
pid = os.getpid()
@@ -424,7 +402,6 @@ def main() -> None:
parser.add_argument(
"--website-directory", type=path, help="Root directory of docusarus website", default=script_dir
)
- parser.add_argument("--workers", help="Number of workers to use", type=int, default=-1)
render_parser = subparsers.add_parser("render")
render_parser.add_argument("--quarto-bin", help="Path to quarto binary", default="quarto")
@@ -435,10 +412,9 @@ def main() -> None:
test_parser.add_argument("--timeout", help="Timeout for each notebook", type=int, default=60)
test_parser.add_argument("--exit-on-first-fail", "-e", help="Exit after first test fail", action="store_true")
test_parser.add_argument("notebooks", type=path, nargs="*", default=None)
+ test_parser.add_argument("--workers", help="Number of workers to use", type=int, default=-1)
args = parser.parse_args()
- if args.workers == -1:
- args.workers = None
if args.subcommand is None:
print("No subcommand specified")
@@ -453,13 +429,13 @@ def main() -> None:
for notebook in collected_notebooks:
reason = skip_reason_or_none_if_ok(notebook)
if reason:
- print(f"{colored('[Skip]', 'yellow')} {colored(notebook.name, 'blue')}: {reason}")
+ print(fmt_skip(notebook, reason))
else:
filtered_notebooks.append(notebook)
- print(f"Processing {len(filtered_notebooks)} notebook{'s' if len(filtered_notebooks) != 1 else ''}...")
-
if args.subcommand == "test":
+ if args.workers == -1:
+ args.workers = None
failure = False
with concurrent.futures.ProcessPoolExecutor(
max_workers=args.workers,
@@ -471,14 +447,15 @@ def main() -> None:
notebook, optional_error_or_skip = future.result()
if isinstance(optional_error_or_skip, NotebookError):
if optional_error_or_skip.error_name == "timeout":
- print(
- f"{colored('[Error]', 'red')} {colored(notebook.name, 'blue')}: {optional_error_or_skip.error_name}"
- )
+ print(fmt_error(notebook, optional_error_or_skip.error_name))
else:
print("-" * 80)
+
print(
- f"{colored('[Error]', 'red')} {colored(notebook.name, 'blue')}: {optional_error_or_skip.error_name} - {optional_error_or_skip.error_value}"
+ fmt_error(
+ notebook, f"{optional_error_or_skip.error_name} - {optional_error_or_skip.error_value}"
+ )
)
print(optional_error_or_skip.traceback)
print("-" * 80)
@@ -486,11 +463,9 @@ def main() -> None:
sys.exit(1)
failure = True
elif isinstance(optional_error_or_skip, NotebookSkip):
- print(
- f"{colored('[Skip]', 'yellow')} {colored(notebook.name, 'blue')}: {optional_error_or_skip.reason}"
- )
+ print(fmt_skip(notebook, optional_error_or_skip.reason))
else:
- print(f"{colored('[OK]', 'green')} {colored(notebook.name, 'blue')}")
+ print(fmt_ok(notebook))
if failure:
sys.exit(1)
@@ -501,15 +476,12 @@ def main() -> None:
if not notebooks_target_dir(args.website_directory).exists():
notebooks_target_dir(args.website_directory).mkdir(parents=True)
- with concurrent.futures.ProcessPoolExecutor(max_workers=args.workers) as executor:
- futures = [
- executor.submit(
- process_notebook, f, args.website_directory, args.notebook_directory, args.quarto_bin, args.dry_run
+ for notebook in filtered_notebooks:
+ print(
+ process_notebook(
+ notebook, args.website_directory, args.notebook_directory, args.quarto_bin, args.dry_run
)
- for f in filtered_notebooks
- ]
- for future in concurrent.futures.as_completed(futures):
- print(future.result())
+ )
else:
print("Unknown subcommand")
sys.exit(1)
From f2e42326e1c8f5a448a3dcc2d6a359073619dd84 Mon Sep 17 00:00:00 2001
From: Grigorij Dudnik <50213363+GregorD1A1@users.noreply.github.com>
Date: Sat, 2 Mar 2024 19:03:52 +0100
Subject: [PATCH 04/13] Fix for bugs appeared when using function calls with
clear history functionality (#1531)
* resolved errors happening when using function calling and clear history
* checking in nr_of_messages_to_preserve were provided
* code formatting
* test added, dict signature improved
* test added, dict signature improved
* test added, dict signature improved
* test added, dict signature improved
* test added, dict signature improved
* test added, dict signature improved
* test added, dict signature improved
* test added, dict signature improved
* Test updated
Co-authored-by: Chi Wang
* test improved
* test improved
* comment about preserving additional message added
* commentary about clear history called in tool response improved
* created test for clear hisotry called from tool response
* code formatting
* added 'USER INTERRUPTED' as internal content of tool response
* added separate vatiable 'nr_messages_to_preserve_internal'
---------
Co-authored-by: Chi Wang
---
autogen/agentchat/conversable_agent.py | 12 +++-
autogen/agentchat/groupchat.py | 26 +++++--
test/agentchat/test_groupchat.py | 97 ++++++++++++++++++++++++++
3 files changed, 127 insertions(+), 8 deletions(-)
diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py
index d952fc35e..b503403d0 100644
--- a/autogen/agentchat/conversable_agent.py
+++ b/autogen/agentchat/conversable_agent.py
@@ -1126,8 +1126,18 @@ class ConversableAgent(LLMAgent):
if recipient is None:
if nr_messages_to_preserve:
for key in self._oai_messages:
+ nr_messages_to_preserve_internal = nr_messages_to_preserve
+ # if breaking history between function call and function response, save function call message
+ # additionally, otherwise openai will return error
+ first_msg_to_save = self._oai_messages[key][-nr_messages_to_preserve_internal]
+ if "tool_responses" in first_msg_to_save:
+ nr_messages_to_preserve_internal += 1
+ print(
+ f"Preserving one more message for {self.name} to not divide history between tool call and "
+ f"tool response."
+ )
# Remove messages from history except last `nr_messages_to_preserve` messages.
- self._oai_messages[key] = self._oai_messages[key][-nr_messages_to_preserve:]
+ self._oai_messages[key] = self._oai_messages[key][-nr_messages_to_preserve_internal:]
else:
self._oai_messages.clear()
else:
diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py
index fbb7b88af..c9ebe82c3 100644
--- a/autogen/agentchat/groupchat.py
+++ b/autogen/agentchat/groupchat.py
@@ -596,9 +596,11 @@ class GroupChatManager(ConversableAgent):
if (
groupchat.enable_clear_history
and isinstance(reply, dict)
+ and reply["content"]
and "CLEAR HISTORY" in reply["content"].upper()
):
- reply["content"] = self.clear_agents_history(reply["content"], groupchat)
+ reply["content"] = self.clear_agents_history(reply, groupchat)
+
# The speaker sends the message without requesting a reply
speaker.send(reply, self, request_reply=False)
message = self.last_message(speaker)
@@ -684,7 +686,7 @@ class GroupChatManager(ConversableAgent):
for agent in self._groupchat.agents:
agent._raise_exception_on_async_reply_functions()
- def clear_agents_history(self, reply: str, groupchat: GroupChat) -> str:
+ def clear_agents_history(self, reply: dict, groupchat: GroupChat) -> str:
"""Clears history of messages for all agents or selected one. Can preserve selected number of last messages.
That function is called when user manually provide "clear history" phrase in his reply.
When "clear history" is provided, the history of messages for all agents is cleared.
@@ -696,23 +698,27 @@ class GroupChatManager(ConversableAgent):
Phrase "clear history" and optional arguments are cut out from the reply before it passed to the chat.
Args:
- reply (str): Admin reply to analyse.
+ reply (dict): reply message dict to analyze.
groupchat (GroupChat): GroupChat object.
"""
+ reply_content = reply["content"]
# Split the reply into words
- words = reply.split()
+ words = reply_content.split()
# Find the position of "clear" to determine where to start processing
clear_word_index = next(i for i in reversed(range(len(words))) if words[i].upper() == "CLEAR")
# Extract potential agent name and steps
words_to_check = words[clear_word_index + 2 : clear_word_index + 4]
nr_messages_to_preserve = None
+ nr_messages_to_preserve_provided = False
agent_to_memory_clear = None
for word in words_to_check:
if word.isdigit():
nr_messages_to_preserve = int(word)
+ nr_messages_to_preserve_provided = True
elif word[:-1].isdigit(): # for the case when number of messages is followed by dot or other sign
nr_messages_to_preserve = int(word[:-1])
+ nr_messages_to_preserve_provided = True
else:
for agent in groupchat.agents:
if agent.name == word:
@@ -721,6 +727,12 @@ class GroupChatManager(ConversableAgent):
elif agent.name == word[:-1]: # for the case when agent name is followed by dot or other sign
agent_to_memory_clear = agent
break
+ # preserve last tool call message if clear history called inside of tool response
+ if "tool_responses" in reply and not nr_messages_to_preserve:
+ nr_messages_to_preserve = 1
+ logger.warning(
+ "The last tool call message will be saved to prevent errors caused by tool response without tool call."
+ )
# clear history
if agent_to_memory_clear:
if nr_messages_to_preserve:
@@ -746,7 +758,7 @@ class GroupChatManager(ConversableAgent):
agent.clear_history(nr_messages_to_preserve=nr_messages_to_preserve)
# Reconstruct the reply without the "clear history" command and parameters
- skip_words_number = 2 + int(bool(agent_to_memory_clear)) + int(bool(nr_messages_to_preserve))
- reply = " ".join(words[:clear_word_index] + words[clear_word_index + skip_words_number :])
+ skip_words_number = 2 + int(bool(agent_to_memory_clear)) + int(nr_messages_to_preserve_provided)
+ reply_content = " ".join(words[:clear_word_index] + words[clear_word_index + skip_words_number :])
- return reply
+ return reply_content
diff --git a/test/agentchat/test_groupchat.py b/test/agentchat/test_groupchat.py
index 1c16e6778..d3d07c5b1 100755
--- a/test/agentchat/test_groupchat.py
+++ b/test/agentchat/test_groupchat.py
@@ -779,6 +779,103 @@ def test_clear_agents_history():
{"content": "How you doing?", "name": "sam", "role": "user"},
]
+ # testing saving tool_call message when clear history going to remove it leaving only tool_response message
+ agent1.reset()
+ agent2.reset()
+ agent3.reset()
+ # we want to broadcast the message only in the preparation.
+ groupchat = autogen.GroupChat(agents=[agent1, agent2, agent3], messages=[], max_round=1, enable_clear_history=True)
+ group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
+ # We want to trigger the broadcast of group chat manager, which requires `request_reply` to be set to True.
+ agent1.send("dummy message", group_chat_manager, request_reply=True)
+ agent1.send(
+ {
+ "content": None,
+ "role": "assistant",
+ "function_call": None,
+ "tool_calls": [
+ {"id": "call_test_id", "function": {"arguments": "", "name": "test_tool"}, "type": "function"}
+ ],
+ },
+ group_chat_manager,
+ request_reply=True,
+ )
+ agent1.send(
+ {
+ "role": "tool",
+ "tool_responses": [{"tool_call_id": "call_emulated", "role": "tool", "content": "example tool response"}],
+ "content": "example tool response",
+ },
+ group_chat_manager,
+ request_reply=True,
+ )
+ # increase max_round to 3
+ groupchat.max_round = 3
+ group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
+ with mock.patch.object(builtins, "input", lambda _: "clear history alice 1. How you doing?"):
+ agent1.initiate_chat(group_chat_manager, message="hello", clear_history=False)
+
+ agent1_history = list(agent1._oai_messages.values())[0]
+ assert agent1_history == [
+ {
+ "tool_calls": [
+ {"id": "call_test_id", "function": {"arguments": "", "name": "test_tool"}, "type": "function"},
+ ],
+ "content": None,
+ "role": "assistant",
+ },
+ {
+ "content": "example tool response",
+ "tool_responses": [{"tool_call_id": "call_emulated", "role": "tool", "content": "example tool response"}],
+ "role": "tool",
+ },
+ ]
+
+ # testing clear history called from tool response
+ agent1.reset()
+ agent2.reset()
+ agent3.reset()
+ agent2 = autogen.ConversableAgent(
+ "bob",
+ max_consecutive_auto_reply=10,
+ human_input_mode="NEVER",
+ llm_config=False,
+ default_auto_reply={
+ "role": "tool",
+ "tool_responses": [{"tool_call_id": "call_emulated", "role": "tool", "content": "USER INTERRUPTED"}],
+ "content": "Clear history. How you doing?",
+ },
+ )
+ groupchat = autogen.GroupChat(agents=[agent1, agent2, agent3], messages=[], max_round=1, enable_clear_history=True)
+ group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
+ agent1.send("dummy message", group_chat_manager, request_reply=True)
+ agent1.send(
+ {
+ "content": None,
+ "role": "assistant",
+ "function_call": None,
+ "tool_calls": [
+ {"id": "call_test_id", "function": {"arguments": "", "name": "test_tool"}, "type": "function"}
+ ],
+ },
+ group_chat_manager,
+ request_reply=True,
+ )
+ groupchat.max_round = 2
+ group_chat_manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=False)
+
+ agent1.initiate_chat(group_chat_manager, message="hello")
+ agent1_history = list(agent1._oai_messages.values())[0]
+ assert agent1_history == [
+ {
+ "tool_calls": [
+ {"id": "call_test_id", "function": {"arguments": "", "name": "test_tool"}, "type": "function"},
+ ],
+ "content": None,
+ "role": "assistant",
+ },
+ ]
+
def test_get_agent_by_name():
def agent(name: str) -> autogen.ConversableAgent:
From 2912f554a3876072ed8eb0cf20db7698a5827e6d Mon Sep 17 00:00:00 2001
From: Jack Gerrits
Date: Sat, 2 Mar 2024 14:45:28 -0500
Subject: [PATCH 05/13] revert quarto upgrade (#1840)
* revert quarto upgrade
* fix ruff
---
.devcontainer/Dockerfile | 6 +++---
.devcontainer/dev/Dockerfile | 8 ++++----
.github/workflows/deploy-website.yml | 12 ++++++------
website/process_notebooks.py | 10 +++++-----
4 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 4431ee58b..0ca2c2517 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -14,9 +14,9 @@ RUN apt-get update \
&& apt-get -y install --no-install-recommends build-essential npm \
&& apt-get autoremove -y \
&& apt-get clean -y \
- && wget https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-amd64.deb \
- && dpkg -i quarto-1.5.23-linux-amd64.deb \
- && rm -rf /var/lib/apt/lists/* quarto-1.5.23-linux-amd64.deb
+ && wget https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-amd64.deb \
+ && dpkg -i quarto-1.4.549-linux-amd64.deb \
+ && rm -rf /var/lib/apt/lists/* quarto-1.4.549-linux-amd64.deb
ENV DEBIAN_FRONTEND=dialog
# For docs
diff --git a/.devcontainer/dev/Dockerfile b/.devcontainer/dev/Dockerfile
index 4749e41ba..482157437 100644
--- a/.devcontainer/dev/Dockerfile
+++ b/.devcontainer/dev/Dockerfile
@@ -33,12 +33,12 @@ RUN cd website
RUN yarn install --frozen-lockfile --ignore-engines
RUN arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) && \
- wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-${arch}.tar.gz && \
+ wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-${arch}.tar.gz && \
mkdir -p /home/autogen/quarto/ && \
- tar -xzf quarto-1.5.23-linux-${arch}.tar.gz --directory /home/autogen/quarto/ && \
- rm quarto-1.5.23-linux-${arch}.tar.gz
+ tar -xzf quarto-1.4.549-linux-${arch}.tar.gz --directory /home/autogen/quarto/ && \
+ rm quarto-1.4.549-linux-${arch}.tar.gz
-ENV PATH="${PATH}:/home/autogen/quarto/quarto-1.5.23/bin/"
+ENV PATH="${PATH}:/home/autogen/quarto/quarto-1.4.549/bin/"
# Exposes the Yarn port for Docusaurus
EXPOSE 3000
diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml
index ae866ffca..c9c7deede 100644
--- a/.github/workflows/deploy-website.yml
+++ b/.github/workflows/deploy-website.yml
@@ -44,9 +44,9 @@ jobs:
- name: quarto install
working-directory: ${{ runner.temp }}
run: |
- wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-amd64.tar.gz
- tar -xzf quarto-1.5.23-linux-amd64.tar.gz
- echo "$(pwd)/quarto-1.5.23/bin/" >> $GITHUB_PATH
+ wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-amd64.tar.gz
+ tar -xzf quarto-1.4.549-linux-amd64.tar.gz
+ echo "$(pwd)/quarto-1.4.549/bin/" >> $GITHUB_PATH
- name: quarto run
run: |
quarto render .
@@ -90,9 +90,9 @@ jobs:
- name: quarto install
working-directory: ${{ runner.temp }}
run: |
- wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-amd64.tar.gz
- tar -xzf quarto-1.5.23-linux-amd64.tar.gz
- echo "$(pwd)/quarto-1.5.23/bin/" >> $GITHUB_PATH
+ wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-amd64.tar.gz
+ tar -xzf quarto-1.4.549-linux-amd64.tar.gz
+ echo "$(pwd)/quarto-1.4.549/bin/" >> $GITHUB_PATH
- name: quarto run
run: |
quarto render .
diff --git a/website/process_notebooks.py b/website/process_notebooks.py
index 8a2875c22..56f1f190a 100755
--- a/website/process_notebooks.py
+++ b/website/process_notebooks.py
@@ -67,11 +67,11 @@ class Result:
def check_quarto_bin(quarto_bin: str = "quarto") -> None:
"""Check if quarto is installed."""
try:
- version = subprocess.check_output([quarto_bin, "--version"], text=True).strip()
- version = tuple(map(int, version.split(".")))
- if version < (1, 5, 23):
- print("Quarto version is too old. Please upgrade to 1.5.23 or later.")
- sys.exit(1)
+ subprocess.check_output([quarto_bin, "--version"], text=True).strip()
+ # version = tuple(map(int, version.split(".")))
+ # if version < (1, 5, 23):
+ # print("Quarto version is too old. Please upgrade to 1.5.23 or later.")
+ # sys.exit(1)
except FileNotFoundError:
print("Quarto is not installed. Please install it from https://quarto.org")
From f749deeda94e1379f966450a600198405cd8f9ca Mon Sep 17 00:00:00 2001
From: Chi Wang
Date: Sat, 2 Mar 2024 18:10:35 -0800
Subject: [PATCH 06/13] adjust the order of message processing (#1841)
---
autogen/agentchat/conversable_agent.py | 8 ++++----
autogen/version.py | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py
index b503403d0..0d968f2ac 100644
--- a/autogen/agentchat/conversable_agent.py
+++ b/autogen/agentchat/conversable_agent.py
@@ -1732,14 +1732,14 @@ class ConversableAgent(LLMAgent):
if messages is None:
messages = self._oai_messages[sender]
- # Call the hookable method that gives registered hooks a chance to process all messages.
- # Message modifications do not affect the incoming messages or self._oai_messages.
- messages = self.process_all_messages_before_reply(messages)
-
# Call the hookable method that gives registered hooks a chance to process the last message.
# Message modifications do not affect the incoming messages or self._oai_messages.
messages = self.process_last_received_message(messages)
+ # Call the hookable method that gives registered hooks a chance to process all messages.
+ # Message modifications do not affect the incoming messages or self._oai_messages.
+ messages = self.process_all_messages_before_reply(messages)
+
for reply_func_tuple in self._reply_func_list:
reply_func = reply_func_tuple["reply_func"]
if "exclude" in kwargs and reply_func in kwargs["exclude"]:
diff --git a/autogen/version.py b/autogen/version.py
index 699eb888d..aa0be2139 100644
--- a/autogen/version.py
+++ b/autogen/version.py
@@ -1 +1 @@
-__version__ = "0.2.16"
+__version__ = "0.2.17"
From cd3b5c6bbd84aa3d595a04ae64904b13595b1b96 Mon Sep 17 00:00:00 2001
From: Chi Wang
Date: Sun, 3 Mar 2024 15:15:52 -0800
Subject: [PATCH 07/13] add doc about effects for capabilities (#1842)
* add doc about effects for capabilities
* remove unnecessary imports
* improve doc
* test
* test location
* polish
* improve import of colored
* termcolor
* termcolor
* use pull request for openai test
---
.github/workflows/contrib-openai.yml | 4 ++--
.github/workflows/contrib-tests.yml | 2 +-
.github/workflows/deploy-website.yml | 4 ++--
.github/workflows/openai.yml | 2 +-
autogen/agentchat/chat.py | 10 ++------
.../contrib/capabilities/context_handling.py | 13 +++++++----
.../contrib/capabilities/teachability.py | 23 ++++++++++---------
autogen/agentchat/contrib/llava_agent.py | 15 ++----------
.../contrib/multimodal_conversable_agent.py | 14 ++---------
.../contrib/retrieve_user_proxy_agent.py | 13 +++--------
.../coding/local_commandline_code_executor.py | 9 +-------
notebook/agentchat_teachability.ipynb | 8 -------
.../chat_with_teachable_agent.py | 16 ++++---------
.../capabilities/test_context_handling.py | 2 +-
.../test_teachable_agent.py | 19 +++++----------
website/process_notebooks.py | 15 ++----------
16 files changed, 50 insertions(+), 119 deletions(-)
rename test/agentchat/contrib/{ => capabilities}/chat_with_teachable_agent.py (93%)
rename test/agentchat/contrib/{ => capabilities}/test_teachable_agent.py (96%)
diff --git a/.github/workflows/contrib-openai.yml b/.github/workflows/contrib-openai.yml
index a8cedb29a..6443aa62d 100644
--- a/.github/workflows/contrib-openai.yml
+++ b/.github/workflows/contrib-openai.yml
@@ -4,7 +4,7 @@
name: OpenAI4ContribTests
on:
- pull_request_target:
+ pull_request:
branches: ['main']
paths:
- 'autogen/**'
@@ -173,7 +173,7 @@ jobs:
AZURE_OPENAI_API_BASE: ${{ secrets.AZURE_OPENAI_API_BASE }}
OAI_CONFIG_LIST: ${{ secrets.OAI_CONFIG_LIST }}
run: |
- coverage run -a -m pytest test/agentchat/contrib/test_teachable_agent.py
+ coverage run -a -m pytest test/agentchat/contrib/capabilities/test_teachable_agent.py
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
diff --git a/.github/workflows/contrib-tests.yml b/.github/workflows/contrib-tests.yml
index b82fe1baf..c4d698655 100644
--- a/.github/workflows/contrib-tests.yml
+++ b/.github/workflows/contrib-tests.yml
@@ -172,7 +172,7 @@ jobs:
- name: Coverage
run: |
pip install coverage>=5.3
- coverage run -a -m pytest test/agentchat/contrib/test_teachable_agent.py --skip-openai
+ coverage run -a -m pytest test/agentchat/contrib/capabilities/test_teachable_agent.py --skip-openai
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml
index c9c7deede..13180d504 100644
--- a/.github/workflows/deploy-website.yml
+++ b/.github/workflows/deploy-website.yml
@@ -37,7 +37,7 @@ jobs:
- name: pydoc-markdown install
run: |
python -m pip install --upgrade pip
- pip install pydoc-markdown pyyaml colored
+ pip install pydoc-markdown pyyaml termcolor
- name: pydoc-markdown run
run: |
pydoc-markdown
@@ -83,7 +83,7 @@ jobs:
- name: pydoc-markdown install
run: |
python -m pip install --upgrade pip
- pip install pydoc-markdown pyyaml colored
+ pip install pydoc-markdown pyyaml termcolor
- name: pydoc-markdown run
run: |
pydoc-markdown
diff --git a/.github/workflows/openai.yml b/.github/workflows/openai.yml
index be2840c2d..2018aa8e6 100644
--- a/.github/workflows/openai.yml
+++ b/.github/workflows/openai.yml
@@ -4,7 +4,7 @@
name: OpenAI
on:
- pull_request_target:
+ pull_request:
branches: ["main"]
paths:
- "autogen/**"
diff --git a/autogen/agentchat/chat.py b/autogen/agentchat/chat.py
index 6d2858c18..8c9ed1ee2 100644
--- a/autogen/agentchat/chat.py
+++ b/autogen/agentchat/chat.py
@@ -3,15 +3,9 @@ import logging
from collections import defaultdict
from typing import Dict, List, Any, Set, Tuple
from dataclasses import dataclass
-from .utils import consolidate_chat_info
import warnings
-
-try:
- from termcolor import colored
-except ImportError:
-
- def colored(x, *args, **kwargs):
- return x
+from termcolor import colored
+from .utils import consolidate_chat_info
logger = logging.getLogger(__name__)
diff --git a/autogen/agentchat/contrib/capabilities/context_handling.py b/autogen/agentchat/contrib/capabilities/context_handling.py
index ebbc00e10..1510ae5fc 100644
--- a/autogen/agentchat/contrib/capabilities/context_handling.py
+++ b/autogen/agentchat/contrib/capabilities/context_handling.py
@@ -25,10 +25,9 @@ class TransformChatHistory:
2. Second, it limits the number of message to keep
3. Third, it limits the total number of tokens in the chat history
- Args:
- max_tokens_per_message (Optional[int]): Maximum number of tokens to keep in each message.
- max_messages (Optional[int]): Maximum number of messages to keep in the context.
- max_tokens (Optional[int]): Maximum number of tokens to keep in the context.
+ When adding this capability to an agent, the following are modified:
+ - A hook is added to the hookable method `process_all_messages_before_reply` to transform the received messages for possible truncation.
+ Not modifying the stored message history.
"""
def __init__(
@@ -38,6 +37,12 @@ class TransformChatHistory:
max_messages: Optional[int] = None,
max_tokens: Optional[int] = None,
):
+ """
+ Args:
+ max_tokens_per_message (Optional[int]): Maximum number of tokens to keep in each message.
+ max_messages (Optional[int]): Maximum number of messages to keep in the context.
+ max_tokens (Optional[int]): Maximum number of tokens to keep in the context.
+ """
self.max_tokens_per_message = max_tokens_per_message if max_tokens_per_message else sys.maxsize
self.max_messages = max_messages if max_messages else sys.maxsize
self.max_tokens = max_tokens if max_tokens else sys.maxsize
diff --git a/autogen/agentchat/contrib/capabilities/teachability.py b/autogen/agentchat/contrib/capabilities/teachability.py
index e90612fa5..9e18f99a3 100644
--- a/autogen/agentchat/contrib/capabilities/teachability.py
+++ b/autogen/agentchat/contrib/capabilities/teachability.py
@@ -1,18 +1,12 @@
import os
-from autogen.agentchat.assistant_agent import ConversableAgent
-from autogen.agentchat.contrib.capabilities.agent_capability import AgentCapability
-from autogen.agentchat.contrib.text_analyzer_agent import TextAnalyzerAgent
-from typing import Dict, Optional, Union, List, Tuple, Any
+from typing import Dict, Optional, Union
import chromadb
from chromadb.config import Settings
import pickle
-
-try:
- from termcolor import colored
-except ImportError:
-
- def colored(x, *args, **kwargs):
- return x
+from autogen.agentchat.assistant_agent import ConversableAgent
+from autogen.agentchat.contrib.capabilities.agent_capability import AgentCapability
+from autogen.agentchat.contrib.text_analyzer_agent import TextAnalyzerAgent
+from autogen.agentchat.conversable_agent import colored
class Teachability(AgentCapability):
@@ -23,6 +17,13 @@ class Teachability(AgentCapability):
To make any conversable agent teachable, instantiate both the agent and the Teachability class,
then pass the agent to teachability.add_to_agent(agent).
Note that teachable agents in a group chat must be given unique path_to_db_dir values.
+
+ When adding Teachability to an agent, the following are modified:
+ - The agent's system message is appended with a note about the agent's new ability.
+ - A hook is added to the agent's `process_last_received_message` hookable method,
+ and the hook potentially modifies the last of the received messages to include earlier teachings related to the message.
+ Added teachings do not propagate into the stored message history.
+ If new user teachings are detected, they are added to new memos in the vector database.
"""
def __init__(
diff --git a/autogen/agentchat/contrib/llava_agent.py b/autogen/agentchat/contrib/llava_agent.py
index 376c6c88f..c26f576ab 100644
--- a/autogen/agentchat/contrib/llava_agent.py
+++ b/autogen/agentchat/contrib/llava_agent.py
@@ -1,25 +1,14 @@
import json
import logging
-import os
-import pdb
-import re
-from typing import Any, Dict, List, Optional, Tuple, Union
-
+from typing import List, Optional, Tuple
import replicate
import requests
-from regex import R
from autogen.agentchat.agent import Agent
from autogen.agentchat.contrib.img_utils import get_image_data, llava_formatter
from autogen.agentchat.contrib.multimodal_conversable_agent import MultimodalConversableAgent
from autogen.code_utils import content_str
-
-try:
- from termcolor import colored
-except ImportError:
-
- def colored(x, *args, **kwargs):
- return x
+from autogen.agentchat.conversable_agent import colored
logger = logging.getLogger(__name__)
diff --git a/autogen/agentchat/contrib/multimodal_conversable_agent.py b/autogen/agentchat/contrib/multimodal_conversable_agent.py
index a978e8fb7..2355c630f 100644
--- a/autogen/agentchat/contrib/multimodal_conversable_agent.py
+++ b/autogen/agentchat/contrib/multimodal_conversable_agent.py
@@ -1,26 +1,16 @@
import copy
-from typing import Any, Callable, Dict, List, Optional, Tuple, Union
+from typing import Dict, List, Optional, Tuple, Union
from autogen import OpenAIWrapper
from autogen.agentchat import Agent, ConversableAgent
from autogen.agentchat.contrib.img_utils import (
- convert_base64_to_data_uri,
gpt4v_formatter,
message_formatter_pil_to_b64,
- pil_to_data_uri,
)
+from autogen.code_utils import content_str
from ..._pydantic import model_dump
-try:
- from termcolor import colored
-except ImportError:
-
- def colored(x, *args, **kwargs):
- return x
-
-
-from autogen.code_utils import content_str
DEFAULT_LMM_SYS_MSG = """You are a helpful AI assistant."""
DEFAULT_MODEL = "gpt-4-vision-preview"
diff --git a/autogen/agentchat/contrib/retrieve_user_proxy_agent.py b/autogen/agentchat/contrib/retrieve_user_proxy_agent.py
index facb64d07..b6ec63630 100644
--- a/autogen/agentchat/contrib/retrieve_user_proxy_agent.py
+++ b/autogen/agentchat/contrib/retrieve_user_proxy_agent.py
@@ -1,4 +1,6 @@
import re
+from typing import Callable, Dict, Optional, Union, List, Tuple, Any
+from IPython import get_ipython
try:
import chromadb
@@ -10,16 +12,7 @@ from autogen.retrieve_utils import create_vector_db_from_dir, query_vector_db, T
from autogen.token_count_utils import count_token
from autogen.code_utils import extract_code
from autogen import logger
-
-from typing import Callable, Dict, Optional, Union, List, Tuple, Any
-from IPython import get_ipython
-
-try:
- from termcolor import colored
-except ImportError:
-
- def colored(x, *args, **kwargs):
- return x
+from autogen.agentchat.conversable_agent import colored
PROMPT_DEFAULT = """You're a retrieve augmented chatbot. You answer user's questions based on your own knowledge and the
diff --git a/autogen/coding/local_commandline_code_executor.py b/autogen/coding/local_commandline_code_executor.py
index 25f4e5ebe..0ed4ce1ae 100644
--- a/autogen/coding/local_commandline_code_executor.py
+++ b/autogen/coding/local_commandline_code_executor.py
@@ -3,21 +3,14 @@ import re
import uuid
import warnings
from typing import Any, ClassVar, List, Optional
-
from pydantic import BaseModel, Field, field_validator
+from autogen.agentchat.conversable_agent import colored
from ..agentchat.agent import LLMAgent
from ..code_utils import execute_code
from .base import CodeBlock, CodeExtractor, CodeResult
from .markdown_code_extractor import MarkdownCodeExtractor
-try:
- from termcolor import colored
-except ImportError:
-
- def colored(x: Any, *args: Any, **kwargs: Any) -> str: # type: ignore[misc]
- return x # type: ignore[no-any-return]
-
__all__ = (
"LocalCommandlineCodeExecutor",
diff --git a/notebook/agentchat_teachability.ipynb b/notebook/agentchat_teachability.ipynb
index 0f9550bd7..3785f01fe 100644
--- a/notebook/agentchat_teachability.ipynb
+++ b/notebook/agentchat_teachability.ipynb
@@ -161,14 +161,6 @@
"# Now add the Teachability capability to the agent.\n",
"teachability.add_to_agent(teachable_agent)\n",
"\n",
- "try:\n",
- " from termcolor import colored\n",
- "except ImportError:\n",
- "\n",
- " def colored(x, *args, **kwargs):\n",
- " return x\n",
- "\n",
- "\n",
"# Instantiate a UserProxyAgent to represent the user. But in this notebook, all user input will be simulated.\n",
"user = UserProxyAgent(\n",
" name=\"user\",\n",
diff --git a/test/agentchat/contrib/chat_with_teachable_agent.py b/test/agentchat/contrib/capabilities/chat_with_teachable_agent.py
similarity index 93%
rename from test/agentchat/contrib/chat_with_teachable_agent.py
rename to test/agentchat/contrib/capabilities/chat_with_teachable_agent.py
index 44449091a..66d61386d 100755
--- a/test/agentchat/contrib/chat_with_teachable_agent.py
+++ b/test/agentchat/contrib/capabilities/chat_with_teachable_agent.py
@@ -1,24 +1,16 @@
#!/usr/bin/env python3 -m pytest
+import os
+import sys
+from termcolor import colored
from autogen import UserProxyAgent, config_list_from_json
from autogen.agentchat.contrib.capabilities.teachability import Teachability
from autogen import ConversableAgent
-import os
-import sys
-
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
+sys.path.append(os.path.join(os.path.dirname(__file__), "../.."))
from test_assistant_agent import OAI_CONFIG_LIST, KEY_LOC # noqa: E402
-try:
- from termcolor import colored
-except ImportError:
-
- def colored(x, *args, **kwargs):
- return x
-
-
# Specify the model to use. GPT-3.5 is less reliable than GPT-4 at learning from user input.
filter_dict = {"model": ["gpt-4-0125-preview"]}
# filter_dict = {"model": ["gpt-3.5-turbo-1106"]}
diff --git a/test/agentchat/contrib/capabilities/test_context_handling.py b/test/agentchat/contrib/capabilities/test_context_handling.py
index d1692cc4b..6d7a77411 100755
--- a/test/agentchat/contrib/capabilities/test_context_handling.py
+++ b/test/agentchat/contrib/capabilities/test_context_handling.py
@@ -10,7 +10,7 @@ from autogen import AssistantAgent, UserProxyAgent
# from test_assistant_agent import KEY_LOC, OAI_CONFIG_LIST
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
+sys.path.append(os.path.join(os.path.dirname(__file__), "../../.."))
from conftest import skip_openai # noqa: E402
sys.path.append(os.path.join(os.path.dirname(__file__), "..", ".."))
diff --git a/test/agentchat/contrib/test_teachable_agent.py b/test/agentchat/contrib/capabilities/test_teachable_agent.py
similarity index 96%
rename from test/agentchat/contrib/test_teachable_agent.py
rename to test/agentchat/contrib/capabilities/test_teachable_agent.py
index e12921379..44904b26d 100755
--- a/test/agentchat/contrib/test_teachable_agent.py
+++ b/test/agentchat/contrib/capabilities/test_teachable_agent.py
@@ -3,28 +3,21 @@
import pytest
import os
import sys
+from termcolor import colored
from autogen import ConversableAgent, config_list_from_json
-sys.path.append(os.path.join(os.path.dirname(__file__), "../.."))
+sys.path.append(os.path.join(os.path.dirname(__file__), "../../.."))
from conftest import skip_openai # noqa: E402
-sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
+sys.path.append(os.path.join(os.path.dirname(__file__), "../.."))
from test_assistant_agent import OAI_CONFIG_LIST, KEY_LOC # noqa: E402
try:
- from openai import OpenAI
from autogen.agentchat.contrib.capabilities.teachability import Teachability
except ImportError:
skip = True
else:
- skip = False or skip_openai
-
-try:
- from termcolor import colored
-except ImportError:
-
- def colored(x, *args, **kwargs):
- return x
+ skip = skip_openai
# Specify the model to use by uncommenting one of the following lines.
@@ -141,7 +134,7 @@ def use_task_advice_pair_phrasing():
@pytest.mark.skipif(
skip,
- reason="do not run if dependency is not installed",
+ reason="do not run if dependency is not installed or requested to skip",
)
def test_teachability_code_paths():
"""Runs this file's unit tests."""
@@ -172,7 +165,7 @@ def test_teachability_code_paths():
@pytest.mark.skipif(
skip,
- reason="do not run if dependency is not installed",
+ reason="do not run if dependency is not installed or requested to skip",
)
def test_teachability_accuracy():
"""A very cheap and fast test of teachability accuracy."""
diff --git a/website/process_notebooks.py b/website/process_notebooks.py
index 56f1f190a..2fd70a194 100755
--- a/website/process_notebooks.py
+++ b/website/process_notebooks.py
@@ -14,12 +14,10 @@ import time
import typing
import concurrent.futures
import os
-
-from typing import Any, Dict, Optional, Tuple, Union
+from typing import Dict, Optional, Tuple, Union
from dataclasses import dataclass
-
-
from multiprocessing import current_process
+from termcolor import colored
try:
import yaml
@@ -27,7 +25,6 @@ except ImportError:
print("pyyaml not found.\n\nPlease install pyyaml:\n\tpip install pyyaml\n")
sys.exit(1)
-
try:
import nbclient
from nbclient.client import (
@@ -49,14 +46,6 @@ except ImportError:
print("test won't work without nbclient")
-try:
- from termcolor import colored
-except ImportError:
-
- def colored(x, *args, **kwargs):
- return x
-
-
class Result:
def __init__(self, returncode: int, stdout: str, stderr: str):
self.returncode = returncode
From de757707bcaaedfb62d90a78977d79e3b983671e Mon Sep 17 00:00:00 2001
From: Jack Gerrits
Date: Mon, 4 Mar 2024 09:44:41 -0500
Subject: [PATCH 08/13] Upgrade quarto and include mitigation for title issue,
fix links (#1851)
* Upgrade quarto and include mitigation for title issue, fix links
* repo_root
* dont run quarto independently
* test paths
* test
* resolve intermediate parents
* remove print
---
.devcontainer/Dockerfile | 6 +--
.devcontainer/dev/Dockerfile | 8 +--
.github/workflows/deploy-website.yml | 18 +++----
website/process_notebooks.py | 77 +++++++++++++++++++++-------
4 files changed, 71 insertions(+), 38 deletions(-)
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
index 0ca2c2517..4431ee58b 100644
--- a/.devcontainer/Dockerfile
+++ b/.devcontainer/Dockerfile
@@ -14,9 +14,9 @@ RUN apt-get update \
&& apt-get -y install --no-install-recommends build-essential npm \
&& apt-get autoremove -y \
&& apt-get clean -y \
- && wget https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-amd64.deb \
- && dpkg -i quarto-1.4.549-linux-amd64.deb \
- && rm -rf /var/lib/apt/lists/* quarto-1.4.549-linux-amd64.deb
+ && wget https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-amd64.deb \
+ && dpkg -i quarto-1.5.23-linux-amd64.deb \
+ && rm -rf /var/lib/apt/lists/* quarto-1.5.23-linux-amd64.deb
ENV DEBIAN_FRONTEND=dialog
# For docs
diff --git a/.devcontainer/dev/Dockerfile b/.devcontainer/dev/Dockerfile
index 482157437..4749e41ba 100644
--- a/.devcontainer/dev/Dockerfile
+++ b/.devcontainer/dev/Dockerfile
@@ -33,12 +33,12 @@ RUN cd website
RUN yarn install --frozen-lockfile --ignore-engines
RUN arch=$(arch | sed s/aarch64/arm64/ | sed s/x86_64/amd64/) && \
- wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-${arch}.tar.gz && \
+ wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-${arch}.tar.gz && \
mkdir -p /home/autogen/quarto/ && \
- tar -xzf quarto-1.4.549-linux-${arch}.tar.gz --directory /home/autogen/quarto/ && \
- rm quarto-1.4.549-linux-${arch}.tar.gz
+ tar -xzf quarto-1.5.23-linux-${arch}.tar.gz --directory /home/autogen/quarto/ && \
+ rm quarto-1.5.23-linux-${arch}.tar.gz
-ENV PATH="${PATH}:/home/autogen/quarto/quarto-1.4.549/bin/"
+ENV PATH="${PATH}:/home/autogen/quarto/quarto-1.5.23/bin/"
# Exposes the Yarn port for Docusaurus
EXPOSE 3000
diff --git a/.github/workflows/deploy-website.yml b/.github/workflows/deploy-website.yml
index 13180d504..0f2702e5e 100644
--- a/.github/workflows/deploy-website.yml
+++ b/.github/workflows/deploy-website.yml
@@ -44,12 +44,9 @@ jobs:
- name: quarto install
working-directory: ${{ runner.temp }}
run: |
- wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-amd64.tar.gz
- tar -xzf quarto-1.4.549-linux-amd64.tar.gz
- echo "$(pwd)/quarto-1.4.549/bin/" >> $GITHUB_PATH
- - name: quarto run
- run: |
- quarto render .
+ wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-amd64.tar.gz
+ tar -xzf quarto-1.5.23-linux-amd64.tar.gz
+ echo "$(pwd)/quarto-1.5.23/bin/" >> $GITHUB_PATH
- name: Process notebooks
run: |
python process_notebooks.py render
@@ -90,12 +87,9 @@ jobs:
- name: quarto install
working-directory: ${{ runner.temp }}
run: |
- wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.4.549/quarto-1.4.549-linux-amd64.tar.gz
- tar -xzf quarto-1.4.549-linux-amd64.tar.gz
- echo "$(pwd)/quarto-1.4.549/bin/" >> $GITHUB_PATH
- - name: quarto run
- run: |
- quarto render .
+ wget -q https://github.com/quarto-dev/quarto-cli/releases/download/v1.5.23/quarto-1.5.23-linux-amd64.tar.gz
+ tar -xzf quarto-1.5.23-linux-amd64.tar.gz
+ echo "$(pwd)/quarto-1.5.23/bin/" >> $GITHUB_PATH
- name: Process notebooks
run: |
python process_notebooks.py render
diff --git a/website/process_notebooks.py b/website/process_notebooks.py
index 2fd70a194..2246e07bb 100755
--- a/website/process_notebooks.py
+++ b/website/process_notebooks.py
@@ -56,11 +56,11 @@ class Result:
def check_quarto_bin(quarto_bin: str = "quarto") -> None:
"""Check if quarto is installed."""
try:
- subprocess.check_output([quarto_bin, "--version"], text=True).strip()
- # version = tuple(map(int, version.split(".")))
- # if version < (1, 5, 23):
- # print("Quarto version is too old. Please upgrade to 1.5.23 or later.")
- # sys.exit(1)
+ version = subprocess.check_output([quarto_bin, "--version"], text=True).strip()
+ version = tuple(map(int, version.split(".")))
+ if version < (1, 5, 23):
+ print("Quarto version is too old. Please upgrade to 1.5.23 or later.")
+ sys.exit(1)
except FileNotFoundError:
print("Quarto is not installed. Please install it from https://quarto.org")
@@ -127,6 +127,27 @@ def skip_reason_or_none_if_ok(notebook: Path) -> typing.Optional[str]:
return None
+def extract_title(notebook: Path) -> Optional[str]:
+ """Extract the title of the notebook."""
+ with open(notebook, "r", encoding="utf-8") as f:
+ content = f.read()
+
+ # Load the json and get the first cell
+ json_content = json.loads(content)
+ first_cell = json_content["cells"][0]
+
+ # find the # title
+ for line in first_cell["source"]:
+ if line.startswith("# "):
+ title = line[2:].strip()
+ # Strip off the { if it exists
+ if "{" in title:
+ title = title[: title.find("{")].strip()
+ return title
+
+ return None
+
+
def process_notebook(src_notebook: Path, website_dir: Path, notebook_dir: Path, quarto_bin: str, dry_run: bool) -> str:
"""Process a single notebook."""
@@ -136,6 +157,16 @@ def process_notebook(src_notebook: Path, website_dir: Path, notebook_dir: Path,
if "skip_render" in metadata:
return fmt_skip(src_notebook, "skip_render is in notebook metadata")
+ title = extract_title(src_notebook)
+ if title is None:
+ return fmt_error(src_notebook, "Title not found in notebook")
+
+ front_matter = {}
+ if "front_matter" in metadata:
+ front_matter = metadata["front_matter"]
+
+ front_matter["title"] = title
+
if in_notebook_dir:
relative_notebook = src_notebook.resolve().relative_to(notebook_dir.resolve())
dest_dir = notebooks_target_dir(website_directory=website_dir)
@@ -190,11 +221,7 @@ def process_notebook(src_notebook: Path, website_dir: Path, notebook_dir: Path,
src_notebook, f"Failed to render {src_notebook}\n\nstderr:\n{result.stderr}\nstdout:\n{result.stdout}"
)
- front_matter = {}
- if "front_matter" in metadata:
- front_matter = metadata["front_matter"]
-
- post_process_mdx(target_file, front_matter)
+ post_process_mdx(target_file, src_notebook, front_matter)
return fmt_ok(src_notebook)
@@ -281,8 +308,7 @@ def get_error_info(nb: NotebookNode) -> Optional[NotebookError]:
# rendered_notebook is the final mdx file
-def post_process_mdx(rendered_mdx: Path, front_matter: Dict) -> None:
- notebook_name = f"{rendered_mdx.stem}.ipynb"
+def post_process_mdx(rendered_mdx: Path, source_notebooks: Path, front_matter: Dict) -> None:
with open(rendered_mdx, "r", encoding="utf-8") as f:
content = f.read()
@@ -292,8 +318,22 @@ def post_process_mdx(rendered_mdx: Path, front_matter: Dict) -> None:
front_matter = yaml.safe_load(content[4:front_matter_end])
content = content[front_matter_end + 3 :]
- front_matter["source_notebook"] = f"/notebook/{notebook_name}"
- front_matter["custom_edit_url"] = f"https://github.com/microsoft/autogen/edit/main/notebook/{notebook_name}"
+ # Each intermediate path needs to be resolved for this to work reliably
+ repo_root = Path(__file__).parent.resolve().parent.resolve()
+ repo_relative_notebook = source_notebooks.resolve().relative_to(repo_root)
+ front_matter["source_notebook"] = f"/{repo_relative_notebook}"
+ front_matter["custom_edit_url"] = f"https://github.com/microsoft/autogen/edit/main/{repo_relative_notebook}"
+
+ # Is there a title on the content? Only search up until the first code cell
+ first_code_cell = content.find("```")
+ if first_code_cell != -1:
+ title_search_content = content[:first_code_cell]
+ else:
+ title_search_content = content
+
+ title_exists = title_search_content.find("# ") != -1
+ if not title_exists:
+ content = f"# {front_matter['title']}\n{content}"
# inject in content directly after the markdown title the word done
# Find the end of the line with the title
@@ -305,9 +345,7 @@ def post_process_mdx(rendered_mdx: Path, front_matter: Dict) -> None:
if "{" in title:
title = title[: title.find("{")].strip()
- front_matter["title"] = title
-
- github_link = f"https://github.com/microsoft/autogen/blob/main/notebook/{notebook_name}"
+ github_link = f"https://github.com/microsoft/autogen/blob/main/{repo_relative_notebook}"
content = (
content[:title_end]
+ "\n[]("
@@ -318,10 +356,11 @@ def post_process_mdx(rendered_mdx: Path, front_matter: Dict) -> None:
# If no colab link is present, insert one
if "colab-badge.svg" not in content:
+ colab_link = f"https://colab.research.google.com/github/microsoft/autogen/blob/main/{repo_relative_notebook}"
content = (
content[:title_end]
- + "\n[](https://colab.research.google.com/github/microsoft/autogen/blob/main/notebook/"
- + notebook_name
+ + "\n[]("
+ + colab_link
+ ")"
+ content[title_end:]
)
From 799a24e04f8e0471053c8ff78582403441849249 Mon Sep 17 00:00:00 2001
From: Jack Gerrits
Date: Mon, 4 Mar 2024 13:02:26 -0500
Subject: [PATCH 09/13] Create topics dir and move llm config (#1853)
* create topics dir and move llm config
* fix redirect
* fix link
---
notebook/agentchat_RetrieveChat.ipynb | 8 +++++---
.../agentchat_auto_feedback_from_code_execution.ipynb | 2 +-
notebook/agentchat_cost_token_tracking.ipynb | 2 +-
notebook/agentchat_function_call.ipynb | 2 +-
notebook/agentchat_function_call_async.ipynb | 2 +-
.../agentchat_function_call_currency_calculator.ipynb | 2 +-
notebook/agentchat_groupchat.ipynb | 2 +-
notebook/agentchat_groupchat_RAG.ipynb | 2 +-
notebook/agentchat_groupchat_research.ipynb | 2 +-
notebook/agentchat_groupchat_vis.ipynb | 2 +-
notebook/agentchat_human_feedback.ipynb | 2 +-
notebook/agentchat_langchain.ipynb | 2 +-
notebook/agentchat_multi_task_async_chats.ipynb | 2 +-
notebook/agentchat_multi_task_chats.ipynb | 2 +-
notebook/agentchat_nestedchat.ipynb | 2 +-
notebook/agentchat_oai_assistant_groupchat.ipynb | 2 +-
notebook/agentchat_society_of_mind.ipynb | 2 +-
notebook/agentchat_stream.ipynb | 2 +-
notebook/agentchat_two_users.ipynb | 2 +-
notebook/agentchats_sequential_chats.ipynb | 2 +-
notebook/config_loader_utility_functions.ipynb | 2 +-
notebook/contributing.md | 2 +-
website/.gitignore | 2 +-
website/docs/Examples.md | 2 +-
website/docs/FAQ.mdx | 6 +++---
website/docs/Use-Cases/enhanced_inference.md | 2 +-
website/docs/{ => topics}/llm_configuration.ipynb | 2 +-
website/docusaurus.config.js | 6 +++++-
website/sidebars.js | 2 +-
29 files changed, 39 insertions(+), 33 deletions(-)
rename website/docs/{ => topics}/llm_configuration.ipynb (98%)
diff --git a/notebook/agentchat_RetrieveChat.ipynb b/notebook/agentchat_RetrieveChat.ipynb
index 92620a55b..ae8cecdf4 100644
--- a/notebook/agentchat_RetrieveChat.ipynb
+++ b/notebook/agentchat_RetrieveChat.ipynb
@@ -86,7 +86,7 @@
"source": [
"````{=mdx}\n",
":::tip\n",
- "Learn more about configuring LLMs for agents [here](/docs/llm_configuration).\n",
+ "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n",
":::\n",
"````\n",
"\n",
@@ -3015,8 +3015,10 @@
],
"metadata": {
"front_matter": {
- "tags": ["RAG"],
- "description": "Explore the use of AutoGen's RetrieveChat for tasks like code generation from docstrings, answering complex questions with human feedback, and exploiting features like Update Context, custom prompts, and few-shot learning."
+ "description": "Explore the use of AutoGen's RetrieveChat for tasks like code generation from docstrings, answering complex questions with human feedback, and exploiting features like Update Context, custom prompts, and few-shot learning.",
+ "tags": [
+ "RAG"
+ ]
},
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
diff --git a/notebook/agentchat_auto_feedback_from_code_execution.ipynb b/notebook/agentchat_auto_feedback_from_code_execution.ipynb
index 1598c66f8..c269e7478 100644
--- a/notebook/agentchat_auto_feedback_from_code_execution.ipynb
+++ b/notebook/agentchat_auto_feedback_from_code_execution.ipynb
@@ -55,7 +55,7 @@
"source": [
"````{=mdx}\n",
":::tip\n",
- "Learn more about configuring LLMs for agents [here](/docs/llm_configuration).\n",
+ "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n",
":::\n",
"````"
]
diff --git a/notebook/agentchat_cost_token_tracking.ipynb b/notebook/agentchat_cost_token_tracking.ipynb
index 05655b452..491fa1b1e 100644
--- a/notebook/agentchat_cost_token_tracking.ipynb
+++ b/notebook/agentchat_cost_token_tracking.ipynb
@@ -102,7 +102,7 @@
"]\n",
"```\n",
"\n",
- "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/llm_configuration.ipynb) for full code examples of the different methods."
+ "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/topics/llm_configuration.ipynb) for full code examples of the different methods."
]
},
{
diff --git a/notebook/agentchat_function_call.ipynb b/notebook/agentchat_function_call.ipynb
index 05cb0e58e..1ae6dd81b 100644
--- a/notebook/agentchat_function_call.ipynb
+++ b/notebook/agentchat_function_call.ipynb
@@ -103,7 +103,7 @@
"]\n",
"```\n",
"\n",
- "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/llm_configuration.ipynb) for full code examples of the different methods."
+ "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/topics/llm_configuration.ipynb) for full code examples of the different methods."
]
},
{
diff --git a/notebook/agentchat_function_call_async.ipynb b/notebook/agentchat_function_call_async.ipynb
index b30357259..a57eb916b 100644
--- a/notebook/agentchat_function_call_async.ipynb
+++ b/notebook/agentchat_function_call_async.ipynb
@@ -55,7 +55,7 @@
"source": [
"````{=mdx}\n",
":::tip\n",
- "Learn more about configuring LLMs for agents [here](/docs/llm_configuration).\n",
+ "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n",
":::\n",
"````\n",
"\n",
diff --git a/notebook/agentchat_function_call_currency_calculator.ipynb b/notebook/agentchat_function_call_currency_calculator.ipynb
index 0ff2beb97..6709aec77 100644
--- a/notebook/agentchat_function_call_currency_calculator.ipynb
+++ b/notebook/agentchat_function_call_currency_calculator.ipynb
@@ -106,7 +106,7 @@
"]\n",
"```\n",
"\n",
- "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/llm_configuration.ipynb) for full code examples of the different methods."
+ "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/topics/llm_configuration.ipynb) for full code examples of the different methods."
]
},
{
diff --git a/notebook/agentchat_groupchat.ipynb b/notebook/agentchat_groupchat.ipynb
index bb45ade83..d2c061d14 100644
--- a/notebook/agentchat_groupchat.ipynb
+++ b/notebook/agentchat_groupchat.ipynb
@@ -57,7 +57,7 @@
"source": [
"````{=mdx}\n",
":::tip\n",
- "Learn more about configuring LLMs for agents [here](/docs/llm_configuration).\n",
+ "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n",
":::\n",
"````\n",
"\n",
diff --git a/notebook/agentchat_groupchat_RAG.ipynb b/notebook/agentchat_groupchat_RAG.ipynb
index 50d35f5a9..c2140c42e 100644
--- a/notebook/agentchat_groupchat_RAG.ipynb
+++ b/notebook/agentchat_groupchat_RAG.ipynb
@@ -66,7 +66,7 @@
"source": [
"````{=mdx}\n",
":::tip\n",
- "Learn more about configuring LLMs for agents [here](/docs/llm_configuration).\n",
+ "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n",
":::\n",
"````\n",
"\n",
diff --git a/notebook/agentchat_groupchat_research.ipynb b/notebook/agentchat_groupchat_research.ipynb
index 8b569f8f5..30ad696ab 100644
--- a/notebook/agentchat_groupchat_research.ipynb
+++ b/notebook/agentchat_groupchat_research.ipynb
@@ -93,7 +93,7 @@
"]\n",
"```\n",
"\n",
- "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/llm_configuration.ipynb) for full code examples of the different methods."
+ "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/topics/llm_configuration.ipynb) for full code examples of the different methods."
]
},
{
diff --git a/notebook/agentchat_groupchat_vis.ipynb b/notebook/agentchat_groupchat_vis.ipynb
index d46741d15..ac084bf34 100644
--- a/notebook/agentchat_groupchat_vis.ipynb
+++ b/notebook/agentchat_groupchat_vis.ipynb
@@ -110,7 +110,7 @@
"]\n",
"```\n",
"\n",
- "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/llm_configuration.ipynb) for full code examples of the different methods."
+ "You can set the value of config_list in any way you prefer. Please refer to this [notebook](/docs/topics/llm_configuration) for full code examples of the different methods."
]
},
{
diff --git a/notebook/agentchat_human_feedback.ipynb b/notebook/agentchat_human_feedback.ipynb
index 41fbed5cd..75078e67c 100644
--- a/notebook/agentchat_human_feedback.ipynb
+++ b/notebook/agentchat_human_feedback.ipynb
@@ -102,7 +102,7 @@
"]\n",
"```\n",
"\n",
- "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/llm_configuration.ipynb) for full code examples of the different methods."
+ "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/topics/llm_configuration.ipynb) for full code examples of the different methods."
]
},
{
diff --git a/notebook/agentchat_langchain.ipynb b/notebook/agentchat_langchain.ipynb
index 12ffafff2..83ab2df44 100644
--- a/notebook/agentchat_langchain.ipynb
+++ b/notebook/agentchat_langchain.ipynb
@@ -139,7 +139,7 @@
"]\n",
"```\n",
"\n",
- "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/llm_configuration.ipynb) for full code examples of the different methods."
+ "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/topics/llm_configuration.ipynb) for full code examples of the different methods."
]
},
{
diff --git a/notebook/agentchat_multi_task_async_chats.ipynb b/notebook/agentchat_multi_task_async_chats.ipynb
index bc7d35dab..901624d80 100644
--- a/notebook/agentchat_multi_task_async_chats.ipynb
+++ b/notebook/agentchat_multi_task_async_chats.ipynb
@@ -43,7 +43,7 @@
"source": [
"\\:\\:\\:tip\n",
"\n",
- "Learn more about the various ways to configure LLM endpoints [here](/docs/llm_configuration).\n",
+ "Learn more about the various ways to configure LLM endpoints [here](/docs/topics/llm_configuration).\n",
"\n",
"\\:\\:\\:"
]
diff --git a/notebook/agentchat_multi_task_chats.ipynb b/notebook/agentchat_multi_task_chats.ipynb
index 3f10bb072..e640e15ff 100644
--- a/notebook/agentchat_multi_task_chats.ipynb
+++ b/notebook/agentchat_multi_task_chats.ipynb
@@ -43,7 +43,7 @@
"source": [
"\\:\\:\\:tip\n",
"\n",
- "Learn more about the various ways to configure LLM endpoints [here](/docs/llm_configuration).\n",
+ "Learn more about the various ways to configure LLM endpoints [here](/docs/topics/llm_configuration).\n",
"\n",
"\\:\\:\\:"
]
diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb
index 03a8afa8d..1e902a7f6 100644
--- a/notebook/agentchat_nestedchat.ipynb
+++ b/notebook/agentchat_nestedchat.ipynb
@@ -43,7 +43,7 @@
"source": [
"\\:\\:\\:tip\n",
"\n",
- "Learn more about the various ways to configure LLM endpoints [here](/docs/llm_configuration).\n",
+ "Learn more about the various ways to configure LLM endpoints [here](/docs/topics/llm_configuration).\n",
"\n",
"\\:\\:\\:"
]
diff --git a/notebook/agentchat_oai_assistant_groupchat.ipynb b/notebook/agentchat_oai_assistant_groupchat.ipynb
index 4375af3f2..603d2cf71 100644
--- a/notebook/agentchat_oai_assistant_groupchat.ipynb
+++ b/notebook/agentchat_oai_assistant_groupchat.ipynb
@@ -62,7 +62,7 @@
"]\n",
"```\n",
"\n",
- "Currently Azure OpenAI does not support assistant api. You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/llm_configuration.ipynb) for full code examples of the different methods."
+ "Currently Azure OpenAI does not support assistant api. You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/topics/llm_configuration.ipynb) for full code examples of the different methods."
]
},
{
diff --git a/notebook/agentchat_society_of_mind.ipynb b/notebook/agentchat_society_of_mind.ipynb
index be9ef5521..9a456ccda 100644
--- a/notebook/agentchat_society_of_mind.ipynb
+++ b/notebook/agentchat_society_of_mind.ipynb
@@ -51,7 +51,7 @@
"source": [
"````{=mdx}\n",
":::tip\n",
- "Learn more about configuring LLMs for agents [here](/docs/llm_configuration).\n",
+ "Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).\n",
":::\n",
"````\n",
"\n",
diff --git a/notebook/agentchat_stream.ipynb b/notebook/agentchat_stream.ipynb
index 7f671bc01..8cb899d2b 100644
--- a/notebook/agentchat_stream.ipynb
+++ b/notebook/agentchat_stream.ipynb
@@ -102,7 +102,7 @@
"]\n",
"```\n",
"\n",
- "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/llm_configuration.ipynb) for full code examples of the different methods."
+ "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/topics/llm_configuration.ipynb) for full code examples of the different methods."
]
},
{
diff --git a/notebook/agentchat_two_users.ipynb b/notebook/agentchat_two_users.ipynb
index a7ea0f72f..217492786 100644
--- a/notebook/agentchat_two_users.ipynb
+++ b/notebook/agentchat_two_users.ipynb
@@ -82,7 +82,7 @@
"]\n",
"```\n",
"\n",
- "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/llm_configuration.ipynb) for full code examples of the different methods."
+ "You can set the value of config_list in any way you prefer. Please refer to this [notebook](https://github.com/microsoft/autogen/blob/main/website/docs/topics/llm_configuration.ipynb) for full code examples of the different methods."
]
},
{
diff --git a/notebook/agentchats_sequential_chats.ipynb b/notebook/agentchats_sequential_chats.ipynb
index 9b5904050..3f6eae5f1 100644
--- a/notebook/agentchats_sequential_chats.ipynb
+++ b/notebook/agentchats_sequential_chats.ipynb
@@ -43,7 +43,7 @@
"source": [
"\\:\\:\\:tip\n",
"\n",
- "Learn more about the various ways to configure LLM endpoints [here](/docs/llm_configuration).\n",
+ "Learn more about the various ways to configure LLM endpoints [here](/docs/topics/llm_configuration).\n",
"\n",
"\\:\\:\\:"
]
diff --git a/notebook/config_loader_utility_functions.ipynb b/notebook/config_loader_utility_functions.ipynb
index 84de7cd48..f14288c12 100644
--- a/notebook/config_loader_utility_functions.ipynb
+++ b/notebook/config_loader_utility_functions.ipynb
@@ -6,7 +6,7 @@
"source": [
"# Config loader utility functions\n",
"\n",
- "For an introduction to configuring LLMs, refer to the [main configuration docs](https://microsoft.github.io/autogen/docs/llm_configuration). This guide will run through examples of the more advanced utility functions for managing API configurations.\n",
+ "For an introduction to configuring LLMs, refer to the [main configuration docs](https://microsoft.github.io/autogen/docs/topics/llm_configuration). This guide will run through examples of the more advanced utility functions for managing API configurations.\n",
"\n",
"Managing API configurations can be tricky, especially when dealing with multiple models and API versions. The provided utility functions assist users in managing these configurations effectively. Ensure your API keys and other sensitive data are stored securely. You might store keys in `.txt` or `.env` files or environment variables for local development. Never expose your API keys publicly. If you insist on storing your key files locally on your repo (you shouldn't), ensure the key file path is added to the `.gitignore` file.\n",
"\n",
diff --git a/notebook/contributing.md b/notebook/contributing.md
index c717939b2..fcafe3c71 100644
--- a/notebook/contributing.md
+++ b/notebook/contributing.md
@@ -77,7 +77,7 @@ Then after the code cell where this is used, include the following markdown snip
``````
````{=mdx}
:::tip
-Learn more about configuring LLMs for agents [here](/docs/llm_configuration).
+Learn more about configuring LLMs for agents [here](/docs/topics/llm_configuration).
:::
````
``````
diff --git a/website/.gitignore b/website/.gitignore
index a83bf4912..be132bdc8 100644
--- a/website/.gitignore
+++ b/website/.gitignore
@@ -11,7 +11,7 @@ package-lock.json
docs/reference
/docs/notebooks
-docs/llm_configuration.mdx
+docs/topics/llm_configuration.mdx
# Misc
.DS_Store
diff --git a/website/docs/Examples.md b/website/docs/Examples.md
index 76c82b241..383fe4ecb 100644
--- a/website/docs/Examples.md
+++ b/website/docs/Examples.md
@@ -97,7 +97,7 @@ Links to notebook examples:
## Enhanced Inferences
### Utilities
- API Unification - [View Documentation with Code Example](https://microsoft.github.io/autogen/docs/Use-Cases/enhanced_inference/#api-unification)
-- Utility Functions to Help Managing API configurations effectively - [View Notebook](/docs/llm_configuration)
+- Utility Functions to Help Managing API configurations effectively - [View Notebook](/docs/topics/llm_configuration)
- Cost Calculation - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/oai_client_cost.ipynb)
### Inference Hyperparameters Tuning
diff --git a/website/docs/FAQ.mdx b/website/docs/FAQ.mdx
index ee9266917..fad5abfaf 100644
--- a/website/docs/FAQ.mdx
+++ b/website/docs/FAQ.mdx
@@ -15,15 +15,15 @@ Typical errors that you might face when using the wrong package are `AttributeEr
## Set your API endpoints
-This documentation has been moved [here](/docs/llm_configuration).
+This documentation has been moved [here](/docs/topics/llm_configuration).
### Use the constructed configuration list in agents
-This documentation has been moved [here](/docs/llm_configuration).
+This documentation has been moved [here](/docs/topics/llm_configuration).
### How does an agent decide which model to pick out of the list?
-This documentation has been moved [here](/docs/llm_configuration#how-does-an-agent-decide-which-model-to-pick-out-of-the-list).
+This documentation has been moved [here](/docs/topics/llm_configuration#how-does-an-agent-decide-which-model-to-pick-out-of-the-list).
### Unexpected keyword argument 'base_url'
diff --git a/website/docs/Use-Cases/enhanced_inference.md b/website/docs/Use-Cases/enhanced_inference.md
index ad94b9bc8..5ff61c258 100644
--- a/website/docs/Use-Cases/enhanced_inference.md
+++ b/website/docs/Use-Cases/enhanced_inference.md
@@ -279,7 +279,7 @@ For convenience, we provide a number of utility functions to load config lists.
- `config_list_from_models`: Creates configurations based on a provided list of models, useful when targeting specific models without manually specifying each configuration.
- `config_list_from_dotenv`: Constructs a configuration list from a `.env` file, offering a consolidated way to manage multiple API configurations and keys from a single file.
-We suggest that you take a look at this [notebook](/docs/llm_configuration) for full code examples of the different methods to configure your model endpoints.
+We suggest that you take a look at this [notebook](/docs/topics/llm_configuration) for full code examples of the different methods to configure your model endpoints.
### Logic error
diff --git a/website/docs/llm_configuration.ipynb b/website/docs/topics/llm_configuration.ipynb
similarity index 98%
rename from website/docs/llm_configuration.ipynb
rename to website/docs/topics/llm_configuration.ipynb
index e7fad3cf3..8c6a12211 100644
--- a/website/docs/llm_configuration.ipynb
+++ b/website/docs/topics/llm_configuration.ipynb
@@ -161,7 +161,7 @@
"Being a list allows you to define multiple models that can be used by the agent. This is useful for a few reasons:\n",
"\n",
"- If one model times out or fails, the agent can try another model.\n",
- "- Having a single global list of models and [filtering it](/docs/llm_configuration#config-list-filtering) based on certain keys (e.g. name, tag) in order to pass select models into a certain agent (e.g. use cheaper GPT 3.5 for agents solving easier tasks)\n",
+ "- Having a single global list of models and [filtering it](#config-list-filtering) based on certain keys (e.g. name, tag) in order to pass select models into a certain agent (e.g. use cheaper GPT 3.5 for agents solving easier tasks)\n",
"- While the core agents, (e.g. conversable or assistant) do not have special logic around selecting configs, some of the specialized agents *may* have logic to select the best model based on the task at hand.\n",
"\n",
"### How does an agent decide which model to pick out of the list?\n",
diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js
index c4a7d95a3..995444dad 100644
--- a/website/docusaurus.config.js
+++ b/website/docusaurus.config.js
@@ -183,7 +183,7 @@ module.exports = {
{
redirects: [
{
- to: "/docs/llm_configuration/",
+ to: "/docs/topics/llm_configuration",
from: ["/docs/llm_endpoint_configuration/"],
},
{
@@ -194,6 +194,10 @@ module.exports = {
to: "/docs/Getting-Started",
from: ["/docs/"],
},
+ {
+ to: "/docs/topics/llm_configuration",
+ from: ["/docs/llm_configuration"],
+ },
],
},
]
diff --git a/website/sidebars.js b/website/sidebars.js
index 70f07465b..dfeded23b 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -22,7 +22,7 @@
id: "installation/Installation"
},
},
- 'llm_configuration',
+ {'Topics': [{type: 'autogenerated', dirName: 'topics'}]},
{'Use Cases': [{type: 'autogenerated', dirName: 'Use-Cases'}]},
'Contribute',
'Research',
From b604c4410145e4eb55345c3a1b7f19f6df591fe1 Mon Sep 17 00:00:00 2001
From: Jack Gerrits
Date: Mon, 4 Mar 2024 16:46:40 -0500
Subject: [PATCH 10/13] Raise errors if incompatible args are used with an
executor (#1855)
* Raise errors if incompatible args are used with an executor
* spelling error
---
autogen/agentchat/conversable_agent.py | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py
index 0d968f2ac..19f2a4c2a 100644
--- a/autogen/agentchat/conversable_agent.py
+++ b/autogen/agentchat/conversable_agent.py
@@ -197,6 +197,21 @@ class ConversableAgent(LLMAgent):
self._code_execution_config = code_execution_config
if self._code_execution_config.get("executor") is not None:
+ if "use_docker" in self._code_execution_config:
+ raise ValueError(
+ "'use_docker' in code_execution_config is not valid when 'executor' is set. Use the appropriate arg in the chosen executor instead."
+ )
+
+ if "work_dir" in self._code_execution_config:
+ raise ValueError(
+ "'work_dir' in code_execution_config is not valid when 'executor' is set. Use the appropriate arg in the chosen executor instead."
+ )
+
+ if "timeout" in self._code_execution_config:
+ raise ValueError(
+ "'timeout' in code_execution_config is not valid when 'executor' is set. Use the appropriate arg in the chosen executor instead."
+ )
+
# Use the new code executor.
self._code_executor = CodeExecutorFactory.create(self._code_execution_config)
self.register_reply([Agent, None], ConversableAgent._generate_code_execution_reply_using_executor)
From 0a79512ebd0bec2d41efeb97c73913c2a8caea12 Mon Sep 17 00:00:00 2001
From: Jack Gerrits
Date: Mon, 4 Mar 2024 18:44:20 -0500
Subject: [PATCH 11/13] Add documentation for jupyter code executor (#1809)
* Add documentation for jupyter code executor
* formatting
* move to notebooks
* formatting
* update docs, fix badge bug
* fix lints
* pprint
* stop server
---
autogen/coding/jupyter/base.py | 5 +
autogen/coding/jupyter/jupyter_client.py | 7 +-
.../coding/jupyter/jupyter_code_executor.py | 53 +-
.../coding/jupyter/local_jupyter_server.py | 11 +
website/.gitignore | 1 +
.../topics/code-execution/_category_.json | 5 +
.../jupyter-code-executor.ipynb | 454 ++++++++++++++++++
website/process_notebooks.py | 26 +-
8 files changed, 509 insertions(+), 53 deletions(-)
create mode 100644 website/docs/topics/code-execution/_category_.json
create mode 100644 website/docs/topics/code-execution/jupyter-code-executor.ipynb
diff --git a/autogen/coding/jupyter/base.py b/autogen/coding/jupyter/base.py
index 8e8689724..d896b6ac3 100644
--- a/autogen/coding/jupyter/base.py
+++ b/autogen/coding/jupyter/base.py
@@ -7,9 +7,13 @@ class JupyterConnectionInfo:
"""(Experimental)"""
host: str
+ """`str` - Host of the Jupyter gateway server"""
use_https: bool
+ """`bool` - Whether to use HTTPS"""
port: int
+ """`int` - Port of the Jupyter gateway server"""
token: Optional[str]
+ """`Optional[str]` - Token for authentication. If None, no token is used"""
@runtime_checkable
@@ -18,4 +22,5 @@ class JupyterConnectable(Protocol):
@property
def connection_info(self) -> JupyterConnectionInfo:
+ """Return the connection information for this connectable."""
pass
diff --git a/autogen/coding/jupyter/jupyter_client.py b/autogen/coding/jupyter/jupyter_client.py
index 459add85b..e02d3b49b 100644
--- a/autogen/coding/jupyter/jupyter_client.py
+++ b/autogen/coding/jupyter/jupyter_client.py
@@ -23,9 +23,12 @@ from .base import JupyterConnectionInfo
class JupyterClient:
- """(Experimental) A client for communicating with a Jupyter gateway server."""
-
def __init__(self, connection_info: JupyterConnectionInfo):
+ """(Experimental) A client for communicating with a Jupyter gateway server.
+
+ Args:
+ connection_info (JupyterConnectionInfo): Connection information
+ """
self._connection_info = connection_info
self._session = requests.Session()
retries = Retry(total=5, backoff_factor=0.1)
diff --git a/autogen/coding/jupyter/jupyter_code_executor.py b/autogen/coding/jupyter/jupyter_code_executor.py
index 5e190d5f1..6e63652ef 100644
--- a/autogen/coding/jupyter/jupyter_code_executor.py
+++ b/autogen/coding/jupyter/jupyter_code_executor.py
@@ -22,27 +22,6 @@ from .jupyter_client import JupyterClient
class JupyterCodeExecutor(CodeExecutor):
- """(Experimental) A code executor class that executes code statefully using an embedded
- IPython kernel managed by this class.
-
- **This will execute LLM generated code on the local machine.**
-
- Each execution is stateful and can access variables created from previous
- executions in the same session. The kernel must be installed before using
- this class. The kernel can be installed using the following command:
- `python -m ipykernel install --user --name {kernel_name}`
- where `kernel_name` is the name of the kernel to install.
-
- Args:
- timeout (int): The timeout for code execution, by default 60.
- kernel_name (str): The kernel name to use. Make sure it is installed.
- By default, it is "python3".
- output_dir (str): The directory to save output files, by default ".".
- system_message_update (str): The system message update to add to the
- agent that produces code. By default it is
- `JupyterCodeExecutor.DEFAULT_SYSTEM_MESSAGE_UPDATE`.
- """
-
DEFAULT_SYSTEM_MESSAGE_UPDATE: ClassVar[
str
] = """
@@ -72,24 +51,10 @@ the output will be a path to the image instead of the image itself.
"""
class UserCapability:
- """(Experimental) An AgentCapability class that gives agent ability use a stateful
- IPython code executor. This capability can be added to an agent using
- the `add_to_agent` method which append a system message update to the
- agent's system message."""
-
def __init__(self, system_message_update: str):
self._system_message_update = system_message_update
def add_to_agent(self, agent: LLMAgent) -> None:
- """Add this capability to an agent by appending a system message
- update to the agent's system message.
-
- **Currently we do not check for conflicts with existing content in
- the agent's system message.**
-
- Args:
- agent (LLMAgent): The agent to add the capability to.
- """
agent.update_system_message(agent.system_message + self._system_message_update)
def __init__(
@@ -100,6 +65,19 @@ the output will be a path to the image instead of the image itself.
output_dir: Union[Path, str] = Path("."),
system_message_update: str = DEFAULT_SYSTEM_MESSAGE_UPDATE,
):
+ """(Experimental) A code executor class that executes code statefully using
+ a Jupyter server supplied to this class.
+
+ Each execution is stateful and can access variables created from previous
+ executions in the same session.
+
+ Args:
+ jupyter_server (Union[JupyterConnectable, JupyterConnectionInfo]): The Jupyter server to use.
+ timeout (int): The timeout for code execution, by default 60.
+ kernel_name (str): The kernel name to use. Make sure it is installed.
+ By default, it is "python3".
+ output_dir (str): The directory to save output files, by default ".".
+ """
if timeout < 1:
raise ValueError("Timeout must be greater than or equal to 1.")
@@ -130,8 +108,6 @@ the output will be a path to the image instead of the image itself.
@property
def user_capability(self) -> "JupyterCodeExecutor.UserCapability":
- """(Experimental) Export a user capability for this executor that can be added to
- an agent using the `add_to_agent` method."""
return JupyterCodeExecutor.UserCapability(self._system_message_update)
@property
@@ -142,8 +118,7 @@ the output will be a path to the image instead of the image itself.
def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> IPythonCodeResult:
"""(Experimental) Execute a list of code blocks and return the result.
- This method executes a list of code blocks as cells in an IPython kernel
- managed by this class.
+ This method executes a list of code blocks as cells in the Jupyter kernel.
See: https://jupyter-client.readthedocs.io/en/stable/messaging.html
for the message protocol.
diff --git a/autogen/coding/jupyter/local_jupyter_server.py b/autogen/coding/jupyter/local_jupyter_server.py
index decbb3f43..5ad0fd01a 100644
--- a/autogen/coding/jupyter/local_jupyter_server.py
+++ b/autogen/coding/jupyter/local_jupyter_server.py
@@ -39,6 +39,17 @@ class LocalJupyterServer(JupyterConnectable):
log_max_bytes: int = 1048576,
log_backup_count: int = 3,
):
+ """Runs a Jupyter Kernel Gateway server locally.
+
+ Args:
+ ip (str, optional): IP address to bind to. Defaults to "127.0.0.1".
+ port (Optional[int], optional): Port to use, if None it automatically selects a port. Defaults to None.
+ token (Union[str, GenerateToken], optional): Token to use for Jupyter server. By default will generate a token. Using None will use no token for authentication. Defaults to GenerateToken().
+ log_file (str, optional): File for Jupyter Kernel Gateway logs. Defaults to "jupyter_gateway.log".
+ log_level (str, optional): Level for Jupyter Kernel Gateway logs. Defaults to "INFO".
+ log_max_bytes (int, optional): Max logfile size. Defaults to 1048576.
+ log_backup_count (int, optional): Number of backups for rotating log. Defaults to 3.
+ """
# Remove as soon as https://github.com/jupyter-server/kernel_gateway/issues/398 is fixed
if sys.platform == "win32":
raise ValueError("LocalJupyterServer is not supported on Windows due to kernelgateway bug.")
diff --git a/website/.gitignore b/website/.gitignore
index be132bdc8..9d2fc91aa 100644
--- a/website/.gitignore
+++ b/website/.gitignore
@@ -12,6 +12,7 @@ docs/reference
/docs/notebooks
docs/topics/llm_configuration.mdx
+docs/topics/code-execution/jupyter-code-executor.mdx
# Misc
.DS_Store
diff --git a/website/docs/topics/code-execution/_category_.json b/website/docs/topics/code-execution/_category_.json
new file mode 100644
index 000000000..57f54268a
--- /dev/null
+++ b/website/docs/topics/code-execution/_category_.json
@@ -0,0 +1,5 @@
+{
+ "position": 2,
+ "label": "Code Execution",
+ "collapsible": true
+}
diff --git a/website/docs/topics/code-execution/jupyter-code-executor.ipynb b/website/docs/topics/code-execution/jupyter-code-executor.ipynb
new file mode 100644
index 000000000..1e1e48ad4
--- /dev/null
+++ b/website/docs/topics/code-execution/jupyter-code-executor.ipynb
@@ -0,0 +1,454 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Jupyter Code Executor\n",
+ "\n",
+ "AutoGen is able to execute code in a stateful Jupyter kernel, this is in contrast to the command line code executor where each code block is executed in a new process. This means that you can define variables in one code block and use them in another. One of the interesting properties of this is that when an error is encountered, only the failing code needs to be re-executed, and not the entire script.\n",
+ "\n",
+ "To use the [`JupyterCodeExecutor`](/docs/reference/coding/jupyter/jupyter_code_executor#jupytercodeexecutor) you need a Jupyter server running. This can be local, in Docker or even a remove server. Then, when constructing the [`JupyterCodeExecutor`](/docs/reference/coding/jupyter/jupyter_code_executor#jupytercodeexecutor) you pass it the server it should connect to.\n",
+ "\n",
+ "## Dependencies\n",
+ "\n",
+ "In order to use Jupyter based code execution some extra dependencies are required. These can be installed with the extra `jupyter-executor`:\n",
+ "\n",
+ "```bash\n",
+ "pip install 'pyautogen[jupyter-executor]'\n",
+ "```\n",
+ "\n",
+ "## Jupyter Server\n",
+ "\n",
+ "### Local\n",
+ "\n",
+ "To run a local Jupyter server, the [`LocalJupyterServer`](/docs/reference/coding/jupyter/local_jupyter_server#localjupyterserver) can be used.\n",
+ "\n",
+ "````{=mdx}\n",
+ ":::warning\n",
+ "The [`LocalJupyterServer`](/docs/reference/coding/jupyter/local_jupyter_server#localjupyterserver) does not function on Windows due to a bug. In this case, you can use the [`DockerJupyterServer`](/docs/reference/coding/jupyter/docker_jupyter_server#dockerjupyterserver) instead or use the [`EmbeddedJupyterServer`](/docs/reference/coding/jupyter/embedded_ipython_code_executor). Do note that the intention is to remove the [`EmbeddedJupyterServer`](/docs/reference/coding/jupyter/embedded_ipython_code_executor) when the bug is fixed.\n",
+ ":::\n",
+ "````"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "exit_code=0 output='Hello, World!\\n' output_files=[]\n"
+ ]
+ }
+ ],
+ "source": [
+ "from autogen.coding import CodeBlock\n",
+ "from autogen.coding.jupyter import LocalJupyterServer, JupyterCodeExecutor\n",
+ "\n",
+ "with LocalJupyterServer() as server:\n",
+ " executor = JupyterCodeExecutor(server)\n",
+ " print(\n",
+ " executor.execute_code_blocks(\n",
+ " code_blocks=[\n",
+ " CodeBlock(language=\"python\", code=\"print('Hello, World!')\"),\n",
+ " ]\n",
+ " )\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Docker\n",
+ "\n",
+ "To run a Docker based Jupyter server, the [`DockerJupyterServer`](/docs/reference/coding/jupyter/docker_jupyter_server#dockerjupyterserver) can be used."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "exit_code=0 output='Hello, World!\\n' output_files=[]\n"
+ ]
+ }
+ ],
+ "source": [
+ "from autogen.coding import CodeBlock\n",
+ "from autogen.coding.jupyter import DockerJupyterServer, JupyterCodeExecutor\n",
+ "\n",
+ "with DockerJupyterServer() as server:\n",
+ " executor = JupyterCodeExecutor(server)\n",
+ " print(\n",
+ " executor.execute_code_blocks(\n",
+ " code_blocks=[\n",
+ " CodeBlock(language=\"python\", code=\"print('Hello, World!')\"),\n",
+ " ]\n",
+ " )\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "By default the [`DockerJupyterServer`](/docs/reference/coding/jupyter/docker_jupyter_server#dockerjupyterserver) will build and use a bundled Dockerfile, which can be seen below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "FROM quay.io/jupyter/docker-stacks-foundation\n",
+ "\n",
+ "SHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]\n",
+ "\n",
+ "USER ${NB_UID}\n",
+ "RUN mamba install --yes jupyter_kernel_gateway ipykernel && mamba clean --all -f -y && fix-permissions \"${CONDA_DIR}\" && fix-permissions \"/home/${NB_USER}\"\n",
+ "\n",
+ "ENV TOKEN=\"UNSET\"\n",
+ "CMD python -m jupyter kernelgateway --KernelGatewayApp.ip=0.0.0.0 --KernelGatewayApp.port=8888 --KernelGatewayApp.auth_token=\"${TOKEN}\" --JupyterApp.answer_yes=true --JupyterWebsocketPersonality.list_kernels=true\n",
+ "\n",
+ "EXPOSE 8888\n",
+ "\n",
+ "WORKDIR \"${HOME}\"\n",
+ "\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(DockerJupyterServer.DEFAULT_DOCKERFILE)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Custom Docker Image\n",
+ "\n",
+ "A custom image can be used by passing the `custom_image_name` parameter to the [`DockerJupyterServer`](/docs/reference/coding/jupyter/docker_jupyter_server#dockerjupyterserver) constructor. There are some requirements of the image for it to work correctly:\n",
+ "\n",
+ "- The image must have [Jupyer Kernel Gateway](https://jupyter-kernel-gateway.readthedocs.io/en/latest/) installed and running on port 8888 for the [`JupyterCodeExecutor`](/docs/reference/coding/jupyter/jupyter_code_executor) to be able to connect to it.\n",
+ "- Respect the `TOKEN` environment variable, which is used to authenticate the [`JupyterCodeExecutor`](/docs/reference/coding/jupyter/jupyter_code_executor) with the Jupyter server.\n",
+ "- Ensure the `jupyter kernelgateway` is started with:\n",
+ " - `--JupyterApp.answer_yes=true` - this ensures that the kernel gateway does not prompt for confirmation when shut down.\n",
+ " - `--JupyterWebsocketPersonality.list_kernels=true` - this ensures that the kernel gateway lists the available kernels.\n",
+ "\n",
+ "\n",
+ "If you wanted to add extra dependencies (for example `matplotlib` and `numpy`) to this image you could customize the Dockerfile like so:\n",
+ "\n",
+ "```Dockerfile\n",
+ "FROM quay.io/jupyter/docker-stacks-foundation\n",
+ "\n",
+ "SHELL [\"/bin/bash\", \"-o\", \"pipefail\", \"-c\"]\n",
+ "\n",
+ "USER ${NB_UID}\n",
+ "RUN mamba install --yes jupyter_kernel_gateway ipykernel matplotlib numpy &&\n",
+ " mamba clean --all -f -y &&\n",
+ " fix-permissions \"${CONDA_DIR}\" &&\n",
+ " fix-permissions \"/home/${NB_USER}\"\n",
+ "\n",
+ "ENV TOKEN=\"UNSET\"\n",
+ "CMD python -m jupyter kernelgateway \\\n",
+ " --KernelGatewayApp.ip=0.0.0.0 \\\n",
+ " --KernelGatewayApp.port=8888 \\\n",
+ " --KernelGatewayApp.auth_token=\"${TOKEN}\" \\\n",
+ " --JupyterApp.answer_yes=true \\\n",
+ " --JupyterWebsocketPersonality.list_kernels=true\n",
+ "\n",
+ "EXPOSE 8888\n",
+ "\n",
+ "WORKDIR \"${HOME}\"\n",
+ "```\n"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Remote\n",
+ "\n",
+ "The [`JupyterCodeExecutor`](/docs/reference/coding/jupyter/jupyter_code_executor) can also connect to a remote Jupyter server. This is done by passing connection information rather than an actual server object into the [`JupyterCodeExecutor`](/docs/reference/coding/jupyter/jupyter_code_executor) constructor.\n",
+ "\n",
+ "```python\n",
+ "from autogen.coding import JupyterCodeExecutor, JupyterConnectionInfo\n",
+ "\n",
+ "executor = JupyterCodeExecutor(\n",
+ " jupyter_server=JupyterConnectionInfo(host='example.com', use_https=True, port=7893, token='mytoken')\n",
+ ")\n",
+ "```"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Image outputs\n",
+ "\n",
+ "When Jupyter outputs an image, this is saved as a file into the `output_dir` of the [`JupyterCodeExecutor`](/docs/reference/coding/jupyter/jupyter_code_executor), as specified by the constructor. By default this is the current working directory.\n",
+ "\n",
+ "## Assigning to an agent\n",
+ "\n",
+ "A single server can support multiple agents, as each executor will create its own kernel. To assign an executor to an agent it can be done like so:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from autogen import ConversableAgent\n",
+ "from autogen.coding.jupyter import DockerJupyterServer, JupyterCodeExecutor\n",
+ "from pathlib import Path\n",
+ "\n",
+ "server = DockerJupyterServer()\n",
+ "\n",
+ "output_dir = Path(\"coding\")\n",
+ "output_dir.mkdir(exist_ok=True)\n",
+ "\n",
+ "code_executor_agent = ConversableAgent(\n",
+ " name=\"code_executor_agent\",\n",
+ " llm_config=False,\n",
+ " code_execution_config={\n",
+ " \"executor\": JupyterCodeExecutor(server, output_dir=output_dir),\n",
+ " },\n",
+ " human_input_mode=\"NEVER\",\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "When using code execution it is critical that you update the system prompt of agents you expect to write code to be able to make use of the executor. For example, for the [`JupyterCodeExecutor`](/docs/reference/coding/jupyter/jupyter_code_executor) you might setup a code writing agent like so:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# The code writer agent's system message is to instruct the LLM on how to\n",
+ "# use the Jupyter code executor with IPython kernel.\n",
+ "code_writer_system_message = \"\"\"\n",
+ "You have been given coding capability to solve tasks using Python code in a stateful IPython kernel.\n",
+ "You are responsible for writing the code, and the user is responsible for executing the code.\n",
+ "\n",
+ "When you write Python code, put the code in a markdown code block with the language set to Python.\n",
+ "For example:\n",
+ "```python\n",
+ "x = 3\n",
+ "```\n",
+ "You can use the variable `x` in subsequent code blocks.\n",
+ "```python\n",
+ "print(x)\n",
+ "```\n",
+ "\n",
+ "Write code incrementally and leverage the statefulness of the kernel to avoid repeating code.\n",
+ "Import libraries in a separate code block.\n",
+ "Define a function or a class in a separate code block.\n",
+ "Run code that produces output in a separate code block.\n",
+ "Run code that involves expensive operations like download, upload, and call external APIs in a separate code block.\n",
+ "\n",
+ "When your code produces an output, the output will be returned to you.\n",
+ "Because you have limited conversation memory, if your code creates an image,\n",
+ "the output will be a path to the image instead of the image itself.\"\"\"\n",
+ "\n",
+ "import os\n",
+ "\n",
+ "code_writer_agent = ConversableAgent(\n",
+ " \"code_writer\",\n",
+ " system_message=code_writer_system_message,\n",
+ " llm_config={\"config_list\": [{\"model\": \"gpt-4\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}]},\n",
+ " code_execution_config=False, # Turn off code execution for this agent.\n",
+ " max_consecutive_auto_reply=2,\n",
+ " human_input_mode=\"NEVER\",\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Then we can use these two agents to solve a problem:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\u001b[33mcode_executor_agent\u001b[0m (to code_writer):\n",
+ "\n",
+ "Write Python code to calculate the 14th Fibonacci number.\n",
+ "\n",
+ "--------------------------------------------------------------------------------\n",
+ "\u001b[33mcode_writer\u001b[0m (to code_executor_agent):\n",
+ "\n",
+ "Sure. The Fibonacci sequence is a series of numbers where the next number is found by adding up the two numbers before it. We know that the first two Fibonacci numbers are 0 and 1. After that, the series looks like:\n",
+ "\n",
+ "0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, ...\n",
+ "\n",
+ "So, let's define a Python function to calculate the nth Fibonacci number.\n",
+ "\n",
+ "--------------------------------------------------------------------------------\n",
+ "\u001b[33mcode_executor_agent\u001b[0m (to code_writer):\n",
+ "\n",
+ "\n",
+ "\n",
+ "--------------------------------------------------------------------------------\n",
+ "\u001b[33mcode_writer\u001b[0m (to code_executor_agent):\n",
+ "\n",
+ "Here is the Python function to calculate the nth Fibonacci number:\n",
+ "\n",
+ "```python\n",
+ "def fibonacci(n):\n",
+ " if n <= 1:\n",
+ " return n\n",
+ " else:\n",
+ " a, b = 0, 1\n",
+ " for _ in range(2, n+1):\n",
+ " a, b = b, a+b\n",
+ " return b\n",
+ "```\n",
+ "\n",
+ "Now, let's use this function to calculate the 14th Fibonacci number.\n",
+ "\n",
+ "```python\n",
+ "fibonacci(14)\n",
+ "```\n",
+ "\n",
+ "--------------------------------------------------------------------------------\n",
+ "\u001b[33mcode_executor_agent\u001b[0m (to code_writer):\n",
+ "\n",
+ "exitcode: 0 (execution succeeded)\n",
+ "Code output: \n",
+ "377\n",
+ "\n",
+ "--------------------------------------------------------------------------------\n",
+ "ChatResult(chat_id=None,\n",
+ " chat_history=[{'content': 'Write Python code to calculate the 14th '\n",
+ " 'Fibonacci number.',\n",
+ " 'role': 'assistant'},\n",
+ " {'content': 'Sure. The Fibonacci sequence is a series '\n",
+ " 'of numbers where the next number is '\n",
+ " 'found by adding up the two numbers '\n",
+ " 'before it. We know that the first two '\n",
+ " 'Fibonacci numbers are 0 and 1. After '\n",
+ " 'that, the series looks like:\\n'\n",
+ " '\\n'\n",
+ " '0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, '\n",
+ " '...\\n'\n",
+ " '\\n'\n",
+ " \"So, let's define a Python function to \"\n",
+ " 'calculate the nth Fibonacci number.',\n",
+ " 'role': 'user'},\n",
+ " {'content': '', 'role': 'assistant'},\n",
+ " {'content': 'Here is the Python function to calculate '\n",
+ " 'the nth Fibonacci number:\\n'\n",
+ " '\\n'\n",
+ " '```python\\n'\n",
+ " 'def fibonacci(n):\\n'\n",
+ " ' if n <= 1:\\n'\n",
+ " ' return n\\n'\n",
+ " ' else:\\n'\n",
+ " ' a, b = 0, 1\\n'\n",
+ " ' for _ in range(2, n+1):\\n'\n",
+ " ' a, b = b, a+b\\n'\n",
+ " ' return b\\n'\n",
+ " '```\\n'\n",
+ " '\\n'\n",
+ " \"Now, let's use this function to \"\n",
+ " 'calculate the 14th Fibonacci number.\\n'\n",
+ " '\\n'\n",
+ " '```python\\n'\n",
+ " 'fibonacci(14)\\n'\n",
+ " '```',\n",
+ " 'role': 'user'},\n",
+ " {'content': 'exitcode: 0 (execution succeeded)\\n'\n",
+ " 'Code output: \\n'\n",
+ " '377',\n",
+ " 'role': 'assistant'}],\n",
+ " summary='exitcode: 0 (execution succeeded)\\nCode output: \\n377',\n",
+ " cost=({'gpt-4-0613': {'completion_tokens': 193,\n",
+ " 'cost': 0.028499999999999998,\n",
+ " 'prompt_tokens': 564,\n",
+ " 'total_tokens': 757},\n",
+ " 'total_cost': 0.028499999999999998},\n",
+ " {'gpt-4-0613': {'completion_tokens': 193,\n",
+ " 'cost': 0.028499999999999998,\n",
+ " 'prompt_tokens': 564,\n",
+ " 'total_tokens': 757},\n",
+ " 'total_cost': 0.028499999999999998}),\n",
+ " human_input=[])\n"
+ ]
+ }
+ ],
+ "source": [
+ "import pprint\n",
+ "\n",
+ "chat_result = code_executor_agent.initiate_chat(\n",
+ " code_writer_agent, message=\"Write Python code to calculate the 14th Fibonacci number.\"\n",
+ ")\n",
+ "\n",
+ "pprint.pprint(chat_result)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Finally, stop the server. Or better yet use a context manager for it to be stopped automatically."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "server.stop()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "autogen",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.7"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/website/process_notebooks.py b/website/process_notebooks.py
index 2246e07bb..8c4651fdc 100755
--- a/website/process_notebooks.py
+++ b/website/process_notebooks.py
@@ -105,6 +105,9 @@ def skip_reason_or_none_if_ok(notebook: Path) -> typing.Optional[str]:
metadata = load_metadata(notebook)
+ if "skip_render" in metadata:
+ return metadata["skip_render"]
+
if "front_matter" not in metadata:
return "front matter missing from notebook metadata ⚠️"
@@ -154,8 +157,6 @@ def process_notebook(src_notebook: Path, website_dir: Path, notebook_dir: Path,
in_notebook_dir = "notebook" in src_notebook.parts
metadata = load_metadata(src_notebook)
- if "skip_render" in metadata:
- return fmt_skip(src_notebook, "skip_render is in notebook metadata")
title = extract_title(src_notebook)
if title is None:
@@ -331,7 +332,7 @@ def post_process_mdx(rendered_mdx: Path, source_notebooks: Path, front_matter: D
else:
title_search_content = content
- title_exists = title_search_content.find("# ") != -1
+ title_exists = title_search_content.find("\n# ") != -1
if not title_exists:
content = f"# {front_matter['title']}\n{content}"
@@ -389,16 +390,21 @@ def collect_notebooks(notebook_directory: Path, website_directory: Path) -> typi
return notebooks
-def fmt_skip(notebook: Path, reason: str) -> None:
+def fmt_skip(notebook: Path, reason: str) -> str:
return f"{colored('[Skip]', 'yellow')} {colored(notebook.name, 'blue')}: {reason}"
-def fmt_ok(notebook: Path) -> None:
+def fmt_ok(notebook: Path) -> str:
return f"{colored('[OK]', 'green')} {colored(notebook.name, 'blue')} ✅"
-def fmt_error(notebook: Path, error: NotebookError) -> None:
- return f"{colored('[Error]', 'red')} {colored(notebook.name, 'blue')}: {error.error_name} - {error.error_value}"
+def fmt_error(notebook: Path, error: Union[NotebookError, str]) -> str:
+ if isinstance(error, str):
+ return f"{colored('[Error]', 'red')} {colored(notebook.name, 'blue')}: {error}"
+ elif isinstance(error, NotebookError):
+ return f"{colored('[Error]', 'red')} {colored(notebook.name, 'blue')}: {error.error_name} - {error.error_value}"
+ else:
+ raise ValueError("error must be a string or a NotebookError")
def start_thread_to_terminate_when_parent_process_dies(ppid: int):
@@ -480,11 +486,7 @@ def main() -> None:
else:
print("-" * 80)
- print(
- fmt_error(
- notebook, f"{optional_error_or_skip.error_name} - {optional_error_or_skip.error_value}"
- )
- )
+ print(fmt_error(notebook, optional_error_or_skip))
print(optional_error_or_skip.traceback)
print("-" * 80)
if args.exit_on_first_fail:
From f3289cb987305d07254f768e0ce7b8ba008bb68a Mon Sep 17 00:00:00 2001
From: Chi Wang
Date: Mon, 4 Mar 2024 19:38:30 -0800
Subject: [PATCH 12/13] summary of recent updates (#1850)
* share updates
* updates
* fix url
* address comments
* address comments
---------
Co-authored-by: Qingyun Wu
---
README.md | 3 +-
.../img/.gitattributes | 3 +
.../img/contributors.png | Bin 0 -> 493276 bytes
.../img/dalle_gpt4v.png | Bin 0 -> 3785874 bytes
.../2024-03-03-AutoGen-Update/img/gaia.png | Bin 0 -> 546588 bytes
.../2024-03-03-AutoGen-Update/img/love.png | Bin 0 -> 150529 bytes
.../2024-03-03-AutoGen-Update/img/teach.png | Bin 0 -> 921359 bytes
.../blog/2024-03-03-AutoGen-Update/index.mdx | 178 ++++++++++++++++++
website/docs/Research.md | 26 +++
9 files changed, 209 insertions(+), 1 deletion(-)
create mode 100644 website/blog/2024-03-03-AutoGen-Update/img/.gitattributes
create mode 100644 website/blog/2024-03-03-AutoGen-Update/img/contributors.png
create mode 100644 website/blog/2024-03-03-AutoGen-Update/img/dalle_gpt4v.png
create mode 100644 website/blog/2024-03-03-AutoGen-Update/img/gaia.png
create mode 100644 website/blog/2024-03-03-AutoGen-Update/img/love.png
create mode 100644 website/blog/2024-03-03-AutoGen-Update/img/teach.png
create mode 100644 website/blog/2024-03-03-AutoGen-Update/index.mdx
diff --git a/README.md b/README.md
index c10862baa..247dc90e2 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
[](https://github.com/microsoft/autogen/actions/workflows/python-package.yml)

[](https://pepy.tech/project/pyautogen)
-[](https://discord.gg/pAbnFJrkgZ)
+[](https://discord.gg/pAbnFJrkgZ)
[](https://twitter.com/pyautogen)
@@ -12,6 +12,7 @@
-->
+:fire: Mar 1: the first AutoGen multi-agent experiment on the challenging [GAIA](https://huggingface.co/spaces/gaia-benchmark/leaderboard) benchmark achieved the No. 1 accuracy in all the three levels.
:fire: Jan 30: AutoGen is highlighted by Peter Lee in Microsoft Research Forum [Keynote](https://t.co/nUBSjPDjqD).
diff --git a/website/blog/2024-03-03-AutoGen-Update/img/.gitattributes b/website/blog/2024-03-03-AutoGen-Update/img/.gitattributes
new file mode 100644
index 000000000..32f7e361d
--- /dev/null
+++ b/website/blog/2024-03-03-AutoGen-Update/img/.gitattributes
@@ -0,0 +1,3 @@
+gaia.png filter=lfs diff=lfs merge=lfs -text
+dalle_gpt4v.png filter=lfs diff=lfs merge=lfs -text
+teach.png filter=lfs diff=lfs merge=lfs -text
diff --git a/website/blog/2024-03-03-AutoGen-Update/img/contributors.png b/website/blog/2024-03-03-AutoGen-Update/img/contributors.png
new file mode 100644
index 0000000000000000000000000000000000000000..91ba6b81dcb2a81845329e12f70dc789476e1ced
GIT binary patch
literal 493276
zcmV)GK)%0;P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!~g&e!~vBn4jTXf|D{PpK~#8N?7eqb
z7T5Ovol|m>7)xweQBhD48zNmgA_(^0d+!BB5b3>lklvd}?}9V|1rY@VrQ3UnrYGlq
z*BX3sOpZxRIp6o6-@2}S&BIe>hS__s&$`#%v*!b4Wo4+Vt0OQl5MO@zCEjIUef>2a
zKYE0Y_IBL4eG8wze1Y%2`x@W>@C|;Tf*-yY%2%&n;r`vbXm4x9{rmUv#TQ@D=RdsJ
z%b&9!zW+Bq|NL{@zTJr%H|x-K_aa)m&Z53P2;aW0KyH=?4E3i$ca|JhFIoT_a}$)F
zFTm5MkMZ?4-@e()pSSP7`wkBtK16j*4H7fb;2ReSr>N8LJrjw{{6v%_x*<8p4lzEq
zNDlMCnUpiQbfp2$p1;7qfB5&Cz5IFmn(Cza#&x7*B*V+s9d?d(FgtY$$4{Juxs4+%
z9Gr2;>@du1P2d~oh1|R>w6(Y58~WW}X-}U%MP+3ryuH0~;J^WF*|G&jMn=%p)y1}L
z+u-c%3@*Ek
ze^OphJ9zluA@z}Vw6(V35%sIDzWVCTUjDp&|NVD(LUmGou?ER0SqP3uLO@swqLa@d
zBqkdPDJ4kFszz~HBd#=bEXlsXl-dgdrKWUnl7RJ
zY9%@ws!>;-hk~?dG+fNX-HsaEyk3pQYc;ras|&Blhy44m>n1`S*VWY_GBOe;PoBi~
z?c1?-?OLo{xe}{auNI2FzCJc?+$glw+}vDrb#;+{`|iyi{=9wj%{O@d{5d*1JB9M(
z$rGXf@-e>u{(HLa-wK7V8Mhh!&h6W`@$%(M;r+kLxSu?D_z0J;Tt#I~JsjM8prWRY
zO`A94Y(gAd?5yDv=nr3CZ|vQ$7JiQQxI%vJ%P+rpvxna{0Vv$r-*o}`y<4|#;aYt?
z9uO%0&Ho&Az#qQ<7TAL
zjJ^8&H9EVS(bidwM^EbT`m07fepZUwD?!vD!sr~DP*~&(1A~dsRGI)Cr74)JIR&1M
z`_Oiy3@z6$6X5O&mG_5@S~){9udEb7i3xBG4uN}EFq|WT;1(Bxz|K?f
z88P10i1D%{$8HZ}-xvg($wtl9dU7f+-t6#?*$?0UK>v3wT>Tue`OrEnTsI$!RxZWL
zbsMp5*CFgbdJ_AMEwJ;zF)UiW0`peP!%_32uynG5t*bR+lOxe`vze;s&pA?V?B(U<
z0xULDgWS4xD~=pF0#j2{?AWmb3l}cLf&~k(Y11YwU%njc*RL1e=kGc=IH0hwPyoxH
zF>astA3Q>Rb0;dB?%@(a<5JgGxYY4EYN*uR{uY-RDjly;RNH~{&RvQ`__ihu`T$B&HQ1Ix!DtGD?wMa1ljx9j-Lo
zM*FRo=)U&_y6?Wm&Gvifx^(=4ev15phjTON1?>|8|
z{w>4f&Ye3#TW5%HAjN)&+b+Lm*f8`su;A}8NFGwa%7Gfc=61#H=Fix73zuWb(iPaedM#GWpAVx2^RRZ!60D!2gt_AeV4?DKgocKp
z@p=oseviQC1HOpYu3dZc=YQ<=Yl`SDRw66oEUL;%@bc*+>Nv0P-8Wz2+i$)ib;1|q
zyB@I62{TOoO;BOT2%r(*@(rFnc}&q-hj3y4h~2u?jnb-gT)vTk8+QwE=RpZYc@_BX
z+e-wjblkmPgdcDVd4+xil=09|m4cqe6wIGJ1unK*(AkoM`<)kXx2p-=U7gg>et$H`
zk=xbAX1MzL;joJntO5ey9Ucy^h!D7uI&p~!ghzBB5|YDEp6rM8Fi*t$+9Sc&hN1u$
z9Px?5k$?zF0)-Y;4r60u?BBm%01HQa2M-<;l*@?|Cxi%*|K=!>xBQ#x>gqqFR=%RBskyD2KynvV
ztuJx0^9$5;iOOqKwmd{$MHAvu3lWx>1rNIZHjbX~4T(i!RvAhz6NFk`Pi3V-$*Whd(b3j|I~@&pa;E{0?=(?_TMV-k`!QqYB#an61VhA!
zVBjz@3>-2HV#9`DuAwe6(qeG6rU+N6a!_|66ICVY^t%ed$^H?$ckdofpFSUKm^y=kaBb?(8)0?o0M7ayN8Bk=
zaw-#XaNl9_ACbr^EEd%7uh|EDp?@y9nTYAB$p{beM{z+Gnj0?T^XCuo_`z+wc=ib2
zeEk|dEZV;
zU%T1bL~`96StZ`M(v^k!?kwDXoKHZ>#}8kZ<8F609zCqV>#uJiI>reGx|5-;HWlg$
z6ERP3I(%IA;eJOczW=-xub*_`#lzd^?rtYozJI6X#!zzMBK+cWVCCio%OF441^FW>
zI}35?XAzd32+z1+ct!;vJ~0FpXM&I!>4&5MCnN{h!PVOh$Na)!8XN}Sm|#SnJ%ePD
z)z_O_1V`|`c8{*LO@K8f&zp!bGe=;m!gMI8szPhde9T+C0n69z#hNWgv3BcGtlM}P
z+qNHpvzsTj?%oCcMN47f;)zpkuGnF^A4`ucz;ZJq_{MvX1b%;&z;#z!TPt)l1_F0j
zdwY9WTU!etv9cl+WNRyc!`IhW_>7}H{*1qKic@j~okNEX32J3NMMT_j^YZe3m$ZNN
z>J_dub)f3zQ&hLVrt*qZ$7=$~m#FRj5-AjgZ{M~PhI+b~qpbzCS!z&LR)OsF>5!i(
z2OTXfIxjP16xX4a6is#8=csDDjjr3&K7U7@bl>Yj*jEYMQsB36L38|C9
ziW&-{+6XS$7&~4DQ>M)2ll0k
z+AO3tR<2rs9lLg5Clv=rdz6-*$FmntNxgha7m#Egl`mespx+Zz?%#Vas5CTPhKl}EtNVh@%q)WW1OgD`5~r;wADg0`|O0Ywf`
zAr|=N^CtZ8We1+#ZNleIS_z0Zajl_Rh^Btac)X#Cq6Sw2f_+E|Jc2{v5Ox|~kr7DA
z&ZLro(3DsNpN&F9dK}KCMxin_7&);4ID6U^siz&_8`c{|NYHWFs;)4x#a}2#AS6
zV0=8THPN}fhXcDyAl_rW7fS1tptyKCWYs2NhU^T;E2uz?9K>vcWtd~QN+?DP*JIt9
z-PphL2!aBGU}0&2x$_s}gry@)Z5^<7*Jc=PH-N!DEf^lrgnO6e|zz
zWSq9**Q`z$7(9j&PybVLawj8Ho@~4Xp
z@2A-W3D=_D%Ykv*$;!;a@};Y>bnz<8pSKKp`b)85^>!>>L5_6W0qosljFrnbKuJLz
z3Nuuotf&TE?KxERAvZ%2lO|5ZdbKBcU{XJXB;SWBLRs$c!D0u|o%A
z@;GrE+q)TANx{fZ3qfH<1d6g^s6?Z*Fb#KZcfUE(ubZfSa@vYvz#!qax_I$oL4`0d
zL@@c=-~J{Peoq7!{+j_aYSbvqojVshckcXAt%QYz38|^yGH%1{_&LMF*Y52x5{3z<
zvN$M9OG^{_2=|MWloX-g@b%{RGcqy+$Z(%1JXeU4(h}5PsTcOk<$XZ^?AbHaHQqpd
z`+Zz(y^Z|yl}O7eLQzFEimR$|t-TGAiHR6b{bG{LG}xJ2z{|l2AtB+2i}J@=-~9+X
zstxB|b8*tx6fUPz;1Zb(udp~26qgA;>0KrOMdaW)8n3^6i5#&X+#D@YRa$`Z^F+|h
zMATg_Lw(%^_;|Zu_3Gu2n>hm$CQiWk3F9FxBMs@v(ikf>R@iFK)`pF}4LWai3MsMg
zzxxW`fBO|)lTx|aLJf|{`D=EY8j%0#JutD9g_V;Ia*G|1ns*A(sXLHSbO7ZStx;Cu
zg`<0xKw@Aoj2qM&GslQSk(|f$F%p<1CykA3br2JE63-r2;9hqLx^A4q$FBmG
z9m^5x!x*JL3i1nNp{O?nvU200Fnt;nV%aF!-AtLjtn_Z%o4x5yIUAohCyQV7)XvC3#qZ=FmB8kj2}A&QzuGcj;{rem6nEL0R#p`keolP6Dvl+-xP*3m&l
zdBv}`&(~jkK`?0|h&18Z-DZLcgQW$H7fZ2WkpWbsM`NMtbR1Y^2*(2(5qNShf==#(
z^T91RwtflL&((yg%vfm8lt!?pHPR9TkU=FUC6wS2L2!ve1^J*yj~>1`;=7F%0S7P~
zoN%zh3V8SK-Pp5d52-Q@0VMzW*T00V2qp}V4?g%nDEv1=h0|bt`}W1ei4%o3&c7G!
z^X%EPzuG<-9(*3$m-w3T-)|RI);zqx3M4f(6)_Z8GN>XVB7{i;JZXjB<9^1+Vz{J}
z|Ka|{isjO!OTzwtEB(dCsjI(AAi0H$P3>rG>%^riS5Z}a6_uon&R@KM@T4eGvd1xh
z$wF-1wiSUs?r^oYhPQ7p5@Mo|7H9(BJ<3?FFa$^U?T2?%7Tjag;UANNn50xvRyprX
zN&W~FE;pK+aI>)%zNAk4JnYeR^D1g9&*5~SGtMN1!R*9AC@D?H#0g^|Au$HxBS&Hw
zMS&y9nU9hfB@}UbJ({+Y2rLHA;p;D7;p;Cx$HP0f(A?C3ufKVBG|Gd5
zsc8Z54ww(`ARV}Q=^`=tATqKJAtQeqVp3NiA!7&5q*_5$b}VL17>5%ZSHku1et4Oj
zfW_Y3STK7gtgP0epvVzd8^cg{B@~@EQt|p}Ic_$^psvOjwKXB+^qT4aeskcT8ISvv
z6&0g^q6KH45O~Gsz?Guy6K+nh@pOZKa1i3oB%=IE6*9_m5kpW(PmjQ*tkWn=4n%sG
zD>4Zvetup!=@&^rIg6CsbYz|{KyGCzaxYdOp(r0`3k!r9Ht#YXfcFpc$0&u75LXh%
zbS-I$9>+pfdK6?Qj>B}i##1Lx#>DXibSY`5$|+!}z5#Zx*#H~kW5`L0Mt*8MP8~gp
zZEH5cKvNI17Z`g`UeAuCZh-n)#Ws@B$4p|Nx4T1t^Ucd+y4
z&kLyzo^-(-hT9M~bdI2S%SAMsg*zEbaL#XYl*IsIMI#!h=hWB*a*zz9cR4Vgjn&$5
z3+0XXP}NExX?-aulM6Rr2w-8LIC+IaYUBuv82A|`jvR(b;=_d3Vgvg_LVP4$w~3fZ
z!H>%HshCJ@RAR(Xj2=D&%U7;P>iKJ^BrugCVQY
zIdfpBsRJDqb!aH5L4^RK##?zcD9S29ZmK+{%gn&cX$nvx0RT!UnmvQ}ew%_bYbKPqZ)XC#p1Qb#zmH8Q1Ggk{c7tcYk#X(fX_~2TC
zKN@2^a3#_i6{oFn*3lS2CwAl5x<$~Fmxi^;ZY0t7&cyipMYfV-o3)u-L74`Fn;`a0VF(b!i7^`y?XT$z`?H>A|HM9k?@^<{rU;u
z88~pDF#f{nS>6Y$9o`?Opnl1?ZF67Y>+&{8Se-BgIE9sbCRyl5;o;!|Ko}&Up`pV0
zb8zP8=O?Ioevd!nKElc+Gc!|AF05`uGsWIzeC+eZ#fVEx#<{9GnV=LA9R)iV
zS2%dM!p_SPn-6S(xxFbo{9F+f?2m|0AJ|x%z}X`Zp+S+z33EWM=LT%h8ijq^x56(W
z7v4#E2ujLCbmCd~`T8P@&gbVTxF1tqd@*V&i_v_g649r<;Ok_T|lDr6UK
zKvw=1Qe)4<
ztH*_Cz8Z)NWj1KIMh&|@@tvs!c7C;G#i*bLP>_)hSKknL$7R9E+Y85?t>EDAiO`sE
zM8}3BF(C|IK8^^B4n{_LG-`8B<6P=#q(^xoGt3boL7woT3WzyZg50tKoV!qroQh&(
zRTLq!G#}|jnW(9|^iF4f<=PdfE|~=}*+Gz09)s!1QWQ;!W9s-Zkd~wfn-t;XF(WZ^
z;#ln7xB>yrmPiV4M^RD?Zd8@y#jR#MXsJO)s2{eH)6tqXoxVE(@{=b(LwyD`mym;5
ztpcsB8d!UDr7#Wo=f*%_=f&+KDJeh?Z4kP{=2!kSr}L0
z^n{(AouE)y)o_IMb9+h>zO=T59R5=QCY1z|O4?qy!9XFfG(X19J%=FHw--hX=!fw`
z2SZ}eXBgJ64+i(`jcH>gFk4O*x-+KY$okbVQdfoe-~kY)?@5!&b`6LXR1Xi-mp42V
z2K;#n?oW*Vh4$_?M6?77V;=8yUy`frhdQDIG1SE6sw6oHD#UyeI(t
z@5r1Z)Z1;>@Z$cBH|nGZC=a_EQJ#MW`&Jqu-0}!&V*PMCKMr@YB5{*|(HQ28+F%0cH?KwNH7jWSV^#Q
zW|-{Tw@+~X3f?chY`6+%A4o$AdD{wDKrc-IDm
zCubozwE)4%xd=~4hX?stcSn0%zuxrb$UXJ}4_vW~t12(S`J6L28ykYGq(}sL+9M_A
zH2mGH;cR;X+G_F`DewGFZDAz}8zA
zc0Pu1J-rs*5et!=xdw%K`v`iz$UI|(*{bA7m(9g-Y8bZbH^Oq`Hn<%>1wR)jIM^LR
z^~G4cdRB?okIM1&^9!U<3eesXg6b-3l$D)C34x`){-=`u2YTJv(unHZv#27{7iDI^
z#VZItu~~3%bHz~ttS5oiFT@u|_ilncb%xdYv!EvqV)foLi_DE-@?Yn76TLr)^Y_RTjj5wXLTAbZggT$ZjY~PWf4vmXn=jzy&3e4(Y{3`zn(?x|7A=KmV7+M-
z)W?s-G|91$mmGvJ;_Auu@;A4uhSup}S5M-qe}D``L72b{gDKI3nVlFLy%jfIO)9
zXN^;=Jb2H5a&&YQX1j3bV+A9E5gV%$zQ^SJ)jI@~#{?4s$y-2SWsz6ej3o;fqwh!m
zz{DW~p*C(L#`W(5@qV8|cJxRrRa3@V9d(#(UW0fSTb$UsP5{aVf=kG*?f1lVHDZu>aj)OtGV(BW(Qqh36hAxcs^s$5#$|5}-ES#;41-jYfX9LLa5=OE$G0ql`Tk9a33NeXgctcCKcQqMQGk{i
zjncdXRF@b0SDmmT;PffOgq=Ps6P_@_PMarya5|Nh2ZMsA3$u#habH%%?5w%niPB1(
zb{#o#E2}C6nDG66Jd2DWcgo_F
zFnNFh!sCw&5H~kB0VbTfaFF-5@s#bflrwO%ItfQh
zb6DCq!z(b7l!q!142+znzXxcdh#%93P)f;YhHb_Z|7yfUNeZa)Ld-o2`=cOYi
z$OA4`CWsI7MLI>^!6c1b?95<9(p6GoI3x%XBL)t@2!e*hz~9q0-thuz38bVEm5B3?Z?hbQ-P@bF$5T5pEn%2j97Tyey;CNEs7i^ucl
z{~14E`FyQB7gs34sxCfHkj;XdPcVE1h2jlUFIRZSgu&W^!GvF*Od$;1DFA&qZ`mK2GQ5AT%!}}9#UMbu|!24(dq%4!`9YDrj5Y@?4ndm3y%ja}A8p=fl5anZi^Ym@^AH6UJb@
ziX2?Gtb^ZyU5GFsuLpMab!0pP}}_M(@!vrf}<@v58_-M1M3;e8Xu#y{x&JL
zpN?;JcXi|LqdSQG9YA^d{3)s6mx2<0_3{-up0vFeP^?;vk#ai@x4Lir#EEj91XEjD
zuwV%c4CiAODG6OtDvONB`Rfr(^mH+wpt49`4~q#hM!I?g34(<@DGvo@tY5qss|@Bq
zj{=}2q|&x5oDVIz8IYekRZt~5D%#MPsg8NN^RQw0dhA%g9cve@MtE@8PtqF?A3ngn
zuIqw2`8iPTbzj4^t0nNWKaN5QsLpzrBiv>`B5e;M$HxX|DQNUMOsaI(GHSoeVX}P*
zcC69Ii9_oV=tKQC)D_85-W1TX9|{!4QaJ5ZlAlcd`NmJ`gzq&ueE9HBlnMXM?TM$Q
z^ME|3uqI5HAj~x6v=NU{Fid#Ro71-S_4NXziz$HQw*A>>6cqRB1r<_j3=mc*3@U!j
zKw_uQfse>oQFB1Z*N0)k0lMfl&-mgo6NVfsaRwa2j6ubzV;)0f@G(p{xMHv{P*@2u
z?09-UD;HKPtYSDd#)^i6FP?4nc0cK)_UTH$ako4HA17P*2Zh5YG7U%V{4rB!Ddw--
zj;z80#GMJp+8q>-80cWxx(#q~a7To9Fg)yRVdrECcMmrt2Kpi=zzn`7%dl(h5(Gw_
zL2z~nl?n>3GvVyv3lB#J1p9lDPrdQx3^?UQ3Z=556yX7GusLyn8qiW$9^Z%TGf@a|
zx5KKXhL|op2_r~#j1lXPiBuFu42HJEQ0Pkzh3c?Rpe5c9hU11pbL1dQ9@H1Z`+thh
zC^GCn;4=&(=QmM$9I~=fDI$G=honxPKD>ilPEuu!Z0S;#K;J9xEe7A3f%j)Iu+p`s+md6p{
zbBd_Ek^r<5wU<3`?@khKv_zt=-VYVkcE~R_#@Xz>D5VmN8tVy7(cil2KMU>^W7`x
zHwh>_8;<-)CnQ9Yl5ujuK@T5Tg$2VnG!$FhJ+aBz6(;`P0#wcvXCbq+_$TSN#LOfp
zu2jMp^)VQwERG4vV=-dPK#UqTNQkOO4IKp4iIPYU^2VLZ=SiKE<4MZ}ytrOTr3O!%
zui){G8UZMe>&~O@Y&6Wbu7TnN3CK&0g`A8OtXepw@OJcdv#nEr2`iM6h6kh|o)Snz
zKzSwrCAH)#G}JZF|Kks_K|>K6H59N=RvO!8%|O)dO-Ma?6qf5&V&}3Yu-d;DE=Tu4
zQD!1O{pep9(5E+Msc9e~=OW6R2qsq_5K!*oLT&SpgRtx%`0n?IPapi>0p(UKo<4ah
zfK22>?~qztw|)ckjpkwT%GH=X*9dbA3^0#?vSb0N6vH`~H%Aw9v}R$hjwa@6sY72w
z6`D$Nn5U(NW!f58Ma5#4y;-ZP1$J#90jP%kjL~1GB6?~vwp=&tY5wo
ziwV@n4;lYhCwwlqyE^dr_Pc>{pA<@aYc1mZoe^w#1aZzM;BUSQu17Y)=lFIUS)qsH
z8|J}u%R(4$FvQ8-OR;^m4$O|Mg_r9wgaz7CQ0tB}q-4_Kd4Qf&e^!j3QfjM9e{`bk
zGdOZ*htCe0(^KrM86>>r!s?gDNE8(ng~-09rbbAu@EBzTr1gM*a^15is#
z3kr12dCTcEQJRY(!c*`$9W0uP&v4=2@@M>ePC4_wc{Z0|R43Vag*aw>oPc5iANy1A
z4G2Qe*-{+v$-+jlOz
z0fF#6xeKfGCSmi+W$=$mLttJdg7RzNotOuE4?j5D+rZ!5nfOsA{I6^utXR2{qJEM^
zCl12b#R|JNt`Kr&!QPHS%4^vo0}LHL5aI-s@q>E9KxPb9P8o}RsxmmDCWCF0hvIIZ9-9KwlX7q%sCW@X0)kc{Jaj370_P$+as{G;Ho$Se
z5!^N|fyeq~@ZG%uLFW75>$nTYOmq+&xen!37PxZ74HquDqM*bInfb;@O5cT~v>iBC
zb`k}}mbh>sl_I}?bBY`{uU|uJSr1Syo-aXh7RftOD8VV`;1Lvz{jTmXbF@POMOy6%
z&S(!Y#hDWuF=O-)^djf9R96XE1mohgXyivYAUVb#Cw&8P&@U959GtL`UT+8p#Ug7b
zthcvEWKk~Siu2){OwkuP4mMUvmR^=nSf&6;&9RW2H3pLuB{6td54464>W`5F`(mM*
zBC1lN(OH#?&P#c?d!-1E>r3#o{sJCftHz_IDm-i;(9nBb#TkgQJAuXOa+o%56lA0%
zFjZCx>hqMKyg;55iWX$&PQ&&i+k}qF#toL034?_TOMQ;A*!hbZGB+fK02hAE5i~m-
zQQ=M|dXJs6=)0o#E?&GSM1R~#ITy~KF~CIX^#}T1ZNm+mCy*4AYAC&SALR@bDy&k<
zXv>50k*UR)EG>2lpS~;j@SD1PTMhiry2I
zQ_Y7F)b9CTKxu!{3ZGUdoVtGKC*S}1m0qgW23F72!dhK*SZrR4IJ;AbJY|A_
zV+UcieUqS8v`DqcO&O2L6U3pZIt}v-RIzNKF1?&^#MQVVJZw|iOeNzC*yMXeb
zs{wafYEhOHjw}ybQZ0JRr`{;I_=OrdU0QB7dH~%Y)%kQc{AdnLT^LYZuCK9$9B4*bDSd(+wHd9gnj`p}=5bynW4DRZVTAAbIeNUAPGRP+)Ahb=~O{BF3LEQa|q9XPC>50Bkz;bXoNk*AL$Fmw|l
z64xUwdl$|XA4hh+8S>6sqOjx?QZo0Um|)V<8YqmTCm&xJQ*>~GqPigKL#XmRf?~7P$g$l8W$J`~|KI~CO_oA*h#$(c5|AHe
zkECc{obU<49=|B8ad3lyl@p8{ys$Aa5_<_y?y1RejE#g{bS!F{nuTk@@_E6|1yEg~
zj7i$#Au)3VB&LkSz#*Rzpa)>&kii%=s1J55o{O5)7}QcDI2YrM%G6MFlw{!kg#vV6
z$j9}PG+fMxLLHUHj2NVO+hFG+EliUn#WQX+q{ok<2v-#f`ZJ)oPyuptWnr{x?vFPv
zu~Qcu;TxcE$KV-Re8VC~WE`=w!e9x`h0_n**%?UOxwx}&;qUSqUL1Y#QY}0MlK)4J
zG`YiZbjJ4&baZqG$Kgp4tbiCeUlCxc8oE$?jev6HHcAL6W!Imetmz3sg@8h@c}&GO
z>EL`a#*iD3pU%J(?tX{W;RAM8+V-9>S27m8}Taigtg@(AawAKZV4N6-HoC=3J7Ry#qFgtSwk
zZE%MdQg0;)V<;k^FdS|@?M6~}6iyRtgzvv8JW&5jK;dg4n&A{25&~Iy1xzAGBqJ*a
z`B`dE(NGs=XDKL5gO<)L7|qv%{u~X=*QdyL0|i=+<`i+dA>73pmRr|j?_6D&E}oAa
znkv|+Dvy)vR^i~vrP#f4305zkgC&d9v2vL@RxDS?>JczHS_@!Tbxh_1zYyHxTa
z8W=Av2SdX}m^?)uVnZY#Dd)sOGd2nNsb^4_n}l84cOxn`
z2WQXKA+fLyVHCVMxOv0T`V@SeZIGFg^y94{A1qn2n4De+>`xvRR7z%2gfK9FwW=5y
ziQ(9=av{dBLizL`m^XP84r)$@&_|5$C>dK204H;qUHWiqd
zsbcruY1q1X8g}iFgNu_PMIyQg3R-}W;3Wu)T!Xlj{fLM@1iND^v2UI_wreTi=yH7o
zIUGSm*f9i0Z$s3X4ag}vi27@;xK!(jsu~YmzUqVXW!5OAQdQ}M3uX4Waye0$uFTd@
zTZxW}G}NCt8^eSpqDV8
zec~u`XnsDZ&O3{9;SNZk&U7*`9ET&*u+}vIi=6ymXy=P{evvSajE7rl3S5&Ca55|$
zW!D;nbLhI&g(>wATeqrq{oU==TV08
z968A7PKLCmG}QEz(B0KDO_XPhuyYpyg(EsnFECIz?_5+=Bt&+iTrWp$3?cq5H*z-a
zTnq{xOti7F5ej!Mh7YIHxWkF^(mVlxkH-o{)KR{qPJ5y84vI)Q6xWezsONE%$3hXn
za-Eb4sT4~GAB-710E!dFA%)uVB?og9AKQm>$M@oLpeHVb1tG@F7_o=8@=r{Xo`lmenJA^>6kWcJbC)`Bj#NtX%^skz)8H9b?*fWdvoW%6pT+%W
z_kS!8p4}7F41?tD!s~ZEeExvGbMMFR-g|bJK;%nc;`>^D3KSm9=LlG&8aX1D96te)
z6DMKvjG36Jq=IP_;ZB`A32G`cp{Y9y`txT~z0QKV3NNRjjDwq2!S>J|I3C&ypQ8tH
zYSnUh@7hL+emgvOY=Ph5gRt4T9eY7kK03VB^2y(YVn4cY@Pdg(i(u;gj(0_nJcYIfRP*9MNM&jUx+aOQhpDW9^BR$CLHjviWZH_
z@Mk<&&h3=J!2uYn6_GmO!V@@n2^F6D%g5tEdEO777ekI=!N6m+!{aubnq!s1W0VXV
zJ{JE!M~#nS-~OId8-I`Y!JqR!ICaNuijU2G=j5>yFg|z~)>bEBV}1y3juyx*sfJ^G
z71o?MjlP3MqhIg-7(ILxh71@+>bxI5`rseXlbwo?6UOk}wH|rCCviC=7FjXL$T*vd
z3gSROPzb^T6Of*C4w+e1NJ=dt5PQMF$_!qPR>(M$fTxdpguw@k7A>GJMj_1C8380u
zOL9}tQg=aEHX$`G1jc)|VA5zYNDurN7OUnUeD@MWZ8bpb7CoHZJ0HczS0ZQs0)%bV
zfybIz@K~aXxGjrtmKw`my%`Yi`wxf>=mT5&Ctp5(fQPp_(biaxyKOfFpz!L&Z0C#1
zv2^hQ?B1^fQ%gIrxUAHbHhg&xz
z(RhWY5BH?rn(Hs2^FsO?pqxi>R;B=yz@&WIr$0=ov9H*;7~=KW5Vb`7%^-prjC_>
zn&LF5&XLD>wF!_=7=!6blhDx6v$h*MQSLM%pzuvu+>p5gat@g#H!BaG{>UAVI{;5D
z=K1yEEKg}JNZ))TY${ms;MRo@K87my#AHT<)gOA6BqhMAjoC2$8yoYmF?vbOv
zg<=9qN#moQ(!}MlkovOm2!^V(6qd?QMflz=C^)nO2^*IpW$#9mxL6_2%N1v>PomVp
z8rMPtkwlS|=FI6B-1}4X`uJavl9Yr`=otdmU7V{Wpwx7ru;v!7xAjEB++le<Q^wRT=Y1dCWG@gc_*{EgcmY>SSM!PeQcaF8~)^=LdaRUZP^5y70ckb
zejU6I?8ou7Yp~W}4s_I&psgtnz1i~6)se+)9olNC(*8B!=G+4mUTys0?Ur8#N_Ra$
zSNqc6KIEYivtMSzZMB6wFTH8CVQD9ur~EF+8|$FjRPup6}4-@9^*VGgd7;mcwdEwB>W*41o~5
zQ9pV7;67|kj^P+76iaJMSehS&v-NQl6c&-HxQ6{!ff&$#5JroMVQBBZ=>5-6@X0?v
z#ixJ!2c!q|#wI0FDr(aab95ChMERgFAr!5|eP+%LO_LGoU!?GsK?U
zETpy^DVnvKH5TD(6p?*kF0yycMdaF9a5tI)mpL*BU8#ZW;~U|(*$DE3Kf(Bcec^Qc
z5WabMhZ|V7HmOEC#eD`K7n!gD0>gq6DsRvun?TAl3
zgtY7fC_HC^OBY?x-5H0Qtx-f?7u;wKM?+&U^7DNu>grkatmRtG8=$12^89(6%g%s<
zXAlCCi(v2SiNj_mpfO7hqX{66>vWOlatLcQrr^^L|AD{#{SyrCKNR*>cBnp=jqC^)
zBu9C{h8n1GWIB!{6kxSa81yaOu!NM#MvoxaoymZAMm8K`6HruhSvZHQ1l$o*2a+-o
z!;D!{P*j_QagwA=MvsK}NC`|39}e3Co6uC4hL&@gxLJ~emh)L?Ez7~9`Z9dcT8BrC
zm1rO+R-{CrCOsNgsdSuAMP9foI
zW8m;Yggm#NJ2Q7s5xn>{-}u4#`ig6JQCLTgzNSk^N0l@@!g&EGk5Sz4kbv?WvAGwq
zV2(akDo%mJin*{eRKspnS*)8r5j#|-W4YoC>@=JMyJd!mIY^P>mh~{8_hd$ilM|i~
z6H5Zlxq5<0cTc(4j>1dbKLUlF`mX^c?@s2A!1%p|Rm-mdm}2xO2}nsv
zLzb6HnWc`YGiE?RSrN)=q&78`proM$c_leapDsfIgFLAeO)S*W#av|-tk%)NVe0ey
z2{cCybg^Gg2PX(7%e3dfNYelY+D1@TP=cbuG|Zs(AS)}0X)@z6b-WBkz~(|)m{$^i
zEC8h^<;6t=6yZLUt_Iv~ufyHz6=*9kvXvi8`?z
zzDL%>`N#%1oY(<(2NU>vT9ZPtM^umtDHLx~D4g=*F_c6CN+Ryv`43Rov2wI8QY7r4
z86vEhxv+ZX_R8SkAc%p%=_($tyL$DiuplF+x!CdZq!8XG58g9`SPgJt6~PXkQ)(h;
zaq5i6e;Ft|zV`O`$>*;=7p5VzI^i@I!-7}d=HUgN)WU#Zpzy3Nh8M$$|7K9J`r-7J
z2$cLT3Jcoh8=f}t6zQTdmLsCh$r%0(8AA*C;DN+xnaIiE%Iw`KA(mDhN#X^zl
zQB7tv7OPH$ku0?zu>qJN)*maBCS&`osjy$Fhtr4Gp&-N^mvVBDo05vlwKd2)cOLG?
z9Z~2Rk9gN`_}Y8J+rb&m7G?w#YhI7cIn@m{2%b9HS2{1*pV^c%!kV
z42_pck(m&K_4ACNGk!4AtvBP`sZGe)w*sl#<{@LR9*U06L)npK$lq;*g1ts4J+>0%
zmYY%Kv=7M#S7PCqfzTBnf}rF3@x#4NJSRnRgCg4}k00X7r7~(9JwPca$fF2L4I4J<
zV%shatl2aThKnVzc=c4+IBi2jtOK_0T!<-C#-snhap)~3k0E0lwl3RHSZ0rl)wZ~N$qAJeuE@$gg{XwRNK8A7(AeE1;ateU
z^$aRCUtyqR;>x*nTp-7IE;k#t?m_U1%Y&_>CzdZ+jfum=uwHWtN&-*da)Jxi=*r{c
zzx@**^%{Z!pN+tl^;@X}q#`567s({`PdZp)uXh->1fPYWgC`cc_+kY`)T>-cexE5o
z@VQDjM#Q1)jY8q&)#Iz>c-&Bf
z2NXp-Zm7kxmRh{HS%a@1wBSW|BOYC^0P8>$zrmx1YCOJHg%?fLc-dTqs?=~yml}?Z8A9_RZLTsgMzLamTq2+n{BPad9cG5rJ%U*j2MOnLxTSn8KZQ%k~6Hx6oP!P6eIo8b9!|YkQ(AJm_J?*7XQPjb7SvgFcG!9Y|#4&OFXiOU~
z1v?8X;T&GFLb>&BpzwNLkGq?2zoQYisV#P1DZ=&aXxxbNLydYX3;9$NR?#{>H?`1(iaX<{I?=#W<$UYN}qO3RqN)pNoQhxLqJY!1)
z6CT87kg&4gaS?Xb?5G(k?0k8OFhhjb&fDN6s){
zn6QKAV~M8pa_Wrx2Df*vpKrhGncc_
zD=h{WgM`%yE2FJjw+ZTn_rYM{^cTaC`w+Ju-ae#2&fb=k$;o5zaI%4mjXCyj--b@%JVh$WP%!ljdWpLe3u(CLc0D@A2
zpD(UdRFRs?L5PhfE(V-IterQ)J$&Kk>;iXELcY$nNKc6o76)egU?DZG(t<1@)m4&{
zg3A?!2;zZxTQijBr{Q^bE4)pQV(E-gD0JL~2Ct*2v)zMgQY#f^D{#qT4K5R8s!Ud)
z^2BOfwA_eV$33{{asX!zug11%W3YaT6jB|n@b8DW@Tl`9TCX?Y^G6TSTuZXJyC>Qx
zD=oonZF#I+tBvDkOR#IdCbsQS!`jWWux005tXMSnHbF|gl2sLRTr
zIyDpz>r3#W%K=k6WhJXm>%fWnSmw2lyuiHKfvbjE}0
z+^87<{60s4{62Rsjsp4nqR-j*ecmtUp85Oy|Gqt5!^dM4BLL-#FHm0Jjr`i%D7e
zbC++Uxc)whuRajK!mrN@D&+-|3u-Z!BJsU*RN=772(HWLA?^5Hobz-FRF;&NnzlMr)m5Q3
zO9eU_>d;kJgO=(ns4J>snf_etq(Jh>iX~V<-%*?}2`UO2kW7
zVh?j9ne9dR(QWYDw;qlMHo%dTij(zzxVxH=^Rq%^pfeJ}ypS5lcgsgmfEG(IIfDyD
zX+I9ibFji{funnd0y}2W&43*FGcfob4*r{A!D%jr0sn?)dofga423^uc(9Y_nPBYL
zS(R|giwD731&PLE_!tZo9tY&M%59lbPpnM1A8K9}V8USH!qDVCB7zy;mH&)X>}dyAx`vi;_jZMy)hXyJ
z&BD-L12Jrr6gF(yjFVf}Ai{DVELLb?%S=fe)KI{NY2&eRrWAblu1CDXDcJ4b27Q9e
zGF=TM1^DB7^+lAEa`U&eLXNi&vLk{KecA`7edt=-Tf^Vg5h)}(9z5)s$nyc`^ct^R
z#GO`(Dl3c7-dIBtGZOaZN8n;@g7c|y2sGP|ZR!(|XR{qwT=(OO?Jit2+lY$eD{;wu
z0|8|#N)Inb(Sb#%Jh=`R?Y5)Lb_ZhiEW{y2DIC*M#MP8&;Ke;WzIy`=mn-n>PA6I}
zU#4iI$9dI~v@+D!z={=`u(sQTgGUXpX`31rEmOd3eL0AWk3pZ0hmjK)f)76)fqo+i
zC^8E$X4(>rlG4MJ$p#oRUXNh13F8zuVu++Jdi5KFAz~6xQ`3OA?-7JX?17u-D){-Y
zLww>voJ})ET;d+&<_Azi_*#(jjhCz60t!XrSsAc)^@VS27LHOyYsyH$`RGpMkrd|v
zZ^t8BFkO5g2L0o2m^ge0m~)*^P>W`vQmh&3@oh?yyD
zwyz~;YKST0BrtH$U~JxX1nH$sC?Y`Rk|N2WW8@LI3aW1+ukr?|wVpwF1`1Cb{=Wta
z&+_7HE&@tWU@*pv9#3T)CQqI!tWz~+^e7=6I#zNtB%~xTPFe~w1Q<;%4a_E`qN+sd
zL|z_h1cwFlm(gn#$c&MI?o2sMl^Bbu6Q)2(MUMg)B}|&E06BRb$SWH{fBr@+UO^7V
zXd#qU?sV4Sep?kf%Fm)9(HAWVzGzDDK}E1VQfv<)
z$e2{g?p1I&L_j&Y3odqtNnMyBz}JS?uX%a
z#u2_qVeo5KC7h1pEyIOhGhFyJ1BAiC;9v#8<9MuESZy#cI2FclVHLp6pB+0pc~%q*
z6HzLS)dWv6;W}ZEaNFQ^%5dRp!|j}pAzI#r(_MT|3(w+WaIx}W1;YDg#Uh#jz@KqS
zOW2aS;8|e2G!{dNRnpO;#=%I@_ijp6dwiK;|HNfb!}&S
z2(`I6c+l8@OXmyVYPt_Gt|yQb;f&ZA4+Qwwljt~w0C!g;(X|lyj1QJBU4|<)Rj4V;
z#f8EQv|g*mt(#Yn5biHbA&&8N#PQX0al}Xo>E>Hd>vjN_tT&nV!^UZ0B4}Z$CSf^=_KFN#CZLp+mXkusgr%b=LXvV2>f?cAZ+qlN`XDdL59gA@
zkQnF!N8{bt%TGq!xDpnJ_anl`8%1d;NDB3Z@&2t?zI8no9Xo~DW-gH1XNH+nbWH5A
z(m4=oodU7lD-xDbsp!7d6M1o@bIQsF+H(xBlp3TqMVOMKMqzNj{utDM5TwP2A~VF9
zfRTzjHF#`|(E@
zCN>P?MvjK2oFZ%v9l>d98~B@^Kvrzbk2j7nG;fZMNrwVrGj~rrLj?06DAv0VFg8A%2OsmcFHsyI$I(@Y4x;EKEGeO59NOi6dr%#>&C{56Uxt+i4j9bK$2iE
zWzr;4=rR~HMjTR-V=;>0GJGU`HbDw1s>)DSR)QkIL`j|kGu_$HowE>Q$I3u#z)&bt
z-OrdZ14@b-P#~ZT8ZiMQDViNCHG|a6a?CN>4*hvsF?Z2w%v-z=)fcOTbNK43uW`4#
z^~d{AxbzIn^Lk!QLf>Yf-0dcn+**md6y#h>@Yp=fk)TDe%nG=?N|b*
zV_RTvx)aWJN2za~g1@&7Vnf_W{`*;i_L3>vGQQ`Bf4RdAt4%gXPB`XVVLj$J&&9G
z{5=lJxIGB>M!k8S(N}crt2w#I@pVIRunWR`91*}OOG6U|iqkQ3yf`dZEJUfVEmF;Q
zA)1t7f%OUGS{_Hxw&lq3v_->(QiS?>!p6)PVeU>Sj0!_@aXxOhNPGUvTGe{v-X4=q6E*0~5VQiPZGWQ5G0iKMlfNZzD_w7o`1Bj5yX)WI&r
zu~;A@4zpa;JK*iH9j8MMp{>0~8L&Me>DG8YofJw2%E~Sv
zFDnaH_HKws%tT2>GOAL-QNZdXCIF?>(9b1>B0JI#F>coIIKCf2PL@bK?T_sEXrxDn
z!NbW4>-TPl(V^p5>3$l9ww};C=>(%w&RAgXgt^CUu*D-Bo=I84R6~)SP0PT-r7N&{
z%{u65Yh&WraTxsBAoTn6GmIYe86us{aJ@JMH%n8{O%dS3%5>Zis)_MK1D)IR
znnFAw<!YxOJR<@GAwrOL7bZl!fmW@vApcf8T8y>
z$tr>)A(69Z$nf|Gr>TC)7$oo7{@gg_#kpVxifI2_xKpxX5m{SDmjII-Dy$-StmE8e
zf(j`WVX&UZKd#(I(X|ICAQ*TaJqUy8(paWC6PstNV~LtFR#Swwl%OI@j&`2me7Fb3
zA*>PCUBR^b>W$g=cxOLgA-2-tM@Jd#zQtUq?=Fr+w@#c<;N@cy{)lwbymXmFkP&*0*;7Qbe2h|*dN
z4*o5x7~b-ID%-Yg6~Mwk;WQWb6H!3^J%O#`axDr1ToLYPO+vsHVWdccwr_;fw$(UH
z!IkUU6-YgG03Pf0NuB7!d(A?4u3UiQ+HyF3Y!|Lol^`t86CU;_5#nrxbb?c5d^ql2
zDnWC3Dx6Mjgtg^nI657Mx7#V$oji^;BuG5mJ$`JTAF!O_JX}s%5`1VU`DyXUITMW#
zA18_oVn}XA!O?6VTFUd#ntujm-p5g3u>!FhRS~&F1(}-{;_Lt7$ESFg{|e}oMBOR8eT)HxVANfTldR55&-9)?Uc
z!Z1=UgN9GRr@aQEcV8+4#Dr%?@7THk2lr`^v|dV$cLS{H0>vl!{5XEX#xl0Cs+bf?
zCQ8ezkdv7KYXVAmYzhjK2q4k^$Rs&l5aosPGr=f|3qWe1GXkwlV7YrUqTHFJEL
z$S`CPRD%6IaLD2$j1C$@`y_2?fq!w%2WFo`^il8gUY%7Lwe8s?sT
z`>|l*0%)nJVe7vNW{h$D%IL4^7FZ(Uuy4?yLmd
zEj){^+<4r~i9siQrggDgsdM5l|i?zw|1SO%K9kt{SF}9s&6Yk_1gzC{YwIGfo2Y)Kw7@
zNp)Gtgo3nu6*zY{N{X2p3QW#hlm3MvT
zT|lvBpxln@QG?I_vm_5+w}{{%%$z6*X^Bx7Ib;Y1Qv24@GQi>`YoJZrIRpu9O?~KS
z&xQJ|*#s1As3>b;{P?MuBBKmhS#?N_p9V!G4Xj+X2?qL$Avty``t_!LQGXdccmzg>
zOG9dsDyGWK#tg+d2s#}uvF^8
z;M5dP?`3DsevutNs|Zd>iN-$oI6Uqma^!3rc=13yw{@N+#)?BUh|a*^!FWD4@0U|x
zyyedrAY4Q$gwtF6ITv2@i+{tvoBaD10}rf!~&;uvt6{hc%~S^R%(pI!zJ>Rb_C-(FFD6BZ7VH;q7RO
z2p0>ay4j#8$OCo6fLoXHksd|Y)rr*0X=iviSittgahQ>Ud;1acJ*y3q1K@SF3epk~
z7vh5`+DU2dS+rcK#LcE^is&71wY(UurFke1w!%64wTM`+2+!G*k-cpR@^>vo%u;oD
z>rO)WGDReAn~mUgYH(R1hXk`th@wWKDk+5tpL~j4N|WKSR28}5zW9=0
z55;UfUQ%2M(B=wTxvK1>1f(G*9zgX(YNq
zp23cuogZ(xp59Szot>l2^l9f(t2>b5|cCD=7nM6m2E$T!H9w#}MBhAzb^PU*~oL?zUCSpr@(``RUU!Z0IOb^Xgc$VGpUV
z6_}^L6x%lM!>N;wIBsl-#f#UH8qtBo7#YY;R>kzmD%2ljN%>F4NO36)q`oz%?=Xl_
z>EEj#`t{*8_(o$K0chMrdCXh14oz=XaS$2TU)#-|CF^@i&(j(oc3ne9Lp5qka^Yof
zf)H0zl*jwvE93#&yI~bJ^K*+Kbf#Z$;xN3>0?4{0LxWWo029#p5WP#$w0M
z4xdv^tU?$9yc`POS9XTr$(KKGsF?g)-=k1vlJNFA#4-5nzgy+;5gMg>`GBkL<
z{2niv&x(bo5Q|ic2sR8gLqkL1JA8aT_K~AKcgeF7V}Od%UEELJHdZNV3Gq0#mz346
zEm$&F7aO!RV7G7){8q1lxxO~s*Uf_m2>=uQnbg2x&|B-$Q^r}bg@yO|*>
zz!r(#)`+$|ifj)X6jK0QpOuKV%3{O>x>LRQQ2=TKb2_KQxVWD@+Th3Mp|EVbex({0
zC<-XgqX;fN9>tkwkd+dT`q*0Jdy#}az8kvZM_|s#{x~vY9DQzx_Ll#C_6A#JWjW@}SAl|(
z1ZFBsgpAB23>_jt3S}56l>X?`doTuzjYr>slknNFnHV}wjS8s~X>AOW)WSdsO;Rt4
z=r>3jefo~Tr+o*Z&wzm#KXEjI11wOH>x;@_OCEPsEslFEl(gh_%u=@qj1{K
z8y2okSYv4e-J|BvIbaTreJ3!_%pQg(tgy}9ALW;?{3H?<89R>s+c!gX#x#r?CXNC9
zhmcbkkFlf2VL-1>pesK`NWXO!C!jqi9M{u=(MF2oc40EQ^ApgT6ODHItoi&|*c$J|
zD6#(dtnX(SCnW`m@#8Q*SP?N`er|lsqRM(+d1Id}
z{T9{D(@x=r#$N?W^D)FWh6(q+{M`7OCdNcVUtJBe)iog}s|Y1k9V}S92Ab+~FiCm_
zIi%IFvGYMt=o#2M2SRVoa)Jb@+z}HnK}r@QM~=s*pAJOdenSXIqcMiSGI+pn^!is{
z^zPLkL&Qk^jG6#R0*|lXX?#z0_H$zg+R3Y0-)Y62t`^kQRva1gtZsM~7LSXtI^nkabK|zpQ|fsi{JfPJGiC_O-!q_CZ7@uh
zE$;yazsJB~aPT=XWcZjo&cg5WSPKJ%$5%Mj#_;3+g(nek|KcnPr`EVn{oFXiu+V5O
zdVkgjpY`vH3B!kAm5KtKwyc50`uT96KH-08Eo_%-;;_a<9MX`6@6M$-zKTGquMAI{
z{REc1usXB}+ZN~|(#8b26u1_JdZDQ(1EpzEFgdmzCT0g=Z)q<0sJHDWj{t7HL5<=<
zAvzi9o>-nl5;KMxtZ
z4G>1k#L8$I0(LJ(lHCEs+a86_k*!!hQvzFNh{Hi&9!0ScLZ^6_am9Fg+Cxi62I?Bq
z2q+UFMvZ&GXTzui3?M)ZA$1~&K7%ANaO5-$msG)!akDUNf)0j`*T%q6is(I98ol~U
zpjSWYAbkc?rx^gr2_xX{Vu=`1B*}q6$cT(Un6EorJe{%A
z+7e65Y@vO?4u&U5sT{V0!fq2-1pV@}s(<+5-zdn=z-(nX4DLS=0|yPoh~Xm%umsm$
zy)b5QZ@3)Yg3Bp>Xw3~lYkmZp@*>fk7mbemIJ6eV;wnXiv0fHXoGgLzZ-f{rKKoJ3gVZ&o031_#$UcLpxJ4iQINyg&Xe?~^4w=jneQ{ju?L-rGBGp@0X=>+Yi9$~_d;KM+!1
znMq}cIpKy_GjpVzJcKNhJ;*)26WP=$GWV`V?C#YF+P4`&M|Q)<)CB2;b;!AJ6Y1xh
z2qf*o%&x2p?F5u&q@KTuike2e{^CF5S#R4nUw?yw>p8G*GKI~Jo&_D>1r!FxwTE@E
zX=XSW3&oNkgq(r`9e55+G6pNg`|3?
zKtgI9#*G_`88VVMv2!&FBb?Ec9*8FuXYimf0XIlBR|eZ4+np4Q`5qXrUyL~_au_K#
z2!jRD08C%k1Sv7(tZYnI^X*~v3dIOWBV;ItP*>+P|VpBt-5Ru%jZFIGe|
zXU-Jvj%UCyBp4PvM#HZ;wWg=1C%h*r{60S~g+auh^EeAnZ?3AU`Z3MLg^%+tsEI5q
zD-$EfjK)Ww{0q}2Ny7T@PDHz(g4O<2SUFoB?ngJne7P1j$PUHosY9?^UlG$s4uZV&
zXc%+)aoa*{T&@cx@);I8*5j<5DKgzD7?1HqOVJs)Ivs(r`F`XVzmCd08mS%Rm?r>SF#GG?!fb6b@?0;H-!JFFH_>u~RXoC{sgA7E`9kVDP|^
z6j=|UPS78{X**!ZSbR*t=s$caMvR|@p;GD?Hc=PDsSF&ggg!$iqt^fe3Q71+2`Ih$
z4#4nH;_&lyM1FiQ&IWto5-Fiya-eK1A2|xo%g%?br5$1eNRfp5A<@$ssR5424fjNP
zkTVjfv4`3k!+zg-gptHg_ID#_c_Grv0~ujKi1l%Tue&XF9ovr;#%7p(&=E!_yfFKS
zBX&Capp7DpUo&n%9xnD6HGCjO4j+aQg9bpXUtbLP^b<%9>IJ(!D^V6^W&`eR_
zjiLl}mYzjdaWa7=3VET9usg60vQonYpp21_gxpj`$VtmXT~P~%_Z~&s*)%#%&$QHc
z8MhCH1?P!HP+0yl
zJ&ytYC7?7syaKxx)BmUr0)|(sJprZp7lFe2v%7AJb4_{p_Ur$SK;JfYf(zy^#CR!b
zs3>bdmcSy-YkbK}!{n(7ShjW>e1qd*X61(Q(h3k8CWUF!RfW`7uTR-=OJKx^aTq#G
z0%AkO(SN{D^y^Lig_O&O|LhC-nQ9aj|N4W}xV}L|+P_2m+&@0{4O8d@`^VD#XD7)mf1NCA)7kfGG)
z2VyX_b*Zr<;qK}vw3}ZuZi~D+1JBCh@el?JD-#BZXm*t-mBlGC4ssYOd~+mEdFOEe
zzFVHx(_)~oir_R950*1jc)zS_ekl+Z8N-Q}qi3LSnLK$i`LHR1V&SFgxiDl@R8$1e
zFg*DCZx@CM!;jCCRUr2*QU7{pO895S{lwMXjp}0k>Zyjd7GxEFS>
zFog5|4cI$Z8LL$%W7_Zm==1jvATIV9)-2M-G6N-ODNV%0kpr+?T@KMF4e&jlt
z;(AIjN>W2`=1hz*1M%nf6QHp1B8ly-&3JOR9bL^=kry9E4)Y)`rH7&u=6-gLtPJr8LEyS6u$LT|>5M;a_@lHo^`qXah)|EvdMURi(
zOmq7!YiqlSqsDt7DK!@TNtXBdbdaD>cyNBeka769AE}lR(=c+p8ir9B@dhYE#;IYT
zxGX;DKSlsbuRioU0*cshaU9yS8)rfT(RjI5sGQ$2Zag)YYT;_{f-qMX6h;OhE7%R0
z!7j)SaYvGuHA3x4Ark3s#(p%6$^wIDlrDi$wXfasVg!KwY0aT{Sk
zaAOv!6ovr9;%%t#HREf`-{DR!nu)@Nzt8V+M;5_}3j>DL%&);jWSp-pOU{7v%(*x<
zToyJIJXnfN!b!1-I7UIl-p@v2->@+_Mo#vW+)OxXX~5ZV4(tu*!b0B&w)%_UIeQi2
zk9#0Jtx`}ZJi9BStOa>x^>kkEj>dm(_aEO!R6{T+kmImyKJu3U#j53~pnUk5^NSAm*og!aat>3b3KLLt^bBEY=?Ev!AgtN68$(7&p%1D2p+m;W1_&zu@J8IJkQsSa?i_Z+c{aa1p5(
zPJQugG7jSSm>h`lmirI)n_mlrMaBV~qk}EhEH}We_4+uvV{)Mcfrbi}
zX(~Zx_#k}p_rKvE|M-y9>BrF1kcW}BJO!L1AvLrgR%$CE!fY=h4s1X&DgN^=X1H9K
z@snAYZ`*$Xg^i(d<9Y*HE|#J=$_q($yK$N!VY&rnkuEr5qyk;Be_@l%P^=g?0Qw{P
zl6;bc$3{cMnrw#u-evIEwU~ah8IiX8;cvDBalS4DlmC7e^RHR=t#0hzy$b{T55qtH
z{wY5CxUT?|J_APKlg~(^kC+azu}T;?MhSz)szXdt6T>7)t&CH|r-R1g(|*J8N$-Iq
z{raNsfI(QddL_zBit){Rz(i#1C~Gg(AUWhT@=m)Whd`1U>?)+alDur-ePWNGS~&fc
z9!Nm(v4gLTDFU4BaV8=Zq5ghwaQDMOmvC4oT!4K-8A4JE(bne7Re7h;QWheMdFB!kJ4
zW1+4wTeypQ&U{To$3^~Tgx11j1!f@fJ
znrDddZ$)V`b{Kq}-`5ye=i`#$r7{mLL#DxT$Q0NQpGI(z#&Lqm0fNfzzC*EvoaRP~
zw%30uhV^}hV=DzAhX+rBoj7%psq>Mw;y5yVQ<0usgY#95xOe~kf*p}Pdi)T{jd8GU
zJcgXR>Hh^3PI*~28^eNt!^OP$5ZrEB;l?{sUcnvSaBi@`x$Aj9nLhqL#!>c$^_wt#
zsw`$qpDv_rR`HS>M^9qYu0t?bxDq4BNMq2DF_4y?iQ&Vg@bSk3(7TTqJ|#ucr=J-5
zk^}$n!%qn&A3{T2TbNe+dyJi4cvvLl<&`jk6wes(NffcOZ4#*o3nlQD-4
z;zH_ZkQfzGBZG!vFhOMS;Ne0__cKySy(tJBB`yWyqr7Uve`X!Nk8ykC@dgG5J8jWe
z2B(sEd_+P*0>g$4!^DXb1@*!UMRJOk@9*K177vW`v|WY?J9|!H@ocd7g$Wz?6TW$u
z!NkXzFkylK6n^drpOcc3k^ni8Lg8~`WyApFu@fG%;h>BA6CaoR3a5j){FXv`+jy0Q
zl%!bf*)m^%iu_b5eE5&QG^{~iDM@DqIS&tCXDy+3-yAQ))O
z#0=>%7~c02tkzQ}zrPngTbCe`R82)vG(=0)zsueU6gHlL)q1@VnWrf_-@gcsYt&)2
zMjK_J_Q>)$0o%0(uv)AEr?qq8w{Hz%Om`sc$U1mzUkJMm`taJj9A1ak!rOcwvd+Ze
zb8;Sk#MlwJxq4!f^fdha@1IcPA1aKEd^&I(29XjOG)f+WB$S2mlVKBdF?1XOg&@Nz
zuaEl-#XmplOOe@NtlP9vxaZ-Qa?-zMoc`)+Z$n91BGT!4WKm&-a@NlQ@$Qz$CAg#q
zIMLts2(dSX+eu@jMTa9ZF%ID*qn&)ivDYOARWyB<_yd;T8d*Q
zO;Avr_q#fY$hfUAC|FT2AVg5%Ys&+L3>1a~JAHOCoE~DQ$x4RDOhos!aJq|MGf>_~
znf%TyISrTFFX5Xn-#t$7TgKPd(ZLb($Pvz)Hy`UZ
zY{S~kyRqxg2`Z*gR@29fnHm%YFCgcph~B-$Fl2}XDHaKQ)_*AeMg3&>h|$=;@1Wpd
z|A=uto;z2BwW~K^toTF>r+tqcITpi5j=|6oBQbRN2s++yI__``9x?)h#Hb7=h4iNM
z|4a=12yQAW8VC*!71Zk=F>Z5wlOm_A*l9CJSPigJ;D=|ilV=Cdz~FQiD-|At;Tc_=
zYGQ}aYJz82F=&2|6Mx(II`X&4|3`2xdT>Lk~gdxb&hk3OL?nB%^
zxu0;r#eMBve)_kJ$DJz5ieO`Y5E7$?3Xjf
z(d)y%W15sWWTnSpVDC>LDK-EHmK!3-(ioL_S-HPAMx*aQaTqOF
zg4not0Zf0^xbt7Ftww%w6w-rS1=#W|tDI1Viycl|ABW|>-EcW+hI6TBkP{n$xByRt
zhJ+wGB@0bAJMr}&aN<9=J9q903%1!?SRvTa3gwCZxSVksSF#Bx=i<;>nT7TX*=Wd(
zKvj}E(tOQevSq2TSX@$4%I}W)-en96Rx9kt7%=>r^T#}khOae)h8-qHRUEBxo|+{)
zD;Dk`T-ag!kxufqJs~)k#3jLR!75mdAh7fw3rlJnHWYB!4wQlo!Np;a6x_rn!EMM?
zcnq5kuMsm5q%;rNR<3BTyDCJ2f5yK5=6gJR@K8`{oDyV*#&F^C?8Q?&rShNcn`y((
zUkKNN$Bo)3SmEc!|ND>mtsjkHexafQmKN4nxq36!ZrXk
zF?zH#KIt_GVqz49ju?lD6s&FCx)V7$9ErbQs_oZ|uR&yZBoV(@7!-V@5
z_aENJpS62;@4(N`7fPxc_{XP%@F4{dpM3Zs{z-u0$8LP^_kYs%Z}%|~R9lQ?
zYj(lX#t9{*WkRaxuQa|?pA%SaG&kWwVGc^u;*lR4h-fzpxEwnGOJiQr*#qa&(@>Vf
zOW+rvv!nfYrPozz{b#>Ju>)D|S+Ngy7Dwj$g8j6!pn-`6W++@^L
zl?zLU{MnBBw(&Wz6XQnDHzV>*T|6s=mjvfWta4$%@O$Ouo)kbyl?Vkh5&yCwBJ0WhP
ztiJeqKYL05Ay{x>fbcZndw1^%*MbYb_g7yFc0di}lvC5P5D**#3mZ@DJ7|vOE4EVf
zuS?OgJY;96VD;)v@bDmYTYpuklfT*?KYAqGg?`GylAQGtD9q%gUZ!E%BpKm>V6xMu
zLQh+VRMCDU#3xWc>HMGg3|7;810&zi$m1cbO8DLrp2fxYi158Ad>1^Aq43mSRt0~x
z@%3ee&JzcC`Z3>^!oHW&VhlXKiILBPXQy#r;Q)>M*87Z?{2pW2XXWJK@R4KC)zrX*
zv7<1G;4yUMaN$|{Qlm#gLs0>T_wN&)(8__|cfX@f-U}#i8}FQ*G|z;2aR09Gu$u>W
zZwoi(@%!JrSxV)9ZJbi!NbupKhZNy;;YMo*Zr`~}9kgfW(Eo+;W#c^MV~WW6c?7q*
zyKtA(!()n?`MuW^#s6OzU-PHrnE3AHr;i`~SVUtO-+uf1Q%ZltxYO`4xkIv=j
z=eqb$o&4YQlh;%yTptfeL3MR?lXLC*QAM%J{J${vFC2vNdGPpK>rDo32VILt!gcw-
zx)$s+M0LPvS`KdbT5vsaTm3zL@Xr_npRa{z{E&-Cz5id;34@CNXa3*2$#31b(KFSb
z$47)Z`2%Taw%-ku|7ZWt{-6Cn`+xTT>|OS|fWpx@4>YA_WWg^e7*4M4uyJ&Pqq_&Z
z0|F7Bl#DC&_5TyeD6;Rr|3Qd0n_C)D*HDe?9d)?eQijU9bet=XLv~>ds%whT+11gL
zd;A}Oy|Ltxec+)bUr#XOVG)$^M*EdI=9BFaN>1OLq)YM$W)vI-AYo!RR?WXX&e2&Waoc|Y1gH@O)66eB^
zC)W{2gB*eKb>jLE&6N9J)d`OaaoXfs!&O|YtwGJzMqF;XiKdozG~Z}PW8-z)xOo%z
z?%(SfL;4>CmApsJ7IafzY^bZom8x^NT2+9iONF>mUy6?A8nj-&g3k7vbj_X!efxi5
zTqj)Ttcu#`SY6%RSK84@rM3MAnyxpXsi}e9>-wLh{sg0o!EK+fcMG+}rq(7jbzaA{
z&L&*zzK(|WM$})wN_EqW2h>LX?6}^q8MixbgS>J$&$#6N!tIjV3%}1PTlOj3UVm@<
z{7slHlwic1aYb~x3lj4EkX;>(xI#Z96ga`&e;p3n&c$BK*)Vok
zfyC?(G&Elk20{PG?5=kiH_Vc}90b^!!hF3UOctoX#7G6khN?JhsD>j3syH%78HeX6
z(raa$T)P0lu6C#;xNuJNuP~PG?5Mawuw!Be#+Co_*$aIB{H0J{a-qNZ{T^A{(|{Pp
ze^p~;2hRET*w_Tv+qvL`i3N@spThBD7BHc$>5-E-LFL#na~PYP!YOMzoDK;W;KJwi
zR~SokPWQDDJi0sD@#N71G+w;|Pd68A+qe$vRxiW0t?O`T{|@+jIN|bz65Q!%L&wcl
zp#%L@DF&`vt`n{gQ5uKqiQi{O!q=SN<1Kem?ic)h?zmike?^^eU*)GM*3?wP+shGl
zcE=E#5R9yXJY*DCAid}!N-oq9RLH?Mx1g4;Q*C`6E?vGXOk-zf`o6*CuNl|LqlfoV
zf2kZ7@)J;77=@a=5L`(0MR|&ku&v1p#lz-vc+^sa`^`n@yjG0n`byzxjJ!PlUtwIg
zJQ#iRW(x&aT?F=rc=Gfv^#M-N-^T5`o#fmbDG0uZs;Uy?W}ijMnRt|z6;n|5e)l8&
zmhpt57gQ(L=^Sbr>u~XU4RR~<5tR{#;N);bWl<1ZnTwj6wP?C^9gXeRP}@|Ci}jaq
zqph`PR@GnNy12bDG%j7bButXw2{b%j#ABQ+5;zDI6#*{y?h31G^Jg5Ka$9~M`&FQD
zWR*y&WBbk>(3(95s@i(c(4P-=gN3x62TgrLXwP2)&Bd#rwPY347c7U0;UXyN8DWN|
zF6OOV4d2tj1f*YH>ioCtyYGHL_pLT$=<19Hz6k797%;vaF0BOlb-wF5@7|O
zcuSajZ-$xYI+(hzgt^yx1SFnBVQoB$FJ_?b+9l!snm=k^kYgy!O2@(VD`7NwER3Xv
zV9CVcSUzDWmQqy{u4?YMd_x@H>%h<3;K>Sne+S=0
zC;Gp+4!AD3UikNa)OgZICtZuQj8s@y9>?OvbFgdoMkJn1KuUH#V$;tdHlq|dMHfh|
zP(*yQ4cBhmL{(iqF4WfJN>e*7)iqKC-}&Co<6Xu+<0d)8%jc6&n|~S?v;A=~D*%_M
zRHgc$@~jssQoL{>GXRfo6ys$_IiB7;k0
z4qko!7@xm<^kaGc>^}9QyY!t~!lNrL)l^V@}}1Fmz(pa6bnCX
z^dfya{)&JcbhQhn?8(trNb3ySVr~oSj`@N+2;evx0@WH5D6Jow9-DDS`;Ko0F#W
z8Noz=ikUUNN1&nanh|KsPT1hYacfvwI3V?G23_CZpCT2ady1-`QAhaV^A~vi;xVd9
z@^B_H5a%=E5##3s<87<3ZPh&N*}M{F2e%w_InaZ$0b*dRatmG8vn1qyjxDW28CtJ(5uv3y?w-$2J5nme(`
zcwZbDa;N>hb;50-q_PHasfF+lkAahyH*^dQv3T`b1VqHbH7FJ?A!iYoR7}eLJmRyt
z6d^9Fkb<)UM5N>+Jh=b?QCaW|PC`yWsj$!AW9&lilN#t~szO_B23o7)(S11uw`$J{
zrR!ocIx6CEvospbB{68ONW`<&61=)qi5DGZLV4PHUMTk(^U++FkB+wHpR8p2KE^)d
z)~zl}(FDO`eg|EMSjxS$5!D|M}<2!i%3YmvwFT;iOCUK-9>d3uhxpiWkQhG!lV>VIg_Mb!(GsZ6
zT?A!%txOP_sXYfWsyYOrmB`A?7b3d%F?L*+YV&cmIRbZ{#-sd-H_jH>p!}LQf>ZXu
zC2TVsgAX8{q;pYS3GyzTr|kvgRTm+vEEDHz^C&XN$GNH`#HM+{E9?Y}ZI)68GZS*C
z?`wDO+(xL~Ntn!=g(C(EIJj6DCl{)~(NGnxMzi3&SOX5rG-1C)4YmtZaMDl(`*akr
zNnHt>bhL0}u@S6S8^Ml0U!(*xBPCca*F$Dx2ww3R&wChGAOnC?Gz^8eVZw!h((<$e
zKDU$Law{6Hw_<1;1CLwrXeZV3Hc+_m@44}?lx4N@z7C9?W@>60EG^-L30g7y;smta}JH=*|=7oho*}q
z6!});WmhAogq!`CmK;>*upaN32%9!L4Kc?=y)3{BDKQ*-}}}H!#JMQhlNi9PI)E3!Yd3X
z-Ta|!u!#ENavXE;g^5cr4m*eAfMY0*IECPly+8Kbdg6e+7xvqDV4t-Y6<_SN3c&7D
z{@7~~fYA7?_Xa(m_ib|G-OUwv+EIl&P3P$Pr{LbTOx&gNs4)i*u4Uo=l?*(r&&0he
zndq*|#Iv>%d~uuVrL&xXLP~`I^Y}&)?q4s!o$GmMxtxu*miqTjfpJ~jzTJ)2ulQC0
zekRXXRKCRb-*FpxhA&?}#!G_8v-{L1NVzt3+vIC7j)@FjGkeF>9_CCdtWQ#Mt2&B0dPxvJ+wFW{dMRB`9yYL{O?g^_3c--@dQ$
z>co6s3qQb%RWbvC+Z;c1jF+e3RRMU~GT%(e)0H_5#=$1H!MB0J?=eugjkBuZrD}dj
z(f=1v_{;0I?!-(DJt*lKLTUCqC>t(==HhiQT)hQq1PUqXNfRsc~#EHul7gbrC8M7sKCX>TGTc-pyR=P+@_0h>k&z{
z7j#ix-a~y$1>#fv;OM&_rVcCM6tDrYXB}|o?yWaR`(@*K^rtP3;<(-noSdtOlST?K
zn?DVX^A+H2qyn!+n(*7c2yrL3A?ENpxG&a%{X#9ATws8mbB(ZuK)gyz4|@%CVY5UR
zj!V>Gvq%kQ1SS&$C8UN0;;Y|KDhyyA|6qsvbC8IN2q=D}PTUA2?zduT8w;;n3GW2T
z+eI{Z|68Ev&YG5%PLA*tEG#TxWoaWUn#sk6z_Ne$KH))N(&NWLYOJ_W#*7rlNU