├── .gitignore
├── .run_samples.sh
├── Citation.cff
├── LICENSE
├── README.md
├── ch02
├── .gitignore
├── README.md
├── ch2_1_first_agent.ipynb
├── ch2_2_first_multiagent.ipynb
├── ch2_agent_groups.ipynb
├── ch2_tool_calling.ipynb
└── my_report.pdf
├── ch04
├── interface_agents.ipynb
└── screenshot.png
├── ch05
├── benchmark_dataset.ipynb
├── evaluation.ipynb
└── workflow.json
├── course
├── generate_meta.py
├── generate_results.py
├── notebooks
│ └── tutorial.ipynb
├── requirements.txt
├── samples
│ ├── book_generator
│ │ ├── autogen_core
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ ├── results.json
│ │ │ └── tools.py
│ │ ├── metadata.json
│ │ └── requirements.txt
│ ├── deep_research
│ │ ├── autogen_agentchat
│ │ │ ├── app.py
│ │ │ ├── fetch_webpage.py
│ │ │ ├── google_search.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── metadata.json
│ │ └── requirements.txt
│ ├── hello_world
│ │ ├── autogen_agentchat
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── autogen_core
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── google_adk
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── langgraph
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── llama_index
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── metadata.json
│ │ ├── openai_agents
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── pydanticai
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ └── semantic_kernel
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ ├── requirements.txt
│ │ │ └── results.json
│ ├── interface_agent
│ │ ├── autogen_agentchat
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── metadata.json
│ │ └── requirements.txt
│ ├── investment_agent
│ │ ├── autogen_agentchat
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── metadata.json
│ │ └── requirements.txt
│ ├── mcp_tools
│ │ ├── autogen_agentchat
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── metadata.json
│ │ └── requirements.txt
│ ├── rag_agent
│ │ ├── autogen_agentchat
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── metadata.json
│ │ └── requirements.txt
│ ├── router_pattern
│ │ ├── autogen_core
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── metadata.json
│ │ └── requirements.txt
│ ├── travel_planning
│ │ ├── autogen_agentchat
│ │ │ ├── app.py
│ │ │ ├── metadata.json
│ │ │ └── results.json
│ │ ├── metadata.json
│ │ └── requirements.txt
│ ├── usecases.json
│ └── voice_agent
│ │ ├── autogen_agentchat
│ │ ├── .gitignore
│ │ ├── README.md
│ │ ├── agent_team.py
│ │ ├── app.py
│ │ ├── chainlit-config.txt
│ │ ├── metadata.json
│ │ ├── requirements.txt
│ │ └── results.json
│ │ └── metadata.json
└── web
│ └── chainlit
│ ├── .gitignore
│ ├── README.md
│ ├── app.py
│ ├── chainlit.md
│ ├── public
│ └── avatars
│ │ └── chainlit_autogen.png
│ └── requirements.txt
├── docs
└── images
│ ├── agent.png
│ ├── bookcover.png
│ └── multiagent.png
├── requirements.txt
├── research
├── components
│ ├── gallery
│ │ └── base.json
│ └── teams
│ │ ├── travel_planning.json
│ │ └── user_team.json
├── frameworks
│ ├── data
│ │ ├── autogen.json
│ │ ├── crewai.json
│ │ ├── googleadk.json
│ │ ├── langgraph.json
│ │ ├── llamaindex.json
│ │ ├── openai-agents.json
│ │ └── pydanticai.json
│ ├── logos
│ │ ├── autogen.jpeg
│ │ ├── crewai.png
│ │ ├── googleadk.png
│ │ ├── langchain.png
│ │ ├── llamaindex.jpeg
│ │ ├── openai.png
│ │ └── pydanticai.png
│ ├── metadata.json
│ └── sample.json
├── news
│ └── news.json
└── ycdata
│ ├── .gitignore
│ ├── cluster_metadata.json
│ ├── requirements.txt
│ ├── yc.py
│ ├── yc_agents.json
│ ├── yc_agents.py
│ ├── yc_cluster.py
│ ├── yc_clustered.json
│ └── yc_data.json
└── src
├── .gitignore
└── interface
├── MANIFEST.in
├── README.md
├── interfaceagent
├── __init__.py
├── cli.py
├── components
│ └── memory
│ │ └── chromadb.py
├── datamodel.py
├── eval
│ └── games
│ │ ├── __init__.py
│ │ ├── agents
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── gemini.py
│ │ ├── openai.py
│ │ ├── stockfish.py
│ │ └── tictactoe.py
│ │ ├── datamodel.py
│ │ ├── games
│ │ ├── __init__.py
│ │ ├── engines
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── chess.py
│ │ │ └── tictactoe.py
│ │ └── match
│ │ │ ├── __init__.py
│ │ │ ├── base.py
│ │ │ ├── chess.py
│ │ │ └── tictactoe.py
│ │ ├── match.py
│ │ ├── match
│ │ └── base.py
│ │ └── run_tictactoe_match.py
├── interface
│ ├── __init__.py
│ ├── browsermanager.py
│ ├── model.py
│ ├── planner.py
│ └── webbrowser.py
├── utils.py
├── version.py
└── web
│ ├── __init__.py
│ └── app.py
└── pyproject.toml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | .openai_key
3 | __pycache__/
4 | *.py[cod]
5 | *$py.class
6 |
7 | *.DS_Store
8 |
9 | */work_dir
10 | .chainlit
11 | .files
12 | chainlit.md
13 | *.env
14 | /data*
15 |
16 | # C extensions
17 | *.so
18 |
19 | # Distribution / packaging
20 | .Python
21 | build/
22 | develop-eggs/
23 | dist/
24 | downloads/
25 | eggs/
26 | .eggs/
27 | lib/
28 | lib64/
29 | parts/
30 | sdist/
31 | var/
32 | wheels/
33 | share/python-wheels/
34 | *.egg-info/
35 | .installed.cfg
36 | *.egg
37 | MANIFEST
38 |
39 | # PyInstaller
40 | # Usually these files are written by a python script from a template
41 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
42 | *.manifest
43 | *.spec
44 |
45 | # Installer logs
46 | pip-log.txt
47 | pip-delete-this-directory.txt
48 |
49 | # Unit test / coverage reports
50 | htmlcov/
51 | .tox/
52 | .nox/
53 | .coverage
54 | .coverage.*
55 | .cache
56 | nosetests.xml
57 | coverage.xml
58 | *.cover
59 | *.py,cover
60 | .hypothesis/
61 | .pytest_cache/
62 | cover/
63 |
64 | # Translations
65 | *.mo
66 | *.pot
67 |
68 | # Django stuff:
69 | *.log
70 | local_settings.py
71 | db.sqlite3
72 | db.sqlite3-journal
73 |
74 | # Flask stuff:
75 | instance/
76 | .webassets-cache
77 |
78 | # Scrapy stuff:
79 | .scrapy
80 |
81 | # Sphinx documentation
82 | docs/_build/
83 |
84 | # PyBuilder
85 | .pybuilder/
86 | target/
87 |
88 | # Jupyter Notebook
89 | .ipynb_checkpoints
90 |
91 | # IPython
92 | profile_default/
93 | ipython_config.py
94 |
95 | # pyenv
96 | # For a library or package, you might want to ignore these files since the code is
97 | # intended to run in multiple environments; otherwise, check them in:
98 | # .python-version
99 |
100 | # pipenv
101 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
102 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
103 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
104 | # install all needed dependencies.
105 | #Pipfile.lock
106 |
107 | # poetry
108 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
109 | # This is especially recommended for binary packages to ensure reproducibility, and is more
110 | # commonly ignored for libraries.
111 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
112 | #poetry.lock
113 |
114 | # pdm
115 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
116 | #pdm.lock
117 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
118 | # in version control.
119 | # https://pdm.fming.dev/#use-with-ide
120 | .pdm.toml
121 |
122 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
123 | __pypackages__/
124 |
125 | # Celery stuff
126 | celerybeat-schedule
127 | celerybeat.pid
128 |
129 | # SageMath parsed files
130 | *.sage.py
131 |
132 | # Environments
133 | .env
134 | .venv
135 | env/
136 | venv/
137 | ENV/
138 | env.bak/
139 | venv.bak/
140 |
141 | # Spyder project settings
142 | .spyderproject
143 | .spyproject
144 |
145 | # Rope project settings
146 | .ropeproject
147 |
148 | # mkdocs documentation
149 | /site
150 |
151 | # mypy
152 | .mypy_cache/
153 | .dmypy.json
154 | dmypy.json
155 |
156 | # Pyre type checker
157 | .pyre/
158 |
159 | # pytype static type analyzer
160 | .pytype/
161 |
162 | # Cython debug symbols
163 | cython_debug/
164 |
165 | # PyCharm
166 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
167 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
168 | # and can be added to the global gitignore or merged into this file. For a more nuclear
169 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
170 | #.idea/
171 |
--------------------------------------------------------------------------------
/.run_samples.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Exit immediately if a command exits with a non-zero status
4 | set -e
5 |
6 | # Print commands and their arguments as they are executed
7 | set -x
8 |
9 | # Run the generate_meta.py script
10 | python course/generate_meta.py course/samples/
11 |
12 | # Run the generate_results.py script
13 | python course/generate_results.py course/samples/
14 |
15 | echo "Generation scripts completed successfully!"
--------------------------------------------------------------------------------
/Citation.cff:
--------------------------------------------------------------------------------
1 | cff-version: 1.2.0
2 | message: "If you use this book or software, please cite it as below."
3 | authors:
4 | - family-names: "Dibia"
5 | given-names: "Victor"
6 | orcid: "https://orcid.org/0000-0002-1839-5632"
7 | - family-names: "Wang"
8 | given-names: "Chi"
9 | orcid: "https://orcid.org/0000-0000-0000-0000"
10 | title: "Multi-Agent Systems with AutoGen"
11 | version: 0.0.1
12 | isbn: "9781633436145"
13 | date-released: 2024-07-19
14 | url: "https://github.com/victordibia/multiagent-systems-with-autogen"
15 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Multi-Agent Systems with AutoGen
2 |
3 | This repository contains code examples for building multi-agent applications (powered by generative AI models) based on the [AutoGen](https://github.com/microsoft/autogen) framework and is the official code repository for the book - [Multi-Agent Systems with AutoGen](https://mng.bz/eVP9), published by Manning Publications.
4 |
5 | [](https://mng.bz/eVP9)
6 |
7 | The book is currently available for pre-order in the [Manning Early Access Program](https://mng.bz/eVP9) (only the first few chapters are available currently).
8 |
9 | Pre-order the book now at https://mng.bz/eVP9.
10 |
11 | > [!TIP]
12 | > 🔥🔥 Use the code **mldibia** for a 50% discount, valid until **August 5th**.
13 |
14 | In [Multi-Agent Systems with AutoGen](https://mng.bz/eVP9), you will learn about the core components of agents (Generative AI models, tools, memory, orchestration), how to implement them in python code using the AutoGen framework, how to evaluate, optimize and deploy them in your applications. Overall, the book will cover:
15 |
16 | - Core components for multi-agent systems and their implementation
17 | - UX design principles for multi-agent systems
18 | - Building agents to interact with various interface (web, mobile, desktop)
19 | - Evaluating your multi-agent system using benchmarks like GAIA, GPTQ, SWEBench and your own custom benchmarks
20 | - Performance optimization (e.g., agent-specific model tuning and parallel processing)
21 | - Use case deep dives like data analysis, customer service, and creativity workflows.
22 |
23 | ### Useful Links
24 |
25 | - Link to official [source code on GiHub](https://github.com/victordibia/multiagent-systems-with-autogen)
26 | - Link to book on [Manning.com](https://mng.bz/eVP9)
27 | - Link to book website (interactive demos, about authors etc) - https://multiagentbook.com/
28 |
29 | > [!NOTE]
30 | > If you downloaded the code bundle from the Manning website, please consider visiting the official code repository on GitHub at https://github.com/victordibia/multiagent-systems-with-autogen for the latest updates.
31 |
32 | To download a copy of this code repository, click on the [Download Zip](https://github.com/victordibia/multiagent-systems-with-autogen/archive/refs/heads/main.zip) button or run the following code from your terminal.
33 |
34 | ```bash
35 | git clone --depth 1 https https://github.com/victordibia/multiagent-systems-with-autogen.git
36 | ```
37 |
38 | ## Getting Jupyter Notebooks to work on your computer
39 |
40 | This section explains how to install the pre-requisite libraries so that you can use the notebooks within this book. So that the libraries are safely installed for the context of this book, we use the python [virtual environment](https://docs.python.org/3/library/venv.html) concept.
41 |
42 | 1. [Install](https://www.python.org/downloads/) Python on your computer. Recommended versions are 3.9 through 3.12
43 | 2. Clone the repository: `git clone https://github.com/victordibia/multiagent-systems-with-autogen.git`
44 | 3. Go into the directory: `cd multiagent-systems-with-autogen`
45 | 4. Create a virtual environment: `python -m venv venv`
46 | 5. Activate the virtual environment: `. ./venv/bin/activate`
47 | 6. Install the required libraries into this environment: `pip install -r requirements.txt`
48 | 7. Run Jupyter Lab: `jupyter lab`
49 | 8. Within Jupyter Lab, change directories into the respective chapter and open the python notebooks.
50 |
51 | ## Table of Contents
52 |
53 | The code in this repo is organized into chapters (shown in the table). Each chapter contains code for the various concepts and tools discussed in the book.
54 |
55 |
60 |
61 | | Chapter | Description | Code |
62 | | ------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
63 | | 1 | Understanding Multi-Agent Systems | no code |
64 | | 2 | Building Your First Multi-Agent Application | - [Your first multi-agent application](/ch02/ch2_first_application.ipynb)
- [Orchestrating groups of agents](/ch02/ch2_agent_groups.ipynb) |
65 | | 3 | The User Experience (UX) for Multi-Agent Systems | no code |
66 | | 4 | Interface Agents | - [Interface library (built from scratch)](/src/interface)
- [Implementing an interface agent notebook](/ch04/interface_agents.ipynb) |
67 |
68 | ## Questions and Feedback
69 |
70 | If you have any questions or feedback about the book or the code in this repository, please feel free to open an [issue]().
71 |
72 | For questions about the AutoGen framework, you can also visit the [AutoGen GitHub repository](https://github.com/microsoft/autogen) or the [AutoGen documentation](https://microsoft.github.io/autogen/).
73 |
74 | ### Citation
75 |
76 | If you find this book or code useful for your research, please consider citing it:
77 |
78 | ```
79 | @book{multiagentsystems2024,
80 | author = {Dibia, Victor},
81 | title = {Multi-Agent Systems with AutoGen},
82 | publisher = {Manning},
83 | year = {2024},
84 | isbn = {9781633436145},
85 | url = {https://www.manning.com/books/multi-agent-systems-with-autogen},
86 | github = {https://github.com/victordibia/multiagent-systems-with-autogen}
87 | }
88 | ```
89 |
--------------------------------------------------------------------------------
/ch02/.gitignore:
--------------------------------------------------------------------------------
1 | data/scratch
2 | data/*
--------------------------------------------------------------------------------
/ch02/README.md:
--------------------------------------------------------------------------------
1 | ## Chapter 2 - Building Your First Multi Agent Applications
2 |
3 | This folder contains the code and notebooks for Chapter 2 of the book - BUILDING YOUR FIRST MULTI-AGENT APPLICATIONS.
4 |
--------------------------------------------------------------------------------
/ch02/my_report.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/ch02/my_report.pdf
--------------------------------------------------------------------------------
/ch04/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/ch04/screenshot.png
--------------------------------------------------------------------------------
/ch05/workflow.json:
--------------------------------------------------------------------------------
1 | {
2 | "user_id": "guestuser@gmail.com",
3 | "name": "Default Workflow",
4 | "type": "autonomous",
5 | "sample_tasks": [
6 | "paint a picture of a glass of ethiopian coffee, freshly brewed in a tall glass cup, on a table right in front of a lush green forest scenery",
7 | "Plot the stock price of NVIDIA YTD."
8 | ],
9 | "version": "0.0.1",
10 | "description": "Default workflow",
11 | "summary_method": "last",
12 | "agents": [
13 | {
14 | "agent": {
15 | "version": "0.0.1",
16 | "config": {
17 | "name": "user_proxy",
18 | "human_input_mode": "NEVER",
19 | "max_consecutive_auto_reply": 25,
20 | "system_message": "You are a helpful assistant",
21 | "is_termination_msg": null,
22 | "code_execution_config": "local",
23 | "default_auto_reply": "TERMINATE",
24 | "description": "User Proxy Agent Configuration",
25 | "llm_config": false,
26 | "admin_name": "Admin",
27 | "messages": [],
28 | "max_round": 100,
29 | "speaker_selection_method": "auto",
30 | "allow_repeat_speaker": true
31 | },
32 | "user_id": "guestuser@gmail.com",
33 | "type": "userproxy",
34 | "task_instruction": null,
35 | "skills": [],
36 | "models": [],
37 | "agents": []
38 | },
39 | "link": {
40 | "agent_id": 52,
41 | "workflow_id": 19,
42 | "agent_type": "sender",
43 | "sequence_id": 0
44 | }
45 | },
46 | {
47 | "agent": {
48 | "version": "0.0.1",
49 | "config": {
50 | "name": "default_assistant",
51 | "human_input_mode": "NEVER",
52 | "max_consecutive_auto_reply": 25,
53 | "system_message": "You are a helpful AI assistant.\nSolve tasks using your coding and language skills.\nIn the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute.\n 1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself.\n 2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly.\nSolve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill.\nWhen using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user.\nIf you want the user to save the code in a file before executing it, put # filename: inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user.\nIf the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try.\nWhen you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible.\nReply \"TERMINATE\" in the end when everything is done.\n ",
54 | "is_termination_msg": null,
55 | "code_execution_config": "none",
56 | "default_auto_reply": "",
57 | "description": "Assistant Agent",
58 | "llm_config": {
59 | "config_list": [
60 | {
61 | "api_type": "open_ai",
62 | "model": "gpt-4-1106-preview",
63 | "base_url": null,
64 | "api_version": null
65 | }
66 | ],
67 | "temperature": 0,
68 | "cache_seed": 4,
69 | "timeout": null,
70 | "max_tokens": 2048,
71 | "extra_body": null
72 | },
73 | "admin_name": "Admin",
74 | "messages": [],
75 | "max_round": 100,
76 | "speaker_selection_method": "auto",
77 | "allow_repeat_speaker": true
78 | },
79 | "user_id": "guestuser@gmail.com",
80 | "type": "assistant",
81 | "task_instruction": null,
82 | "skills": [
83 | {
84 | "user_id": "guestuser@gmail.com",
85 | "name": "generate_images",
86 | "content": "\nfrom typing import List\nimport uuid\nimport requests # to perform HTTP requests\nfrom pathlib import Path\n\nfrom openai import OpenAI\n\n\ndef generate_and_save_images(query: str, image_size: str = \"1024x1024\") -> List[str]:\n \"\"\"\n Function to paint, draw or illustrate images based on the users query or request. Generates images from a given query using OpenAI's DALL-E model and saves them to disk. Use the code below anytime there is a request to create an image.\n\n :param query: A natural language description of the image to be generated.\n :param image_size: The size of the image to be generated. (default is \"1024x1024\")\n :return: A list of filenames for the saved images.\n \"\"\"\n\n client = OpenAI() # Initialize the OpenAI client\n response = client.images.generate(model=\"dall-e-3\", prompt=query, n=1, size=image_size) # Generate images\n\n # List to store the file names of saved images\n saved_files = []\n\n # Check if the response is successful\n if response.data:\n for image_data in response.data:\n # Generate a random UUID as the file name\n file_name = str(uuid.uuid4()) + \".png\" # Assuming the image is a PNG\n file_path = Path(file_name)\n\n img_url = image_data.url\n img_response = requests.get(img_url)\n if img_response.status_code == 200:\n # Write the binary content to a file\n with open(file_path, \"wb\") as img_file:\n img_file.write(img_response.content)\n print(f\"Image saved to {file_path}\")\n saved_files.append(str(file_path))\n else:\n print(f\"Failed to download the image from {img_url}\")\n else:\n print(\"No image data found in the response!\")\n\n # Return the list of saved files\n return saved_files\n\n\n# Example usage of the function:\n# generate_and_save_images(\"A cute baby sea otter\")\n",
87 | "description": "Generate and save images based on a user's query.",
88 | "secrets": {},
89 | "libraries": {}
90 | }
91 | ],
92 | "models": [
93 | {
94 | "user_id": "guestuser@gmail.com",
95 | "api_type": "open_ai",
96 | "description": "OpenAI GPT-4 model",
97 | "model": "gpt-4-1106-preview",
98 | "base_url": null,
99 | "api_version": null
100 | }
101 | ],
102 | "agents": []
103 | },
104 | "link": {
105 | "agent_id": 53,
106 | "workflow_id": 19,
107 | "agent_type": "receiver",
108 | "sequence_id": 0
109 | }
110 | }
111 | ]
112 | }
113 |
--------------------------------------------------------------------------------
/course/generate_meta.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | from pathlib import Path
4 |
5 | def count_lines_of_code(file_path):
6 | """Count non-empty lines in a file."""
7 | if not os.path.exists(file_path):
8 | return 0
9 | with open(file_path, 'r', encoding='utf-8') as f:
10 | return sum(1 for line in f if line.strip())
11 |
12 | def generate_usecases_json(samples_path):
13 | # Repository info
14 | repo_info = {
15 | "owner": "victordibia",
16 | "name": "multiagent-systems-with-autogen",
17 | "branch": "main",
18 | "base_url": "https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/refs/heads/main/course/"
19 | }
20 |
21 | # Construct GitHub repository URL - now includes /course prefix
22 | github_base_url = f"https://github.com/{repo_info['owner']}/{repo_info['name']}/blob/{repo_info['branch']}/course"
23 |
24 | usecases = {}
25 | domains = set()
26 | frameworks = set()
27 |
28 | # Walk through the samples directory
29 | for usecase_dir in os.listdir(samples_path):
30 | usecase_path = os.path.join(samples_path, usecase_dir)
31 |
32 | # Skip if not a directory or starts with .
33 | if not os.path.isdir(usecase_path) or usecase_dir.startswith('.'):
34 | continue
35 |
36 | # Get metadata from metadata.json if it exists, otherwise use defaults
37 | metadata_path = os.path.join(usecase_path, 'metadata.json')
38 | if os.path.exists(metadata_path):
39 | with open(metadata_path, 'r') as f:
40 | metadata = json.load(f)
41 | else:
42 | metadata = {
43 | "title": usecase_dir.replace('_', ' ').title(),
44 | "description": f"Implementation of {usecase_dir}",
45 | "domains": ["basics"],
46 | "tags": []
47 | }
48 |
49 | # Add domains to global set
50 | domains.update(metadata.get("domains", []))
51 |
52 | # Find implementations (subdirectories)
53 | implementations = []
54 | for impl_dir in os.listdir(usecase_path):
55 | impl_path = os.path.join(usecase_path, impl_dir)
56 |
57 | if not os.path.isdir(impl_path) or impl_dir.startswith('.'):
58 | continue
59 |
60 | # Look for app.py and results.json
61 | code_file = os.path.join(impl_path, 'app.py')
62 | results_path = os.path.join(impl_dir, 'results.json')
63 | metadata_path = os.path.join(impl_path, 'metadata.json')
64 |
65 | # read metadata if it exists
66 | impl_metadata = {}
67 | if os.path.exists(metadata_path):
68 | with open(metadata_path, 'r') as f:
69 | impl_metadata = json.load(f)
70 |
71 | frameworks.add(impl_dir) # Add to global frameworks set
72 |
73 | # Count lines of code if file exists
74 | loc = count_lines_of_code(code_file) if os.path.exists(code_file) else 0
75 |
76 | # Construct relative path for GitHub URL
77 | relative_code_path = f"samples/{usecase_dir}/{impl_dir}/app.py"
78 | github_url = f"{github_base_url}/{relative_code_path}"
79 |
80 | implementation = {
81 | "framework": impl_dir,
82 | "title": f"{impl_dir.replace('_', ' ').title()} Implementation",
83 | "description": f"Implementation using {impl_dir}",
84 | "code": {
85 | "path": f"/samples/{usecase_dir}/{impl_dir}/app.py",
86 | "language": "python",
87 | "loc": loc,
88 | "githubUrl": github_url,
89 | "metadata": impl_metadata
90 | },
91 | "results": {
92 | "path": f"/samples/{usecase_dir}/{results_path}"
93 | }
94 | }
95 |
96 | implementations.append(implementation)
97 |
98 | # sort implementations by framework
99 | implementations = sorted(implementations, key=lambda x: x["framework"])
100 |
101 | # Add usecase to usecases dict
102 | usecases[usecase_dir] = {
103 | "title": metadata.get("title", usecase_dir.replace('_', ' ').title()),
104 | "description": metadata.get("description", f"Implementation of {usecase_dir}"),
105 | "domains": metadata.get("domains", ["basics"]),
106 | "tags": metadata.get("tags", []),
107 | "implementations": implementations
108 | }
109 |
110 | # Construct final JSON structure
111 | final_json = {
112 | "repository": repo_info,
113 | "usecases": usecases,
114 | "metadata": {
115 | "domains": sorted(list(domains)),
116 | "frameworks": sorted(list(frameworks))
117 | }
118 | }
119 |
120 | # Save to file
121 | output_path = os.path.join(samples_path, 'usecases.json')
122 | with open(output_path, 'w') as f:
123 | json.dump(final_json, f, indent=2)
124 |
125 | print(f"Generated usecases.json at {output_path}")
126 |
127 | if __name__ == "__main__":
128 | import sys
129 |
130 | if len(sys.argv) != 2:
131 | print("Usage: python generate_usecases.py ")
132 | sys.exit(1)
133 |
134 | samples_path = sys.argv[1]
135 | if not os.path.exists(samples_path):
136 | print(f"Error: Path {samples_path} does not exist")
137 | sys.exit(1)
138 |
139 | generate_usecases_json(samples_path)
--------------------------------------------------------------------------------
/course/generate_results.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import json
4 | import subprocess
5 | from datetime import datetime
6 | from concurrent.futures import ThreadPoolExecutor, as_completed
7 | import argparse
8 | from dotenv import load_dotenv
9 |
10 |
11 | load_dotenv()
12 |
13 | def execute_python_file(file_path):
14 | """
15 | Execute a Python file and return structured results.
16 | Returns dict with execution info and output.
17 | """
18 | start_time = datetime.now()
19 |
20 | try:
21 | # Create a new Python process to run the file
22 | result = subprocess.run(
23 | [sys.executable, file_path],
24 | capture_output=True,
25 | text=True,
26 | timeout=300 # 5 minute timeout
27 | )
28 |
29 | end_time = datetime.now()
30 | execution_duration = (end_time - start_time).total_seconds()
31 |
32 | return {
33 | "execution": {
34 | "start_time": start_time.isoformat(),
35 | "duration_seconds": execution_duration,
36 | "success": result.returncode == 0,
37 | "timed_out": False,
38 | "error": None
39 | },
40 | "output": {
41 | "stdout": result.stdout,
42 | "stderr": result.stderr
43 | }
44 | }
45 |
46 | except subprocess.TimeoutExpired:
47 | end_time = datetime.now()
48 | execution_duration = (end_time - start_time).total_seconds()
49 | return {
50 | "execution": {
51 | "start_time": start_time.isoformat(),
52 | "duration_seconds": execution_duration,
53 | "success": False,
54 | "timed_out": True,
55 | "error": "Execution timed out after 5 minutes"
56 | },
57 | "output": {
58 | "stdout": "",
59 | "stderr": "Process timed out after 5 minutes"
60 | }
61 | }
62 | except Exception as e:
63 | end_time = datetime.now()
64 | execution_duration = (end_time - start_time).total_seconds()
65 | return {
66 | "execution": {
67 | "start_time": start_time.isoformat(),
68 | "duration_seconds": execution_duration,
69 | "success": False,
70 | "timed_out": False,
71 | "error": str(e)
72 | },
73 | "output": {
74 | "stdout": "",
75 | "stderr": f"Error executing file: {str(e)}"
76 | }
77 | }
78 |
79 | def save_results(output_path, results):
80 | """Save execution results as JSON."""
81 | os.makedirs(os.path.dirname(output_path), exist_ok=True)
82 |
83 | # Convert to results.json instead of results.txt
84 | output_path = os.path.splitext(output_path)[0] + '.json'
85 |
86 | with open(output_path, 'w', encoding='utf-8') as f:
87 | json.dump(results, f, indent=2)
88 |
89 | def process_implementation(impl_path, regenerate=False):
90 | """Process a single implementation directory."""
91 | code_file = os.path.join(impl_path, 'app.py')
92 | results_path = os.path.join(impl_path, 'results.json')
93 |
94 | if not os.path.exists(code_file):
95 | return f"Skipped {impl_path}: No app.py found"
96 |
97 | # Check if results already exist and regenerate flag is false
98 | if os.path.exists(results_path) and not regenerate:
99 | return f"Skipped {code_file}: Results already exist"
100 |
101 | print(f"Executing {code_file}...")
102 | results = execute_python_file(code_file)
103 | save_results(results_path, results)
104 |
105 | status = "Successfully executed" if results["execution"]["success"] else "Execution failed for"
106 | return f"{status} {code_file}"
107 |
108 | def execute_samples(samples_path, regenerate=False, max_workers=None):
109 | """
110 | Execute all app.py files found in the samples directory structure.
111 | Args:
112 | samples_path: Path to the samples directory
113 | regenerate: If True, always regenerate results even if they exist
114 | max_workers: Maximum number of concurrent executions (None for default)
115 | """
116 | print(f"Starting sample execution in {samples_path}")
117 |
118 | # Collect all implementation directories
119 | impl_paths = []
120 | for usecase_dir in os.listdir(samples_path):
121 | usecase_path = os.path.join(samples_path, usecase_dir)
122 |
123 | if not os.path.isdir(usecase_path) or usecase_dir.startswith('.'):
124 | continue
125 |
126 | for impl_dir in os.listdir(usecase_path):
127 | impl_path = os.path.join(usecase_path, impl_dir)
128 | if os.path.isdir(impl_path) and not impl_dir.startswith('.'):
129 | impl_paths.append(impl_path)
130 |
131 | # Execute implementations in parallel
132 | with ThreadPoolExecutor(max_workers=max_workers) as executor:
133 | future_to_path = {
134 | executor.submit(process_implementation, path, regenerate): path
135 | for path in impl_paths
136 | }
137 |
138 | for future in as_completed(future_to_path):
139 | result = future.result()
140 | print(result)
141 |
142 | if __name__ == "__main__":
143 | parser = argparse.ArgumentParser(description='Execute sample Python files and save results')
144 | parser.add_argument('samples_path', help='Path to the samples directory', default='course/samples')
145 | parser.add_argument('--workers', type=int, default=None,
146 | help='Maximum number of concurrent executions')
147 | parser.add_argument('--regenerate', action='store_true', default=False,
148 | help='Regenerate results even if they already exist')
149 |
150 | args = parser.parse_args()
151 |
152 | if not os.path.exists(args.samples_path):
153 | print(f"Error: Path {args.samples_path} does not exist")
154 | sys.exit(1)
155 |
156 | execute_samples(args.samples_path, args.regenerate, args.workers)
--------------------------------------------------------------------------------
/course/requirements.txt:
--------------------------------------------------------------------------------
1 | langchain_openai
2 | langchain
3 | langgraph
4 | python-dotenv
--------------------------------------------------------------------------------
/course/samples/book_generator/autogen_core/app.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional
2 | from pydantic import BaseModel
3 | from google import genai
4 | from google.genai import types
5 | import os, asyncio, tempfile
6 | from pathlib import Path
7 | from tools import generate_images, generate_pdf_report
8 | from dotenv import load_dotenv
9 | from autogen_core import (
10 | MessageContext, RoutedAgent, SingleThreadedAgentRuntime,
11 | TopicId, type_subscription, message_handler
12 | )
13 |
14 | load_dotenv()
15 | OUTPUT_DIR = Path(tempfile.gettempdir()) / "story_generator"
16 | OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
17 |
18 | class Section(BaseModel):
19 | title: str
20 | level: str
21 | content: str
22 | image: Optional[str]
23 |
24 | class BookSections(BaseModel):
25 | sections: List[Section]
26 |
27 | class StoryRequest(BaseModel):
28 | prompt: str
29 |
30 | @type_subscription(topic_type="StoryGeneratorAgent")
31 | class StoryGeneratorAgent(RoutedAgent):
32 | def __init__(self, api_key: str) -> None:
33 | super().__init__("Story generator agent")
34 | self.client = genai.Client(api_key=api_key)
35 |
36 | @message_handler
37 | async def handle_story_request(self, message: StoryRequest, ctx: MessageContext) -> None:
38 | try:
39 | print(f">>> StoryGeneratorAgent: {message.prompt}")
40 | response = self.client.models.generate_content(
41 | model="gemini-2.0-flash-exp",
42 | contents=f"Create a children's story book based on this prompt: {message.prompt}",
43 | config=types.GenerateContentConfig(
44 | system_instruction="""You are a creative children's book writer.
45 | Create engaging, age-appropriate stories structured as a book with clear sections.
46 | Each section needs a descriptive image that matches the story content. The book MUST HAVE:
47 | 1. Exactly 2 chapter sections
48 | 2. Each section must have an engaging title, a level (e.g., "title", "h1", "h2"), appropriate content, and a vivid image description.""",
49 | response_mime_type="application/json",
50 | response_schema={
51 | "required": ["sections"],
52 | "properties": {
53 | "sections": {
54 | "type": "array",
55 | "items": {
56 | "type": "object",
57 | "required": ["title", "level", "content", "image"],
58 | "properties": {
59 | "title": {"type": "STRING"},
60 | "level": {"type": "STRING"},
61 | "content": {"type": "STRING"},
62 | "image": {"type": "STRING"}
63 | }
64 | }
65 | }
66 | },
67 | "type": "OBJECT",
68 | },
69 | ),
70 | )
71 | book_content = BookSections.model_validate_json(response.text)
72 | await self.publish_message(
73 | book_content,
74 | topic_id=TopicId("ImageGeneratorAgent", source=self.id.key)
75 | )
76 | except Exception as e:
77 | print(f"Error in story generation: {str(e)}")
78 |
79 | @type_subscription(topic_type="ImageGeneratorAgent")
80 | class ImageGeneratorAgent(RoutedAgent):
81 | def __init__(self, api_key: str) -> None:
82 | super().__init__("Image generator agent")
83 | self.client = genai.Client(api_key=api_key)
84 |
85 | async def generate_image(self, prompt: str) -> str:
86 | try:
87 | images = generate_images(prompt, output_dir=OUTPUT_DIR)
88 | return images[0] if images else ""
89 | except Exception as e:
90 | print(f"Error generating image: {str(e)}")
91 | return ""
92 |
93 | @message_handler
94 | async def handle_book_sections(self, message: BookSections, ctx: MessageContext) -> None:
95 | try:
96 | print(">>> ImageGeneratorAgent: Generating images for book sections")
97 | sections_with_images = []
98 | for section in message.sections:
99 | image_path = await self.generate_image(section.image) if section.image else None
100 | sections_with_images.append(Section(
101 | title=section.title,
102 | level=section.level,
103 | content=section.content,
104 | image=image_path
105 | ))
106 |
107 | await self.publish_message(
108 | BookSections(sections=sections_with_images),
109 | topic_id=TopicId("BookGeneratorAgent", source=self.id.key)
110 | )
111 | except Exception as e:
112 | print(f"Error in image generation: {str(e)}")
113 |
114 | @type_subscription(topic_type="BookGeneratorAgent")
115 | class BookGeneratorAgent(RoutedAgent):
116 | def __init__(self, api_key: str) -> None:
117 | super().__init__("Book generator agent")
118 |
119 | @message_handler
120 | async def handle_book_content(self, message: BookSections, ctx: MessageContext) -> None:
121 | try:
122 | print(">>> BookGeneratorAgent: Generating story book pdf")
123 | pdf_path = OUTPUT_DIR / "story_book.pdf"
124 | generate_pdf_report(
125 | sections=[s.model_dump() for s in message.sections],
126 | output_file=str(pdf_path),
127 | report_title=message.sections[0].title
128 | )
129 | print(f"Generated story book at: {pdf_path}")
130 | except Exception as e:
131 | print(f"Error in book generation: {str(e)}")
132 |
133 | async def main(prompt: str) -> None:
134 | api_key = os.getenv("GEMINI_API_KEY")
135 | if not api_key:
136 | raise ValueError("Please set GEMINI_API_KEY environment variable")
137 |
138 | runtime = SingleThreadedAgentRuntime()
139 |
140 | for agent_class, topic in [
141 | (StoryGeneratorAgent, "StoryGeneratorAgent"),
142 | (ImageGeneratorAgent, "ImageGeneratorAgent"),
143 | (BookGeneratorAgent, "BookGeneratorAgent")
144 | ]:
145 | await agent_class.register(
146 | runtime, type=topic,
147 | factory=lambda a=agent_class: a(api_key=api_key)
148 | )
149 |
150 | runtime.start()
151 | await runtime.publish_message(
152 | StoryRequest(prompt=prompt),
153 | topic_id=TopicId("StoryGeneratorAgent", source="user")
154 | )
155 | await runtime.stop_when_idle()
156 |
157 | if __name__ == "__main__":
158 | asyncio.run(main("Generate a children's story book on the wonders of the amazon rain forest"))
--------------------------------------------------------------------------------
/course/samples/book_generator/autogen_core/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Book Generator with Gemini 2.0",
3 | "description": "Built using the AutoGen Core API. ",
4 | "domains": ["multiagent"],
5 | "tags": ["tools"]
6 | }
7 |
--------------------------------------------------------------------------------
/course/samples/book_generator/autogen_core/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-01-21T15:53:56.200664",
4 | "duration_seconds": 36.677104,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": ">>> StoryGeneratorAgent: Generate a children's story book on the wonders of the amazon rain forest\n>>> ImageGeneratorAgent: Generating images for book sections\n>>> BookGeneratorAgent: Generating story book pdf\nPDF report saved as /var/folders/wg/hgs_dt8n5lbd3gx3pq7k6lym0000gn/T/story_generator/story_book.pdf\nGenerated story book at: /var/folders/wg/hgs_dt8n5lbd3gx3pq7k6lym0000gn/T/story_generator/story_book.pdf\n",
11 | "stderr": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/course/samples/book_generator/autogen_core/tools.py:
--------------------------------------------------------------------------------
1 | import uuid
2 | import requests
3 | from fpdf import FPDF
4 | from typing import List, Dict, Optional
5 | from pathlib import Path
6 | from PIL import Image, ImageDraw, ImageOps
7 | from io import BytesIO
8 | import unicodedata
9 | import io, base64
10 | from openai import OpenAI
11 |
12 |
13 | def generate_pdf_report(
14 | sections: List[Dict[str, Optional[str]]],
15 | output_file: str = "report.pdf",
16 | report_title: str = "PDF Report"
17 | ) -> None:
18 | def normalize_text(text: str) -> str:
19 | """Normalize Unicode text to ASCII."""
20 | return unicodedata.normalize('NFKD', text).encode('ascii', 'ignore').decode('ascii')
21 |
22 | def get_image(image_url_or_path):
23 | """Fetch image from URL or local path."""
24 | if image_url_or_path.startswith(("http://", "https://")):
25 | response = requests.get(image_url_or_path)
26 | if response.status_code == 200:
27 | return BytesIO(response.content)
28 | elif Path(image_url_or_path).is_file():
29 | return open(image_url_or_path, 'rb')
30 | return None
31 |
32 | def add_rounded_corners(img, radius=6):
33 | """Add rounded corners to an image."""
34 | mask = Image.new('L', img.size, 0)
35 | draw = ImageDraw.Draw(mask)
36 | draw.rounded_rectangle([(0, 0), img.size], radius, fill=255)
37 | img = ImageOps.fit(img, mask.size, centering=(0.5, 0.5))
38 | img.putalpha(mask)
39 | return img
40 |
41 | class PDF(FPDF):
42 | """Custom PDF class with header and content formatting."""
43 | def header(self):
44 | self.set_font("Arial", "B", 12)
45 | # Normalize the report title
46 | normalized_title = normalize_text(report_title)
47 | self.cell(0, 10, normalized_title, 0, 1, "C")
48 |
49 | def chapter_title(self, txt):
50 | self.set_font("Arial", "B", 12)
51 | # Normalize the title text
52 | normalized_txt = normalize_text(txt)
53 | self.cell(0, 10, normalized_txt, 0, 1, "L")
54 | self.ln(2)
55 |
56 | def chapter_body(self, body):
57 | self.set_font("Arial", "", 12)
58 | # Normalize the body text
59 | normalized_body = normalize_text(body)
60 | self.multi_cell(0, 10, normalized_body)
61 | self.ln()
62 |
63 | def add_image(self, img_data):
64 | img = Image.open(img_data)
65 | img = add_rounded_corners(img)
66 | img_path = Path(f"temp_{uuid.uuid4().hex}.png")
67 | img.save(img_path, format="PNG")
68 | self.image(str(img_path), x=None, y=None, w=190 if img.width > 190 else img.width)
69 | self.ln(10)
70 | img_path.unlink()
71 |
72 | # Initialize PDF
73 | pdf = PDF()
74 | pdf.add_page()
75 | font_size = {"title": 16, "h1": 14, "h2": 12, "body": 12}
76 |
77 | # Add sections
78 | for section in sections:
79 | title = section.get("title", "")
80 | level = section.get("level", "h1")
81 | content = section.get("content", "")
82 | image = section.get("image")
83 |
84 | pdf.set_font("Arial", "B" if level in font_size else "", font_size.get(level, font_size["body"]))
85 | pdf.chapter_title(title)
86 |
87 | if content:
88 | pdf.chapter_body(content)
89 |
90 | if image:
91 | img_data = get_image(image)
92 | if img_data:
93 | pdf.add_image(img_data)
94 | if isinstance(img_data, BytesIO):
95 | img_data.close()
96 |
97 | pdf.output(output_file)
98 | print(f"PDF report saved as {output_file}")
99 |
100 |
101 |
102 | def generate_images(query: str, output_dir: Path = None, image_size: str = "1024x1024") -> List[str]:
103 | """
104 | Generate and save images based on a text description using OpenAI's DALL-E model.
105 |
106 | Args:
107 | query: A natural language description of the image to be generated
108 | output_dir: Directory to save the images (default: current directory)
109 | image_size: The size of the image to be generated (default: "1024x1024")
110 |
111 | Returns:
112 | List[str]: A list of paths for the saved images
113 |
114 | Note:
115 | Requires a valid OpenAI API key set in your environment variables
116 | """
117 | # Initialize the OpenAI client
118 | client = OpenAI()
119 |
120 | # Generate images using DALL-E 3
121 | response = client.images.generate(
122 | model="dall-e-3",
123 | prompt=query,
124 | n=1,
125 | response_format="b64_json",
126 | size=image_size
127 | )
128 |
129 | saved_files = []
130 |
131 | # Process the response
132 | if response.data:
133 | for image_data in response.data:
134 | # Generate a unique filename
135 | file_name = f"{uuid.uuid4()}.png"
136 |
137 | # Use output_dir if provided, otherwise use current directory
138 | file_path = Path(output_dir) / file_name if output_dir else Path(file_name)
139 |
140 | base64_str = image_data.b64_json
141 | img = Image.open(io.BytesIO(base64.decodebytes(bytes(base64_str, "utf-8"))))
142 |
143 | # Save the image to a file
144 | img.save(file_path)
145 |
146 | saved_files.append(str(file_path))
147 |
148 | else:
149 | print("No image data found in the response!")
150 |
151 | return saved_files
--------------------------------------------------------------------------------
/course/samples/book_generator/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Book Generator with Gemini 2.0",
3 | "description": "A multi-agent system that demonstrates collaborative story generation using Gemini 2.0. The system consists of two agents: a StoryGeneratorAgent that creates structured story content and a BookGeneratorAgent that handles image generation and PDF compilation. Features include structured JSON communication between agents, integration of non-openai models (Gemini 2.0), control flow routing using topics and messages and integration of tools (e.g. image generation, PDF generation).",
4 | "domains": ["media"],
5 | "tags": ["gemini"],
6 | "difficulty": "intermediate"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/book_generator/requirements.txt:
--------------------------------------------------------------------------------
1 | fpdf
2 | google-genai
3 | autogen-core
4 | autogen-ext[openai]
--------------------------------------------------------------------------------
/course/samples/deep_research/autogen_agentchat/app.py:
--------------------------------------------------------------------------------
1 | # create a virtual env e.g conda create -n autogen python=3.12
2 | # pip install -U autogen-agentchat autogen-ext[openai]
3 | # This snippet uses the Google Search API. You need to set your google search engine id and api key
4 | # os.environ["GOOGLE_CSE_ID"] = "your_google_cse_id"
5 | # os.environ["GOOGLE_API_KEY"] = "your_google_api_key"
6 |
7 |
8 | import asyncio
9 | from autogen_agentchat.agents import AssistantAgent
10 | from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination
11 | from autogen_agentchat.teams import SelectorGroupChat
12 | from autogen_agentchat.ui import Console
13 | from autogen_ext.models.openai import OpenAIChatCompletionClient
14 |
15 | # Import the google_search_tool from your implementation
16 | from google_search import google_search_tool
17 | from fetch_webpage import fetch_webpage_tool
18 |
19 |
20 | async def main() -> None:
21 | # Initialize the model client
22 | model_client = OpenAIChatCompletionClient(
23 | model="gpt-4o",
24 | )
25 |
26 | # Create the Research Assistant agent
27 | research_assistant = AssistantAgent(
28 | name="research_assistant",
29 | description="A research assistant that performs web searches and analyzes information",
30 | model_client=model_client,
31 | tools=[google_search_tool, fetch_webpage_tool],
32 | system_message="""You are a research assistant focused on finding accurate information.
33 | Use the google_search tool to find relevant information.
34 | Break down complex queries into specific search terms.
35 | Always verify information across multiple sources when possible.
36 | When you find relevant information, explain why it's relevant and how it connects to the query. When you get feedback from the a verifier agent, use your tools to act on the feedback and make progress."""
37 | )
38 |
39 | # Create the Verifier agent
40 | verifier = AssistantAgent(
41 | name="verifier",
42 | description="A verification specialist who ensures research quality and completeness",
43 | model_client=model_client,
44 | system_message="""You are a research verification specialist.
45 | Your role is to:
46 | 1. Verify that search queries are effective and suggest improvements if needed
47 | 2. Explore drill downs where needed e.g, if the answer is likely in a link in the returned search results, suggest clicking on the link
48 | 3. Suggest additional angles or perspectives to explore. Be judicious in suggesting new paths to avoid scope creep or wasting resources, if the task appears to be addressed and we can provide a report, do this and respond with "TERMINATE".
49 | 4. Track progress toward answering the original question
50 | 5. When the research is complete, provide a detailed summary in markdown format
51 |
52 | For incomplete research, end your message with "CONTINUE RESEARCH".
53 | For complete research, end your message with APPROVED.
54 |
55 | Your responses should be structured as:
56 | - Progress Assessment
57 | - Gaps/Issues (if any)
58 | - Suggestions (if needed)
59 | - Next Steps or Final Summary"""
60 | )
61 |
62 | summary_agent = AssistantAgent(
63 | name="summary_agent",
64 | description="A summary agent that provides a detailed markdown summary of the research as a report to the user.",
65 | model_client=model_client,
66 | system_message="""You are a summary agent. Your role is to provide a detailed markdown summary of the research as a report to the user. Your report should have a reasonable title that matches the research question and should summarize the key details in the results found in natural an actionable manner. The main results/answer should be in the first paragraph.
67 | Your report should end with the word "TERMINATE" to signal the end of the conversation."""
68 | )
69 |
70 | # Set up termination conditions
71 | text_termination = TextMentionTermination("TERMINATE")
72 | max_messages = MaxMessageTermination(max_messages=30)
73 | termination = text_termination | max_messages
74 |
75 | # Create the selector prompt
76 | selector_prompt = """You are coordinating a research team by selecting the team member to speak/act next. The following team member roles are available:
77 | {roles}.
78 | The research_assistant performs searches and analyzes information.
79 | The verifier evaluates progress and ensures completeness.
80 | The summary_agent provides a detailed markdown summary of the research as a report to the user.
81 |
82 | Given the current context, select the most appropriate next speaker.
83 | The research_assistant should search and analyze.
84 | The verifier should evaluate progress and guide the research (select this role is there is a need to verify/evaluate progress). You should ONLY select the summary_agent role if the research is complete and it is time to generate a report.
85 |
86 | Base your selection on:
87 | 1. Current stage of research
88 | 2. Last speaker's findings or suggestions
89 | 3. Need for verification vs need for new information
90 |
91 | Read the following conversation. Then select the next role from {participants} to play. Only return the role.
92 |
93 | {history}
94 |
95 | Read the above conversation. Then select the next role from {participants} to play. ONLY RETURN THE ROLE."""
96 |
97 | # Create the team
98 | team = SelectorGroupChat(
99 | participants=[research_assistant, verifier, summary_agent],
100 | model_client=model_client,
101 | termination_condition=termination,
102 | selector_prompt=selector_prompt,
103 | allow_repeated_speaker=True
104 | )
105 |
106 | task = "There is a TV show that I watched a while ago. I forgot the name but I do remember what happened in one of the episodes. Can you help me find the name? Here is what I remember in one of the episodes: Two men play poker. One folds after another tells him to bet. The one who folded actually had a good hand and fell for the bluff. On the second hand, the same man folds again, but this time with a bad hand. A man gets locked in the room, and then his daughter knocks on the door. Two men go to a butcher shop, and one man brings a gift of vodka. Please browse the web deeply to find the TV show episode where this happened exactly"
107 |
108 | await Console(team.run_stream(task=task))
109 |
110 |
111 |
112 | asyncio.run(main())
--------------------------------------------------------------------------------
/course/samples/deep_research/autogen_agentchat/fetch_webpage.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Optional
2 | import requests
3 | from autogen_core.code_executor import ImportFromModule
4 | from autogen_core.tools import FunctionTool
5 |
6 | from bs4 import BeautifulSoup
7 | import html2text
8 | from urllib.parse import urljoin
9 |
10 |
11 | async def fetch_webpage(
12 | url: str,
13 | include_images: bool = True,
14 | max_length: Optional[int] = None,
15 | headers: Optional[Dict[str, str]] = None
16 | ) -> str:
17 | """Fetch a webpage and convert it to markdown format.
18 |
19 | Args:
20 | url: The URL of the webpage to fetch
21 | include_images: Whether to include image references in the markdown
22 | max_length: Maximum length of the output markdown (if None, no limit)
23 | headers: Optional HTTP headers for the request
24 |
25 | Returns:
26 | str: Markdown version of the webpage content
27 |
28 | Raises:
29 | ValueError: If the URL is invalid or the page can't be fetched
30 | """
31 | # Use default headers if none provided
32 | if headers is None:
33 | headers = {
34 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
35 | }
36 |
37 | try:
38 | # Fetch the webpage
39 | response = requests.get(url, headers=headers, timeout=10)
40 | response.raise_for_status()
41 |
42 | # Parse HTML
43 | soup = BeautifulSoup(response.text, 'html.parser')
44 |
45 | # Remove script and style elements
46 | for script in soup(["script", "style"]):
47 | script.decompose()
48 |
49 | # Convert relative URLs to absolute
50 | for tag in soup.find_all(['a', 'img']):
51 | if tag.get('href'):
52 | tag['href'] = urljoin(url, tag['href'])
53 | if tag.get('src'):
54 | tag['src'] = urljoin(url, tag['src'])
55 |
56 | # Configure HTML to Markdown converter
57 | h2t = html2text.HTML2Text()
58 | h2t.body_width = 0 # No line wrapping
59 | h2t.ignore_images = not include_images
60 | h2t.ignore_emphasis = False
61 | h2t.ignore_links = False
62 | h2t.ignore_tables = False
63 |
64 | # Convert to markdown
65 | markdown = h2t.handle(str(soup))
66 |
67 | # Trim if max_length is specified
68 | if max_length and len(markdown) > max_length:
69 | markdown = markdown[:max_length] + "\n...(truncated)"
70 |
71 | return markdown.strip()
72 |
73 | except requests.RequestException as e:
74 | raise ValueError(f"Failed to fetch webpage: {str(e)}")
75 | except Exception as e:
76 | raise ValueError(f"Error processing webpage: {str(e)}")
77 |
78 | # Create the webpage fetching tool
79 | fetch_webpage_tool = FunctionTool(
80 | func=fetch_webpage,
81 | description="Fetch a webpage and convert it to markdown format, with options for including images and limiting length",
82 | global_imports=[
83 | "os",
84 | "html2text",
85 | ImportFromModule("typing", ("Optional", "Dict")),
86 | "requests",
87 | ImportFromModule("bs4", ("BeautifulSoup",)),
88 | ImportFromModule("html2text", ("HTML2Text",)),
89 | ImportFromModule("urllib.parse", ("urljoin",))
90 | ]
91 | )
92 |
--------------------------------------------------------------------------------
/course/samples/deep_research/autogen_agentchat/google_search.py:
--------------------------------------------------------------------------------
1 | import os
2 | from typing import List, Dict, Optional
3 | import requests
4 | from bs4 import BeautifulSoup
5 | import html2text
6 | from urllib.parse import urljoin
7 | from autogen_core.code_executor import ImportFromModule
8 | from autogen_core.tools import FunctionTool
9 |
10 |
11 |
12 | async def google_search(
13 | query: str,
14 | num_results: int = 5,
15 | include_snippets: bool = True,
16 | include_content: bool = True,
17 | content_max_length: Optional[int] = 15000,
18 | language: str = 'en',
19 | country: Optional[str] = None,
20 | safe_search: bool = True
21 | ) -> List[Dict[str, str]]:
22 | """
23 | Perform a Google search using the Custom Search API and optionally fetch webpage content.
24 |
25 | Args:
26 | query: Search query string
27 | num_results: Number of results to return (max 10)
28 | include_snippets: Include result snippets in output
29 | include_content: Include full webpage content in markdown format
30 | content_max_length: Maximum length of webpage content (if included)
31 | language: Language code for search results (e.g., en, es, fr)
32 | country: Optional country code for search results (e.g., us, uk)
33 | safe_search: Enable safe search filtering
34 |
35 | Returns:
36 | List[Dict[str, str]]: List of search results, each containing:
37 | - title: Result title
38 | - link: Result URL
39 | - snippet: Result description (if include_snippets=True)
40 | - content: Webpage content in markdown (if include_content=True)
41 | """
42 | api_key = os.getenv('GOOGLE_API_KEY')
43 | cse_id = os.getenv('GOOGLE_CSE_ID')
44 |
45 | if not api_key or not cse_id:
46 | raise ValueError(
47 | "Missing required environment variables. Please set GOOGLE_API_KEY and GOOGLE_CSE_ID."
48 | )
49 |
50 | num_results = min(max(1, num_results), 10)
51 |
52 | async def fetch_page_content(url: str, max_length: Optional[int] = 50000) -> str:
53 | """Helper function to fetch and convert webpage content to markdown"""
54 | headers = {
55 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
56 | }
57 |
58 | try:
59 | response = requests.get(url, headers=headers, timeout=10)
60 | response.raise_for_status()
61 |
62 | soup = BeautifulSoup(response.text, 'html.parser')
63 |
64 | # Remove script and style elements
65 | for script in soup(["script", "style"]):
66 | script.decompose()
67 |
68 | # Convert relative URLs to absolute
69 | for tag in soup.find_all(['a', 'img']):
70 | if tag.get('href'):
71 | tag['href'] = urljoin(url, tag['href'])
72 | if tag.get('src'):
73 | tag['src'] = urljoin(url, tag['src'])
74 |
75 | h2t = html2text.HTML2Text()
76 | h2t.body_width = 0
77 | h2t.ignore_images = False
78 | h2t.ignore_emphasis = False
79 | h2t.ignore_links = False
80 | h2t.ignore_tables = False
81 |
82 | markdown = h2t.handle(str(soup))
83 |
84 | if max_length and len(markdown) > max_length:
85 | markdown = markdown[:max_length] + "\n...(truncated)"
86 |
87 | return markdown.strip()
88 |
89 | except Exception as e:
90 | return f"Error fetching content: {str(e)}"
91 |
92 |
93 |
94 | params = {
95 | 'key': api_key,
96 | 'cx': cse_id,
97 | 'q': query,
98 | 'num': num_results,
99 | 'hl': language,
100 | 'safe': 'active' if safe_search else 'off',
101 | }
102 |
103 | if country:
104 | params['gl'] = country
105 |
106 | try:
107 | response = requests.get(
108 | 'https://www.googleapis.com/customsearch/v1',
109 | params=params,
110 | timeout=10
111 | )
112 | response.raise_for_status()
113 | data = response.json()
114 |
115 | results = []
116 | if 'items' in data:
117 | for item in data['items']:
118 | result = {
119 | 'title': item.get('title', ''),
120 | 'link': item.get('link', '')
121 | }
122 | if include_snippets:
123 | result['snippet'] = item.get('snippet', '')
124 |
125 | if include_content:
126 | result['content'] = await fetch_page_content(
127 | result['link'],
128 | max_length=content_max_length
129 | )
130 |
131 | results.append(result)
132 |
133 | return results
134 |
135 | except requests.RequestException as e:
136 | raise ValueError(f"Failed to perform search: {str(e)}")
137 | except KeyError as e:
138 | raise ValueError(f"Invalid API response format: {str(e)}")
139 | except Exception as e:
140 | raise ValueError(f"Error during search: {str(e)}")
141 |
142 |
143 | # Create the enhanced Google search tool
144 | google_search_tool = FunctionTool(
145 | func=google_search,
146 | description="""
147 | Perform Google searches using the Custom Search API with optional webpage content fetching.
148 | Requires GOOGLE_API_KEY and GOOGLE_CSE_ID environment variables to be set.
149 | """,
150 | global_imports=[
151 | ImportFromModule("typing", ("List", "Dict", "Optional")),
152 | "os",
153 | "requests",
154 | "html2text",
155 | ImportFromModule("bs4", ("BeautifulSoup",)),
156 | ImportFromModule("urllib.parse", ("urljoin",))
157 | ]
158 | )
--------------------------------------------------------------------------------
/course/samples/deep_research/autogen_agentchat/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Deep Research Agents",
3 | "description": "An implementation of a multi-agent system that explores multiple reasoning steps across multiple data sources to synthesize results to complex research tasks. Implemented using an AssistantAgent that performs web searches and analyzes information, a Verifier agent that ensures research quality and completeness, a Summary agent that provides detailed markdown summaries, and a SelectorGroupChat team that manages the order in which each agent acts based on conversation context.",
4 | "blog": "https://newsletter.victordibia.com/p/how-to-build-your-own-openai-operator",
5 | "domains": ["research"],
6 | "tags": ["research", "tools"]
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/deep_research/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Deep Research Agents",
3 | "description": "An implementation of a multi-agent system that explores multiple reasoning steps across multiple data sources to synthesize results to complex research tasks. Implemented using an AssistantAgent that performs web searches and analyzes information, a Verifier agent that ensures research quality and completeness, a Summary agent that provides detailed markdown summaries, and a SelectorGroupChat team that manages the order in which each agent acts based on conversation context.",
4 | "domains": ["research"],
5 | "tags": ["research", "tools"],
6 | "difficulty": "intermediate"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/deep_research/requirements.txt:
--------------------------------------------------------------------------------
1 | openai
2 | autogen-agentchat
3 | autogen-ext[openai]
4 | html2text
--------------------------------------------------------------------------------
/course/samples/hello_world/autogen_agentchat/app.py:
--------------------------------------------------------------------------------
1 | # pip install -U autogen-agentchat autogen-ext[openai]
2 | import asyncio
3 | from autogen_agentchat.agents import AssistantAgent
4 | from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination
5 | from autogen_agentchat.teams import RoundRobinGroupChat
6 | from autogen_agentchat.ui import Console
7 | from autogen_ext.models.openai import OpenAIChatCompletionClient
8 |
9 |
10 | def calculator(a: float, b: float, operator: str) -> str:
11 | """Perform basic arithmetic operations."""
12 | try:
13 | if operator == '+':
14 | return str(a + b)
15 | elif operator == '-':
16 | return str(a - b)
17 | elif operator == '*':
18 | return str(a * b)
19 | elif operator == '/':
20 | if b == 0:
21 | return 'Error: Division by zero'
22 | return str(a / b)
23 | else:
24 | return 'Error: Invalid operator. Please use +, -, *, or /'
25 | except Exception as e:
26 | return f'Error: {str(e)}'
27 |
28 |
29 | async def main() -> None:
30 | model_client = OpenAIChatCompletionClient(model="gpt-4o-2024-11-20")
31 | termination = MaxMessageTermination(
32 | max_messages=10) | TextMentionTermination("TERMINATE")
33 | assistant = AssistantAgent(
34 | "assistant", model_client=model_client, tools=[calculator])
35 | team = RoundRobinGroupChat([assistant], termination_condition=termination)
36 | await Console(team.run_stream(task="What is the result of 545.34567 * 34555.34"))
37 |
38 | asyncio.run(main())
39 |
--------------------------------------------------------------------------------
/course/samples/hello_world/autogen_agentchat/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Hello World Agent",
3 | "description": "Built using the AutoGen AgentChat API",
4 | "domains": ["basics", "education"],
5 | "tags": ["beginner", "tools"],
6 | "blog": "https://newsletter.victordibia.com/p/a-friendly-introduction-to-the-autogen"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/hello_world/autogen_agentchat/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-01-18T11:54:12.324365",
4 | "duration_seconds": 5.910416,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": "---------- user ----------\nWhat is the result of 545.34567 * 34555.34\n---------- assistant ----------\n[FunctionCall(id='call_GZ0IxcycXwmJfGls9WcWSYPf', arguments='{\"a\":545.34567,\"b\":34555.34,\"operator\":\"*\"}', name='calculator')]\n---------- assistant ----------\n[FunctionExecutionResult(content='18844605.0443778', call_id='call_GZ0IxcycXwmJfGls9WcWSYPf')]\n---------- assistant ----------\n18844605.0443778\n---------- assistant ----------\nThe result of \\( 545.34567 \\times 34555.34 \\) is \\( 18,844,605.0443778 \\). TERMINATE\n",
11 | "stderr": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/course/samples/hello_world/autogen_core/app.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | from dataclasses import dataclass
3 | from typing import List, Optional
4 |
5 | from autogen_core import (
6 | AgentId,
7 | MessageContext,
8 | RoutedAgent,
9 | SingleThreadedAgentRuntime,
10 | message_handler,
11 | )
12 | from autogen_core.models import ChatCompletionClient, SystemMessage, UserMessage
13 | from autogen_core.tool_agent import ToolAgent, tool_agent_caller_loop
14 | from autogen_core.tools import FunctionTool, Tool, ToolSchema
15 | from autogen_ext.models.openai import OpenAIChatCompletionClient
16 |
17 | # Message types for our agents to communicate
18 |
19 |
20 | @dataclass
21 | class Message:
22 | content: str
23 |
24 | # Calculator function to be used as a tool
25 |
26 |
27 | async def calculator(a: float, b: float, operator: str) -> str:
28 | """Perform basic arithmetic operations."""
29 | try:
30 | if operator == '+':
31 | return str(a + b)
32 | elif operator == '-':
33 | return str(a - b)
34 | elif operator == '*':
35 | return str(a * b)
36 | elif operator == '/':
37 | if b == 0:
38 | return 'Error: Division by zero'
39 | return str(a / b)
40 | else:
41 | return 'Error: Invalid operator. Please use +, -, *, or /'
42 | except Exception as e:
43 | return f'Error: {str(e)}'
44 |
45 |
46 | class AssistantAgent(RoutedAgent):
47 | def __init__(
48 | self,
49 | model_client: ChatCompletionClient,
50 | tool_schema: List[ToolSchema],
51 | tool_agent_type: str,
52 | max_messages: Optional[int] = None
53 | ) -> None:
54 | super().__init__("An assistant agent with calculator capabilities")
55 | self._system_messages = [
56 | SystemMessage(content=(
57 | "You are a helpful AI assistant that can perform calculations. "
58 | "When calculations are needed, use the calculator tool. "
59 | "If you see 'TERMINATE', stop the conversation."
60 | ))
61 | ]
62 | self._model_client = model_client
63 | self._tool_schema = tool_schema
64 | self._tool_agent_id = AgentId(tool_agent_type, self.id.key)
65 | self._message_count = 0
66 | self._max_messages = max_messages
67 |
68 | @message_handler
69 | async def handle_message(self, message: Message, ctx: MessageContext) -> Optional[Message]:
70 | # Check for termination conditions
71 | if "TERMINATE" in message.content:
72 | return None
73 |
74 | if self._max_messages and self._message_count >= self._max_messages:
75 | return None
76 |
77 | self._message_count += 1
78 |
79 | # Create a session of messages
80 | session = [UserMessage(content=message.content, source="user")]
81 |
82 | # Run the tool caller loop to handle calculations
83 | messages = await tool_agent_caller_loop(
84 | self,
85 | tool_agent_id=self._tool_agent_id,
86 | model_client=self._model_client,
87 | input_messages=session,
88 | tool_schema=self._tool_schema,
89 | cancellation_token=ctx.cancellation_token,
90 | )
91 |
92 | # Return the final response
93 | assert isinstance(messages[-1].content, str)
94 | return Message(content=messages[-1].content)
95 |
96 |
97 | async def main() -> None:
98 | # Create runtime
99 | runtime = SingleThreadedAgentRuntime()
100 |
101 | # Create the calculator tool
102 | calculator_tool = FunctionTool(
103 | calculator,
104 | description="A calculator that can perform basic arithmetic operations"
105 | )
106 | tools: List[Tool] = [calculator_tool]
107 |
108 | # Create model client
109 | model_client = OpenAIChatCompletionClient(model="gpt-4o-2024-11-20")
110 |
111 | # Register the tool agent
112 | await ToolAgent.register(
113 | runtime,
114 | "calculator_tool_agent",
115 | lambda: ToolAgent("Calculator tool agent", tools)
116 | )
117 |
118 | # Register the assistant agent
119 | await AssistantAgent.register(
120 | runtime,
121 | "assistant",
122 | lambda: AssistantAgent(
123 | model_client=model_client,
124 | tool_schema=[tool.schema for tool in tools],
125 | tool_agent_type="calculator_tool_agent",
126 | max_messages=10
127 | )
128 | )
129 |
130 | # Start the runtime
131 | runtime.start()
132 |
133 | # Send the calculation request
134 | assistant_id = AgentId("assistant", "default")
135 | response = await runtime.send_message(
136 | Message("What is the result of 545.34567 * 34555.34"),
137 | assistant_id
138 | )
139 |
140 | if response:
141 | print(f"Response: {response.content}")
142 |
143 | # Stop the runtime
144 | await runtime.stop_when_idle()
145 |
146 | if __name__ == "__main__":
147 | asyncio.run(main())
148 |
--------------------------------------------------------------------------------
/course/samples/hello_world/autogen_core/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Hello World Agent",
3 | "description": "Built using the AutoGen Core API",
4 | "domains": ["basics", "education"],
5 | "tags": ["beginner", "tools"],
6 | "blog": "https://newsletter.victordibia.com/p/a-friendly-introduction-to-the-autogen"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/hello_world/autogen_core/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-01-18T11:54:12.324184",
4 | "duration_seconds": 5.827655,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": "Response: The result of \\( 545.34567 \\times 34555.34 \\) is approximately 18,844,605.0444.\n",
11 | "stderr": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/course/samples/hello_world/google_adk/app.py:
--------------------------------------------------------------------------------
1 | # pip install google-adk
2 | from google.adk.agents import LlmAgent
3 | from google.adk.runners import Runner
4 | from google.adk.sessions import InMemorySessionService
5 | from google.genai import types
6 | import asyncio
7 |
8 | # Define a calculator function to be used as a tool
9 | def calculator(a: float, b: float, operator: str) -> str:
10 | """Perform basic arithmetic operations."""
11 | try:
12 | if operator == '+':
13 | return str(a + b)
14 | elif operator == '-':
15 | return str(a - b)
16 | elif operator == '*':
17 | return str(a * b)
18 | elif operator == '/':
19 | if b == 0:
20 | return 'Error: Division by zero'
21 | return str(a / b)
22 | else:
23 | return 'Error: Invalid operator. Please use +, -, *, or /'
24 | except Exception as e:
25 | return f'Error: {str(e)}'
26 |
27 | async def main():
28 | # App constants
29 | APP_NAME = "calculator_app"
30 | USER_ID = "test_user"
31 | SESSION_ID = "test_session"
32 |
33 | # Create a session service
34 | session_service = InMemorySessionService()
35 | session_service.create_session(
36 | app_name=APP_NAME,
37 | user_id=USER_ID,
38 | session_id=SESSION_ID
39 | )
40 |
41 | # Create an agent with the calculator tool
42 | calculator_agent = LlmAgent(
43 | model="gemini-2.0-flash-exp", # Using Google's Gemini model
44 | name="calculator_agent",
45 | description="A helpful assistant that can perform calculations",
46 | instruction="""You are a helpful AI assistant that can perform calculations.
47 | When calculations are needed, use the calculator tool.
48 | If you see 'TERMINATE', respond with a goodbye message.""",
49 | tools=[calculator]
50 | )
51 |
52 | # Create a runner for the agent with the session service
53 | runner = Runner(
54 | agent=calculator_agent,
55 | app_name=APP_NAME,
56 | session_service=session_service
57 | )
58 |
59 | # Create a properly formatted user message
60 | user_message = types.Content(
61 | role='user',
62 | parts=[types.Part(text="What is the result of 545.34567 * 34555.34?")]
63 | )
64 |
65 | # Run the agent with a calculation query (matching the exact query from the original example)
66 | print("Running calculation...")
67 | response = None
68 | async for event in runner.run_async(
69 | user_id=USER_ID,
70 | session_id=SESSION_ID,
71 | new_message=user_message
72 | ):
73 | if event.is_final_response() and event.content and event.content.parts:
74 | response = event.content.parts[0].text
75 |
76 | print(f"Response: {response}")
77 |
78 | if __name__ == "__main__":
79 | asyncio.run(main())
--------------------------------------------------------------------------------
/course/samples/hello_world/google_adk/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Hello World Agent",
3 | "description": "Built using the Google Agent Development Kit (ADK).",
4 | "domains": ["basics", "education"],
5 | "tags": ["beginner", "tools"],
6 | "blog": "https://newsletter.victordibia.com/p/a-friendly-introduction-to-the-autogen"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/hello_world/google_adk/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-04-09T18:17:06.006078",
4 | "duration_seconds": 16.737862,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": "Running calculation...\nResponse: The result of 545.34567 * 34555.34 is 18844605.0443778.\n\n",
11 | "stderr": "Warning: there are non-text parts in the response: ['function_call'],returning concatenated text result from text parts,check out the non text parts for full response from model.\n"
12 | }
13 | }
--------------------------------------------------------------------------------
/course/samples/hello_world/langgraph/app.py:
--------------------------------------------------------------------------------
1 | from typing import Annotated, Literal, TypedDict
2 | from langchain_core.messages import HumanMessage
3 | from langchain_openai import ChatOpenAI
4 | from langchain_core.tools import tool
5 | from langgraph.checkpoint.memory import MemorySaver
6 | from langgraph.graph import END, START, StateGraph, MessagesState
7 | from langgraph.prebuilt import ToolNode
8 |
9 | # Define the calculator tool
10 |
11 |
12 | @tool
13 | def calculator(a: float, b: float, operator: str) -> str:
14 | """Perform basic arithmetic operations."""
15 | try:
16 | if operator == '+':
17 | return str(a + b)
18 | elif operator == '-':
19 | return str(a - b)
20 | elif operator == '*':
21 | return str(a * b)
22 | elif operator == '/':
23 | if b == 0:
24 | return 'Error: Division by zero'
25 | return str(a / b)
26 | else:
27 | return 'Error: Invalid operator. Please use +, -, *, or /'
28 | except Exception as e:
29 | return f'Error: {str(e)}'
30 |
31 |
32 | # Set up tools and model
33 | tools = [calculator]
34 | tool_node = ToolNode(tools)
35 | model = ChatOpenAI(
36 | model="gpt-4o-2024-11-20",
37 | temperature=0
38 | ).bind_tools(tools)
39 |
40 | # Define the routing logic
41 |
42 |
43 | def should_continue(state: MessagesState) -> Literal["tools", END]:
44 | messages = state['messages']
45 | last_message = messages[-1]
46 |
47 | # Check for termination condition
48 | if "TERMINATE" in last_message.content:
49 | return END
50 |
51 | # If the LLM makes a tool call, route to the "tools" node
52 | if last_message.tool_calls:
53 | return "tools"
54 |
55 | # If no tool calls and no termination, continue the conversation
56 | return END
57 |
58 | # Define the model calling function
59 |
60 |
61 | def call_model(state: MessagesState):
62 | messages = state['messages']
63 | response = model.invoke(messages)
64 | return {"messages": [response]}
65 |
66 |
67 | # Create the graph
68 | workflow = StateGraph(MessagesState)
69 |
70 | # Add nodes
71 | workflow.add_node("agent", call_model)
72 | workflow.add_node("tools", tool_node)
73 |
74 | # Set up graph structure
75 | workflow.add_edge(START, "agent")
76 | workflow.add_conditional_edges(
77 | "agent",
78 | should_continue,
79 | )
80 | workflow.add_edge("tools", "agent")
81 |
82 | # Initialize memory
83 | checkpointer = MemorySaver()
84 |
85 | # Compile the graph
86 | app = workflow.compile(checkpointer=checkpointer)
87 |
88 | # Example usage
89 |
90 |
91 | def run_calculation(expression: str):
92 | final_state = app.invoke(
93 | {"messages": [HumanMessage(content=expression)]},
94 | config={"configurable": {"thread_id": 42}}
95 | )
96 | return final_state["messages"][-1].content
97 |
98 |
99 | # Test the calculator
100 | result = run_calculation("What is 545.34567 * 34555.34?")
101 | print(result)
102 |
--------------------------------------------------------------------------------
/course/samples/hello_world/langgraph/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Hello World Agent",
3 | "description": "Built using the LangGraph framework",
4 | "domains": ["basics", "education"],
5 | "tags": ["beginner", "tools"]
6 | }
7 |
--------------------------------------------------------------------------------
/course/samples/hello_world/langgraph/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-01-18T11:54:12.324075",
4 | "duration_seconds": 6.63603,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": "The result of \\( 545.34567 \\times 34555.34 \\) is approximately \\( 18,844,605.0444 \\).\n",
11 | "stderr": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/course/samples/hello_world/llama_index/app.py:
--------------------------------------------------------------------------------
1 | # pip install -U llama-index llama-index-llms-openai
2 | import asyncio
3 | from llama_index.core.agent.workflow import FunctionAgent
4 | from llama_index.llms.openai import OpenAI
5 |
6 |
7 | def calculator(a: float, b: float, operator: str) -> str:
8 | """Perform basic arithmetic operations."""
9 | try:
10 | if operator == '+':
11 | return str(a + b)
12 | elif operator == '-':
13 | return str(a - b)
14 | elif operator == '*':
15 | return str(a * b)
16 | elif operator == '/':
17 | if b == 0:
18 | return 'Error: Division by zero'
19 | return str(a / b)
20 | else:
21 | return 'Error: Invalid operator. Please use +, -, *, or /'
22 | except Exception as e:
23 | return f'Error: {str(e)}'
24 |
25 |
26 | async def main():
27 | # Create the LLM client - using OpenAI's model
28 | llm = OpenAI(model="gpt-4o")
29 |
30 | # Create an agent with the calculator tool
31 | agent = FunctionAgent(
32 | tools=[calculator],
33 | llm=llm,
34 | system_prompt="You are a helpful assistant that can perform calculations.When calculations are needed, use the calculator tool"
35 | )
36 |
37 | response = await agent.run("What is the result of 545.34567 * 34555.34?")
38 | print(f"Response: {response}")
39 |
40 |
41 | if __name__ == "__main__":
42 | asyncio.run(main())
--------------------------------------------------------------------------------
/course/samples/hello_world/llama_index/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Hello World Agent",
3 | "description": "Built using the LlamaIndex framework",
4 | "domains": ["basics", "education"],
5 | "tags": ["beginner", "tools"],
6 | "blog": "https://newsletter.victordibia.com/p/a-friendly-introduction-to-the-autogen"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/hello_world/llama_index/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-04-10T11:31:08.333290",
4 | "duration_seconds": 4.997376,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": "Response: The result of \\( 545.34567 \\times 34555.34 \\) is approximately 18,844,605.0443778.\n",
11 | "stderr": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/course/samples/hello_world/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Hello World Agent",
3 | "description": "A agent implementation that demonstrates the basic concepts of defining an agent that used a generative AI model and tools to address tasks. Users can explore how different frameworks handle basic message passing and agent communication.",
4 | "domains": ["basics", "education"],
5 | "tags": ["beginner", "tools"],
6 | "difficulty": "beginner"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/hello_world/openai_agents/app.py:
--------------------------------------------------------------------------------
1 | # pip install openai-agents
2 | from agents import Agent, Runner, function_tool, OpenAIChatCompletionsModel
3 | import asyncio
4 |
5 | from openai import AsyncOpenAI
6 |
7 | # Define a calculator function to be used as a tool
8 | @function_tool
9 | async def calculator(a: float, b: float, operator: str) -> str:
10 | try:
11 | if operator == '+':
12 | return str(a + b)
13 | elif operator == '-':
14 | return str(a - b)
15 | elif operator == '*':
16 | return str(a * b)
17 | elif operator == '/':
18 | if b == 0:
19 | return 'Error: Division by zero'
20 | return str(a / b)
21 | else:
22 | return 'Error: Invalid operator. Please use +, -, *, or /'
23 | except Exception as e:
24 | return f'Error: {str(e)}'
25 |
26 |
27 |
28 | async def main():
29 | model_client = model= OpenAIChatCompletionsModel(
30 | model="gpt-4o",
31 | openai_client=AsyncOpenAI()
32 | )
33 | assistant = Agent(
34 | name="Calculator Assistant",
35 | instructions=(
36 | "You are a helpful AI assistant that can perform calculations. "
37 | "When calculations are needed, use the calculator tool. "
38 | "If you see 'TERMINATE', respond with a goodbye message."
39 | ),
40 | tools=[calculator],
41 | model = model_client
42 | )
43 |
44 | result = await Runner.run(
45 | assistant,
46 | "What is the result of 545.34567 * 34555.34?"
47 | )
48 |
49 | print(f"Result: {result.final_output}")
50 |
51 | if __name__ == "__main__":
52 | # Ensure you have set the OPENAI_API_KEY environment variable
53 | # export OPENAI_API_KEY=sk-...
54 | asyncio.run(main())
--------------------------------------------------------------------------------
/course/samples/hello_world/openai_agents/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Hello World Agent",
3 | "description": "Built using the OpenAI Agents SDK API",
4 | "domains": ["basics", "education"],
5 | "tags": ["beginner", "tools"],
6 | "blog": "https://newsletter.victordibia.com/p/a-friendly-introduction-to-the-autogen"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/hello_world/openai_agents/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-03-11T17:54:13.441418",
4 | "duration_seconds": 4.878089,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": "Result: The result of \\( 545.34567 \\times 34555.34 \\) is approximately \\( 18,844,605.04 \\).\n",
11 | "stderr": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/course/samples/hello_world/pydanticai/app.py:
--------------------------------------------------------------------------------
1 | # pip install pydantic-ai
2 |
3 | import asyncio
4 | from pydantic_ai import Agent, RunContext
5 |
6 | # Define a calculator function to be used as a tool
7 | def calculator(a: float, b: float, operator: str) -> str:
8 | """Perform basic arithmetic operations.
9 |
10 | Args:
11 | a: First number
12 | b: Second number
13 | operator: One of '+', '-', '*', '/'
14 |
15 | Returns:
16 | The result of the calculation as a string
17 | """
18 | try:
19 | if operator == '+':
20 | return str(a + b)
21 | elif operator == '-':
22 | return str(a - b)
23 | elif operator == '*':
24 | return str(a * b)
25 | elif operator == '/':
26 | if b == 0:
27 | return 'Error: Division by zero'
28 | return str(a / b)
29 | else:
30 | return 'Error: Invalid operator. Please use +, -, *, or /'
31 | except Exception as e:
32 | return f'Error: {str(e)}'
33 |
34 | # Create the agent
35 | calculator_agent = Agent(
36 | # You can use various models: 'openai:gpt-4o', 'anthropic:claude-3-opus', etc.
37 | 'openai:gpt-4o',
38 | # System prompt to define the agent's behavior
39 | system_prompt=(
40 | "You are a helpful calculator assistant that can perform arithmetic operations. "
41 | "When a calculation is needed, use the calculator tool. "
42 | "If you see 'TERMINATE', respond with a goodbye message."
43 | )
44 | )
45 |
46 | # Register the calculator tool with the agent
47 | @calculator_agent.tool_plain
48 | def calculator_tool(a: float, b: float, operator: str) -> str:
49 | """Perform basic arithmetic operations.
50 |
51 | Args:
52 | a: First number
53 | b: Second number
54 | operator: One of '+', '-', '*', '/'
55 |
56 | Returns:
57 | The result of the calculation
58 | """
59 | return calculator(a, b, operator)
60 |
61 | async def main():
62 | # Run the agent asynchronously
63 | result = await calculator_agent.run("What is the result of 545.34567 * 34555.34?")
64 | print("\nFinal result:", result.output)
65 |
66 | if __name__ == "__main__":
67 | asyncio.run(main())
--------------------------------------------------------------------------------
/course/samples/hello_world/pydanticai/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Hello World Agent",
3 | "description": "Built using the Pydantic AI SDK",
4 | "domains": ["basics", "education"],
5 | "tags": ["beginner", "tools"],
6 | "blog": "https://newsletter.victordibia.com/p/a-friendly-introduction-to-the-autogen"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/hello_world/pydanticai/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-04-29T10:43:17.652708",
4 | "duration_seconds": 2.960278,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": "\nFinal result: The result of \\(545.34567 \\times 34555.34\\) is approximately \\(18,844,605.0443778\\).\n",
11 | "stderr": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/course/samples/hello_world/semantic_kernel/app.py:
--------------------------------------------------------------------------------
1 | # pip install semantic-kernel httpx python-dotenv
2 | import asyncio
3 | from typing import Annotated
4 | from semantic_kernel.agents import ChatCompletionAgent
5 | from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
6 | from semantic_kernel.functions import kernel_function
7 | from dotenv import load_dotenv
8 |
9 | load_dotenv()
10 |
11 | # Define a calculator plugin for Semantic Kernel
12 | class CalculatorPlugin:
13 | @kernel_function(
14 | description="Perform basic arithmetic operations."
15 | )
16 | def calculate(
17 | self,
18 | a: Annotated[float, "First number"],
19 | b: Annotated[float, "Second number"],
20 | operator: Annotated[str, "One of '+', '-', '*', '/'"],
21 | ) -> str:
22 | try:
23 | if operator == '+':
24 | return str(a + b)
25 | elif operator == '-':
26 | return str(a - b)
27 | elif operator == '*':
28 | return str(a * b)
29 | elif operator == '/':
30 | if b == 0:
31 | return 'Error: Division by zero'
32 | return str(a / b)
33 | else:
34 | return 'Error: Invalid operator. Please use +, -, *, or /'
35 | except Exception as e:
36 | return f'Error: {str(e)}'
37 |
38 | def sk_agent() -> ChatCompletionAgent:
39 | agent = ChatCompletionAgent(
40 | service=OpenAIChatCompletion(ai_model_id="gpt-4o-mini"),
41 | name="calculator_agent",
42 | instructions="You are a helpful calculator assistant that can perform arithmetic operations using the calculator tool.",
43 | plugins=[CalculatorPlugin()],
44 | )
45 | return agent
46 |
47 | async def main():
48 | agent = sk_agent()
49 | result = await agent.get_response(messages="What is the result of 545.34567 * 34555.34?")
50 | print("\nFinal result:", result)
51 |
52 | if __name__ == "__main__":
53 | asyncio.run(main())
54 |
--------------------------------------------------------------------------------
/course/samples/hello_world/semantic_kernel/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Hello World Agent",
3 | "description": "Built using the Semantic Kernel framework",
4 | "domains": ["basics", "education"],
5 | "tags": ["beginner", "tools"],
6 | "blog": "https://newsletter.victordibia.com/p/a-friendly-introduction-to-the-autogen"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/hello_world/semantic_kernel/requirements.txt:
--------------------------------------------------------------------------------
1 | semantic-kernel
2 | httpx
3 | python-dotenv
4 |
--------------------------------------------------------------------------------
/course/samples/hello_world/semantic_kernel/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-05-07T16:14:42.856143",
4 | "duration_seconds": 6.010962,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": "\nFinal result: The result of \\( 545.34567 \\times 34555.34 \\) is approximately \\( 18844605.04 \\).\n",
11 | "stderr": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/course/samples/interface_agent/autogen_agentchat/app.py:
--------------------------------------------------------------------------------
1 | # create a virtual env e.g conda create -n autogen python=3.12
2 | # pip install -U autogen-agentchat autogen-ext[openai,web-surfer]
3 | # playwright install
4 |
5 | import asyncio
6 | from autogen_agentchat.agents import AssistantAgent, UserProxyAgent
7 | from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination
8 | from autogen_agentchat.teams import SelectorGroupChat
9 | from autogen_agentchat.ui import Console
10 | from autogen_ext.models.openai import OpenAIChatCompletionClient
11 | from autogen_ext.agents.web_surfer import MultimodalWebSurfer
12 |
13 |
14 | async def main() -> None:
15 | model_client = OpenAIChatCompletionClient(model="gpt-4o-2024-11-20")
16 | termination = MaxMessageTermination(
17 | max_messages=20) | TextMentionTermination("TERMINATE")
18 | websurfer_agent = MultimodalWebSurfer(
19 | name="websurfer_agent",
20 | description="an agent that solves tasks by browsing the web",
21 | model_client=model_client,
22 | headless=False,
23 | )
24 | assistant_agent = AssistantAgent(
25 | name="assistant_agent",
26 | description="an agent that verifies and summarizes information",
27 | system_message="You are a task verification assistant who is working with a web surfer agent to solve tasks. At each point, check if the task has been completed as requested by the user. If the websurfer_agent responds and the task has not yet been completed, respond with what is left to do and then say 'keep going'. If and only when the task has been completed, summarize and present a final answer that directly addresses the user task in detail and then respond with TERMINATE."
28 | "assistant", model_client=model_client)
29 |
30 | selector_prompt = """You are the cordinator of role play game. The following roles are available:
31 | {roles}. Given a task, the websurfer_agent will be tasked to address it by browsing the web and providing information. The assistant_agent will be tasked with verifying the information provided by the websurfer_agent and summarizing the information to present a final answer to the user.
32 | If the task needs assistance from a human user (e.g., providing feedback, preferences, or the task is stalled), you should select the user_proxy role to provide the necessary information.
33 |
34 | Read the following conversation. Then select the next role from {participants} to play. Only return the role.
35 |
36 | {history}
37 |
38 | Read the above conversation. Then select the next role from {participants} to play. Only return the role.
39 | """
40 | user_proxy = UserProxyAgent(name="user_proxy", description="a human user that should be consulted only when the assistant_agent is unable to verify the information provided by the websurfer_agent")
41 | team = SelectorGroupChat(
42 | [websurfer_agent, assistant_agent, user_proxy],
43 | selector_prompt=selector_prompt,
44 | model_client=OpenAIChatCompletionClient(model="gpt-4o-mini"), termination_condition=termination)
45 |
46 | await Console(team.run_stream(task="book me a flight to sand diego for next week"))
47 |
48 | await websurfer_agent.close()
49 |
50 | asyncio.run(main())
--------------------------------------------------------------------------------
/course/samples/interface_agent/autogen_agentchat/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Browser Interface Agents",
3 | "description": "Built using the AutoGen AgentChat API, includes 3 agents - a WebSurfer agent that addresses tasks by controlling/driving a web browser, an AssistantAgent that verifies progress and a UserAgent that will request human feedback when necessary.",
4 | "blog": "https://newsletter.victordibia.com/p/how-to-build-your-own-openai-operator",
5 | "domains": ["browser", "interfaceagent"],
6 | "tags": ["tools"]
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/interface_agent/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Browser Interface Agents",
3 | "description": "A multi-agent system that addresses tasks by controlling/driving a web browser, similar to the OpenAI Operator agent. The AutoGen implementation includes 3 agents - a WebSurfer agent that controls the browser, an AssistantAgent that verifies progress, and a UserAgent that will request human feedback when necessary.",
4 | "domains": ["media"],
5 | "tags": ["browser", "operator", "human-in-the-loop"],
6 | "difficulty": "intermediate"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/interface_agent/requirements.txt:
--------------------------------------------------------------------------------
1 | fpdf
2 | google-genai
3 | openai
4 | autogen-agentchat
5 | autogen-ext[openai]
--------------------------------------------------------------------------------
/course/samples/investment_agent/autogen_agentchat/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Investment Advisory Agents",
3 | "description": "An implementation of a multi-agent system that creates personalized investment portfolios through a coordinated research and analysis process. Implemented using a Client Profiler Agent that analyzes client requirements, a Research Strategy Agent that crafts targeted search queries, a Research Agent that performs market research and data analysis, a Portfolio Constructor that builds recommendations, a Verifier agent that ensures compliance and suitability, and a Summary agent that generates a complete investment proposal. The system uses a SelectorGroupChat team that manages the workflow between agents to progressively build comprehensive investment recommendations.",
4 | "domains": ["finance"],
5 | "tags": ["tools", "groupchat-pattern"]
6 | }
7 |
--------------------------------------------------------------------------------
/course/samples/investment_agent/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Investment Advisory Agents",
3 | "description": "An implementation of a multi-agent system that creates personalized investment portfolios through a coordinated research and analysis process. Implemented using a Client Profiler Agent that analyzes client requirements, a Research Strategy Agent that crafts targeted search queries, a Research Agent that performs market research and data analysis, a Portfolio Constructor that builds recommendations, a Verifier agent that ensures compliance and suitability, and a Summary agent that generates a complete investment proposal. The system uses a SelectorGroupChat team that manages the workflow between agents to progressively build comprehensive investment recommendations.",
4 | "domains": ["finance"],
5 | "tags": ["tools", "groupchat-pattern"]
6 | }
7 |
--------------------------------------------------------------------------------
/course/samples/investment_agent/requirements.txt:
--------------------------------------------------------------------------------
1 | fpdf
2 | google-genai
3 | openai
4 | autogen-agentchat
5 | autogen-ext[openai]
--------------------------------------------------------------------------------
/course/samples/mcp_tools/autogen_agentchat/app.py:
--------------------------------------------------------------------------------
1 | # pip install -U autogen-ext[mcp] json-schema-to-pydantic>=0.2.2
2 | # uv tool install mcp-server-fetch
3 | # verify it in path by running uv tool update-shell
4 | import asyncio
5 | from autogen_ext.models.openai import OpenAIChatCompletionClient
6 | from autogen_ext.tools.mcp import StdioServerParams, mcp_server_tools
7 | from autogen_agentchat.agents import AssistantAgent
8 | from autogen_agentchat.teams import RoundRobinGroupChat
9 | from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination
10 | from autogen_core import CancellationToken
11 | from autogen_agentchat.ui import Console
12 |
13 | async def main() -> None:
14 | # Setup server params for local filesystem access
15 | fetch_mcp_server = StdioServerParams(command="uvx", args=["mcp-server-fetch"])
16 | tools = await mcp_server_tools(fetch_mcp_server)
17 |
18 | # Create an agent that can use the fetch tool.
19 | model_client = OpenAIChatCompletionClient(model="gpt-4o")
20 | agent = AssistantAgent(name="fetcher", model_client=model_client, tools=tools, reflect_on_tool_use=True) # type: ignore
21 |
22 | termination = MaxMessageTermination(
23 | max_messages=5) | TextMentionTermination("TERMINATE")
24 |
25 | team = RoundRobinGroupChat([agent], termination_condition=termination)
26 | # team.dump_component().model_dump()
27 |
28 | await Console(team.run_stream(task="Summarize the content of https://newsletter.victordibia.com/p/you-have-ai-fatigue-thats-why-you", cancellation_token=CancellationToken()))
29 |
30 | if __name__ == "__main__":
31 | asyncio.run(main())
32 |
--------------------------------------------------------------------------------
/course/samples/mcp_tools/autogen_agentchat/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "MCP Tool Agent",
3 | "description": "An implementation demonstrating how to integrate Model Context Protocol (MCP) servers with AutoGen agents. This example shows how to connect an agent to an MCP server for fetching web resources, enabling content summarization and analysis. You can extend it to any MCP server.",
4 | "domains": ["research"],
5 | "tags": ["mcp", "tools"],
6 | "blog": "https://newsletter.victordibia.com/p/you-have-ai-fatigue-thats-why-you"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/mcp_tools/autogen_agentchat/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-03-08T16:24:58.639880",
4 | "duration_seconds": 10.639458,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": "---------- user ----------\nSummarize the content of https://newsletter.victordibia.com/p/you-have-ai-fatigue-thats-why-you\n---------- fetcher ----------\n[FunctionCall(id='call_M33OXagTKOz8cZoVqnPEq38V', arguments='{\"url\":\"https://newsletter.victordibia.com/p/you-have-ai-fatigue-thats-why-you\"}', name='fetch')]\n---------- fetcher ----------\n[FunctionExecutionResult(content='[TextContent(type=\\'text\\', text=\\'Contents of https://newsletter.victordibia.com/p/you-have-ai-fatigue-thats-why-you:\\\\n[](https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faa0f402f-7438-4620-9708-2fe9e220bce6_1901x1163.png)\\\\n\\\\nAbout 2 months ago, I had arrived at the office at about 8:45am, sat at my desk and was about to start my day. The only problem was that I, for the life of me, could not remember my password. You see, this is a bit laughable because for the past year or so, I had typed in that exact password, sometimes from muscle memory. I had done it at the beginning of *that* week, all the days before, and even late the previous night.\\\\n\\\\nBut in that moment I couldn\\\\\\'t even remember the first letter. After about an hour, taking a break, missing a morning meeting, getting locked out (a Mac will do that after several failed attempts), I *did* get back in.\\\\n\\\\nThis wasn\\\\\\'t just about forgetting a password. It was a wake-up call \u2013 a small but telling sign of cognitive overload. As I sat there that morning, staring at my login screen, I realized this memory lapse was symptomatic of something larger: the mental toll of trying to keep pace with the relentless advancement of AI. You see, that period, like most of the year, had been marked by lots of effort to stay current \u2013 tracking breakthrough papers, implementing new models, adapting to paradigm shifts, all while maintaining regular work responsibilities. And I wasn\\\\\\'t alone. In conversations with colleagues across the AI research and engineering space, a pattern emerged. While we all shared genuine excitement about being in the field during this revolutionary time, there was also an undercurrent of exhaustion \u2013 a particular kind of fatigue that comes from trying to match the unprecedented pace of AI advancement.\\\\n\\\\nThis is what I\\\\\\'ve come to think of as **\"AI Fatigue.\"**\\\\n\\\\n> Note: This post is more of a personal reflection, than other more focused AI/Agents posts. It\u2019s inspired by some recent events. Bear with me. \\\\n> *Podcast Audio is generated using NotebookLM[1](https://newsletter.victordibia.com/p/you-have-ai-fatigue-thats-why-you#footnote-1-154157793)*\\\\n\\\\n[](https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F404dda91-93db-4040-ab0c-864d2fb193e3_1901x1163.png)\\\\n\\\\n> **AI fatigue** is the collective exhaustion experienced by individuals and organizations in response to the unrelenting pace of AI advancement. It reflects the mental, emotional, and operational toll of trying to adapt to an unprecedented rate of change that has sustained for a relatively long period (24+ months) with little/no signs of slowing down.\\\\n\\\\nIn some way, it\\\\\\'s an acknowledgement that the pace of AI is *gargantuan*[2](https://newsletter.victordibia.com/p/you-have-ai-fatigue-thats-why-you#footnote-2-154157793) and adapting to it has costs that we must all be clearly aware of. The duration of this rapid pace has also been gargantuan - the release of ChatGPT (~ 24 months ago, Nov 2022) marked the beginning of this period .. that sometimes feels like many, many years.\\\\n\\\\nAnd it is new territory for the humans of AI, and organizations too.\\\\n\\\\n[](https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbc89aa95-9c44-4c5b-9deb-bf53e7e23517_1901x1163.png)\\\\n\\\\nWhen I say the pace is gargantuan, what do I mean? Here are some examples:\\\\n\\\\n[](https://substackcdn.com/image/fetch/f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9e857499-9cad-4ba6-9c80-5e41a6dbdc04_1340x702.png)\\\\n\\\\n~6000 papers per month, **~288 papers each day**, submitted on arXiv for AI related categories (CV, ML, IR, Robotics, AI)\\\\n\\\\n* **The Research Firehose**. \\\\n There were **21206** submissions on [arXiv](https://arxiv.org/) in just December 2024 (the data is all [here](https://info.arxiv.org/about/reports/submission_category_by_year.html)). If we focus on AI related sub categories (Computer Vision, Machine Learning, NLP, Robotics, AI, and Information Retrieval), there were:\\\\n\\\\n\\\\n\\\\nContent truncated. Call the fetch tool with a start_index of 5000 to get more content.\\', annotations=None)]', name='fetch', call_id='call_M33OXagTKOz8cZoVqnPEq38V', is_error=False)]\n---------- fetcher ----------\nThe newsletter by Victor Dibia explores the concept of \"AI Fatigue,\" a sense of exhaustion experienced by individuals and organizations due to the rapid advancement of AI technologies. The author shares a personal anecdote of forgetting a password, viewing it as a symptom of cognitive overload from trying to keep up with AI developments. This fatigue reflects the mental, emotional, and operational toll of adapting to the evolving AI landscape, which has been advancing at an unprecedented pace for over two years. The piece highlights the overwhelming volume of AI research, marked by thousands of new papers being submitted each month, illustrating the challenges faced by professionals in the field.\n---------- fetcher ----------\nThe newsletter by Victor Dibia discusses \"AI Fatigue,\" a sense of exhaustion from trying to keep pace with the rapid advancement of AI technologies. The author shares personal experiences to illustrate cognitive overload due to the fast-evolving AI landscape, which has been particularly intense since the release of ChatGPT. This fatigue affects individuals and organizations, as adapting to the fast-changing environment imposes significant mental and operational burdens. The volume of AI research adds to the challenge, with thousands of new papers being submitted monthly, highlighting the difficulty of staying updated.\n\nTERMINATE\n",
11 | "stderr": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/course/samples/mcp_tools/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "MCP Tool Agent",
3 | "description": "An implementation demonstrating how to integrate Model Context Protocol (MCP) servers with AutoGen agents. This example shows how to connect an agent to an MCP server for fetching web resources, enabling content summarization and analysis. You can extend it to any MCP server.",
4 | "domains": ["research"],
5 | "tags": ["mcp", "tools"],
6 | "blog": "https://newsletter.victordibia.com/p/a-friendly-introduction-to-the-autogen"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/mcp_tools/requirements.txt:
--------------------------------------------------------------------------------
1 | openai
2 | autogen-agentchat
3 | autogen-ext[openai, mcp]
4 | html2text
--------------------------------------------------------------------------------
/course/samples/rag_agent/autogen_agentchat/app.py:
--------------------------------------------------------------------------------
1 | # pip install -U autogen-agentchat autogen-ext[openai]
2 | import asyncio
3 | from autogen_agentchat.agents import AssistantAgent
4 | from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination
5 | from autogen_agentchat.teams import RoundRobinGroupChat
6 | from autogen_agentchat.ui import Console
7 | from autogen_ext.models.openai import OpenAIChatCompletionClient
8 |
9 |
10 | def calculator(a: float, b: float, operator: str) -> str:
11 | try:
12 | if operator == '+':
13 | return str(a + b)
14 | elif operator == '-':
15 | return str(a - b)
16 | elif operator == '*':
17 | return str(a * b)
18 | elif operator == '/':
19 | if b == 0:
20 | return 'Error: Division by zero'
21 | return str(a / b)
22 | else:
23 | return 'Error: Invalid operator. Please use +, -, *, or /'
24 | except Exception as e:
25 | return f'Error: {str(e)}'
26 |
27 |
28 | async def main() -> None:
29 | model_client = OpenAIChatCompletionClient(model="gpt-4o-2024-11-20")
30 | termination = MaxMessageTermination(
31 | max_messages=10) | TextMentionTermination("TERMINATE")
32 | assistant = AssistantAgent(
33 | "assistant", model_client=model_client, tools=[calculator])
34 | team = RoundRobinGroupChat([assistant], termination_condition=termination)
35 | await Console(team.run_stream(task="What is the result of 545.34567 * 34555.34"))
36 |
37 | asyncio.run(main())
38 |
--------------------------------------------------------------------------------
/course/samples/rag_agent/autogen_agentchat/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "RAG Agents",
3 | "description": "Retrieval Augmented Generation (RAG) explores the use of external knowledge sources to improve the quality of generated text from a model. External knowlege may come from providing an agent with access to tools that it can intelligently call based on the task, or explicitly retrieving relevant context from a datastore (e.g., a vector database) .",
4 | "blog": "https://newsletter.victordibia.com/p/how-to-build-your-own-openai-operator",
5 | "domains": ["media"],
6 | "tags": ["rag", "tools"]
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/rag_agent/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "RAG Agents",
3 | "description": "Retrieval Augmented Generation (RAG) explores the use of external knowledge sources to improve the quality of generated text from a model. External knowlege may come from providing an agent with access to tools that it can intelligently call based on the task, or explicitly retrieving relevant context from a datastore (e.g., a vector database) .",
4 | "domains": ["media"],
5 | "tags": ["rag", "tools"],
6 | "difficulty": "intermediate"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/rag_agent/requirements.txt:
--------------------------------------------------------------------------------
1 | fpdf
2 | google-genai
3 | openai
4 | autogen-agentchat
5 | autogen-ext[openai]
--------------------------------------------------------------------------------
/course/samples/router_pattern/autogen_core/app.py:
--------------------------------------------------------------------------------
1 | # pip install -U autogen-core
2 | # Mock implementation of Sales Multi-Agent System using a Router Pattern
3 | # A Router Agent receives user messages and routes them to specialized agents - LeadQualificationAgent, QuotationAgent, ProductInfoAgent, and SalesFollowupAgent
4 |
5 | from dataclasses import dataclass
6 | from typing import List, Dict
7 | import asyncio
8 |
9 | from autogen_core import (
10 | AgentId,
11 | MessageContext,
12 | RoutedAgent,
13 | SingleThreadedAgentRuntime,
14 | message_handler,
15 | DefaultTopicId,
16 | default_subscription
17 | )
18 |
19 | # Message Types
20 | @dataclass
21 | class Message:
22 | content: str
23 |
24 | @dataclass
25 | class LeadInquiry:
26 | customer_name: str
27 | inquiry_text: str
28 | contact_info: str
29 |
30 | @dataclass
31 | class QuoteRequest:
32 | product_id: str
33 | quantity: int
34 | customer_details: Dict[str, str]
35 | special_requirements: str
36 |
37 | @dataclass
38 | class ProductQuery:
39 | product_id: str
40 | query_type: str
41 | specific_questions: List[str]
42 |
43 | @dataclass
44 | class FollowupTask:
45 | lead_id: str
46 | task_type: str
47 | priority: str
48 | details: str
49 |
50 | @dataclass
51 | class UserMessage:
52 | content: str
53 | metadata: Dict[str, str]
54 |
55 | # Router Agent
56 | @default_subscription
57 | class RouterAgent(RoutedAgent):
58 | def __init__(self) -> None:
59 | super().__init__("Sales Router Agent")
60 |
61 | @message_handler
62 | async def handle_user_message(self, message: UserMessage, ctx: MessageContext) -> Message:
63 | content = message.content.lower()
64 |
65 | if any(word in content for word in ["price", "quote", "cost", "pricing"]):
66 | quote_request = QuoteRequest(
67 | product_id="default",
68 | quantity=1,
69 | customer_details={"source": "user"},
70 | special_requirements=message.content
71 | )
72 | print(">>> [RouterAgent] Delegating to QuotationAgent for pricing request")
73 | await self.publish_message(quote_request, topic_id=DefaultTopicId())
74 |
75 | elif any(word in content for word in ["product", "specs", "features", "compare"]):
76 | product_query = ProductQuery(
77 | product_id="default",
78 | query_type="specs",
79 | specific_questions=[message.content]
80 | )
81 | print(">>> [RouterAgent] Delegating to ProductInfoAgent for product information")
82 | await self.publish_message(product_query, topic_id=DefaultTopicId())
83 |
84 | elif any(word in content for word in ["interested", "inquiry", "learn more"]):
85 | lead_inquiry = LeadInquiry(
86 | customer_name="User",
87 | inquiry_text=message.content,
88 | contact_info="user@example.com"
89 | )
90 | print(">>> [RouterAgent] Delegating to LeadQualificationAgent for new inquiry")
91 | await self.publish_message(lead_inquiry, topic_id=DefaultTopicId())
92 |
93 | else:
94 | followup_task = FollowupTask(
95 | lead_id="default",
96 | task_type="general",
97 | priority="medium",
98 | details=message.content
99 | )
100 | print(">>> [RouterAgent] Delegating to SalesFollowupAgent for follow-up task")
101 | await self.publish_message(followup_task, topic_id=DefaultTopicId())
102 |
103 | return Message(content="Processed")
104 |
105 | # Specialized Agents
106 | @default_subscription
107 | class LeadQualificationAgent(RoutedAgent):
108 | def __init__(self) -> None:
109 | super().__init__("Lead Qualification Agent")
110 |
111 | @message_handler
112 | async def handle_lead(self, message: LeadInquiry, ctx: MessageContext) -> Message:
113 | print(f">>> [LeadQualificationAgent] Qualifying lead: {message.customer_name} interested in {message.inquiry_text}")
114 | return Message(content="Lead processed")
115 |
116 | @default_subscription
117 | class QuotationAgent(RoutedAgent):
118 | def __init__(self) -> None:
119 | super().__init__("Quotation Agent")
120 |
121 | @message_handler
122 | async def handle_quote(self, message: QuoteRequest, ctx: MessageContext) -> Message:
123 | print(f">>> [QuotationAgent] Generating quote for {message.product_id}, quantity: {message.quantity}")
124 | return Message(content="Quote generated")
125 |
126 | @default_subscription
127 | class ProductInfoAgent(RoutedAgent):
128 | def __init__(self) -> None:
129 | super().__init__("Product Information Agent")
130 |
131 | @message_handler
132 | async def handle_product_query(self, message: ProductQuery, ctx: MessageContext) -> Message:
133 | print(f">>> [ProductInfoAgent] Providing {message.query_type} information for product {message.product_id}")
134 | return Message(content="Product info provided")
135 |
136 | @default_subscription
137 | class SalesFollowupAgent(RoutedAgent):
138 | def __init__(self) -> None:
139 | super().__init__("Sales Followup Agent")
140 |
141 | @message_handler
142 | async def handle_followup(self, message: FollowupTask, ctx: MessageContext) -> Message:
143 | print(f">>> [SalesFollowupAgent] Scheduling {message.task_type} task (Priority: {message.priority})")
144 | return Message(content="Followup scheduled")
145 |
146 | async def main() -> None:
147 | # Create runtime
148 | runtime = SingleThreadedAgentRuntime()
149 |
150 | # Register all agents
151 | await RouterAgent.register(
152 | runtime,
153 | "router",
154 | lambda: RouterAgent()
155 | )
156 |
157 | for agent_class, agent_type in [
158 | (LeadQualificationAgent, "lead_qualifier"),
159 | (QuotationAgent, "quotation"),
160 | (ProductInfoAgent, "product_info"),
161 | (SalesFollowupAgent, "sales_followup")
162 | ]:
163 | await agent_class.register(
164 | runtime,
165 | type=agent_type,
166 | factory=lambda a=agent_class: a()
167 | )
168 |
169 | # Start the runtime
170 | runtime.start()
171 |
172 | # Test with some example messages
173 | router_id = AgentId("router", "default")
174 |
175 | test_messages = [
176 | "I'm interested in learning more about your products",
177 | "What's the price for 100 units?",
178 | "Can you compare Model A vs Model B?",
179 | "Please schedule a follow-up meeting",
180 | ]
181 |
182 | for msg in test_messages:
183 | print(f"\n>>> User: {msg}")
184 | await runtime.send_message(
185 | UserMessage(content=msg, metadata={}),
186 | router_id
187 | )
188 | print("--------------------------------")
189 | await asyncio.sleep(1) # Give time for message processing
190 |
191 | # Stop the runtime
192 | await runtime.stop_when_idle()
193 |
194 | if __name__ == "__main__":
195 | asyncio.run(main())
--------------------------------------------------------------------------------
/course/samples/router_pattern/autogen_core/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Router Pattern",
3 | "description": "A mock implementation of a Sales Multi-Agent System using a Router Pattern. A Router Agent receives user messages and routes them to specialized agents - LeadQualificationAgent, QuotationAgent, ProductInfoAgent, and SalesFollowupAgent.",
4 | "domains": ["multiagent"],
5 | "tags": ["router-pattern", "pattern"]
6 | }
7 |
--------------------------------------------------------------------------------
/course/samples/router_pattern/autogen_core/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-01-28T15:28:00.568517",
4 | "duration_seconds": 4.583611,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": "\n>>> User: I'm interested in learning more about your products\n>>> [RouterAgent] Delegating to ProductInfoAgent for product information\n>>> [ProductInfoAgent] Providing specs information for product default\n--------------------------------\n\n>>> User: What's the price for 100 units?\n>>> [RouterAgent] Delegating to QuotationAgent for pricing request\n>>> [QuotationAgent] Generating quote for default, quantity: 1\n--------------------------------\n\n>>> User: Can you compare Model A vs Model B?\n>>> [RouterAgent] Delegating to ProductInfoAgent for product information\n>>> [ProductInfoAgent] Providing specs information for product default\n--------------------------------\n\n>>> User: Please schedule a follow-up meeting\n>>> [RouterAgent] Delegating to SalesFollowupAgent for follow-up task\n>>> [SalesFollowupAgent] Scheduling general task (Priority: medium)\n--------------------------------\n",
11 | "stderr": ""
12 | }
13 | }
--------------------------------------------------------------------------------
/course/samples/router_pattern/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Router Pattern",
3 | "description": "A mock implementation of a Sales Multi-Agent System using a Router Pattern. A Router Agent receives user messages and routes them to specialized agents - LeadQualificationAgent, QuotationAgent, ProductInfoAgent, and SalesFollowupAgent.",
4 | "domains": ["salese"],
5 | "tags": ["router-pattern", "pattern"],
6 | "difficulty": "intermediate"
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/router_pattern/requirements.txt:
--------------------------------------------------------------------------------
1 | autogen-core
--------------------------------------------------------------------------------
/course/samples/travel_planning/autogen_agentchat/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Travel Planning Agents",
3 | "description": "An implementation of a multi-agent system that creates personalized travel itineraries through a coordinated planning process. Implemented using a Planner Agent that develops comprehensive travel routes based on traveler preferences, a Local Agent that recommends authentic experiences and hidden gems, a Language Agent that provides essential communication tips for the destination, and a Travel Summary Agent that integrates all recommendations into a cohesive final itinerary. The system uses a SelectorGroupChat team that manages the workflow between agents to progressively build comprehensive travel plans while minimizing unnecessary user involvement.",
4 | "domains": ["travel", "tourism"],
5 | "tags": ["tools", "groupchat-pattern", "web-search"]
6 | }
7 |
--------------------------------------------------------------------------------
/course/samples/travel_planning/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Travel Planning Agents",
3 | "description": "An implementation of a multi-agent system that creates personalized travel itineraries through a coordinated planning process. Implemented using a Planner Agent that develops comprehensive travel routes based on traveler preferences, a Local Agent that recommends authentic experiences and hidden gems, a Language Agent that provides essential communication tips for the destination, and a Travel Summary Agent that integrates all recommendations into a cohesive final itinerary. The system uses a SelectorGroupChat team that manages the workflow between agents to progressively build comprehensive travel plans while minimizing unnecessary user involvement.",
4 | "domains": ["travel", "tourism"],
5 | "tags": ["tools", "groupchat-pattern", "web-search"]
6 | }
7 |
--------------------------------------------------------------------------------
/course/samples/travel_planning/requirements.txt:
--------------------------------------------------------------------------------
1 | autogen-agentchat
2 | autogen-ext[openai]
--------------------------------------------------------------------------------
/course/samples/voice_agent/autogen_agentchat/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | dist
3 |
4 | *.egg-info
5 |
6 | .env
7 |
8 | *.files
9 |
10 | venv
11 | .venv
12 | .DS_Store
13 |
14 | .chainlit
15 | !cypress/e2e/**/*/.chainlit/*
16 | chainlit.md
17 |
18 | cypress/screenshots
19 | cypress/videos
20 | cypress/downloads
21 |
22 | __pycache__
23 |
24 | .ipynb_checkpoints
25 |
26 | *.db
27 |
28 | .mypy_cache
29 |
30 | chat_files
31 |
32 | .chroma
33 |
34 | # Logs
35 | logs
36 | *.log
37 | npm-debug.log*
38 | yarn-debug.log*
39 | yarn-error.log*
40 | pnpm-debug.log*
41 | lerna-debug.log*
42 |
43 | node_modules
44 | dist
45 | dist-ssr
46 | *.local
47 |
48 | # Editor directories and files
49 | .vscode/*
50 | !.vscode/extensions.json
51 | .idea
52 | .DS_Store
53 | *.suo
54 | *.ntvs*
55 | *.njsproj
56 | *.sln
57 | *.sw?
58 |
59 | .aider*
60 | .coverage
61 |
62 | backend/README.md
63 | backend/.dmypy.json
--------------------------------------------------------------------------------
/course/samples/voice_agent/autogen_agentchat/README.md:
--------------------------------------------------------------------------------
1 | # Voice-Enabled Agent Assistant
2 |
3 | A Chainlit application that lets users speak to an AutoGen agent team and receive voice responses.
4 |
5 | ## Features
6 |
7 | - **Voice Input**: Capture user speech with automatic silence detection (or just type)
8 | - **Speech-to-Text**: Convert speech to text using OpenAI Whisper
9 | - **Agent Processing**: Process requests with an AutoGen agent team
10 | - **Text-to-Speech**: Convert responses back to speech using OpenAI TTS
11 | - **Voice Customization**: Select from different voices and adjust speaking style
12 |
13 | ## Installation
14 |
15 | 1. Clone this repository
16 | 2. Install the required packages:
17 | ```
18 | pip install -r requirements.txt
19 | ```
20 | 3. Copy `.env.example` to `.env` and add your API keys:
21 | ```
22 | cp .env.example .env
23 | ```
24 | 4. Edit `.env` with your OpenAI API key
25 |
26 | ## Usage
27 |
28 | 1. Start the Chainlit application:
29 | ```
30 | chainlit run app.py
31 | ```
32 | 2. Open your browser at `http://localhost:8000`
33 | 3. Press `p` to start speaking or type your request
34 | 4. Adjust voice settings using the settings panel
35 |
36 | ## Project Structure
37 |
38 | - `app.py`: Main Chainlit application
39 | - `agent_team.py`: AutoGen team configuration
40 | - `chainlit.md`: Chainlit welcome page
41 |
42 | ## Acknowledgements
43 |
44 | - [Chainlit](https://github.com/Chainlit/chainlit)
45 | - [AutoGen](https://github.com/microsoft/autogen)
46 | - [OpenAI](https://github.com/openai/openai-python)
47 |
--------------------------------------------------------------------------------
/course/samples/voice_agent/autogen_agentchat/agent_team.py:
--------------------------------------------------------------------------------
1 | """
2 | Agent team configuration for Chainlit application.
3 | This module creates and configures the agent team that processes user requests.
4 | """
5 |
6 | from autogen_agentchat.agents import AssistantAgent
7 | from autogen_agentchat.conditions import MaxMessageTermination, TextMentionTermination
8 | from autogen_agentchat.teams import SelectorGroupChat
9 | from autogen_ext.models.openai import OpenAIChatCompletionClient
10 | from autogenstudio.gallery.tools import google_search_tool, fetch_webpage_tool
11 |
12 | def create_agent_team(
13 | model_name: str = "gpt-4o-mini",
14 | ) -> SelectorGroupChat:
15 | """
16 | Create a deep research team with specialized agents for comprehensive information gathering.
17 |
18 | Args:
19 | model_name: Name of the OpenAI model to use
20 |
21 | Returns:
22 | Configured SelectorGroupChat with research-focused agents
23 | """
24 |
25 | # Create the model client
26 | model_client = OpenAIChatCompletionClient(model=model_name)
27 |
28 | # Create the Research Assistant agent
29 | research_assistant = AssistantAgent(
30 | name="research_assistant",
31 | description="A research assistant that performs web searches and analyzes information",
32 | model_client=model_client,
33 | tools=[google_search_tool, fetch_webpage_tool],
34 | system_message="""You are a research assistant focused on finding accurate information.
35 | Use the google_search tool to find relevant information. However, if you can answer the question without searching, do and end with "TERMINATE".
36 | Break down complex queries into specific search terms.
37 | Always verify information across multiple sources when possible.
38 | When you find relevant information, explain why it's relevant and how it connects to the query.
39 | When you get feedback from the verifier agent, use your tools to act on the feedback and make progress. If the request is underspecified or cannot be answered, mention the reasons for this and then respond with TERMINATE.""",
40 | model_client_stream=True
41 | )
42 |
43 | # Create the Verifier agent
44 | verifier = AssistantAgent(
45 | name="verifier",
46 | description="A verification specialist who ensures research quality and completeness",
47 | model_client=model_client,
48 | model_client_stream=True,
49 | system_message="""You are a research verification specialist.
50 | Your role is to:
51 | 1. Verify that search queries are effective and suggest improvements if needed
52 | 2. Explore drill downs where needed e.g, if the answer is likely in a link in the returned search results, suggest clicking on the link
53 | 3. Suggest additional angles or perspectives to explore. Be judicious in suggesting new paths to avoid scope creep or wasting resources, if the task appears to be addressed and we can provide a report, do this and respond with "TERMINATE".
54 | 4. Track progress toward answering the original question
55 | 5. When the research is complete, provide a detailed summary in markdown format
56 |
57 | For incomplete research, end your message with "CONTINUE RESEARCH".
58 | For complete research, end your message with "APPROVED".
59 |
60 | Your responses should be structured as:
61 | - Progress Assessment
62 | - Gaps/Issues (if any)
63 | - Suggestions (if needed)
64 | - Next Steps or Final Summary"""
65 | )
66 |
67 | summary_agent = AssistantAgent(
68 | name="summary_agent",
69 | description="A summary agent that provides a detailed markdown summary of the research as a report to the user.",
70 | model_client=model_client,
71 | system_message="""You are a summary agent. Your role is to provide a detailed markdown summary of the research as a report to the user. Your report should have a reasonable title that matches the research question and should summarize the key details in the results found in natural an actionable manner. The main results/answer should be in the first paragraph.
72 | Your report should end with the word "TERMINATE" to signal the end of the conversation.""",
73 | model_client_stream=True
74 | )
75 |
76 | # Set up termination conditions
77 | text_termination = TextMentionTermination("TERMINATE")
78 | max_messages = MaxMessageTermination(max_messages=30)
79 | termination = text_termination | max_messages
80 |
81 | # Create the selector prompt
82 | selector_prompt = """You are coordinating a research team by selecting the team member to speak/act next. The following team member roles are available:
83 | {roles}.
84 | The research_assistant performs searches and analyzes information.
85 | The verifier evaluates progress and ensures completeness.
86 | The summary_agent provides a detailed markdown summary of the research as a report to the user.
87 |
88 | Given the current context, select the most appropriate next speaker.
89 | The research_assistant should search and analyze.
90 | The verifier should evaluate progress and guide the research (select this role is there is a need to verify/evaluate progress). You should ONLY select the summary_agent role if the research is complete and it is time to generate a report.
91 |
92 | Base your selection on:
93 | 1. Current stage of research
94 | 2. Last speaker's findings or suggestions
95 | 3. Need for verification vs need for new information
96 |
97 | Read the following conversation. Then select the next role from {participants} to play. Only return the role.
98 |
99 | {history}
100 |
101 | Read the above conversation. Then select the next role from {participants} to play. ONLY RETURN THE ROLE."""
102 |
103 | # Create the team
104 | team = SelectorGroupChat(
105 | participants=[research_assistant, verifier, summary_agent],
106 | model_client=model_client,
107 | termination_condition=termination,
108 | selector_prompt=selector_prompt,
109 | allow_repeated_speaker=True
110 | )
111 |
112 | return team
--------------------------------------------------------------------------------
/course/samples/voice_agent/autogen_agentchat/chainlit-config.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/course/samples/voice_agent/autogen_agentchat/chainlit-config.txt
--------------------------------------------------------------------------------
/course/samples/voice_agent/autogen_agentchat/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Voice Agents",
3 | "description": "An implementation of a voice enabled multi-agent system where speech to text models are used to transcribe user queries and text to speech models are used to read out a summarized version of agent responses. .",
4 | "blog": "https://newsletter.victordibia.com/p/how-to-build-voice-agents-with-autogen",
5 | "domains": ["research"],
6 | "tags": ["ui", "groupchat-pattern"]
7 | }
8 |
--------------------------------------------------------------------------------
/course/samples/voice_agent/autogen_agentchat/requirements.txt:
--------------------------------------------------------------------------------
1 | chainlit>=2.4.0
2 | autogenstudio>=0.4.2
--------------------------------------------------------------------------------
/course/samples/voice_agent/autogen_agentchat/results.json:
--------------------------------------------------------------------------------
1 | {
2 | "execution": {
3 | "start_time": "2025-04-09T06:52:56.260400",
4 | "duration_seconds": 23.57266,
5 | "success": true,
6 | "timed_out": false,
7 | "error": null
8 | },
9 | "output": {
10 | "stdout": "2025-04-09 06:53:02 - Created default translation file at /Users/victordibia/projects/autogenbook/dibia/code/autogeninaction/.chainlit/translations/nl.json\nPlease run this script using Chainlit.\n",
11 | "stderr": "/Users/victordibia/projects/autogenbook/dibia/code/autogeninaction/course/samples/voice_agent/autogen_agentchat/app.py:7: DeprecationWarning: 'audioop' is deprecated and slated for removal in Python 3.13\n import audioop\n"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/course/samples/voice_agent/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "Voice Agents",
3 | "description": "An implementation of a voice enabled multi-agent system where speech to text models are used to transcribe user queries and text to speech models are used to read out a summarized version of agent responses. .",
4 | "domains": ["research"],
5 | "tags": ["ui", "groupchat-pattern"]
6 | }
7 |
--------------------------------------------------------------------------------
/course/web/chainlit/.gitignore:
--------------------------------------------------------------------------------
1 | *.chainlit
--------------------------------------------------------------------------------
/course/web/chainlit/README.md:
--------------------------------------------------------------------------------
1 | # Building a Multi-Agent Application with AutoGen and Chainlit
2 |
3 | The AutoGen framework lets you built autonomous multi-agent applications. In this example, we will build a simple chat interface that interacts with an agent team built using the AutoGen AgentChat framework.
4 |
5 | 
6 |
7 | ## High-Level Description
8 |
9 | The `app.py` script sets up a Chainlit chat interface that communicates with the AutoGen team. When a chat starts, it
10 |
11 | - Initializes an AgentChat team and displays an Avatar.
12 | - As users interact with the chat, their queries are sent to the team which responds.
13 | - As agents respond/act, their responses are streamed back to the chat interface.
14 |
15 | ## Quickstart
16 |
17 | To get started, ensure you have setup an API Key. We will be using the OpenAI API for this example.
18 |
19 | 1. Ensure you have an OPENAPI API key. Set this key in your environment variables as `OPENAI_API_KEY`.
20 |
21 | 2. Install the required Python packages by running:
22 |
23 | ```shell
24 | pip install -r requirements.txt
25 | ```
26 |
27 | 3. Run the `app.py` script to start the Chainlit server.
28 |
29 | ```shell
30 | chainlit run app.py
31 | ```
32 |
33 | 4. Interact with the Agent Team Chainlit interface.
34 |
35 | ### Function Definitions
36 |
37 | - `start_chat`: Initializes the chat session and sets up the avatar for Claude.
38 | - `run_team`: Sends the user's query to the Anthropic API and streams the response back to the chat interface.
39 | - `chat`: Receives messages from the user and passes them to the `call_claude` function.
40 |
41 | ## Next Steps (Extra Credit)
42 |
43 | In this example, we created a basic AutoGen team with a single agent, Claude. To extend this example, you can:
44 |
--------------------------------------------------------------------------------
/course/web/chainlit/app.py:
--------------------------------------------------------------------------------
1 | import os
2 | import chainlit as cl
3 |
4 | from autogen_agentchat.agents import AssistantAgent
5 | from autogen_agentchat.conditions import TextMentionTermination, MaxMessageTermination
6 | from autogen_agentchat.teams import RoundRobinGroupChat
7 | from autogen_ext.models.openai import OpenAIChatCompletionClient
8 |
9 |
10 | async def get_weather(city: str) -> str:
11 | return f"The weather in {city} is 73 degrees and Sunny."
12 |
13 | assistant_agent = AssistantAgent(
14 | name="assistant_agent",
15 | tools=[get_weather],
16 | model_client=OpenAIChatCompletionClient(
17 | model="gpt-4o-2024-08-06"))
18 |
19 |
20 | termination = TextMentionTermination("TERMINATE") | MaxMessageTermination(10)
21 | team = RoundRobinGroupChat(
22 | participants=[assistant_agent], termination_condition=termination)
23 |
24 |
25 | @cl.on_chat_start
26 | async def start_chat():
27 | cl.user_session.set(
28 | "prompt_history",
29 | "",
30 | )
31 |
32 |
33 | async def run_team(query: str):
34 |
35 | response_stream = team.run_stream(task=query)
36 | async for msg in response_stream:
37 | if hasattr(msg, "content"):
38 | msg = cl.Message(content=msg.content, author="Agent Team")
39 | await msg.send()
40 |
41 |
42 | @cl.on_message
43 | async def chat(message: cl.Message):
44 | await run_team(message.content)
45 |
--------------------------------------------------------------------------------
/course/web/chainlit/chainlit.md:
--------------------------------------------------------------------------------
1 | # Your own Claude by Anthropic 👋🔥
2 |
3 | An example app using the Anthropic API!
4 |
5 | # Welcome to Chainlit! 🚀🤖
6 |
7 | - **Documentation:** Get started with our comprehensive [Chainlit Documentation](https://docs.chainlit.io) 📚
8 | - **Discord Community:** Join our friendly [Chainlit Discord](https://discord.gg/ZThrUxbAYw) to ask questions, share your projects, and connect with other developers! 💬
9 |
--------------------------------------------------------------------------------
/course/web/chainlit/public/avatars/chainlit_autogen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/course/web/chainlit/public/avatars/chainlit_autogen.png
--------------------------------------------------------------------------------
/course/web/chainlit/requirements.txt:
--------------------------------------------------------------------------------
1 | chainlit
2 | autogen-agentchat==0.4.0.dev11
--------------------------------------------------------------------------------
/docs/images/agent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/docs/images/agent.png
--------------------------------------------------------------------------------
/docs/images/bookcover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/docs/images/bookcover.png
--------------------------------------------------------------------------------
/docs/images/multiagent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/docs/images/multiagent.png
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | # jupyter lab so you can work with the notebooks from your browser
2 | jupyterlab
3 |
4 | # libraries used within the notebook examples:
5 | autogen
6 | openai
7 | yfinance
8 | matplotlib
9 | flaml[automl]
10 | fpdf
11 |
--------------------------------------------------------------------------------
/research/components/teams/travel_planning.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "component_type": "team",
4 | "name": "travel_team",
5 | "participants": [
6 | {
7 | "component_type": "agent",
8 | "name": "planner_agent",
9 | "agent_type": "AssistantAgent",
10 | "system_message": "You are a helpful assistant that can suggest a travel plan for a user based on their request. Respond with a single sentence",
11 | "model_client": {
12 | "component_type": "model",
13 | "model": "gpt-4",
14 | "model_type": "OpenAIChatCompletionClient"
15 | }
16 | },
17 | {
18 | "component_type": "agent",
19 | "name": "local_agent",
20 | "agent_type": "AssistantAgent",
21 | "system_message": "You are a helpful assistant that can suggest authentic and interesting local activities or places to visit for a user and can utilize any context information provided. Respond with a single sentence",
22 | "model_client": {
23 | "component_type": "model",
24 | "model": "gpt-4",
25 | "model_type": "OpenAIChatCompletionClient"
26 | }
27 | },
28 | {
29 | "component_type": "agent",
30 | "name": "language_agent",
31 | "agent_type": "AssistantAgent",
32 | "system_message": "You are a helpful assistant that can review travel plans, providing feedback on important/critical tips about how best to address language or communication challenges for the given destination. If the plan already includes language tips, you can mention that the plan is satisfactory, with rationale. Respond with a single sentence",
33 | "model_client": {
34 | "component_type": "model",
35 | "model": "gpt-4",
36 | "model_type": "OpenAIChatCompletionClient"
37 | }
38 | },
39 | {
40 | "component_type": "agent",
41 | "name": "travel_summary_agent",
42 | "agent_type": "AssistantAgent",
43 | "system_message": "You are a helpful assistant that can take in all of the suggestions and advice from the other agents and provide a detailed final travel plan. You must ensure that the final plan is integrated and complete. YOUR FINAL RESPONSE MUST BE THE COMPLETE PLAN. When the plan is complete and all perspectives are integrated, you can respond with TERMINATE. Respond with a single sentence",
44 | "model_client": {
45 | "component_type": "model",
46 | "model": "gpt-4",
47 | "model_type": "OpenAIChatCompletionClient"
48 | }
49 | }
50 | ],
51 | "team_type": "RoundRobinGroupChat",
52 | "termination_condition": {
53 | "component_type": "termination",
54 | "termination_type": "TextMentionTermination",
55 | "text": "TERMINATE"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/research/components/teams/user_team.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "component_type": "team",
4 | "name": "user_team",
5 | "participants": [
6 | {
7 | "component_type": "agent",
8 | "name": "assistant_agent",
9 | "agent_type": "AssistantAgent",
10 | "system_message": "You are a helpful assistant. Solve tasks carefully.",
11 | "model_client": {
12 | "component_type": "model",
13 | "model": "gpt-4o-2024-08-06",
14 | "model_type": "OpenAIChatCompletionClient"
15 | }
16 | },
17 | {
18 | "component_type": "agent",
19 | "name": "user_agent",
20 | "agent_type": "UserProxyAgent"
21 | }
22 | ],
23 | "team_type": "RoundRobinGroupChat",
24 | "termination_condition": {
25 | "component_type": "termination",
26 | "termination_type": "CombinationTermination",
27 | "operator": "or",
28 | "conditions": [
29 | {
30 | "component_type": "termination",
31 | "termination_type": "TextMentionTermination",
32 | "text": "TERMINATE"
33 | },
34 | {
35 | "component_type": "termination",
36 | "termination_type": "MaxMessageTermination",
37 | "max_messages": 10
38 | }
39 | ]
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/research/frameworks/logos/autogen.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/research/frameworks/logos/autogen.jpeg
--------------------------------------------------------------------------------
/research/frameworks/logos/crewai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/research/frameworks/logos/crewai.png
--------------------------------------------------------------------------------
/research/frameworks/logos/googleadk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/research/frameworks/logos/googleadk.png
--------------------------------------------------------------------------------
/research/frameworks/logos/langchain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/research/frameworks/logos/langchain.png
--------------------------------------------------------------------------------
/research/frameworks/logos/llamaindex.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/research/frameworks/logos/llamaindex.jpeg
--------------------------------------------------------------------------------
/research/frameworks/logos/openai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/research/frameworks/logos/openai.png
--------------------------------------------------------------------------------
/research/frameworks/logos/pydanticai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/research/frameworks/logos/pydanticai.png
--------------------------------------------------------------------------------
/research/frameworks/metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "frameworks": [
3 | {
4 | "id": "crewai",
5 | "name": "CrewAI",
6 | "file": "data/crewai.json",
7 | "imageUrl": "logos/crewai.png"
8 | },
9 | {
10 | "id": "googleadk",
11 | "name": "Google ADK",
12 | "file": "data/googleadk.json",
13 | "imageUrl": "logos/googleadk.png"
14 | },
15 | {
16 | "id": "langgraph",
17 | "name": "LangGraph",
18 | "file": "data/langgraph.json",
19 | "imageUrl": "logos/langchain.png"
20 | },
21 | {
22 | "id": "llamaindex",
23 | "name": "LlamaIndex",
24 | "file": "data/llamaindex.json",
25 | "imageUrl": "logos/llamaindex.png"
26 | },
27 | {
28 | "id": "openai-agents",
29 | "name": "OpenAI Agents SDK",
30 | "file": "data/openai-agents.json",
31 | "imageUrl": "logos/openai.png"
32 | },
33 | {
34 | "id": "autogen",
35 | "name": "AutoGen",
36 | "file": "data/autogen.json",
37 | "imageUrl": "logos/autogen.jpeg"
38 | },
39 | {
40 | "id": "pydanticai",
41 | "name": "PydanticAI",
42 | "file": "data/pydanticai.json",
43 | "imageUrl": "logos/pydanticai.png"
44 | }
45 | ]
46 | }
47 |
--------------------------------------------------------------------------------
/research/ycdata/.gitignore:
--------------------------------------------------------------------------------
1 | yc_embeddings.json
2 | yc_data.json
3 | yc_data.json
4 |
5 | clusters.png
--------------------------------------------------------------------------------
/research/ycdata/requirements.txt:
--------------------------------------------------------------------------------
1 | # Requirements for YC clustering with auto-detection
2 | # Core data science
3 | pandas>=1.5.0
4 | numpy>=1.20.0
5 | scikit-learn>=1.0.0
6 |
7 | # Visualization
8 | matplotlib>=3.5.0
9 | seaborn>=0.11.0
10 |
11 | # Dimensionality reduction
12 | umap-learn>=0.5.0
13 |
14 | # LLM integration
15 | openai>=1.0.0
16 | pydantic>=2.0.0
17 |
18 | # Optional: For better elbow detection (if you want to use kneed library)
19 | # kneed>=0.7.0
20 |
--------------------------------------------------------------------------------
/research/ycdata/yc_agents.py:
--------------------------------------------------------------------------------
1 | import pandas as pd
2 | import json
3 | import time
4 | from openai import OpenAI
5 | from pydantic import BaseModel
6 | import os
7 | import concurrent.futures
8 | import random
9 | import datetime
10 |
11 | # 1. Pydantic model for LLM response
12 | class AgentAnalysis(BaseModel):
13 | domain: str
14 | subdomain: str
15 | is_agent: bool
16 | reason: str
17 |
18 | # 2. Load DataFrame
19 | def load_df(data_path='yc_data.json'):
20 | df = pd.read_json(data_path)
21 | return df
22 |
23 | # 3. Filter for AI-related companies
24 | def filter_mentions_ai(df):
25 | return df[df['mentions_ai'] == True].copy()
26 |
27 | # 4. Load already processed results
28 | def load_existing_results(json_path='yc_ai_agents_analysis.json'):
29 | if os.path.exists(json_path):
30 | try:
31 | with open(json_path, 'r') as f:
32 | results = json.load(f)
33 | done_ids = set(row.get('long_slug') for row in results if row.get('long_slug'))
34 | return results, done_ids
35 | except Exception:
36 | return [], set()
37 | return [], set()
38 |
39 | # 5. LLM prediction function using Pydantic and .parse
40 | def analyze_agent_usecase(desc, model="gpt-4.1-mini"):
41 | client = OpenAI()
42 | # Normalized top-level domains with descriptions
43 | allowed_domains = {
44 | "health": "Healthcare, medical services, and life sciences",
45 | "finance": "Banking, investing, insurance, and financial services",
46 | "legal": "Law, legal services, and compliance",
47 | "government": "Public sector, civic tech, and government services",
48 | "education": "Learning, training, and educational technology",
49 | "productivity": "Workflow, automation, and tools to improve efficiency",
50 | "software": "Developer tools, platforms, and infrastructure",
51 | "e_commerce": "Online retail, marketplaces, and commerce platforms",
52 | "media": "Content creation, publishing, and entertainment",
53 | "real_estate": "Property, housing, and real estate services",
54 | "transportation": "Mobility, logistics, and transportation services",
55 | "other": "Does not fit in the above categories"
56 | }
57 | allowed_domain_keys = list(allowed_domains.keys())
58 | system_message = (
59 | "You are an expert in AI company analysis. "
60 | "Given a company description, predict if it is about generative AI agents (where a gen AI model is delegated to address tasks on the user's behalf). "
61 | f"Return a JSON with: domain (choose one of: {allowed_domain_keys}), subdomain (fine-grained), is_agent (true/false), and reason (justification). "
62 | "Be concise and accurate. "
63 | "IMPORTANT: If the company is about AI or ML in general, or about human agents, but is NOT about generative AI agents with some ability to act on the user's behalf or autonomously, then is_agent should be false. Do NOT mark as agent unless it is clearly about generative AI agents. "
64 | "For reference, here are the domain descriptions: " + ", ".join([f"{k}: {v}" for k, v in allowed_domains.items()])
65 | )
66 | user_message = f"Description: {desc}\nRespond in structured JSON."
67 | try:
68 | completion = client.beta.chat.completions.parse(
69 | model=model,
70 | messages=[
71 | {"role": "system", "content": system_message},
72 | {"role": "user", "content": user_message}
73 | ],
74 | response_format=AgentAnalysis,
75 | temperature=0.1
76 | )
77 | parsed = completion.choices[0].message.parsed
78 | return parsed.model_dump() if parsed else {}
79 | except Exception as e:
80 | print(f"Error: {e}")
81 | return {"domain": "", "subdomain": "", "is_agent": False, "reason": str(e)}
82 |
83 | def process_batch(rows, model="gpt-4.1-mini", max_retries=3):
84 | batch_results = []
85 | for row in rows:
86 | desc = row.get('desc', '')
87 | retries = 0
88 | while retries < max_retries:
89 | try:
90 | analysis = analyze_agent_usecase(desc, model=model)
91 | # Save all fields from the row, plus analysis
92 | result_row = dict(row)
93 | result_row.update(analysis)
94 | batch_results.append(result_row)
95 | print(f"Analyzed: {row.get('name')} | is_agent: {analysis.get('is_agent')}")
96 | break
97 | except Exception as e:
98 | wait = 2 ** retries + random.uniform(0, 1)
99 | print(f"Error for {row.get('name')}: {e}. Backing off {wait:.1f}s.")
100 | time.sleep(wait)
101 | retries += 1
102 | else:
103 | print(f"Failed to process {row.get('name')} after {max_retries} retries.")
104 | return batch_results
105 |
106 | def ensure_json_serializable(obj):
107 | """Recursively convert datetime, pd.Timestamp, and similar objects to strings for JSON serialization."""
108 | import datetime
109 | import pandas as pd
110 | if isinstance(obj, dict):
111 | return {k: ensure_json_serializable(v) for k, v in obj.items()}
112 | elif isinstance(obj, list):
113 | return [ensure_json_serializable(v) for v in obj]
114 | elif isinstance(obj, (datetime.datetime, datetime.date)):
115 | return obj.isoformat()
116 | elif isinstance(obj, pd.Timestamp):
117 | return obj.isoformat()
118 | else:
119 | return obj
120 |
121 | # 6. Main pipeline with batching and checkpointing
122 | def main(batch_size=10, output_path='yc_agents.json', max_workers=3):
123 | df = load_df()
124 | ai_df = filter_mentions_ai(df)
125 | results, done_ids = load_existing_results(output_path)
126 | print(f"Loaded {len(done_ids)} already processed companies.")
127 | to_process = ai_df[~ai_df['long_slug'].isin(done_ids)]
128 | print(f"Need to process {len(to_process)} companies.")
129 | rows = [row for _, row in to_process.iterrows()]
130 | batches = [rows[i:i+batch_size] for i in range(0, len(rows), batch_size)]
131 | with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
132 | future_to_batch = {executor.submit(process_batch, batch): batch for batch in batches}
133 | for future in concurrent.futures.as_completed(future_to_batch):
134 | batch_results = future.result()
135 | results.extend(batch_results)
136 | # Ensure all results are JSON serializable before dumping
137 | serializable_results = ensure_json_serializable(results)
138 | # Write as a dict with last_updated and agents keys
139 | output_dict = {
140 | "last_updated": datetime.datetime.now().isoformat(),
141 | "agents": serializable_results
142 | }
143 | with open(output_path, 'w') as f:
144 | json.dump(output_dict, f, indent=2)
145 | print(f"Checkpoint: saved {len(results)} results.")
146 | print("Done! Results saved to yc_agents.json")
147 |
148 | if __name__ == "__main__":
149 | main()
150 |
--------------------------------------------------------------------------------
/src/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 | **/screenshot.png
6 | .DS_Store
7 | **/.DS_Store
8 | .release.sh
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | build/
16 | develop-eggs/
17 | dist/
18 | downloads/
19 | eggs/
20 | .eggs/
21 | lib/
22 | lib64/
23 | parts/
24 | sdist/
25 | var/
26 | wheels/
27 | share/python-wheels/
28 | *.egg-info/
29 | .installed.cfg
30 | *.egg
31 | MANIFEST
32 |
33 | # PyInstaller
34 | # Usually these files are written by a python script from a template
35 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
36 | *.manifest
37 | *.spec
38 |
39 | # Installer logs
40 | pip-log.txt
41 | pip-delete-this-directory.txt
42 |
43 | # Unit test / coverage reports
44 | htmlcov/
45 | .tox/
46 | .nox/
47 | .coverage
48 | .coverage.*
49 | .cache
50 | nosetests.xml
51 | coverage.xml
52 | *.cover
53 | *.py,cover
54 | .hypothesis/
55 | .pytest_cache/
56 | cover/
57 |
58 | # Translations
59 | *.mo
60 | *.pot
61 |
62 | # Django stuff:
63 | *.log
64 | local_settings.py
65 | db.sqlite3
66 | db.sqlite3-journal
67 |
68 | # Flask stuff:
69 | instance/
70 | .webassets-cache
71 |
72 | # Scrapy stuff:
73 | .scrapy
74 |
75 | # Sphinx documentation
76 | docs/_build/
77 |
78 | # PyBuilder
79 | .pybuilder/
80 | target/
81 |
82 | # Jupyter Notebook
83 | .ipynb_checkpoints
84 |
85 | # IPython
86 | profile_default/
87 | ipython_config.py
88 |
89 | # pyenv
90 | # For a library or package, you might want to ignore these files since the code is
91 | # intended to run in multiple environments; otherwise, check them in:
92 | # .python-version
93 |
94 | # pipenv
95 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
96 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
97 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
98 | # install all needed dependencies.
99 | #Pipfile.lock
100 |
101 | # poetry
102 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
103 | # This is especially recommended for binary packages to ensure reproducibility, and is more
104 | # commonly ignored for libraries.
105 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
106 | #poetry.lock
107 |
108 | # pdm
109 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
110 | #pdm.lock
111 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
112 | # in version control.
113 | # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
114 | .pdm.toml
115 | .pdm-python
116 | .pdm-build/
117 |
118 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
119 | __pypackages__/
120 |
121 | # Celery stuff
122 | celerybeat-schedule
123 | celerybeat.pid
124 |
125 | # SageMath parsed files
126 | *.sage.py
127 |
128 | # Environments
129 | .env
130 | .venv
131 | env/
132 | venv/
133 | ENV/
134 | env.bak/
135 | venv.bak/
136 |
137 | # Spyder project settings
138 | .spyderproject
139 | .spyproject
140 |
141 | # Rope project settings
142 | .ropeproject
143 |
144 | # mkdocs documentation
145 | /site
146 |
147 | # mypy
148 | .mypy_cache/
149 | .dmypy.json
150 | dmypy.json
151 |
152 | # Pyre type checker
153 | .pyre/
154 |
155 | # pytype static type analyzer
156 | .pytype/
157 |
158 | # Cython debug symbols
159 | cython_debug/
160 |
161 | # PyCharm
162 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
163 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
164 | # and can be added to the global gitignore or merged into this file. For a more nuclear
165 | # option (not recommended) you can uncomment the following to ignore the entire idea folder.
166 | #.idea/
--------------------------------------------------------------------------------
/src/interface/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-exclude notebooks *
2 |
3 | recursive-exclude frontend *
4 | recursive-exclude docs *
5 | recursive-exclude tests *
6 |
--------------------------------------------------------------------------------
/src/interface/README.md:
--------------------------------------------------------------------------------
1 | # Interface Agent: Addressing Tasks by Controlling Interfaces
2 |
3 |
10 |
11 |
12 |
13 | Written as part of the book "Multi-Agent Systems with AutoGen" by Victor Dibia.
14 |
15 | Interface Agent package demonstrates how to build an agent that can accomplish tasks by driving interfaces (web browser). It combines the capabilities of large language models with web browsing to accomplish complex tasks autonomously.
16 |
17 | ## Installation
18 |
19 | ```bash
20 | pip install interfaceagent
21 | ```
22 |
23 | Or install the latest version from the source code:
24 |
25 | ```bash
26 | cd interfaceagent
27 | pip install -e .
28 | ```
29 |
30 | ## Components
31 |
32 | 1. **WebBrowser**: A wrapper around Playwright for browser control
33 | 2. **WebBrowserManager**: Manages multiple browser sessions
34 | 3. **Planner**: Uses OpenAI models to plan and execute tasks
35 | 4. **Web Api**: Provides a RESTful API to interact with the agent based on FastAPI
36 |
37 | ## Usage
38 |
39 | ```python
40 |
41 | from interfaceagent import WebBrowser, Planner , OpenAIPlannerModel
42 |
43 | browser = WebBrowser(start_url="http://google.com/",headless=False)
44 | model = OpenAIPlannerModel(model="gpt-4o-mini-2024-07-18")
45 | task = "What is the website for the Manning Book - Multi-Agent Systems with AutoGen. Navigate to the book website and find the author of the book."
46 | planner = Planner(model=model, web_browser=browser, task=task)
47 | result = await planner.run(task=task)
48 |
49 | ```
50 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/__init__.py:
--------------------------------------------------------------------------------
1 | from .interface import *
2 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/cli.py:
--------------------------------------------------------------------------------
1 | import typer
2 | import uvicorn
3 | import os
4 | from typing_extensions import Annotated
5 | # from llmx import providers
6 |
7 | # from interfaceagent.web.backend.app import launch
8 |
9 | app = typer.Typer()
10 |
11 |
12 | @app.command()
13 | def start(host: str = "127.0.0.1",
14 | port: int = 8082,
15 | workers: int = 1,
16 | reload: Annotated[bool, typer.Option("--reload")] = True,
17 | docs: bool = False):
18 | """
19 | Launch the interfaceagent .Pass in parameters host, port, workers, and reload to override the default values.
20 | """
21 |
22 | os.environ["interfaceagent_API_DOCS"] = str(docs)
23 |
24 | uvicorn.run(
25 | "interfaceagent.web.app:app",
26 | host=host,
27 | port=port,
28 | workers=workers,
29 | reload=reload,
30 | )
31 |
32 |
33 | @app.command()
34 | def models():
35 | print("A list of supported providers:")
36 |
37 |
38 | def run():
39 | app()
40 |
41 |
42 | if __name__ == "__main__":
43 | app()
44 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/datamodel.py:
--------------------------------------------------------------------------------
1 | from typing import Any, Optional
2 | from pydantic import BaseModel
3 |
4 |
5 | class BrowserAction(BaseModel):
6 | action: str
7 | # e.g., 'a[href]', 'button', 'input', 'select', 'textarea', '[role="button"]', '[role="link"]', '[role="checkbox"]', '[role="menuitem"]'
8 | selector: Optional[str] = ""
9 | value: Optional[str] = None
10 |
11 |
12 | class WebResponse(BaseModel):
13 | status: bool
14 | data: Optional[Any] = None
15 |
16 |
17 | class WebRequestBrowserAction(BaseModel):
18 | action: str
19 | selector: Optional[str] = ""
20 | value: Optional[str] = None
21 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/__init__.py:
--------------------------------------------------------------------------------
1 | from .games.engines import *
2 | from .games.match import *
3 | from .agents import *
4 | from .datamodel import *
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/agents/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import AIAgent, AgentConfig
2 | from .openai import OpenAIBaseAgent
3 | from .stockfish import StockfishChessAgent
4 | from .tictactoe import BasicTicTacToeAgent # added BasicTicTacToeAgent
5 |
6 | __all__ = [
7 | "AIAgent",
8 | "AgentConfig",
9 | "OpenAIBaseAgent",
10 | "StockfishChessAgent",
11 | "BasicTicTacToeAgent" # exported agent
12 | ]
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/agents/base.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from typing import Optional, Dict, Any
3 | import time
4 | from datetime import datetime
5 | import openai
6 | from openai import OpenAI
7 |
8 | from ..datamodel import (
9 | GameState,
10 | AIMove,
11 | Player,
12 | Move
13 | )
14 |
15 | class AgentConfig:
16 | """Base configuration for AI agents"""
17 | max_retries: int = 3
18 | timeout_seconds: float = 30.0
19 | temperature: float = 0.7
20 |
21 | class AIAgent(ABC):
22 | """Base class for AI agents"""
23 |
24 | def __init__(self, name: str, config: AgentConfig = None):
25 | self.name = name
26 | self.config = config or AgentConfig()
27 | self._total_tokens = 0
28 | self._total_moves = 0
29 |
30 | @abstractmethod
31 | async def _generate_move(self, game_state: GameState, attempt: int) -> AIMove:
32 | """Generate a single move attempt"""
33 | pass
34 |
35 | @abstractmethod
36 | def _create_prompt(self, game_state: GameState) -> str:
37 | """Create the prompt for move generation"""
38 | pass
39 |
40 | async def move(self, game_state: GameState) -> Optional[AIMove]:
41 | """
42 | Main method to generate a move. Handles retries and validation.
43 | Returns None if unable to generate a valid move within max retries.
44 | """
45 | start_time = time.time()
46 |
47 | for attempt in range(self.config.max_retries):
48 | try:
49 | ai_move = await self._generate_move(game_state, attempt)
50 |
51 | # Update statistics
52 | self._total_tokens += ai_move.tokens_used
53 | self._total_moves += 1
54 |
55 | # If move is valid, return it
56 | if ai_move.is_valid:
57 | return ai_move
58 |
59 | except Exception as e:
60 | print(f"Error generating move (attempt {attempt + 1}): {str(e)}")
61 | continue
62 |
63 | return None
64 |
65 | def get_stats(self) -> Dict[str, Any]:
66 | """Get agent statistics"""
67 | return {
68 | "name": self.name,
69 | "total_tokens": self._total_tokens,
70 | "total_moves": self._total_moves,
71 | "average_tokens_per_move": self._total_tokens / max(1, self._total_moves),
72 | }
73 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/agents/gemini.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/src/interface/interfaceagent/eval/games/agents/gemini.py
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/agents/openai.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from openai import OpenAI
3 | from .base import AIAgent, AgentConfig
4 |
5 | from ..datamodel import (
6 | GameState,
7 | AIMove
8 | )
9 |
10 | class OpenAIBaseAgent(AIAgent):
11 | """Base class for OpenAI-based agents"""
12 |
13 | def __init__(
14 | self,
15 | name: str,
16 | api_key: str,
17 | model: str = "gpt-4",
18 | config: AgentConfig = None
19 | ):
20 | super().__init__(name, config)
21 | self.client = OpenAI(api_key=api_key)
22 | self.model = model
23 |
24 | def _create_system_prompt(self) -> str:
25 | """Create the system prompt for the agent"""
26 | return """You are a chess engine. Your task is to analyze the current board position
27 | and suggest the best move. You should:
28 | 1. Analyze the current position
29 | 2. Consider possible moves and their consequences
30 | 3. Select the best move
31 | 4. Explain your reasoning
32 | 5. Return the move in UCI format (e.g., 'e2e4')
33 |
34 | Only return moves that are valid in the current position."""
35 |
36 | def _create_chess_prompt(self, game_state: GameState) -> str:
37 | """Create a chess-specific prompt"""
38 | return f"""Current board position (FEN):
39 | {game_state.state_repr}
40 |
41 | Playing as: {game_state.current_player}
42 | Valid moves: {', '.join(game_state.valid_moves)}
43 |
44 | Previous moves: {game_state.metadata.get('move_history', [])}
45 |
46 | Analyze the position and provide:
47 | 1. Your chosen move in UCI format
48 | 2. A brief explanation of your reasoning
49 |
50 | Format your response as:
51 | MOVE:
52 | REASONING: """
53 |
54 | def _parse_response(self, response: str) -> tuple[str, str]:
55 | """Parse the response to extract move and reasoning"""
56 | try:
57 | # Split into lines and clean
58 | lines = [line.strip() for line in response.split('\n') if line.strip()]
59 |
60 | move = None
61 | reasoning = None
62 |
63 | for line in lines:
64 | if line.startswith('MOVE:'):
65 | move = line.replace('MOVE:', '').strip()
66 | elif line.startswith('REASONING:'):
67 | reasoning = line.replace('REASONING:', '').strip()
68 |
69 | if not move or not reasoning:
70 | raise ValueError("Missing move or reasoning in response")
71 |
72 | return move, reasoning
73 |
74 | except Exception as e:
75 | raise ValueError(f"Failed to parse response: {str(e)}")
76 |
77 | async def _generate_move(self, game_state: GameState, attempt: int) -> AIMove:
78 | """Generate a move using OpenAI API"""
79 | system_prompt = self._create_system_prompt()
80 | chess_prompt = self._create_chess_prompt(game_state)
81 |
82 | try:
83 | response = await self.client.chat.completions.create(
84 | model=self.model,
85 | messages=[
86 | {"role": "system", "content": system_prompt},
87 | {"role": "user", "content": chess_prompt}
88 | ],
89 | temperature=self.config.temperature,
90 | timeout=self.config.timeout_seconds
91 | )
92 |
93 | # Extract the response content
94 | response_text = response.choices[0].message.content
95 | move_text, reasoning = self._parse_response(response_text)
96 |
97 | # Create AIMove object
98 | ai_move = AIMove(
99 | player=game_state.current_player,
100 | move_notation=move_text,
101 | model_name=self.name,
102 | reasoning=reasoning,
103 | tokens_used=response.usage.total_tokens,
104 | retry_count=attempt,
105 | raw_response=response_text,
106 | prompt_used=chess_prompt,
107 | is_valid=move_text in game_state.valid_moves,
108 | timestamp=datetime.utcnow()
109 | )
110 |
111 | return ai_move
112 |
113 | except Exception as e:
114 | # Create an invalid move object for tracking
115 | return AIMove(
116 | player=game_state.current_player,
117 | move_notation="",
118 | model_name=self.name,
119 | reasoning=f"Error: {str(e)}",
120 | tokens_used=0,
121 | retry_count=attempt,
122 | raw_response=str(e),
123 | prompt_used=chess_prompt,
124 | is_valid=False,
125 | timestamp=datetime.utcnow()
126 | )
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/agents/stockfish.py:
--------------------------------------------------------------------------------
1 | from typing import Dict, Any
2 | import time
3 | from datetime import datetime
4 | from .base import AIAgent, AgentConfig
5 | import chess
6 | from ..datamodel import (
7 | GameState,
8 | AIMove
9 | )
10 |
11 | class StockfishChessAgent(AIAgent):
12 | """Chess engine based agent using Stockfish"""
13 |
14 | def __init__(
15 | self,
16 | name: str = "Stockfish",
17 | depth: int = 15,
18 | config: AgentConfig = None
19 | ):
20 | super().__init__(name, config)
21 | import chess.engine
22 | self.engine = chess.engine.SimpleEngine.popen_uci("stockfish")
23 | self.depth = depth
24 |
25 | def __del__(self):
26 | """Ensure engine cleanup on deletion"""
27 | if hasattr(self, 'engine'):
28 | self.engine.quit()
29 |
30 | def _create_prompt(self, game_state: GameState) -> str:
31 | """Create a simple prompt for logging purposes"""
32 | return f"Analyzing position: {game_state.state_repr}"
33 |
34 | async def _generate_move(self, game_state: GameState, attempt: int) -> AIMove:
35 | """Generate a move using Stockfish"""
36 | try:
37 | # Create a chess.Board from FEN
38 | board = chess.Board(game_state.state_repr)
39 |
40 | # Get engine analysis
41 | start_time = time.time()
42 | result = self.engine.analyse(board, chess.engine.Limit(depth=self.depth))
43 | analysis_time = time.time() - start_time
44 |
45 | # Extract best move and score
46 | best_move = result["pv"][0]
47 | score = result["score"].relative.score()
48 |
49 | # Convert to UCI format
50 | move_uci = best_move.uci()
51 |
52 | # Create reasoning string
53 | if score is not None:
54 | reasoning = f"Position evaluation: {score/100:.2f}, Analysis depth: {self.depth}"
55 | else:
56 | reasoning = f"Analysis depth: {self.depth}"
57 |
58 | # Create AIMove object
59 | ai_move = AIMove(
60 | player=game_state.current_player,
61 | move_notation=move_uci,
62 | model_name=self.name,
63 | reasoning=reasoning,
64 | tokens_used=0, # Traditional engines don't use tokens
65 | retry_count=attempt,
66 | raw_response=str(result),
67 | prompt_used=self._create_prompt(game_state),
68 | is_valid=move_uci in game_state.valid_moves,
69 | timestamp=datetime.utcnow()
70 | )
71 |
72 | return ai_move
73 |
74 | except Exception as e:
75 | # Create an invalid move object for tracking
76 | return AIMove(
77 | player=game_state.current_player,
78 | move_notation="",
79 | model_name=self.name,
80 | reasoning=f"Error: {str(e)}",
81 | tokens_used=0,
82 | retry_count=attempt,
83 | raw_response=str(e),
84 | prompt_used=self._create_prompt(game_state),
85 | is_valid=False,
86 | timestamp=datetime.utcnow()
87 | )
88 |
89 | def get_stats(self) -> Dict[str, Any]:
90 | """Get agent statistics"""
91 | stats = super().get_stats()
92 | stats.update({
93 | "engine_type": "Stockfish",
94 | "analysis_depth": self.depth
95 | })
96 | return stats
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/agents/tictactoe.py:
--------------------------------------------------------------------------------
1 | import random
2 | from datetime import datetime
3 | from ..datamodel import GameState, AIMove, Player, GeneralPlayer
4 | from .base import AIAgent, AgentConfig
5 |
6 | class BasicTicTacToeAgent(AIAgent):
7 | """Basic agent for TicTacToe that picks a random valid move."""
8 |
9 | def __init__(self, name: str = "BasicTicTacToe", config: AgentConfig = None):
10 | super().__init__(name, config)
11 |
12 | def _create_prompt(self, game_state: GameState) -> str:
13 | # Minimal prompt for logging or debugging
14 | return f"TicTacToe state: {game_state.state_repr}"
15 |
16 | def _map_player(self, gp: GeneralPlayer) -> Player:
17 | # Map generic players to Player (assume PLAYER_ONE -> white, PLAYER_TWO -> black)
18 | return Player.WHITE if gp == GeneralPlayer.PLAYER_ONE else Player.BLACK
19 |
20 | async def _generate_move(self, game_state: GameState, attempt: int) -> AIMove:
21 | valid_moves = game_state.valid_moves
22 | if not valid_moves:
23 | move_choice = ""
24 | is_valid = False
25 | else:
26 | move_choice = random.choice(valid_moves)
27 | is_valid = True
28 |
29 | # Map current generic player to a game-specific Player enum
30 | mapped_player = self._map_player(game_state.current_player)
31 |
32 | return AIMove(
33 | player=mapped_player,
34 | move_notation=move_choice,
35 | model_name=self.name,
36 | reasoning="Random move selection",
37 | tokens_used=0,
38 | retry_count=attempt,
39 | raw_response="",
40 | prompt_used=self._create_prompt(game_state),
41 | is_valid=is_valid,
42 | timestamp=datetime.utcnow()
43 | )
44 |
45 | # ...existing code if needed...
46 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/datamodel.py:
--------------------------------------------------------------------------------
1 | from pydantic import BaseModel, Field
2 | from typing import List, Optional, Dict, Any, Tuple
3 | from datetime import datetime
4 | from enum import Enum
5 |
6 | class Player(str, Enum):
7 | WHITE = "white"
8 | BLACK = "black"
9 |
10 | class GameResult(str, Enum):
11 | WHITE_WIN = "white_win"
12 | BLACK_WIN = "black_win"
13 | DRAW = "draw"
14 | IN_PROGRESS = "in_progress"
15 |
16 | class GeneralPlayer(str, Enum):
17 | PLAYER_ONE = "player_one"
18 | PLAYER_TWO = "player_two"
19 |
20 | class ChessMetadata(BaseModel):
21 | check: bool = Field(description="Whether the current player is in check")
22 | checkmate: bool = Field(description="Whether the game ended in checkmate")
23 | stalemate: bool = Field(description="Whether the game ended in stalemate")
24 | insufficient_material: bool = Field(description="Whether there is insufficient material to win")
25 | can_claim_draw: bool = Field(description="Whether a draw can be claimed")
26 | halfmove_clock: int = Field(description="Number of halfmoves since last pawn advance or capture")
27 | fullmove_number: int = Field(description="The number of the full move")
28 | move_history: List[str] = Field(default_factory=list, description="List of moves in UCI format")
29 |
30 | class GameState(BaseModel):
31 | """Represents the current state of any game"""
32 | current_player: GeneralPlayer # changed from Player to GeneralPlayer
33 | is_terminal: bool = Field(description="Whether the game has ended")
34 | winner: Optional[GeneralPlayer] = Field(None, description="Winner of the game if terminated")
35 | valid_moves: List[str] = Field(description="List of valid moves in the current state")
36 | state_repr: str = Field(description="String representation of the game state (e.g., FEN in chess)")
37 | metadata: Dict[str, Any] = Field(description="Game-specific metadata")
38 |
39 | class Move(BaseModel):
40 | """Represents a single move in the game"""
41 | player: Player
42 | move_notation: str = Field(description="Move in game-specific notation (e.g., UCI in chess)")
43 | timestamp: datetime = Field(default_factory=datetime.utcnow)
44 | is_valid: bool = Field(description="Whether the move was valid")
45 |
46 | class AIMove(Move):
47 | """Represents a move made by an AI agent"""
48 | model_name: str = Field(description="Name of the AI model")
49 | reasoning: str = Field(description="AI's reasoning for the move")
50 | tokens_used: int = Field(description="Number of tokens used for this move")
51 | retry_count: int = Field(0, description="Number of retries needed to generate valid move")
52 | raw_response: str = Field(description="Raw response from the AI model")
53 | prompt_used: str = Field(description="Prompt used to generate the move")
54 |
55 | class GameTurn(BaseModel):
56 | """Represents a complete turn in the game"""
57 | turn_number: int
58 | game_state_before: GameState
59 | move: AIMove
60 | game_state_after: GameState
61 | duration_ms: int = Field(description="Time taken for this turn in milliseconds")
62 |
63 | class Game(BaseModel):
64 | """Represents a complete game"""
65 | game_id: str = Field(description="Unique identifier for the game")
66 | start_time: datetime = Field(default_factory=datetime.utcnow)
67 | end_time: Optional[datetime] = None
68 | player_white: str = Field(description="Name/ID of white player")
69 | player_black: str = Field(description="Name/ID of black player")
70 | initial_state: GameState
71 | turns: List[GameTurn] = Field(default_factory=list)
72 | final_state: Optional[GameState] = None
73 | result: GameResult = Field(default=GameResult.IN_PROGRESS)
74 | metadata: Dict[str, Any] = Field(default_factory=dict)
75 |
76 | class TournamentResult(BaseModel):
77 | """Represents results from a tournament between AI models"""
78 | tournament_id: str = Field(description="Unique identifier for the tournament")
79 | start_time: datetime
80 | end_time: datetime
81 | participants: List[str] = Field(description="List of AI model names")
82 | games: List[Game]
83 | statistics: Dict[str, Any] = Field(description="Tournament statistics")
84 |
85 | @property
86 | def total_games(self) -> int:
87 | return len(self.games)
88 |
89 | @property
90 | def winner(self) -> Optional[str]:
91 | # Logic to determine tournament winner based on games
92 | pass
93 |
94 | class ModelPerformance(BaseModel):
95 | """Statistics for a single AI model's performance"""
96 | model_name: str
97 | games_played: int
98 | games_won: int
99 | games_lost: int
100 | games_drawn: int
101 | average_moves_per_game: float
102 | average_tokens_per_move: float
103 | average_retries_per_move: float
104 | total_tokens_used: int
105 | average_time_per_move_ms: float
106 | win_rate: float = Field(description="Win rate as a percentage")
107 |
108 | @property
109 | def win_rate_percentage(self) -> float:
110 | if self.games_played == 0:
111 | return 0.0
112 | return (self.games_won / self.games_played) * 100
113 |
114 |
115 |
116 | class GameType(str, Enum):
117 | CHESS = "chess"
118 | TIC_TAC_TOE = "tic_tac_toe"
119 | BACKGAMMON = "backgammon"
120 |
121 | class ChessMetadata(BaseModel):
122 | check: bool
123 | checkmate: bool
124 |
125 | class TicTacToeMetadata(BaseModel):
126 | last_mark_position: Optional[Tuple[int, int]] = None
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/games/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/games/engines/__init__.py:
--------------------------------------------------------------------------------
1 | from .base import GameEngine
2 | from .chess import ChessGameEngine
3 | from .tictactoe import TicTacToeEngine
4 |
5 | __all__ = [
6 | "GameEngine",
7 | "ChessGameEngine",
8 | "TicTacToeEngine"
9 | ]
10 |
11 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/games/engines/base.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from typing import List, Optional
3 |
4 | from ...datamodel import (
5 | GameState,
6 | Player,
7 | Game,
8 | Move
9 | )
10 |
11 | class GameEngine(ABC):
12 | """Abstract base class for game engines"""
13 |
14 | @abstractmethod
15 | def get_state(self) -> GameState:
16 | """Returns current game state"""
17 | pass
18 |
19 | @abstractmethod
20 | def is_valid_move(self, move: str) -> bool:
21 | """Checks if a move is valid in current state"""
22 | pass
23 |
24 | @abstractmethod
25 | def make_move(self, move: Move) -> bool:
26 | """Attempts to make a move. Returns True if successful"""
27 | pass
28 |
29 | @abstractmethod
30 | def is_game_over(self) -> bool:
31 | """Checks if the game has ended"""
32 | pass
33 |
34 | @abstractmethod
35 | def get_winner(self) -> Optional[Player]:
36 | """Returns winner if game is over, None otherwise"""
37 | pass
38 |
39 | @abstractmethod
40 | def get_valid_moves(self) -> List[str]:
41 | """Returns list of valid moves in current state"""
42 | pass
43 |
44 | @abstractmethod
45 | def reset(self) -> None:
46 | """Resets the game to initial state"""
47 | pass
48 |
49 | @abstractmethod
50 | def create_game(self, player_white: str, player_black: str) -> Game:
51 | """Creates a new game instance"""
52 | pass
53 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/games/engines/chess.py:
--------------------------------------------------------------------------------
1 | from typing import List, Optional, Dict, Any
2 | import chess
3 | import uuid
4 | from datetime import datetime
5 | from .base import GameEngine
6 | from ...datamodel import (
7 | GameState,
8 | GeneralPlayer, # use generic players
9 | Player,
10 | ChessMetadata,
11 | Game,
12 | GameResult,
13 | Move
14 | )
15 |
16 |
17 | class ChessGameEngine(GameEngine):
18 | """Chess implementation using python-chess with generic players"""
19 |
20 | def __init__(self, player_one_is_white: bool = True):
21 | self.board = chess.Board()
22 | self.game: Optional[Game] = None
23 | self.player_map = { # map generic players to chess colors based on configuration
24 | GeneralPlayer.PLAYER_ONE: (Player.WHITE if player_one_is_white else Player.BLACK),
25 | GeneralPlayer.PLAYER_TWO: (Player.BLACK if player_one_is_white else Player.WHITE)
26 | }
27 | self.current_player = GeneralPlayer.PLAYER_ONE
28 | self.reset()
29 |
30 | def get_state(self) -> GameState:
31 | return GameState(
32 | current_player=self.current_player,
33 | is_terminal=self.board.is_game_over(),
34 | winner=self.get_winner(),
35 | valid_moves=self.get_valid_moves(),
36 | state_repr=self.board.fen(),
37 | metadata=self._get_metadata()
38 | )
39 |
40 | def is_valid_move(self, move: str) -> bool:
41 | try:
42 | chess_move = chess.Move.from_uci(move)
43 | return chess_move in self.board.legal_moves
44 | except ValueError:
45 | return False
46 |
47 | def make_move(self, move: Move) -> bool:
48 | if not self.is_valid_move(move.move_notation):
49 | return False
50 |
51 | chess_move = chess.Move.from_uci(move.move_notation)
52 | self.board.push(chess_move)
53 |
54 | # Switch generic current player
55 | self.current_player = (GeneralPlayer.PLAYER_TWO if self.current_player == GeneralPlayer.PLAYER_ONE else GeneralPlayer.PLAYER_ONE)
56 |
57 | # Update game metadata
58 | if self.game:
59 | metadata = self._get_metadata()
60 | metadata["move_history"].append(move.move_notation)
61 |
62 | # Update game state
63 | if self.is_game_over():
64 | self.game.end_time = datetime.utcnow()
65 | self.game.final_state = self.get_state()
66 | self.game.result = self._get_game_result()
67 |
68 | return True
69 |
70 | def is_game_over(self) -> bool:
71 | return self.board.is_game_over()
72 |
73 | def get_winner(self) -> Optional[GeneralPlayer]:
74 | winner = self._determine_winner()
75 | if winner is None:
76 | return None
77 | for general_player, player in self.player_map.items():
78 | if player == winner:
79 | return general_player
80 | return None
81 |
82 | def get_valid_moves(self) -> List[str]:
83 | return [move.uci() for move in self.board.legal_moves]
84 |
85 | def reset(self) -> None:
86 | self.board = chess.Board()
87 | self.game = None
88 |
89 | def create_game(self, player_white: str, player_black: str) -> Game:
90 | self.reset()
91 | initial_state = self.get_state()
92 |
93 | self.game = Game(
94 | game_id=str(uuid.uuid4()),
95 | start_time=datetime.utcnow(),
96 | player_white=player_white,
97 | player_black=player_black,
98 | initial_state=initial_state,
99 | result=GameResult.IN_PROGRESS,
100 | metadata={
101 | "engine": "chess",
102 | "initial_fen": self.board.fen(),
103 | "moves": []
104 | }
105 | )
106 |
107 | return self.game
108 |
109 | def _determine_winner(self) -> Optional[Player]:
110 | if not self.is_game_over():
111 | return None
112 |
113 | if self.board.is_checkmate():
114 | # If it's checkmate, the player who just moved won
115 | return Player.BLACK if self.board.turn else Player.WHITE
116 |
117 | # Draw conditions
118 | if (self.board.is_stalemate() or
119 | self.board.is_insufficient_material() or
120 | self.board.is_fifty_moves() or
121 | self.board.is_repetition()):
122 | return None
123 |
124 | return None
125 |
126 | def _get_game_result(self) -> GameResult:
127 | winner = self._determine_winner()
128 |
129 | if winner == Player.WHITE:
130 | return GameResult.WHITE_WIN
131 | elif winner == Player.BLACK:
132 | return GameResult.BLACK_WIN
133 | elif self.is_game_over():
134 | return GameResult.DRAW
135 | else:
136 | return GameResult.IN_PROGRESS
137 |
138 | def _get_metadata(self) -> Dict[str, Any]:
139 | return ChessMetadata(
140 | check=self.board.is_check(),
141 | checkmate=self.board.is_checkmate(),
142 | stalemate=self.board.is_stalemate(),
143 | insufficient_material=self.board.is_insufficient_material(),
144 | can_claim_draw=self.board.can_claim_draw(),
145 | halfmove_clock=self.board.halfmove_clock,
146 | fullmove_number=self.board.fullmove_number,
147 | move_history=[] if not self.game else self.game.metadata.get("moves", [])
148 | ).dict()
149 |
150 | def get_board_visual(self) -> str:
151 | """Returns a string representation of the current board state"""
152 | return str(self.board)
153 |
154 | def get_game_pgn(self) -> str:
155 | """Returns the game in PGN format"""
156 | return str(self.board.epd())
157 |
158 | def load_fen(self, fen: str) -> bool:
159 | """Loads a position from FEN notation"""
160 | try:
161 | self.board = chess.Board(fen)
162 | if self.game:
163 | self.game.metadata["current_fen"] = fen
164 | return True
165 | except ValueError:
166 | return False
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/games/match/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/games/match/base.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from typing import Optional, List, Tuple, Dict, Any, Generic, TypeVar
3 | import asyncio
4 | from datetime import datetime
5 | import logging
6 | from ...datamodel import (
7 | Game,
8 | GameState,
9 | GameTurn,
10 | Player,
11 | GameResult,
12 | AIMove,
13 | GameType,
14 | ChessMetadata,
15 | TicTacToeMetadata
16 | )
17 | from ..engines import GameEngine, ChessGameEngine, TicTacToeEngine
18 | from ...agents import AIAgent
19 |
20 | logging.basicConfig(level=logging.INFO)
21 | logger = logging.getLogger(__name__)
22 |
23 | # Type variables for generic typing
24 | TEngine = TypeVar('TEngine', bound=GameEngine)
25 | TMetadata = TypeVar('TMetadata')
26 |
27 | class BaseGameMatch(ABC, Generic[TEngine, TMetadata]):
28 | """Base class for game matches"""
29 |
30 | def __init__(
31 | self,
32 | player_one: AIAgent,
33 | player_two: AIAgent,
34 | game_engine: TEngine,
35 | max_moves: int = 100,
36 | move_timeout: float = 30.0
37 | ):
38 | self.player_one = player_one
39 | self.player_two = player_two
40 | self.game_engine = game_engine
41 | self.max_moves = max_moves
42 | self.move_timeout = move_timeout
43 | self.game: Optional[Game] = None
44 |
45 | @abstractmethod
46 | def get_current_agent(self, state: GameState) -> AIAgent:
47 | """Get the agent for the current turn"""
48 | pass
49 |
50 | @abstractmethod
51 | def create_forfeit_move(self, state: GameState, agent: AIAgent) -> AIMove:
52 | """Create a forfeit move for the specific game type"""
53 | pass
54 |
55 | @abstractmethod
56 | def validate_game_state(self, state: GameState) -> bool:
57 | """Validate game state for specific game rules"""
58 | pass
59 |
60 | @abstractmethod
61 | def get_game_specific_summary(self) -> Dict[str, Any]:
62 | """Get game-type specific summary data"""
63 | pass
64 |
65 | async def play(self) -> Game:
66 | """Play a complete game between the agents"""
67 | # Initialize new game
68 | self.game = self.game_engine.create_game(
69 | player_one=self.player_one.name,
70 | player_two=self.player_two.name
71 | )
72 |
73 | turn_number = 0
74 |
75 | try:
76 | while not self.game_engine.is_game_over() and turn_number < self.max_moves:
77 | turn_number += 1
78 |
79 | # Get and validate current state
80 | current_state = self.game_engine.get_state()
81 | if not self.validate_game_state(current_state):
82 | logger.error("Invalid game state detected")
83 | break
84 |
85 | # Get current agent
86 | current_agent = self.get_current_agent(current_state)
87 |
88 | # Record turn start time
89 | turn_start = datetime.utcnow()
90 |
91 | # Get agent's move with timeout
92 | try:
93 | move = await asyncio.wait_for(
94 | current_agent.move(current_state),
95 | timeout=self.move_timeout
96 | )
97 | except asyncio.TimeoutError:
98 | logger.warning(f"Agent {current_agent.name} move timed out")
99 | move = None
100 |
101 | # Calculate turn duration
102 | turn_duration = (datetime.utcnow() - turn_start).total_seconds() * 1000
103 |
104 | if not move or not move.is_valid:
105 | logger.warning(f"Agent {current_agent.name} failed to make a valid move")
106 | game_turn = GameTurn(
107 | turn_number=turn_number,
108 | game_state_before=current_state,
109 | move=self.create_forfeit_move(current_state, current_agent),
110 | game_state_after=current_state,
111 | duration_ms=int(turn_duration)
112 | )
113 | self.game.turns.append(game_turn)
114 | self.handle_forfeit(current_state.current_player)
115 | break
116 |
117 | # Make the move
118 | success = self.game_engine.make_move(move)
119 | if not success:
120 | logger.error(f"Failed to make move {move.move_notation}")
121 | continue
122 |
123 | # Record the turn
124 | game_turn = GameTurn(
125 | turn_number=turn_number,
126 | game_state_before=current_state,
127 | move=move,
128 | game_state_after=self.game_engine.get_state(),
129 | duration_ms=int(turn_duration)
130 | )
131 | self.game.turns.append(game_turn)
132 |
133 | # Finalize game
134 | self.finalize_game(turn_number)
135 | return self.game
136 |
137 | except Exception as e:
138 | logger.error(f"Error during game: {str(e)}")
139 | self.handle_error(e)
140 | return self.game
141 |
142 | def handle_forfeit(self, forfeiting_player: Player) -> None:
143 | """Handle game end due to forfeit"""
144 | self.game.result = (
145 | GameResult.PLAYER_TWO_WIN if forfeiting_player == Player.PLAYER_ONE
146 | else GameResult.PLAYER_ONE_WIN
147 | )
148 |
149 | def handle_error(self, error: Exception) -> None:
150 | """Handle game error"""
151 | self.game.end_time = datetime.utcnow()
152 | self.game.final_state = self.game_engine.get_state()
153 | self.game.result = GameResult.DRAW
154 | self.game.metadata["error"] = str(error)
155 |
156 | def finalize_game(self, turn_number: int) -> None:
157 | """Finalize the game state"""
158 | if turn_number >= self.max_moves:
159 | logger.info("Game ended due to maximum moves reached")
160 | self.game.result = GameResult.DRAW
161 |
162 | self.game.final_state = self.game_engine.get_state()
163 | self.game.end_time = datetime.utcnow()
164 |
165 | def get_game_summary(self) -> dict:
166 | """Get a summary of the game"""
167 | if not self.game:
168 | return {"status": "No game played"}
169 |
170 | summary = {
171 | "game_id": self.game.game_id,
172 | "player_one": self.game.player_one,
173 | "player_two": self.game.player_two,
174 | "result": self.game.result,
175 | "total_turns": len(self.game.turns),
176 | "duration_seconds": (self.game.end_time - self.game.start_time).total_seconds(),
177 | "moves": [turn.move.move_notation for turn in self.game.turns],
178 | "player_one_stats": self.player_one.get_stats(),
179 | "player_two_stats": self.player_two.get_stats()
180 | }
181 |
182 | # Add game-specific summary data
183 | summary.update(self.get_game_specific_summary())
184 |
185 | return summary
186 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/games/match/chess.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from typing import Optional, List, Tuple, Dict, Any, Generic, TypeVar
3 | import asyncio
4 | from datetime import datetime
5 | import logging
6 | from ...datamodel import (
7 | Game,
8 | GameState,
9 | GameTurn,
10 | Player,
11 | GameResult,
12 | AIMove,
13 | GameType,
14 | ChessMetadata,
15 | TicTacToeMetadata
16 | )
17 | from ..engines import GameEngine, ChessGameEngine, TicTacToeEngine
18 | from ...agents import AIAgent
19 | from .base import BaseGameMatch
20 |
21 | logging.basicConfig(level=logging.INFO)
22 | logger = logging.getLogger(__name__)
23 |
24 | # Type variables for generic typing
25 | TEngine = TypeVar('TEngine', bound=GameEngine)
26 | TMetadata = TypeVar('TMetadata')
27 |
28 | class ChessMatch(BaseGameMatch[ChessGameEngine, ChessMetadata]):
29 | """Chess-specific match implementation"""
30 |
31 | def get_current_agent(self, state: GameState) -> AIAgent:
32 | return self.player_one if state.current_player == Player.PLAYER_ONE else self.player_two
33 |
34 | def create_forfeit_move(self, state: GameState, agent: AIAgent) -> AIMove:
35 | return AIMove(
36 | player=state.current_player,
37 | move_notation="forfeit",
38 | model_name=agent.name,
39 | reasoning="Failed to generate valid chess move",
40 | tokens_used=0,
41 | retry_count=agent.config.max_retries,
42 | raw_response="",
43 | prompt_used="",
44 | is_valid=False,
45 | game_type=GameType.CHESS,
46 | timestamp=datetime.utcnow()
47 | )
48 |
49 | def validate_game_state(self, state: GameState) -> bool:
50 | # Chess-specific validation
51 | metadata = ChessMetadata(**state.metadata)
52 | return not (metadata.checkmate or metadata.stalemate)
53 |
54 | def get_game_specific_summary(self) -> Dict[str, Any]:
55 | if not self.game:
56 | return {}
57 |
58 | final_state = self.game.final_state
59 | metadata = ChessMetadata(**final_state.metadata)
60 |
61 | return {
62 | "checkmate": metadata.checkmate,
63 | "stalemate": metadata.stalemate,
64 | "final_position": final_state.state_repr,
65 | "total_captures": len([m for m in metadata.move_history if 'x' in m])
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/games/match/tictactoe.py:
--------------------------------------------------------------------------------
1 | from abc import ABC, abstractmethod
2 | from typing import Optional, List, Tuple, Dict, Any, Generic, TypeVar
3 | import asyncio
4 | from datetime import datetime
5 | import logging
6 | from ...datamodel import (
7 | Game,
8 | GameState,
9 | GameTurn,
10 | Player,
11 | GameResult,
12 | AIMove,
13 | GameType,
14 | ChessMetadata,
15 | TicTacToeMetadata
16 | )
17 | from ..engines import GameEngine, ChessGameEngine, TicTacToeEngine
18 | from ...agents import AIAgent
19 | from .base import BaseGameMatch
20 |
21 | logging.basicConfig(level=logging.INFO)
22 | logger = logging.getLogger(__name__)
23 |
24 | # Type variables for generic typing
25 | TEngine = TypeVar('TEngine', bound=GameEngine)
26 | TMetadata = TypeVar('TMetadata')
27 |
28 |
29 | class TicTacToeMatch(BaseGameMatch[TicTacToeEngine, TicTacToeMetadata]):
30 | """Tic-tac-toe specific match implementation"""
31 |
32 | def get_current_agent(self, state: GameState) -> AIAgent:
33 | return self.player_one if state.current_player == Player.PLAYER_ONE else self.player_two
34 |
35 | def create_forfeit_move(self, state: GameState, agent: AIAgent) -> AIMove:
36 | return AIMove(
37 | player=state.current_player,
38 | move_notation="forfeit",
39 | model_name=agent.name,
40 | reasoning="Failed to generate valid tic-tac-toe move",
41 | tokens_used=0,
42 | retry_count=agent.config.max_retries,
43 | raw_response="",
44 | prompt_used="",
45 | is_valid=False,
46 | game_type=GameType.TIC_TAC_TOE,
47 | timestamp=datetime.utcnow()
48 | )
49 |
50 | def validate_game_state(self, state: GameState) -> bool:
51 | # Tic-tac-toe specific validation
52 | metadata = TicTacToeMetadata(**state.metadata)
53 | board = state.state_repr.split(',')
54 | return len(board) == 9 # Simple validation for 3x3 board
55 |
56 | def get_game_specific_summary(self) -> Dict[str, Any]:
57 | if not self.game:
58 | return {}
59 |
60 | final_state = self.game.final_state
61 |
62 | return {
63 | "final_board": final_state.state_repr,
64 | "winning_line": self._find_winning_line(final_state.state_repr)
65 | }
66 |
67 | def _find_winning_line(self, board_str: str) -> Optional[List[int]]:
68 | # Tic-tac-toe specific helper method
69 | board = board_str.split(',')
70 | # Check winning combinations...
71 | return None
72 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/match.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/src/interface/interfaceagent/eval/games/match.py
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/match/base.py:
--------------------------------------------------------------------------------
1 | class BaseGameMatch:
2 | def __init__(self, game, player_one, player_two):
3 | self.game = game
4 | self.player_one = player_one
5 | self.player_two = player_two
6 |
7 | def get_game_summary(self) -> dict:
8 | """Get a summary of the game"""
9 | if not self.game:
10 | return {"status": "No game played"}
11 |
12 | summary = {
13 | "game_id": self.game.game_id,
14 | "player_one": self.game.player_white, # changed from player_one to player_white
15 | "player_two": self.game.player_black, # changed from player_two to player_black
16 | "result": self.game.result,
17 | "total_turns": len(self.game.turns),
18 | "duration_seconds": (self.game.end_time - self.game.start_time).total_seconds(),
19 | "moves": [turn.move.move_notation for turn in self.game.turns],
20 | "player_one_stats": self.player_one.get_stats(),
21 | "player_two_stats": self.player_two.get_stats()
22 | }
23 |
24 | # Add game-specific summary data
25 | summary.update(self.get_game_specific_summary())
26 |
27 | return summary
28 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/eval/games/run_tictactoe_match.py:
--------------------------------------------------------------------------------
1 | import asyncio
2 | # ...existing imports...
3 | from interfaceagent.eval.games.engines.tictactoe import TicTacToeEngine
4 | from interfaceagent.eval.games.agents.tictactoe import BasicTicTacToeAgent
5 |
6 | async def run_match():
7 | engine = TicTacToeEngine()
8 | game = engine.create_game("Player1", "Player2")
9 | agent = BasicTicTacToeAgent()
10 |
11 | print("Starting TicTacToe match")
12 | print(engine.get_board_visual())
13 |
14 | while not engine.is_game_over():
15 | state = engine.get_state()
16 | ai_move = await agent.move(state)
17 | if not ai_move:
18 | print("No valid move returned, ending match.")
19 | break
20 | print(f"Move chosen: {ai_move.move_notation}")
21 | engine.make_move(ai_move.move_notation)
22 | print(engine.get_board_visual())
23 |
24 | winner = engine.get_winner()
25 | if winner:
26 | print(f"Winner: {winner}")
27 | else:
28 | print("Match draw.")
29 |
30 | if __name__ == "__main__":
31 | asyncio.run(run_match())
32 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/interface/__init__.py:
--------------------------------------------------------------------------------
1 | from .browsermanager import *
2 | from .webbrowser import *
3 | from .planner import *
4 | from .model import *
5 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/interface/browsermanager.py:
--------------------------------------------------------------------------------
1 | from pydantic import HttpUrl
2 | from uuid import UUID, uuid4
3 | from typing import Dict, Optional, Any, List
4 | from .webbrowser import WebBrowser
5 | from loguru import logger
6 | import asyncio
7 |
8 | # Configure loguru
9 | logger.add("api.log", rotation="500 MB", level="INFO")
10 |
11 |
12 | class WebBrowserManager:
13 | def __init__(self):
14 | self.sessions: Dict[UUID, WebBrowser] = {}
15 | self.lock = asyncio.Lock()
16 |
17 | async def create_session(self, start_url: HttpUrl, headless: bool = True) -> UUID:
18 | """
19 | Create a new browser session.
20 |
21 | Args:
22 | start_url (HttpUrl): The initial URL for the browser session.
23 | headless (bool): Whether to run the browser in headless mode.
24 |
25 | Returns:
26 | UUID: The unique identifier for the created session.
27 |
28 | Raises:
29 | Exception: If there's an error creating the session.
30 | """
31 | session_id = uuid4()
32 | try:
33 | browser = WebBrowser(str(start_url), headless=headless)
34 | await browser.initialize()
35 | async with self.lock:
36 | self.sessions[session_id] = browser
37 | logger.info(f"Created new session with ID: {session_id}")
38 | return session_id
39 | except Exception as e:
40 | logger.error(f"Failed to create session: {str(e)}")
41 | raise
42 |
43 | async def get_session(self, session_id: UUID) -> Optional[WebBrowser]:
44 | """
45 | Retrieve a browser session by its ID.
46 |
47 | Args:
48 | session_id (UUID): The unique identifier of the session.
49 |
50 | Returns:
51 | Optional[WebBrowser]: The WebBrowser instance if found, None otherwise.
52 | """
53 | async with self.lock:
54 | session = self.sessions.get(session_id)
55 | if not session:
56 | logger.warning(f"Session not found: {session_id}")
57 | return session
58 |
59 | async def close_session(self, session_id: UUID) -> None:
60 | """
61 | Close a specific browser session.
62 |
63 | Args:
64 | session_id (UUID): The unique identifier of the session to close.
65 | """
66 | async with self.lock:
67 | if session_id in self.sessions:
68 | try:
69 | await self.sessions[session_id].close()
70 | del self.sessions[session_id]
71 | logger.info(f"Closed session: {session_id}")
72 | except Exception as e:
73 | logger.error(
74 | f"Error closing session {session_id}: {str(e)}")
75 | else:
76 | logger.warning(
77 | f"Attempted to close non-existent session: {session_id}")
78 |
79 | async def close_all_sessions(self) -> None:
80 | """Close all active browser sessions."""
81 | session_ids = list(self.sessions.keys())
82 | close_tasks = [self.close_session(session_id)
83 | for session_id in session_ids]
84 | await asyncio.gather(*close_tasks)
85 | logger.info(f"Closed all {len(session_ids)} sessions")
86 |
87 | async def list_sessions(self) -> List[Dict[str, Any]]:
88 | """
89 | List all active sessions.
90 |
91 | Returns:
92 | List[Dict[str, Any]]: A list of dictionaries containing session information.
93 | """
94 | async with self.lock:
95 | return [
96 | {
97 | "session_id": str(session_id),
98 | "start_url": browser.start_url,
99 | "current_url": await self._get_current_url(browser),
100 | "headless": browser.headless
101 | }
102 | for session_id, browser in self.sessions.items()
103 | ]
104 |
105 | async def _get_current_url(self, browser: WebBrowser) -> Optional[str]:
106 | """Helper method to safely get the current URL of a browser session."""
107 | try:
108 | return browser.page.url if browser.page else None
109 | except Exception as e:
110 | logger.error(f"Error getting current URL: {str(e)}")
111 | return None
112 |
113 | async def get_session_count(self) -> int:
114 | """
115 | Get the number of active sessions.
116 |
117 | Returns:
118 | int: The number of active sessions.
119 | """
120 | async with self.lock:
121 | return len(self.sessions)
122 |
123 | async def session_exists(self, session_id: UUID) -> bool:
124 | """
125 | Check if a session exists.
126 |
127 | Args:
128 | session_id (UUID): The unique identifier of the session.
129 |
130 | Returns:
131 | bool: True if the session exists, False otherwise.
132 | """
133 | async with self.lock:
134 | return session_id in self.sessions
135 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/interface/model.py:
--------------------------------------------------------------------------------
1 | import os
2 | from openai import OpenAI
3 | from typing import List, Dict, Any
4 |
5 |
6 | class OpenAIPlannerModel:
7 | def __init__(self, model: str = "gpt-4", temperature: float = 0.7, max_tokens: int = 500):
8 | self.client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
9 | self.model = model
10 | self.temperature = temperature
11 | self.max_tokens = max_tokens
12 |
13 | def generate(self, prompt: str) -> str:
14 | try:
15 | response = self.client.chat.completions.create(
16 | model=self.model,
17 | messages=[{"role": "user", "content": prompt}],
18 | temperature=self.temperature,
19 | max_tokens=self.max_tokens,
20 | top_p=1,
21 | frequency_penalty=0,
22 | presence_penalty=0
23 | )
24 | return response.choices[0].message.content.strip()
25 | except Exception as e:
26 | print(f"Error generating response: {e}")
27 | return ""
28 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/utils.py:
--------------------------------------------------------------------------------
1 | import json
2 | import re
3 | from typing import Any, Optional
4 |
5 | from loguru import logger
6 |
7 |
8 | def extract_code_snippet(code_string):
9 | # Extract code snippet using regex
10 | cleaned_snippet = re.search(r'```(?:\w+)?\s*([\s\S]*?)\s*```', code_string)
11 |
12 | if cleaned_snippet:
13 | cleaned_snippet = cleaned_snippet.group(1)
14 | else:
15 | cleaned_snippet = code_string
16 |
17 | # remove non-printable characters
18 | # cleaned_snippet = re.sub(r'[\x00-\x1F]+', ' ', cleaned_snippet)
19 |
20 | return cleaned_snippet
21 |
22 |
23 | def parse_json(response: str) -> Optional[Any]:
24 | """
25 | Parse a JSON string into a Python object.
26 |
27 | Args:
28 | response (str): The JSON string to parse.
29 |
30 | Returns:
31 | Optional[Any]: The parsed Python object, or None if parsing fails.
32 | """
33 | try:
34 | return json.loads(extract_code_snippet(response))
35 | except json.JSONDecodeError:
36 | logger.error(f"Error parsing JSON: {response}")
37 | return None
38 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/version.py:
--------------------------------------------------------------------------------
1 | VERSION = "0.0.1"
2 |
--------------------------------------------------------------------------------
/src/interface/interfaceagent/web/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/victordibia/multiagent-systems-with-autogen/7242cdd0953b34c149e1d8e56282765293866c07/src/interface/interfaceagent/web/__init__.py
--------------------------------------------------------------------------------
/src/interface/interfaceagent/web/app.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI, Depends, HTTPException
2 | from pydantic import AnyHttpUrl
3 | from uuid import UUID
4 | from interfaceagent.datamodel import WebRequestBrowserAction, WebResponse
5 | from interfaceagent.interface import WebBrowser
6 | from fastapi.middleware.cors import CORSMiddleware
7 | from fastapi.responses import JSONResponse
8 | from contextlib import asynccontextmanager
9 | from loguru import logger
10 |
11 | from interfaceagent.interface import WebBrowserManager
12 |
13 | # Configure loguru
14 | logger.add("api.log", rotation="500 MB", level="INFO")
15 |
16 |
17 | @asynccontextmanager
18 | async def lifespan(app: FastAPI):
19 | # Startup
20 | app.state.browser_manager = WebBrowserManager()
21 | yield
22 | # Shutdown
23 | await app.state.browser_manager.close_all_sessions()
24 |
25 | app = FastAPI(lifespan=lifespan)
26 |
27 | # Add CORS middleware
28 | app.add_middleware(
29 | CORSMiddleware,
30 | allow_origins=["*"], # Allows all origins
31 | allow_credentials=True,
32 | allow_methods=["*"], # Allows all methods
33 | allow_headers=["*"], # Allows all headers
34 | )
35 |
36 |
37 | async def get_browser_manager():
38 | return app.state.browser_manager
39 |
40 |
41 | async def validate_session(
42 | session_id: UUID,
43 | browser_manager: WebBrowserManager = Depends(get_browser_manager)
44 | ) -> WebBrowser:
45 | browser = browser_manager.get_session(session_id)
46 | if not browser:
47 | raise HTTPException(status_code=404, detail="Invalid session ID")
48 | return browser
49 |
50 |
51 | @app.post("/browser/session/create", response_model=WebResponse)
52 | async def create_session(start_url: AnyHttpUrl, browser_manager: WebBrowserManager = Depends(get_browser_manager)):
53 | try:
54 | session_id = await browser_manager.create_session(start_url)
55 | return WebResponse(status=True, data={"session_id": session_id})
56 | except Exception as e:
57 | logger.error(f"Error creating session: {str(e)}")
58 | return WebResponse(status=False, data={"error": "Failed to create session"})
59 |
60 |
61 | @app.get("/browser/sessions", response_model=WebResponse)
62 | async def list_sessions(browser_manager: WebBrowserManager = Depends(get_browser_manager)):
63 | try:
64 | sessions = browser_manager.list_sessions()
65 | return WebResponse(status=True, data={"sessions": sessions})
66 | except Exception as e:
67 | logger.error(f"Error listing sessions: {str(e)}")
68 | return WebResponse(status=False, data={"error": "Failed to list sessions"})
69 |
70 |
71 | @app.post("/browser/session/{session_id}/action", response_model=WebResponse)
72 | async def perform_action(
73 | action: WebRequestBrowserAction,
74 | browser: WebBrowser = Depends(validate_session)
75 | ):
76 | try:
77 | await browser.action(action.action, action.selector, action.value)
78 | return WebResponse(status=True, data={"message": "Action performed successfully"})
79 | except ValueError as e:
80 | logger.warning(f"Invalid action parameters: {str(e)}")
81 | return WebResponse(status=False, data={"error": str(e)})
82 | except Exception as e:
83 | logger.error(f"Error performing action: {str(e)}")
84 | return WebResponse(status=False, data={"error": "Failed to perform action"})
85 |
86 |
87 | @app.get("/browser/session/{session_id}/state", response_model=WebResponse)
88 | async def get_state(
89 | state_type: str = "text",
90 | browser: WebBrowser = Depends(validate_session)
91 | ):
92 | try:
93 | state = await browser.get_state(state_type)
94 | return WebResponse(status=True, data={"state": state})
95 | except ValueError as e:
96 | logger.warning(f"Invalid state type: {str(e)}")
97 | return WebResponse(status=False, data={"error": str(e)})
98 | except Exception as e:
99 | logger.error(f"Error getting state: {str(e)}")
100 | return WebResponse(status=False, data={"error": "Failed to get state"})
101 |
102 |
103 | @app.post("/browser/session/{session_id}/close", response_model=WebResponse)
104 | async def close_session(
105 | browser: WebBrowser = Depends(validate_session),
106 | browser_manager: WebBrowserManager = Depends(get_browser_manager)
107 | ):
108 | try:
109 | await browser_manager.close_session(browser.session_id)
110 | return WebResponse(status=True, data={"message": "Session closed successfully"})
111 | except Exception as e:
112 | logger.error(f"Error closing session: {str(e)}")
113 | return WebResponse(status=False, data={"error": "Failed to close session"})
114 |
115 |
116 | @app.exception_handler(Exception)
117 | async def global_exception_handler(request, exc):
118 | logger.exception(f"Unhandled exception: {str(exc)}")
119 | return JSONResponse(
120 | status_code=500,
121 | content=WebResponse(status=False, data={
122 | "error": "An unexpected error occurred. Please try again later."}).dict(),
123 | )
124 |
--------------------------------------------------------------------------------
/src/interface/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["setuptools", "setuptools-scm"]
3 | build-backend = "setuptools.build_meta"
4 |
5 | [project]
6 | name = "interfaceagent"
7 | authors = [
8 | { name="Victor Dibia", email="victor.dibia@gmail.com" },
9 | ]
10 | description = "Interface Agent: A light library demonstrating AI Agent Concepts"
11 | readme = "README.md"
12 | license = { file="LICENSE" }
13 | requires-python = ">=3.10"
14 | classifiers = [
15 | "Programming Language :: Python :: 3",
16 | "License :: OSI Approved :: MIT License",
17 | "Operating System :: OS Independent",
18 | ]
19 |
20 | dependencies = [
21 | "pydantic",
22 | "loguru",
23 | "uvicorn",
24 | "typer",
25 | "fastapi",
26 | "python-multipart",
27 | "playwright",
28 | "openai",
29 |
30 | ]
31 | optional-dependencies = {web = ["fastapi", "uvicorn"], memory = ["chromadb"], eval = ["chess"]}
32 |
33 | dynamic = ["version"]
34 |
35 | [tool.setuptools]
36 | include-package-data = true
37 |
38 | [tool.setuptools.dynamic]
39 | version = {attr = "interfaceagent.version.VERSION"}
40 | readme = {file = ["README.md"]}
41 |
42 | [tool.setuptools.packages.find]
43 | include = ["interfaceagent*"]
44 | exclude = ["*.tests*"]
45 | namespaces = false
46 |
47 | [tool.setuptools.package-data]
48 | "interfaceagent" = ["*.*"]
49 |
50 | [project.urls]
51 | "Homepage" = "https://github.com/yourusername/interfaceagent"
52 | "Bug Tracker" = "https://github.com/yourusername/interfaceagent/issues"
53 |
54 | [project.scripts]
55 | interfaceagent = "interfaceagent.cli:run"
--------------------------------------------------------------------------------