Compare commits

..

No commits in common. "main" and "python-v0.4.6" have entirely different histories.

703 changed files with 13429 additions and 69170 deletions

View File

@ -1,5 +1,5 @@
# Note: You can use any Debian/Ubuntu based image you want.
FROM mcr.microsoft.com/devcontainers/base:ubuntu
FROM mcr.microsoft.com/devcontainers/universal:2
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \

View File

@ -20,10 +20,7 @@
},
"ghcr.io/elanhasson/devcontainer-features/dotnet-aspire-daily:1": {},
"ghcr.io/devcontainers/features/azure-cli:1": {},
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/dotnet:2": {},
"ghcr.io/azure/azure-dev/azd:0": {},
"ghcr.io/devcontainers/features/python:1": {}
"ghcr.io/azure/azure-dev/azd:0": {}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

View File

@ -1,174 +0,0 @@
name: 🐛 Bug Report
description: Report a bug
type: "bug"
labels:
- needs-triage
body:
- type: markdown
attributes:
value: |
## Please Read the following before submitting an issue.
### Have you read the docs?
- [Python AgentChat User Guide and Tutorial](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/index.html)
- [Python Core API User Guide](https://microsoft.github.io/autogen/stable/user-guide/core-user-guide/index.html)
- [Python API Doc](https://microsoft.github.io/autogen/stable/reference/index.html)
- [.NET Doc](https://microsoft.github.io/autogen/dotnet/)
### Have you searched for related issues?
- Some other users might have the same issue as yours.
### Are you familiar with GitHub Markdown Syntax?
Please use [GitHub Markdown](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax)
syntax to format your input.
Pay attention to code blocks. Use "```" blocks for code and output.
For examples:
````
```python
# your Python code here.
```
````
````
```bash
# your bash shell command here.
```
````
If your output contains "```", use "````" (four "`") to escape them.
You can use the "Preview" switcher to check your formatted output.
- type: textarea
attributes:
label: What happened?
description: Please provide as much information as possible, this helps us address the issue. Use Markdown to format your text.
value: |
**Describe the bug**
A clear and concise description of what the bug is.
If it is a question or suggestion, please use [Discussions](https://github.com/microsoft/autogen/discussions)
instead.
**To Reproduce**
Steps to reproduce the behavior. Please include code and outputs such as stacktrace.
- If your input is just "I tried X, and it didn't work" or
"X is not working", your issue will be ignored.
- If your input is not well formatted, it will hurt readability and
may be ignored as well.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
validations:
required: true
- type: dropdown
attributes:
label: Which packages was the bug in?
multiple: true
options:
- Python Core (autogen-core)
- Python AgentChat (autogen-agentchat>=0.4.0)
- Python Extensions (autogen-ext)
- .NET Core (Microsoft.AutoGen.Core)
- AutoGen Studio (autogensudio)
- AutoGen Bench (agbench)
- Magentic One CLI (magentic-one-cli)
- V0.2 (autogen-agetnchat==0.2.*)
validations:
required: true
- type: dropdown
attributes:
label: AutoGen library version.
description: What is the version of the library was used.
multiple: false
options:
- "Python dev (main branch)"
- "Python 0.5.5"
- "Python 0.5.4"
- "Python 0.5.3"
- "Python 0.5.2"
- "Python 0.5.1"
- "Python 0.4.9"
- "Python 0.4.8"
- "Python 0.4.7"
- "Python 0.4.6"
- "Python 0.4.5"
- "Python 0.4.4"
- "Python 0.4.3"
- "Python 0.4.2"
- "Python 0.4.1"
- "Python 0.4.0"
- ".NET dev (main branch)"
- "Studio 0.4.1"
- "Studio 0.4.0"
- "Other (please specify)"
validations:
required: True
- type: input
attributes:
label: Other library version.
description: "Please specify if selected 'Other' above"
- type: input
attributes:
label: Model used
description: If a model was used, please name here. Use full model name with version number.
placeholder: "e.g., gpt-4o-2024-11-20"
- type: dropdown
attributes:
label: Model provider
description: The provider or hosting service that runs the model.
options:
- "Anthropic"
- "AWS Bedrock"
- "Azure OpenAI"
- "Azure AI Foundary (Azure AI Studio)"
- "DeepSeek (Hosted)"
- "GitHub Models"
- "Google Gemini"
- "Google Vertex AI"
- "HuggingFace Models (Hosted)"
- "HuggingFace Transformers (Local)"
- "LlamaCpp"
- "Mistral AI"
- "Ollama"
- "OpenAI"
- "OpenRouter"
- "Together AI"
- "vLLM"
- "Other (please specify below)"
- type: input
attributes:
label: Other model provider
description: "Other provider if not found above."
- type: dropdown
attributes:
label: Python version
options:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
- Other (please note we only support Python 3.10+)
- type: dropdown
attributes:
label: .NET version
options:
- ".NET 9"
- ".NET 8"
- type: dropdown
attributes:
label: Operating system
options:
- Windows
- MacOS
- Ubuntu
- Fedora
- CentOS
- Other

View File

@ -1,47 +0,0 @@
name: 📘 Doc Issue
description: Report an issue in the documentation, including missing or incorrect information.
type: "bug"
labels:
- needs-triage
- documentation
body:
- type: markdown
attributes:
value: |
## Please Read the following before submitting an issue.
### Have you read the docs?
- [Python AgentChat User Guide and Tutorial](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/index.html)
- [Python Core API User Guide](https://microsoft.github.io/autogen/stable/user-guide/core-user-guide/index.html)
- [Python API Doc](https://microsoft.github.io/autogen/stable/reference/index.html)
- [.NET Doc](https://microsoft.github.io/autogen/dotnet/)
### Have you searched for related issues?
- Some other users might have the same issue as yours.
- type: textarea
attributes:
label: What is the doc issue?
description: Please provide as much information as possible, this helps us address the issue. Use Markdown to format your text.
value: |
**Describe the issue**
A clear and concise description of what the issue is. What is missing or incorrect?
**What do you want to see in the doc?**
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
validations:
required: true
- type: input
id: doc-link
attributes:
label: Link to the doc page, if applicable
description: Please provide a link to the doc page that has the issue.
placeholder: https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/index.html
validations:
required: true

View File

@ -1,20 +0,0 @@
name: 🔒 Maintainer Only
description: Only use this template if you are a maintainer.
body:
- type: checkboxes
attributes:
label: Confirmation
description: Please only use this template if you are a maintainer. Thanks for helping us keep the issue tracker organized!
options:
- label: I confirm that I am a maintainer and so can use this template. If I am not, I understand this issue will be closed and I will be asked to use a different template.
required: true
- type: textarea
id: body
attributes:
label: Issue body
description: "How do you trigger this bug? Please walk us through it step by step."
validations:
required: true

55
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,55 @@
name: Bug Report
description: Report a bug
type: "bug"
body:
- type: textarea
attributes:
label: What happened?
description: Please provide as much information as possible, this helps us address the issue.
validations:
required: true
- type: textarea
attributes:
label: What did you expect to happen?
validations:
required: true
- type: textarea
attributes:
label: How can we reproduce it (as minimally and precisely as possible)?
description: Please provide steps to reproduce. Provide code that can be run if possible.
validations:
required: true
- type: input
attributes:
label: AutoGen version
description: What version or commit of the library was used
validations:
required: true
- type: dropdown
attributes:
label: Which package was this bug in
options:
- Core
- AgentChat
- Extensions
- AutoGen Studio
- Magentic One
- AutoGen Bench
- Other
validations:
required: true
- type: input
attributes:
label: Model used
description: If a model was used, please describe it here, indicating whether it is a local model or a cloud-hosted model
placeholder: gpt-4, mistral-7B etc
- type: input
attributes:
label: Python version
- type: input
attributes:
label: Operating system
- type: textarea
attributes:
label: Any additional info you think would be helpful for fixing this bug

View File

@ -1,8 +1,5 @@
blank_issues_enabled: false
blank_issues_enabled: true
contact_links:
- name: 💬 Questions or general help
- name: Questions or general help 💬
url: https://github.com/microsoft/autogen/discussions
about: Please ask and answer questions here.
- name: 💡 Suggest a new feature
url: https://github.com/microsoft/autogen/discussions/categories/feature-suggestions
about: Please suggest new features here and once the feature is accepted a maintainer will create an issue.

View File

@ -0,0 +1,18 @@
name: Feature Request
description: Request a new feature or enhancement
type: "feature"
body:
- type: textarea
attributes:
label: What feature would you like to be added?
description: Please describe the desired feature. Be descriptive, provide examples and if possible, provide a proposed solution.
validations:
required: true
- type: textarea
attributes:
label: Why is this needed?
description: Why is it important that this feature is implemented? What problem or need does it solve?
validations:
required: true

View File

@ -12,6 +12,6 @@
## Checks
- [ ] I've included any doc changes needed for <https://microsoft.github.io/autogen/>. See <https://github.com/microsoft/autogen/blob/main/CONTRIBUTING.md> to build and test documentation locally.
- [ ] I've included any doc changes needed for https://microsoft.github.io/autogen/. See https://microsoft.github.io/autogen/docs/Contribute#documentation to build and test documentation locally.
- [ ] I've added tests (if relevant) corresponding to the changes introduced in this PR.
- [ ] I've made sure all auto checks have passed.

View File

@ -181,47 +181,9 @@ jobs:
name: coverage-autogen-ext-grpc
path: ./python/coverage_autogen-ext-grpc.xml
test-autogen-ext-pwsh:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
with:
enable-cache: true
version: "0.5.18"
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install Python deps
run: |
uv sync --locked --all-extras
shell: pwsh
working-directory: ./python
- name: Run tests for Windows
run: |
.venv/Scripts/activate.ps1
poe --directory ./packages/autogen-ext test-windows
shell: pwsh
working-directory: ./python
- name: Move coverage file
run: |
mv ./packages/autogen-ext/coverage.xml coverage_autogen_ext_windows.xml
working-directory: ./python
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-autogen-ext-windows
path: ./python/coverage_autogen_ext_windows.xml
codecov:
runs-on: ubuntu-latest
needs: [test, test-grpc]
needs: [test]
strategy:
matrix:
package:

View File

@ -106,11 +106,6 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- if: matrix.build-mode == 'manual'
name: Setup .NET 9.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- if: matrix.build-mode == 'manual'
shell: bash
working-directory: dotnet

View File

@ -33,22 +33,27 @@ jobs:
[
# For main use the workflow target
{ ref: "${{github.ref}}", dest-dir: dev, uv-version: "0.5.13", sphinx-release-override: "dev" },
{ ref: "python-v0.5.5", dest-dir: stable, uv-version: "0.5.13", sphinx-release-override: "stable" },
{ ref: "python-v0.4.5", dest-dir: stable, uv-version: "0.5.13", sphinx-release-override: "stable" },
{ ref: "v0.4.0.dev0", dest-dir: "0.4.0.dev0", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev1", dest-dir: "0.4.0.dev1", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev2", dest-dir: "0.4.0.dev2", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev3", dest-dir: "0.4.0.dev3", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev4", dest-dir: "0.4.0.dev4", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev5", dest-dir: "0.4.0.dev5", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev6", dest-dir: "0.4.0.dev6", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev7", dest-dir: "0.4.0.dev7", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev8", dest-dir: "0.4.0.dev8", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev9", dest-dir: "0.4.0.dev9", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev10", dest-dir: "0.4.0.dev10", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev11", dest-dir: "0.4.0.dev11", uv-version: "0.5.11", sphinx-release-override: "" },
{ ref: "v0.4.0.dev12", dest-dir: "0.4.0.dev12", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "v0.4.0.dev13", dest-dir: "0.4.0.dev13", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "v0.4.0.post1", dest-dir: "0.4.0", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "v0.4.1", dest-dir: "0.4.1", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "v0.4.2", dest-dir: "0.4.2", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "v0.4.3", dest-dir: "0.4.3", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "v0.4.4", dest-dir: "0.4.4", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "python-v0.4.5", dest-dir: "0.4.5", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "python-v0.4.6", dest-dir: "0.4.6", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "python-v0.4.7", dest-dir: "0.4.7", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "python-v0.4.8", dest-dir: "0.4.8", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "python-v0.4.9-website", dest-dir: "0.4.9", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "python-v0.5.1", dest-dir: "0.5.1", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "python-v0.5.2", dest-dir: "0.5.2", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "python-v0.5.3", dest-dir: "0.5.3", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "python-v0.5.4", dest-dir: "0.5.4", uv-version: "0.5.13", sphinx-release-override: "" },
{ ref: "python-v0.5.5", dest-dir: "0.5.5", uv-version: "0.5.13", sphinx-release-override: "" },
]
steps:
- name: Checkout

View File

@ -81,10 +81,6 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Setup .NET 9.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Restore dependencies
run: dotnet restore -bl
- name: Format check
@ -173,7 +169,7 @@ jobs:
dotnet-version: '9.0.x'
- name: Install Temp Global.JSON
run: |
echo "{\"sdk\": {\"version\": \"9.0\"}}" > global.json
echo "{\"sdk\": {\"version\": \"9.0.101\"}}" > global.json
- name: Install .NET Aspire workload
run: dotnet workload install aspire
- name: Install dev certs
@ -211,10 +207,6 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: Setup .NET 9.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: publish AOT testApp, assert static analysis warning count, and run the app
shell: pwsh
@ -257,10 +249,6 @@ jobs:
with:
dotnet-version: '8.0.x'
global-json-file: dotnet/global.json
- name: Setup .NET 9.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Install dev certs
run: dotnet --version && dotnet dev-certs https --trust
- name: Restore dependencies

View File

@ -0,0 +1,18 @@
name: Label issues with needs-triage
on:
issues:
types:
- reopened
- opened
jobs:
label_issues:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- run: gh issue edit "$NUMBER" --add-label "$LABELS"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
NUMBER: ${{ github.event.issue.number }}
LABELS: needs-triage

View File

@ -6,9 +6,10 @@ on:
- "0.2.*"
workflow_dispatch:
inputs:
tag:
description: 'Tag to deploy the package'
branch:
description: 'Branch to deploy the package'
required: true
default: '0.2'
permissions: {}
jobs:
deploy:
@ -26,7 +27,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.inputs.tag }}
ref: ${{ github.event.inputs.branch }}
- name: Build
shell: pwsh
run: |

5
.gitignore vendored
View File

@ -198,7 +198,4 @@ notebook/coding
artifacts
# project data
registry.json
# files created by the gitty agent in python/samples/gitty
.gitty/
registry.json

View File

@ -34,7 +34,7 @@ For common tasks that are helpful during development and run in CI, see [here](.
## Roadmap
We use GitHub issues and milestones to track our roadmap. You can view the upcoming milestones [here]([Roadmap Issues](https://aka.ms/autogen-roadmap)).
We use GitHub issues and milestones to track our roadmap. You can view the upcoming milestones [here]([Roadmap Issues](https://aka.ms/autogen-roadmap).
## Versioning
@ -48,11 +48,11 @@ We will update verion numbers according to the following rules:
## Release process
1. Create a PR that updates the version numbers across the codebase ([example](https://github.com/microsoft/autogen/pull/4359))
2. The docs CI will fail for the PR, but this is expected and will be resolved in the next step
3. After merging the PR, create and push a tag that corresponds to the new verion. For example, for `0.4.0.dev13`:
2. The docs CI will fail for the PR, but this is expected and will be resolved in the next step
2. After merging the PR, create and push a tag that corresponds to the new verion. For example, for `0.4.0.dev13`:
- `git tag v0.4.0.dev13 && git push origin v0.4.0.dev13`
4. Restart the docs CI by finding the failed [job corresponding to the `push` event](https://github.com/microsoft/autogen/actions/workflows/docs.yml) and restarting all jobs
5. Run [this](https://github.com/microsoft/autogen/actions/workflows/single-python-package.yml) workflow for each of the packages that need to be released and get an approval for the release for it to run
3. Restart the docs CI by finding the failed [job corresponding to the `push` event](https://github.com/microsoft/autogen/actions/workflows/docs.yml) and restarting all jobs
4. Run [this](https://github.com/microsoft/autogen/actions/workflows/single-python-package.yml) workflow for each of the packages that need to be released and get an approval for the release for it to run
## Triage process

View File

@ -47,24 +47,22 @@ from autogen_agentchat.agents import AssistantAgent
from autogen_ext.models.openai import OpenAIChatCompletionClient
async def main() -> None:
model_client = OpenAIChatCompletionClient(model="gpt-4o")
agent = AssistantAgent("assistant", model_client=model_client)
agent = AssistantAgent("assistant", OpenAIChatCompletionClient(model="gpt-4o"))
print(await agent.run(task="Say 'Hello World!'"))
await model_client.close()
asyncio.run(main())
```
### Web Browsing Agent Team
### Team
Create a group chat team with a web surfer agent and a user proxy agent
Create a group chat team with an assistant agent, a web surfer agent, and a user proxy agent
for web browsing tasks. You need to install [playwright](https://playwright.dev/python/docs/library).
```python
# pip install -U autogen-agentchat autogen-ext[openai,web-surfer]
# playwright install
import asyncio
from autogen_agentchat.agents import UserProxyAgent
from autogen_agentchat.agents import AssistantAgent, UserProxyAgent
from autogen_agentchat.conditions import TextMentionTermination
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.ui import Console
@ -73,21 +71,12 @@ from autogen_ext.agents.web_surfer import MultimodalWebSurfer
async def main() -> None:
model_client = OpenAIChatCompletionClient(model="gpt-4o")
# The web surfer will open a Chromium browser window to perform web browsing tasks.
web_surfer = MultimodalWebSurfer("web_surfer", model_client, headless=False, animate_actions=True)
# The user proxy agent is used to get user input after each step of the web surfer.
# NOTE: you can skip input by pressing Enter.
assistant = AssistantAgent("assistant", model_client)
web_surfer = MultimodalWebSurfer("web_surfer", model_client)
user_proxy = UserProxyAgent("user_proxy")
# The termination condition is set to end the conversation when the user types 'exit'.
termination = TextMentionTermination("exit", sources=["user_proxy"])
# Web surfer and user proxy take turns in a round-robin fashion.
team = RoundRobinGroupChat([web_surfer, user_proxy], termination_condition=termination)
try:
# Start the team and wait for it to terminate.
await Console(team.run_stream(task="Find information about AutoGen and write a short summary."))
finally:
await web_surfer.close()
await model_client.close()
termination = TextMentionTermination("exit") # Type 'exit' to end the conversation.
team = RoundRobinGroupChat([web_surfer, assistant, user_proxy], termination_condition=termination)
await Console(team.run_stream(task="Find information about AutoGen and write a short summary."))
asyncio.run(main())
```
@ -112,7 +101,7 @@ The AutoGen ecosystem provides everything you need to create AI agents, especial
The _framework_ uses a layered and extensible design. Layers have clearly divided responsibilities and build on top of layers below. This design enables you to use the framework at different levels of abstraction, from high-level APIs to low-level components.
- [Core API](./python/packages/autogen-core/) implements message passing, event-driven agents, and local and distributed runtime for flexibility and power. It also support cross-language support for .NET and Python.
- [AgentChat API](./python/packages/autogen-agentchat/) implements a simpler but opinionated API for rapid prototyping. This API is built on top of the Core API and is closest to what users of v0.2 are familiar with and supports common multi-agent patterns such as two-agent chat or group chats.
- [AgentChat API](./python/packages/autogen-agentchat/) implements a simpler but opinionated API rapid for prototyping. This API is built on top of the Core API and is closest to what users of v0.2 are familiar with and supports familiar multi-agent patterns such as two-agent chat or group chats.
- [Extensions API](./python/packages/autogen-ext/) enables first- and third-party extensions continuously expanding framework capabilities. It support specific implementation of LLM clients (e.g., OpenAI, AzureOpenAI), and capabilities such as code execution.
The ecosystem also supports two essential _developer tools_:
@ -124,7 +113,7 @@ The ecosystem also supports two essential _developer tools_:
- [AutoGen Studio](./python/packages/autogen-studio/) provides a no-code GUI for building multi-agent applications.
- [AutoGen Bench](./python/packages/agbench/) provides a benchmarking suite for evaluating agent performance.
You can use the AutoGen framework and developer tools to create applications for your domain. For example, [Magentic-One](./python/packages/magentic-one-cli/) is a state-of-the-art multi-agent team built using AgentChat API and Extensions API that can handle a variety of tasks that require web browsing, code execution, and file handling.
You can use the AutoGen framework and developer tools to create applications for your domain. For example, [Magentic-One](./python/packages/magentic-one-cli/) is a state-of-art multi-agent team built using AgentChat API and Extensions API that can handle variety of tasks that require web browsing, code execution, and file handling.
With AutoGen you get to join and contribute to a thriving ecosystem. We host weekly office hours and talks with maintainers and community. We also have a [Discord server](https://aka.ms/autogen-discord) for real-time chat, GitHub Discussions for Q&A, and a blog for tutorials and updates.
@ -134,14 +123,15 @@ With AutoGen you get to join and contribute to a thriving ecosystem. We host wee
| | [![Python](https://img.shields.io/badge/AutoGen-Python-blue?logo=python&logoColor=white)](./python) | [![.NET](https://img.shields.io/badge/AutoGen-.NET-green?logo=.net&logoColor=white)](./dotnet) | [![Studio](https://img.shields.io/badge/AutoGen-Studio-purple?logo=visual-studio&logoColor=white)](./python/packages/autogen-studio) |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Installation | [![Installation](https://img.shields.io/badge/Install-blue)](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/installation.html) | [![Install](https://img.shields.io/badge/Install-green)](https://microsoft.github.io/autogen/dotnet/dev/core/installation.html) | [![Install](https://img.shields.io/badge/Install-purple)](https://microsoft.github.io/autogen/stable/user-guide/autogenstudio-user-guide/installation.html) |
| Quickstart | [![Quickstart](https://img.shields.io/badge/Quickstart-blue)](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/quickstart.html#) | [![Quickstart](https://img.shields.io/badge/Quickstart-green)](https://microsoft.github.io/autogen/dotnet/dev/core/index.html) | [![Usage](https://img.shields.io/badge/Quickstart-purple)](https://microsoft.github.io/autogen/stable/user-guide/autogenstudio-user-guide/usage.html#) |
| Tutorial | [![Tutorial](https://img.shields.io/badge/Tutorial-blue)](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/index.html) | [![Tutorial](https://img.shields.io/badge/Tutorial-green)](https://microsoft.github.io/autogen/dotnet/dev/core/tutorial.html) | [![Usage](https://img.shields.io/badge/Tutorial-purple)](https://microsoft.github.io/autogen/stable/user-guide/autogenstudio-user-guide/usage.html#) |
| API Reference | [![API](https://img.shields.io/badge/Docs-blue)](https://microsoft.github.io/autogen/stable/reference/index.html#) | [![API](https://img.shields.io/badge/Docs-green)](https://microsoft.github.io/autogen/dotnet/dev/api/Microsoft.AutoGen.Contracts.html) | [![API](https://img.shields.io/badge/Docs-purple)](https://microsoft.github.io/autogen/stable/user-guide/autogenstudio-user-guide/usage.html) |
| Packages | [![PyPi autogen-core](https://img.shields.io/badge/PyPi-autogen--core-blue?logo=pypi)](https://pypi.org/project/autogen-core/) <br> [![PyPi autogen-agentchat](https://img.shields.io/badge/PyPi-autogen--agentchat-blue?logo=pypi)](https://pypi.org/project/autogen-agentchat/) <br> [![PyPi autogen-ext](https://img.shields.io/badge/PyPi-autogen--ext-blue?logo=pypi)](https://pypi.org/project/autogen-ext/) | [![NuGet Contracts](https://img.shields.io/badge/NuGet-Contracts-green?logo=nuget)](https://www.nuget.org/packages/Microsoft.AutoGen.Contracts/) <br> [![NuGet Core](https://img.shields.io/badge/NuGet-Core-green?logo=nuget)](https://www.nuget.org/packages/Microsoft.AutoGen.Core/) <br> [![NuGet Core.Grpc](https://img.shields.io/badge/NuGet-Core.Grpc-green?logo=nuget)](https://www.nuget.org/packages/Microsoft.AutoGen.Core.Grpc/) <br> [![NuGet RuntimeGateway.Grpc](https://img.shields.io/badge/NuGet-RuntimeGateway.Grpc-green?logo=nuget)](https://www.nuget.org/packages/Microsoft.AutoGen.RuntimeGateway.Grpc/) | [![PyPi autogenstudio](https://img.shields.io/badge/PyPi-autogenstudio-purple?logo=pypi)](https://pypi.org/project/autogenstudio/) |
| Installation | [![Installation](https://img.shields.io/badge/Install-blue)](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/installation.html) | \* | [![Install](https://img.shields.io/badge/Install-purple)](https://microsoft.github.io/autogen/stable/user-guide/autogenstudio-user-guide/installation.html) |
| Quickstart | [![Quickstart](https://img.shields.io/badge/Quickstart-blue)](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/quickstart.html#) | \* | [![Usage](https://img.shields.io/badge/Quickstart-blue)](https://microsoft.github.io/autogen/stable/user-guide/autogenstudio-user-guide/usage.html#) |
| Tutorial | [![Tutorial](https://img.shields.io/badge/Tutorial-blue)](https://microsoft.github.io/autogen/stable/user-guide/agentchat-user-guide/tutorial/index.html) | \* | [![Usage](https://img.shields.io/badge/Quickstart-blue)](https://microsoft.github.io/autogen/stable/user-guide/autogenstudio-user-guide/usage.html#) |
| API Reference | [![API](https://img.shields.io/badge/Docs-blue)](https://microsoft.github.io/autogen/stable/reference/index.html#) | \* | [![API](https://img.shields.io/badge/Docs-purple)](https://microsoft.github.io/autogen/stable/user-guide/autogenstudio-user-guide/usage.html) |
| Packages | [![PyPi autogen-core](https://img.shields.io/badge/PyPi-autogen--core-blue?logo=pypi)](https://pypi.org/project/autogen-core/) <br> [![PyPi autogen-agentchat](https://img.shields.io/badge/PyPi-autogen--agentchat-blue?logo=pypi)](https://pypi.org/project/autogen-agentchat/) <br> [![PyPi autogen-ext](https://img.shields.io/badge/PyPi-autogen--ext-blue?logo=pypi)](https://pypi.org/project/autogen-ext/) | \* | [![PyPi autogenstudio](https://img.shields.io/badge/PyPi-autogenstudio-purple?logo=pypi)](https://pypi.org/project/autogenstudio/) |
</div>
\*_Releasing soon_
Interested in contributing? See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines on how to get started. We welcome contributions of all kinds, including bug fixes, new features, and documentation improvements. Join our community and help us make AutoGen better!

View File

@ -11,7 +11,7 @@ Each event in the system is defined using the [CloudEvents Specification](https:
1. *id* - A unique id (eg. a UUID).
2. *source* - A URI or URN indicating the event's origin.
3. *type* - The namespace of the event - prefixed with a reverse-DNS name.
- The prefixed domain dictates the organization which defines the semantics of this event type: e.g (`com.github.pull_request.opened` or `com.example.object.deleted.v2`), and optionally fields describing the data schema/content-type or extensions.
- The prefixed domain dictates the organization which defines the semantics of this event type: e.g `com.github.pull_request.opened` or `com.example.object.deleted.v2`), and optionally fields describing the data schema/content-type or extensions.
## Event Handlers

View File

@ -62,7 +62,7 @@ For this subscription source should map directly to agent key.
This subscription will therefore receive all events for the following well known topics:
- `{AgentType}:` - General purpose direct messages. These should be routed to the appropriate message handler.
- `{AgentType}:rpc_request={RequesterAgentType}` - RPC request messages. These should be routed to the appropriate RPC handler, and RequesterAgentType used to publish the response
- `{AgentType}:` - General purpose direct messages. These should be routed to the approriate message handler.
- `{AgentType}:rpc_request={RequesterAgentType}` - RPC request messages. These should be routed to the approriate RPC handler, and RequesterAgentType used to publish the response
- `{AgentType}:rpc_response={RequestId}` - RPC response messages. These should be routed back to the response future of the caller.
- `{AgentType}:error={RequestId}` - Error message that corresponds to the given request.

View File

@ -1,16 +1,13 @@
# How to build and run the website
## How to build and run the website
## Prerequisites
- dotnet 8.0 or later
## Build
### Prerequisites
- dotnet 7.0 or later
### Build
Firstly, go to autogen/dotnet folder and run the following command to build the website:
```bash
dotnet tool restore
dotnet tool run docfx ../docs/dotnet/docfx.json --serve
dotnet tool run docfx website/docfx.json --serve
```
After the command is executed, you can open your browser and navigate to `http://localhost:8080` to view the website.
After the command is executed, you can open your browser and navigate to `http://localhost:8080` to view the website.

View File

@ -1,165 +1,7 @@
# AutoGen Core
AutoGen Core for .NET follows the same concepts and conventions of its Python counterpart. In fact, in order to understand the concepts in the .NET version, we recommend reading the [Python documentation](https://microsoft.github.io/autogen/stable/) first. Unless otherwise stated, the concepts in the Python version map to .NET.
AutoGen Core for .NET follows the same concepts and conventions of its Python counterpart. In fact, in order to understand the concepts in the .NET version, we recommend reading the Python documentation first. Unless otherwise stated, the concepts in the Python version map to .NET.
Any important differences between the language versions are documented in the [Differences from Python](./differences-from-python.md) section. For things that only affect a given language, such as dependency injection or host builder patterns, these will not be specified in the differences document.
## Getting Started
You can obtain the SDK as a nuget package or by cloning the repository. The SDK is available on [NuGet](https://www.nuget.org/packages/Microsoft.AutoGen).
Minimally you will need the following:
```bash
dotnet add package Microsoft.AutoGen.Contracts
dotnet add package Microsoft.AutoGen.Core
```
See [Installation](./installation.md) for more detailed notes on installing all the related packages.
You can quickly get started by looking at the samples in the [samples](https://github.com/microsoft/autogen/tree/main/dotnet/samples) directory of the repository.
### Creating an Agent
To create an agent, you can inherit from BaseAgent and implement event handlers for the events you care about. Here is a minimal example demonstrating how to inherit from BaseAgent and implement an event handler:
```csharp
public class MyAgent : BaseAgent, IHandle<MyMessage>
{
// ...
public async ValueTask HandleAsync(MyMessage item, MessageContext context)
{
// ...logic here...
}
}
```
By overriding BaseAgent, you gain access to the runtime and logging utilities, and by implementing IHandle<T>, you can easily define event-handling methods for your custom messages.
### Running an Agent in an Application
To run your agent in an application, you can use the `AgentsAppBuilder` class. Here is an example of how to run an agent 'HelloAgent' in an application:
```csharp
AgentsAppBuilder appBuilder = new AgentsAppBuilder()
.UseInProcessRuntime(deliverToSelf: true)
.AddAgent<HelloAgent>("HelloAgent");
var app = await appBuilder.BuildAsync();
// start the app by publishing a message to the runtime
await app.PublishMessageAsync(new NewMessageReceived
{
Message = "Hello from .NET"
}, new TopicId("HelloTopic"));
// Wait for shutdown
await app.WaitForShutdownAsync();
```
## .NET SDK Runtimes
The .NET SDK includes both an InMemory Single Process Runtime and a Remote, Distributed Runtime meant for running your agents in the cloud. The Distributed Runtime supports running agents in python and in .NET, allowing those agents to talk to one another. The distributed runtime uses Microsoft Orleans to provide resilience, persistence, and integration with messaging services such as Azure Event Hubs. The xlang functionality requires that your agent's Messages are serializable as CloudEvents. The messages are exchanged as CloudEvents over Grpc, and the runtime takes care of ensuring that the messages are delivered to the correct agents.
To use the Distributed Runtime, you will need to add the following package to your project:
```bash
dotnet add package Microsoft.AutoGen.Core.Grpc
```
This is the package that runs in the application with your agent(s) and connects to the distributed system.
To Run the backend/server side you need:
```bash
dotnet add package Microsoft.AutoGen.RuntimeGateway
dotnet add package Microsoft.AutoGen.AgentHost
```
You can run the backend on its own:
```bash
dotnet run --project Microsoft.AutoGen.AgentHost
```
or you can run iclude it inside your own application:
```csharp
using Microsoft.AutoGen.RuntimeGateway;
using Microsoft.AutoGen.AgentHost;
var autogenBackend = await Microsoft.AutoGen.RuntimeGateway.Grpc.Host.StartAsync(local: false, useGrpc: true).ConfigureAwait(false);
```
You can also install the runtime as a dotnet tool:
```
dotnet pack --no-build --configuration Release --output './output/release' -bl\n
dotnet tool install --add-source ./output/release Microsoft.AutoGen.AgentHost
# run the tool
# dotnet agenthost
# or just...
agenthost
```
### Running Multiple Agents and the Runtime in separate processes with .NET Aspire
The [Hello.AppHost project](https://github.com/microsoft/autogen/blob/50d7587a4649504af3bb79ab928b2a3882a1a394/dotnet/samples/Hello/Hello.AppHost/Program.cs#L4) illustrates how to orchestrate a distributed system with multiple agents and the runtime in separate processes using .NET Aspire. It also points to a [python agent that illustrates how to run agents in different languages in the same distributed system](https://github.com/microsoft/autogen/blob/50d7587a4649504af3bb79ab928b2a3882a1a394/python/samples/core_xlang_hello_python_agent/README.md#L1).
```csharp
// Copyright (c) Microsoft Corporation. All rights reserved.
// Program.cs
using Microsoft.Extensions.Hosting;
var builder = DistributedApplication.CreateBuilder(args);
var backend = builder.AddProject<Projects.Microsoft_AutoGen_AgentHost>("backend").WithExternalHttpEndpoints();
var client = builder.AddProject<Projects.HelloAgent>("HelloAgentsDotNET")
.WithReference(backend)
.WithEnvironment("AGENT_HOST", backend.GetEndpoint("https"))
.WithEnvironment("STAY_ALIVE_ON_GOODBYE", "true")
.WaitFor(backend);
// xlang is over http for now - in prod use TLS between containers
builder.AddPythonApp("HelloAgentsPython", "../../../../python/samples/core_xlang_hello_python_agent", "hello_python_agent.py", "../../.venv")
.WithReference(backend)
.WithEnvironment("AGENT_HOST", backend.GetEndpoint("http"))
.WithEnvironment("STAY_ALIVE_ON_GOODBYE", "true")
.WithEnvironment("GRPC_DNS_RESOLVER", "native")
.WithOtlpExporter()
.WaitFor(client);
using var app = builder.Build();
await app.StartAsync();
var url = backend.GetEndpoint("http").Url;
Console.WriteLine("Backend URL: " + url);
await app.WaitForShutdownAsync();
```
You can find more examples of how to use Aspire and XLang agents in the [Microsoft.AutoGen.Integration.Tests.AppHost](https://github.com/microsoft/autogen/blob/acd7e864300e24a3ee67a89a916436e8894bb143/dotnet/test/Microsoft.AutoGen.Integration.Tests.AppHosts/) directory.
### Configuring Logging
The SDK uses the Microsoft.Extensions.Logging framework for logging. Here is an example appsettings.json file with some useful defaults:
```json
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.AspNetCore": "Information",
"Microsoft": "Information",
"Microsoft.Orleans": "Warning",
"Orleans.Runtime": "Error",
"Grpc": "Information"
}
},
"AllowedHosts": "*",
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http2"
}
}
}
```
### Defining Message Types in Protocol Buffers
A convenient way to define common event or message types to be used in both python and .NET agents is to define your events. This is covered here: [Using Protocol Buffers to Define Message Types](./protobuf-message-types.md).
For .NET we are starting with the core functionality and will be expanding support progressively. So far the core abstractions of Agent and Runtime are available. The InProcessRuntime is the only runtime available at this time. We will be expanding to cross language support in upcoming releases.

View File

@ -20,29 +20,3 @@ Or, add via `<PackageReference>`
<PackageReference Include="Microsoft.AutoGen.Contracts" Version="0.4.0-dev.1" />
<PackageReference Include="Microsoft.AutoGen.Core" Version="0.4.0-dev.1" />
```
# Additional Packages
The *Core* and *Contracts* packages will give you what you need for writing and running agents using the Core API within a single process.
- *Microsoft.AutoGen.AgentChat* - An implementation of the AgentChat package for building chat-centric agent orchestration on top of the Core SDK
- *Microsoft.AutoGen.Agents* - a package that has a small number of default agents you can use.
- *Microsoft.AutoGen.Extensions* - Extensions to support closely related projects including Aspire, Microsoft.Extensions.AI, and Semantic Kernel
```sh
dotnet add package Microsoft.AutoGen.AgentChat --version 0.4.0-dev-1
dotnet add package Microsoft.AutoGen.Agents --version 0.4.0-dev-1
dotnet add package Microsoft.AutoGen.Extensions --version 0.4.0-dev-1
```
To enable running a system with agents in different processes that allows for x-language communication between python and .NET agents, there are additional packages:
- *Microsoft.AutoGen.Core.Grpc* - the .NET client runtime for agents in a distributed system. It has the same API as *Microsoft.AutoGen.Core*.
- *Microsoft.AutoGen.RuntimeGatewway.Grpc* - the .NET server side of the distributed system that allows you to run multiple gateways to manage fleets of agents and enables x-language interoperability.
- *Microsoft.AutoGen.AgentHost* - A .NET Aspire project that hosts the Grpc Service
```sh
dotnet add package Microsoft.AutoGen.Core.Grpc --version 0.4.0-dev-1
dotnet add package Microsoft.AutoGen.RuntimeGateway.Grpc --version 0.4.0-dev-1
dotnet add package Microsoft.AutoGen.AgentHost --version 0.4.0-dev-1
```

View File

@ -7,4 +7,4 @@
- name: Differences from Python
href: differences-from-python.md
- name: Protobuf message types
href: protobuf-message-types.md
href: protobuf-message-types.md

View File

@ -5,11 +5,7 @@
{
"files": [
"src/Microsoft.AutoGen/Core/**/*.csproj",
"src/Microsoft.AutoGen/Contracts/**/*.csproj",
"src/Microsoft.AutoGen/Core.Grpc/**/*.csproj",
"src/Microsoft.AutoGen/AgentHost/**/*.csproj",
"src/Microsoft.AutoGen/RuntimeGateway.Grpc/**/*.csproj",
"src/Microsoft.AutoGen/Extensions/**/*.csproj"
"src/Microsoft.AutoGen/Contracts/**/*.csproj"
],
"src": "../../dotnet/"
}
@ -73,4 +69,4 @@
"keepFileLink": false,
"disableGitFeatures": false
}
}
}

View File

@ -1,6 +1,15 @@
---
_disableAffix: true
---
<style>
.center {
text-align: center;
}
.subheader {
font-size: 1.5em;
}
</style>
<div class="center">
<h1>AutoGen .NET</h1>
@ -14,46 +23,7 @@ _disableAffix: true
<div class="card">
<div class="card-body">
<h5 class="card-title">Core</h5>
<p>
[![dotnet-ci](https://github.com/microsoft/autogen/actions/workflows/dotnet-build.yml/badge.svg)](https://github.com/microsoft/autogen/actions/workflows/dotnet-build.yml)
[![NuGet version](https://badge.fury.io/nu/Microsoft.AutoGen.Contracts.svg)](https://badge.fury.io/nu/Microsoft.AutoGen.Contracts)
[![NuGet version](https://badge.fury.io/nu/Microsoft.AutoGen.Core.svg)](https://badge.fury.io/nu/Microsoft.AutoGen.Core)
[![NuGet version](https://badge.fury.io/nu/Microsoft.AutoGen.Core.Grpc.svg)](https://badge.fury.io/nu/Microsoft.AutoGen.Core.Grpc)
[![NuGet version](https://badge.fury.io/nu/Microsoft.AutoGen.RuntimeGateway.Grpc.svg)](https://badge.fury.io/nu/Microsoft.AutoGen.RuntimeGateway.Grpc)
[![NuGet version](https://badge.fury.io/nu/Microsoft.AutoGen.AgentHost.svg)](https://badge.fury.io/nu/Microsoft.AutoGen.AgentHost)
</p>
<p class="card-text">An event-driven programming framework for building scalable multi-agent AI systems.</p>
- Deterministic and dynamic agentic workflows for business processes
- Research on multi-agent collaboration
- Distributed agents for multi-language applications
- integration with event-driven, cloud native applications
*Start here if you are building workflows or distributed agent systems*
<p>
<div class="highlight">
<pre id="codecell0" tabindex="0">
```bash
dotnet add package Microsoft.AutoGen.Contracts
dotnet add package Microsoft.AutoGen.Core
# optionally - for distributed agent systems:
dotnet add package Microsoft.AutoGen.RuntimeGateway.Grpc
dotnet add package Microsoft.AutoGen.AgentHost
# other optional packages
dotnet add package Microsoft.AutoGen.Agents
dotnet add package Microsoft.AutoGen.Extensions.Aspire
dotnet add package Microsoft.AutoGen.Extensions.MEAI
dotnet add package Microsoft.AutoGen.Extensions.SemanticKernel
```
</pre></div></p>
<p>
<a href="core/index.md" class="btn btn-primary">Get started</a>
</div>
</div>

View File

@ -2,114 +2,3 @@
height: 50px;
margin-right: 0.5rem;
}
.bd-footer {
font-size: 0.8rem;
}
html[data-theme="light"] {
--pst-color-primary: hsl(222.2 47.4% 11.2%);
--pst-color-secondary: #007bff;
--pst-color-secondary-bg: #007bff;
--pst-color-accent: #007bff;
--sd-color-secondary-highlight: #0062cc;
--pst-color-shadow: rgba(0, 0, 0, 0.0);
}
html[data-theme="dark"] {
--pst-color-primary: hsl(213 31% 91%);
--pst-color-secondary: #007bff;
--pst-color-secondary-bg: #007bff;
--pst-color-accent: #007bff;
--sd-color-secondary-highlight: #0062cc;
--pst-color-shadow: rgba(0, 0, 0, 0.0);
}
.bd-header-announcement {
color: white;
}
.bd-header-announcement a {
color: white;
}
.bd-header-announcement a:hover {
color: white;
text-shadow: 0.5px 0 0 currentColor;
}
nav.bd-links .current>a {
box-shadow: inset 1px 0 0 var(--pst-color-primary);
}
html[data-theme="light"] .bd-header {
border-bottom: 1px solid var(--pst-color-border);
}
.admonition, div.admonition {
border: 1px solid var(--pst-color-border);
}
.api-card {
text-align: center;
font-size: 1.2rem;
}
.api-card svg {
font-size: 2rem;
}
.search-button-field {
border-radius: var(--bs-btn-border-radius);
}
.bd-content .sd-tab-set .sd-tab-content {
border: none;
border-top: 3px solid var(--pst-color-border);
}
.bd-content .sd-tab-set>input:checked+label {
border: none;
transform: translateY(0);
font-weight: 600;
border-bottom: 2px solid var(--pst-color-secondary);
}
.bd-content .sd-tab-set>label {
background-color: transparent;
border: none;
}
.center {
text-align: center;
}
.subheader {
font-size: 1.5em;
}
.hero-title {
font-size: 60px;
font-weight: bold;
margin: 2rem auto 0;
}
.wip-card {
border: 1px solid var(--pst-color-success);
background-color: var(--pst-color-success-bg);
border-radius: .25rem;
padding: 0.3rem;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 1rem;
}
.card-title {
font-size: 1.2rem;
font-weight: bold;
}
.card-title svg {
font-size: 2rem;
vertical-align: bottom;
margin-right: 5px;
}

View File

@ -1,88 +1,18 @@
[
{
"name": "0.4.5 (stable)",
"version": "stable",
"url": "/autogen/stable/",
"preferred": true
},
{
"name": "dev (main)",
"version": "dev",
"url": "/autogen/dev/"
},
{
"name": "0.5.5 (stable)",
"version": "stable",
"url": "/autogen/stable/",
"preferred": true
},
{
"name": "0.5.4",
"version": "0.5.4",
"url": "/autogen/0.5.4/"
},
{
"name": "0.5.3",
"version": "0.5.3",
"url": "/autogen/0.5.3/"
},
{
"name": "0.5.2",
"version": "0.5.2",
"url": "/autogen/0.5.2/"
},
{
"name": "0.5.1",
"version": "0.5.1",
"url": "/autogen/0.5.1/"
},
{
"name": "0.4.9",
"version": "0.4.9",
"url": "/autogen/0.4.9/"
},
{
"name": "0.4.8",
"version": "0.4.8",
"url": "/autogen/0.4.8/"
},
{
"name": "0.4.7",
"version": "0.4.7",
"url": "/autogen/0.4.7/"
},
{
"name": "0.4.6",
"version": "0.4.6",
"url": "/autogen/0.4.6/"
},
{
"name": "0.4.5",
"version": "0.4.5",
"url": "/autogen/0.4.5/"
},
{
"name": "0.4.4",
"version": "0.4.4",
"url": "/autogen/0.4.4/"
},
{
"name": "0.4.3",
"version": "0.4.3",
"url": "/autogen/0.4.3/"
},
{
"name": "0.4.2",
"version": "0.4.2",
"url": "/autogen/0.4.2/"
},
{
"name": "0.4.1",
"version": "0.4.1",
"url": "/autogen/0.4.1/"
},
{
"name": "0.4.0",
"version": "0.4.0",
"url": "/autogen/0.4.0/"
},
{
"name": "0.2",
"version": "0.2",
"url": "/autogen/0.2/"
}
]
]

View File

@ -6,15 +6,13 @@
"version": "0.1.205",
"commands": [
"dotnet-repl"
],
"rollForward": true
]
},
"docfx": {
"version": "2.67.5",
"commands": [
"docfx"
],
"rollForward": true
]
}
}
}

View File

@ -15,7 +15,7 @@ foreach ($line in $($publishOutput -split "`r`n"))
}
}
pushd $rootDirectory/artifacts/bin/AutoGen.AotCompatibility.Tests/release/native
pushd $rootDirectory/artifacts/bin/AutoGen.AotCompatibility.Tests/release
Write-Host "Executing test App..."
./AutoGen.AotCompatibility.Tests

View File

@ -140,13 +140,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.AgentChat
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AutoGen.AgentChat.Tests", "test\Microsoft.AutoGen.AgentChat.Tests\Microsoft.AutoGen.AgentChat.Tests.csproj", "{217A4F86-8ADD-4998-90BA-880092A019F5}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Microsoft.AutoGen.Integration.Tests.AppHosts", "Microsoft.AutoGen.Integration.Tests.AppHosts", "{D1C2B0BB-1276-4146-A699-D1983AE8ED04}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloAgentTests", "test\Microsoft.AutoGen.Integration.Tests.AppHosts\HelloAgentTests\HelloAgentTests.csproj", "{CD10E29A-725E-4BEF-9CFF-6C0E0A652926}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InMemoryTests.AppHost", "test\Microsoft.AutoGen.Integration.Tests.AppHosts\InMemoryTests.AppHost\InMemoryTests.AppHost.csproj", "{1E4E1ED4-7701-4A05-A861-64461C3B1EE3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XlangTests.AppHost", "test\Microsoft.AutoGen.Integration.Tests.AppHosts\XLangTests.AppHost\XlangTests.AppHost.csproj", "{62CDFB27-3B02-4D4B-B789-8AAD5E20688A}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HelloAgent.AppHost", "test\Microsoft.AutoGen.Integration.Tests.AppHosts\HelloAgent.AppHost\HelloAgent.AppHost.csproj", "{0C371D65-7EF9-44EA-8128-A105DA82A80E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -378,18 +372,6 @@ Global
{0C371D65-7EF9-44EA-8128-A105DA82A80E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C371D65-7EF9-44EA-8128-A105DA82A80E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C371D65-7EF9-44EA-8128-A105DA82A80E}.Release|Any CPU.Build.0 = Release|Any CPU
{CD10E29A-725E-4BEF-9CFF-6C0E0A652926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CD10E29A-725E-4BEF-9CFF-6C0E0A652926}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CD10E29A-725E-4BEF-9CFF-6C0E0A652926}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CD10E29A-725E-4BEF-9CFF-6C0E0A652926}.Release|Any CPU.Build.0 = Release|Any CPU
{1E4E1ED4-7701-4A05-A861-64461C3B1EE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E4E1ED4-7701-4A05-A861-64461C3B1EE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E4E1ED4-7701-4A05-A861-64461C3B1EE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E4E1ED4-7701-4A05-A861-64461C3B1EE3}.Release|Any CPU.Build.0 = Release|Any CPU
{62CDFB27-3B02-4D4B-B789-8AAD5E20688A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{62CDFB27-3B02-4D4B-B789-8AAD5E20688A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{62CDFB27-3B02-4D4B-B789-8AAD5E20688A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{62CDFB27-3B02-4D4B-B789-8AAD5E20688A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -454,10 +436,7 @@ Global
{EF954ED3-87D5-40F1-8557-E7179F43EA0E} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{7F828599-56E8-4597-8F68-EE26FD631417} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{217A4F86-8ADD-4998-90BA-880092A019F5} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{D1C2B0BB-1276-4146-A699-D1983AE8ED04} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{CD10E29A-725E-4BEF-9CFF-6C0E0A652926} = {D1C2B0BB-1276-4146-A699-D1983AE8ED04}
{1E4E1ED4-7701-4A05-A861-64461C3B1EE3} = {D1C2B0BB-1276-4146-A699-D1983AE8ED04}
{62CDFB27-3B02-4D4B-B789-8AAD5E20688A} = {D1C2B0BB-1276-4146-A699-D1983AE8ED04}
{0C371D65-7EF9-44EA-8128-A105DA82A80E} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B}

View File

@ -2,12 +2,10 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<MicrosoftSemanticKernelVersion>1.22.0</MicrosoftSemanticKernelVersion>
<MicrosoftSemanticKernelStableVersion>1.45.0</MicrosoftSemanticKernelStableVersion>
<MicrosoftSemanticKernelPreviewVersion>$(MicrosoftSemanticKernelStableVersion)-preview</MicrosoftSemanticKernelPreviewVersion>
<MicrosoftSemanticKernelAlphaVersion>$(MicrosoftSemanticKernelStableVersion)-alpha</MicrosoftSemanticKernelAlphaVersion>
<MicrosoftExtensionsAIVersion>9.3.0-preview.1.25161.3</MicrosoftExtensionsAIVersion>
<MicrosoftSemanticKernelExperimentalVersion>1.22.0-alpha</MicrosoftSemanticKernelExperimentalVersion>
<MicrosoftExtensionsAIVersion>9.0.0-preview.9.24525.1</MicrosoftExtensionsAIVersion>
<MicrosoftExtensionConfiguration>9.0.0</MicrosoftExtensionConfiguration>
<MicrosoftExtensionDependencyInjection>9.0.3</MicrosoftExtensionDependencyInjection>
<MicrosoftExtensionDependencyInjection>9.0.0</MicrosoftExtensionDependencyInjection>
<MicrosoftExtensionLogging>9.0.0</MicrosoftExtensionLogging>
<MicrosoftExtensionOptions>9.0.0</MicrosoftExtensionOptions>
<MicrosoftOrleans>9.0.1</MicrosoftOrleans>
@ -20,7 +18,7 @@
<PackageVersion Include="Aspire.Hosting" Version="9.0.0" />
<PackageVersion Include="Aspire.Hosting.Python" Version="9.0.0" />
<PackageVersion Include="Aspire.Hosting.Testing" Version="9.0.0" />
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="9.0.0-preview.5.24551.3" />
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="8.0.1-preview.8.24267.1" />
<PackageVersion Include="Aspire.Hosting.AppHost" Version="9.0.0" />
<PackageVersion Include="Aspire.Hosting.Azure.ApplicationInsights" Version="9.0.0" />
<PackageVersion Include="Aspire.Hosting.Azure.CognitiveServices" Version="9.0.0" />
@ -29,10 +27,9 @@
<PackageVersion Include="Aspire.Hosting.Qdrant" Version="9.0.0" />
<PackageVersion Include="Aspire.Hosting.Redis" Version="8.2.0" />
<PackageVersion Include="AspNetCore.Authentication.ApiKey" Version="8.0.1" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.2.0-beta.4" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.1.0-beta.2" />
<PackageVersion Include="Azure.AI.Inference" Version="1.0.0-beta.1" />
<PackageVersion Include="Azure.Data.Tables" Version="12.9.1" />
<PackageVersion Include="Azure.Core" Version="1.44.1" />
<PackageVersion Include="Azure.Identity" Version="1.13.1" />
<PackageVersion Include="Azure.ResourceManager.ContainerInstance" Version="1.2.1" />
<PackageVersion Include="Azure.Storage.Files.Shares" Version="12.21.0" />
@ -103,18 +100,18 @@
<PackageVersion Include="Microsoft.Orleans.Streaming.EventHubs" Version="$(MicrosoftOrleans)" />
<PackageVersion Include="Microsoft.Orleans.TestingHost" Version="9.0.1" />
<PackageVersion Include="Microsoft.PowerShell.SDK" Version="7.5.0" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="$(MicrosoftSemanticKernelStableVersion)" />
<PackageVersion Include="Microsoft.SemanticKernel.Agents.Core" Version="$(MicrosoftSemanticKernelStableVersion)" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.AzureOpenAI" Version="$(MicrosoftSemanticKernelStableVersion)" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Qdrant" Version="$(MicrosoftSemanticKernelPreviewVersion)" />
<PackageVersion Include="Microsoft.SemanticKernel.Plugins.Memory" Version="$(MicrosoftSemanticKernelAlphaVersion)" />
<PackageVersion Include="Microsoft.SemanticKernel.Plugins.Web" Version="$(MicrosoftSemanticKernelAlphaVersion)" />
<PackageVersion Include="Microsoft.SemanticKernel" Version="1.29.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Agents.Core" Version="$(MicrosoftSemanticKernelExperimentalVersion)" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.AzureOpenAI" Version="1.29.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Qdrant" Version="$(MicrosoftSemanticKernelExperimentalVersion)" />
<PackageVersion Include="Microsoft.SemanticKernel.Plugins.Memory" Version="$(MicrosoftSemanticKernelExperimentalVersion)" />
<PackageVersion Include="Microsoft.SemanticKernel.Plugins.Web" Version="$(MicrosoftSemanticKernelExperimentalVersion)" />
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="Octokit" Version="13.0.1" />
<PackageVersion Include="Octokit.Webhooks.AspNetCore" Version="2.4.1" />
<PackageVersion Include="OpenAI" Version="2.2.0-beta.4" />
<PackageVersion Include="OpenAI" Version="2.1.0-beta.2" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.10.0" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.10.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="$(OpenTelemetryInstrumentation)" />
@ -130,7 +127,7 @@
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="8.2.1" />
<PackageVersion Include="System.IO.Packaging" Version="9.0.0" />
<PackageVersion Include="System.Memory.Data" Version="9.0.0" />
<PackageVersion Include="System.Text.Json" Version="9.0.3" />
<PackageVersion Include="System.Text.Json" Version="9.0.0" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.console" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />

View File

@ -2,7 +2,7 @@
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VersionPrefix>0.4.0</VersionPrefix>
<VersionPrefixForAutoGen0_2>0.2.3</VersionPrefixForAutoGen0_2>
<VersionPrefixForAutoGen0_2>0.2.2</VersionPrefixForAutoGen0_2>
<Authors>Microsoft</Authors>
<PackageProjectUrl>https://microsoft.github.io/autogen-for-net/</PackageProjectUrl>
<RepositoryUrl>https://github.com/microsoft/autogen</RepositoryUrl>

View File

@ -1,6 +1,6 @@
{
"sdk": {
"version": "9.0.100",
"rollForward": "latestFeature"
"version": "8.0.401",
"rollForward": "latestMinor"
}
}

View File

@ -50,7 +50,7 @@ public class Example10_SemanticKernel
kernel.Plugins.AddFromObject(new LightPlugin());
var skAgent = kernel
.ToSemanticKernelAgent(name: "assistant", systemMessage: "You control the light", settings: settings);
.ToSemanticKernelAgent(name: "assistant", systemMessage: "You control the light", settings);
// Send a message to the skAgent, the skAgent supports the following message types:
// - IMessage<ChatMessageContent>

View File

@ -42,6 +42,8 @@ public class Tool_Call_With_Ollama_And_LiteLLM
});
#endregion Create_tools
#region Create_Agent
var liteLLMUrl = "http://localhost:4000";
// api-key is not required for local server
// so you can use any string here
var openAIClient = new OpenAIClient(new ApiKeyCredential("api-key"), new OpenAIClientOptions
@ -57,7 +59,7 @@ public class Tool_Call_With_Ollama_And_LiteLLM
.RegisterMiddleware(functionMiddleware)
.RegisterPrintMessage();
await agent.SendAsync("what's the weather in new york");
var reply = await agent.SendAsync("what's the weather in new york");
#endregion Create_Agent
}
}

View File

@ -40,6 +40,6 @@ public class DummyAgent : IStreamingAgent
foreach (var c in reply)
{
yield return new TextMessageUpdate(Role.Assistant, c.ToString(), this.Name);
}
};
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// HelloAgent.cs
using Microsoft.AutoGen.Agents;
using Microsoft.AutoGen.Contracts;
using Microsoft.AutoGen.Core;
using Microsoft.Extensions.Hosting;

View File

@ -21,12 +21,10 @@
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Contracts\Microsoft.AutoGen.Contracts.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Core.Grpc\Microsoft.AutoGen.Core.Grpc.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Core\Microsoft.AutoGen.Core.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Core.Grpc\Microsoft.AutoGen.Core.Grpc.csproj" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="..\..\..\src\Microsoft.AutoGen\Agents\protos\agent_events.proto" Link="protos\agent_events.proto" />
<Protobuf Include="..\protos\agent_events.proto" Link="protos\agent_events.proto" />
</ItemGroup>
</Project>

View File

@ -1,82 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Program.cs
using Microsoft.AutoGen.Agents;
using Microsoft.AutoGen.Contracts;
using Microsoft.AutoGen.Core;
using Microsoft.AutoGen.Core.Grpc;
using Samples;
string? hostAddress = null;
bool in_host_address = false;
bool sendHello = true;
foreach (string arg in args)
{
switch (arg)
{
case "--host":
in_host_address = true;
break;
case "--nosend":
sendHello = false;
break;
case "-h":
case "--help":
PrintHelp();
Environment.Exit(0);
break;
default:
if (in_host_address)
{
hostAddress = arg;
}
break;
}
}
hostAddress ??= Environment.GetEnvironmentVariable("AGENT_HOST");
var appBuilder = new AgentsAppBuilder(); // Create app builder
// if we are using distributed, we need the AGENT_HOST var defined and then we will use the grpc runtime
bool usingGrpc = false;
if (hostAddress is string agentHost)
{
usingGrpc = true;
Console.WriteLine($"connecting to {agentHost}");
appBuilder.AddGrpcAgentWorker(agentHost)
.AddAgent<HelloAgent>("HelloAgent");
}
else
{
// Set up app builder for in-process runtime, allow message delivery to self, and add the Hello agent
appBuilder.UseInProcessRuntime(deliverToSelf: true).AddAgent<HelloAgent>("HelloAgent");
}
// Set up app builder for in-process runtime, allow message delivery to self, and add the Hello agent
AgentsAppBuilder appBuilder = new AgentsAppBuilder()
.UseInProcessRuntime(deliverToSelf: true)
.AddAgent<HelloAgent>("HelloAgent");
var app = await appBuilder.BuildAsync(); // Build the app
await app.StartAsync();
// Create a custom message type from proto and define message
if (sendHello)
{
var message = new NewMessageReceived { Message = "Hello World!" };
await app.PublishMessageAsync(message, new TopicId("HelloTopic")).ConfigureAwait(false); // Publish custom message (handler has been set in HelloAgent)
}
else if (!usingGrpc)
{
Console.Write("Warning: Using --nosend with the InProcessRuntime will hang. Terminating.");
Environment.Exit(-1);
}
await app.WaitForShutdownAsync().ConfigureAwait(false); // Wait for shutdown from agent
static void PrintHelp()
{
/*
HelloAgent [--host <hostAddress>] [--nosend]
--host Use gRPC gateway at <hostAddress>; this can also be set using the AGENT_HOST Environment Variable
--nosend Do not send the starting message. Note: This means HelloAgent will wait until some other agent will send
that message. This will not work when using the InProcessRuntime.
*/
Console.WriteLine("HelloAgent [--host <hostAddress>] [--nosend]");
Console.WriteLine(" --host \tUse gRPC gateway at <hostAddress>; this can also be set using the AGENT_HOST Environment Variable");
Console.WriteLine(" --nosend \tDo not send the starting message. Note: This means HelloAgent will wait until some other agent will send");
}
NewMessageReceived message = new NewMessageReceived { Message = "Hello World!" };
await app.PublishMessageAsync(message, new TopicId("HelloTopic")); // Publish custom message (handler has been set in HelloAgent)
await app.WaitForShutdownAsync(); // Wait for shutdown from agent

View File

@ -4,8 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.AI;
@ -71,48 +69,36 @@ public class FunctionContract
/// </summary>
public string? ReturnDescription { get; set; }
public static implicit operator FunctionContract(AIFunction function)
public static implicit operator FunctionContract(AIFunctionMetadata metadata)
{
var openapiScheme = function.JsonSchema;
var parameters = new List<FunctionParameterContract>();
string[] isRequiredProperties = [];
if (openapiScheme.TryGetProperty("required", out var requiredElement))
{
isRequiredProperties = requiredElement.Deserialize<string[]>() ?? [];
}
var parameterList = function.UnderlyingMethod?.GetParameters() ?? Array.Empty<ParameterInfo>();
if (openapiScheme.TryGetProperty("properties", out var propertiesElement))
{
var properties = propertiesElement.Deserialize<Dictionary<string, JsonElement>>() ?? new Dictionary<string, JsonElement>();
foreach (var property in properties)
{
var parameterType = parameterList.FirstOrDefault(p => p.Name == property.Key)?.ParameterType;
var parameter = new FunctionParameterContract
{
Name = property.Key,
ParameterType = parameterType, // TODO: Need to get the type from the schema
IsRequired = isRequiredProperties.Contains(property.Key),
};
if (property.Value.TryGetProperty("description", out var descriptionElement))
{
parameter.Description = descriptionElement.GetString();
}
if (property.Value.TryGetProperty("default", out var defaultValueElement))
{
parameter.DefaultValue = defaultValueElement.Deserialize<object>();
}
parameters.Add(parameter);
}
}
return new FunctionContract
{
Namespace = function.AdditionalProperties.ContainsKey(NamespaceKey) ? function.AdditionalProperties[NamespaceKey] as string : null,
ClassName = function.AdditionalProperties.ContainsKey(ClassNameKey) ? function.AdditionalProperties[ClassNameKey] as string : null,
Name = function.Name,
Description = function.Description,
Parameters = parameters,
Namespace = metadata.AdditionalProperties.ContainsKey(NamespaceKey) ? metadata.AdditionalProperties[NamespaceKey] as string : null,
ClassName = metadata.AdditionalProperties.ContainsKey(ClassNameKey) ? metadata.AdditionalProperties[ClassNameKey] as string : null,
Name = metadata.Name,
Description = metadata.Description,
Parameters = metadata.Parameters?.Select(p => (FunctionParameterContract)p).ToList(),
ReturnType = metadata.ReturnParameter.ParameterType,
ReturnDescription = metadata.ReturnParameter.Description,
};
}
public static implicit operator AIFunctionMetadata(FunctionContract contract)
{
return new AIFunctionMetadata(contract.Name)
{
Description = contract.Description,
ReturnParameter = new AIFunctionReturnParameterMetadata()
{
Description = contract.ReturnDescription,
ParameterType = contract.ReturnType,
},
AdditionalProperties = new Dictionary<string, object?>
{
[NamespaceKey] = contract.Namespace,
[ClassNameKey] = contract.ClassName,
},
Parameters = [.. contract.Parameters?.Select(p => (AIFunctionParameterMetadata)p)!],
};
}
}
@ -146,4 +132,29 @@ public class FunctionParameterContract
/// The default value of the parameter.
/// </summary>
public object? DefaultValue { get; set; }
// convert to/from FunctionParameterMetadata
public static implicit operator FunctionParameterContract(AIFunctionParameterMetadata metadata)
{
return new FunctionParameterContract
{
Name = metadata.Name,
Description = metadata.Description,
ParameterType = metadata.ParameterType,
IsRequired = metadata.IsRequired,
DefaultValue = metadata.DefaultValue,
};
}
public static implicit operator AIFunctionParameterMetadata(FunctionParameterContract contract)
{
return new AIFunctionParameterMetadata(contract.Name!)
{
DefaultValue = contract.DefaultValue,
Description = contract.Description,
IsRequired = contract.IsRequired,
ParameterType = contract.ParameterType,
HasDefaultValue = contract.DefaultValue != null,
};
}
}

View File

@ -53,9 +53,9 @@ public class FunctionCallMiddleware : IStreamingMiddleware
public FunctionCallMiddleware(IEnumerable<AIFunction> functions, string? name = null)
{
this.Name = name ?? nameof(FunctionCallMiddleware);
this.functions = functions.Select(f => (FunctionContract)f).ToArray();
this.functions = functions.Select(f => (FunctionContract)f.Metadata).ToArray();
this.functionMap = functions.Select(f => (f.Name, this.AIToolInvokeWrapper(f.InvokeAsync))).ToDictionary(f => f.Name, f => f.Item2);
this.functionMap = functions.Select(f => (f.Metadata.Name, this.AIToolInvokeWrapper(f.InvokeAsync))).ToDictionary(f => f.Name, f => f.Item2);
}
public string? Name { get; }

View File

@ -8,9 +8,9 @@ namespace AutoGen.SemanticKernel.Extension;
public static class KernelExtension
{
public static SemanticKernelAgent ToSemanticKernelAgent(this Kernel kernel, string name, string systemMessage = "You are a helpful AI assistant", string? modelServiceId = null, PromptExecutionSettings? settings = null)
public static SemanticKernelAgent ToSemanticKernelAgent(this Kernel kernel, string name, string systemMessage = "You are a helpful AI assistant", PromptExecutionSettings? settings = null)
{
return new SemanticKernelAgent(kernel, name, systemMessage, modelServiceId, settings);
return new SemanticKernelAgent(kernel, name, systemMessage, settings);
}
/// <summary>

View File

@ -32,28 +32,17 @@ public class SemanticKernelAgent : IStreamingAgent
{
private readonly Kernel _kernel;
private readonly string _systemMessage;
private readonly string? _modelServiceId;
private readonly PromptExecutionSettings? _settings;
/// <summary>
/// Create a new instance of <see cref="SemanticKernelAgent"/>
/// </summary>
/// <param name="kernel">The Semantic Kernel - Kernel object</param>
/// <param name="name">The name of the agent.</param>
/// <param name="systemMessage">The system message.</param>
/// <param name="modelServiceId">Optional serviceId for the model.</param>
/// <param name="settings">The prompt execution settings.</param>
public SemanticKernelAgent(
Kernel kernel,
string name,
string systemMessage = "You are a helpful AI assistant",
string? modelServiceId = null,
PromptExecutionSettings? settings = null)
{
_kernel = kernel;
this.Name = name;
_systemMessage = systemMessage;
_modelServiceId = modelServiceId;
_settings = settings;
}
@ -63,7 +52,7 @@ public class SemanticKernelAgent : IStreamingAgent
{
var chatHistory = BuildChatHistory(messages);
var option = BuildOption(options);
var chatService = GetChatCompletionService();
var chatService = _kernel.GetRequiredService<IChatCompletionService>();
var reply = await chatService.GetChatMessageContentsAsync(chatHistory, option, _kernel, cancellationToken);
@ -82,7 +71,7 @@ public class SemanticKernelAgent : IStreamingAgent
{
var chatHistory = BuildChatHistory(messages);
var option = BuildOption(options);
var chatService = GetChatCompletionService();
var chatService = _kernel.GetRequiredService<IChatCompletionService>();
var response = chatService.GetStreamingChatMessageContentsAsync(chatHistory, option, _kernel, cancellationToken);
await foreach (var content in response)
@ -119,13 +108,6 @@ public class SemanticKernelAgent : IStreamingAgent
};
}
private IChatCompletionService GetChatCompletionService()
{
return string.IsNullOrEmpty(_modelServiceId)
? _kernel.GetRequiredService<IChatCompletionService>()
: _kernel.GetRequiredService<IChatCompletionService>(_modelServiceId);
}
private IEnumerable<ChatMessageContent> ProcessMessage(IEnumerable<IMessage> messages)
{
return messages.Select(m => m switch

View File

@ -26,9 +26,8 @@ public class SemanticKernelChatCompletionAgent : IAgent
public async Task<IMessage> GenerateReplyAsync(IEnumerable<IMessage> messages, GenerateReplyOptions? options = null,
CancellationToken cancellationToken = default)
{
var agentThread = new ChatHistoryAgentThread(BuildChatHistory(messages));
var reply = await _chatCompletionAgent
.InvokeAsync(agentThread, cancellationToken: cancellationToken)
ChatMessageContent[] reply = await _chatCompletionAgent
.InvokeAsync(BuildChatHistory(messages), cancellationToken: cancellationToken)
.ToArrayAsync(cancellationToken: cancellationToken);
return reply.Length > 1

View File

@ -2,7 +2,6 @@
// ChatAgent.cs
using System.Text.RegularExpressions;
using Microsoft.AutoGen.Contracts;
namespace Microsoft.AutoGen.AgentChat.Abstractions;
@ -166,29 +165,29 @@ public class ChatStreamFrame : StreamingFrame<Response, AgentMessage>;
/// </summary>
public interface IChatAgent :
IHandleChat<IEnumerable<ChatMessage>, Response>,
IHandleStream<IEnumerable<ChatMessage>, ChatStreamFrame>,
ISaveState
IHandleStream<IEnumerable<ChatMessage>, ChatStreamFrame>
{
/// <summary>
/// The name of the agent. This is used by team to uniquely identify the agent.It should be unique within the team.
/// </summary>
public AgentName Name { get; }
AgentName Name { get; }
/// <summary>
/// The description of the agent. This is used by team to make decisions about which agents to use.The description
/// should describe the agent's capabilities and how to interact with it.
/// </summary>
public string Description { get; }
string Description { get; }
/// <summary>
/// The types of messages that the agent produces.
/// </summary>
public IEnumerable<Type> ProducedMessageTypes { get; } // TODO: Is there a way to make this part of the type somehow? Annotations, or IProduce<>? Do we ever actually access this?
IEnumerable<Type> ProducedMessageTypes { get; } // TODO: Is there a way to make this part of the type somehow?
// Annotations, or IProduce<>? Do we ever actually access this?
/// <summary>
/// Reset the agent to its initialization state.
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public ValueTask ResetAsync(CancellationToken cancellationToken);
ValueTask ResetAsync(CancellationToken cancellationToken);
}

View File

@ -1,14 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ITeam.cs
using Microsoft.AutoGen.Contracts;
namespace Microsoft.AutoGen.AgentChat.Abstractions;
/// <summary>
/// A team of agents.
/// </summary>
public interface ITeam : ITaskRunner, ISaveState
public interface ITeam : ITaskRunner
{
/// <summary>
/// Reset the team and all its participants to its initial state.

View File

@ -5,22 +5,22 @@ namespace Microsoft.AutoGen.AgentChat.Abstractions;
public interface IHandleChat<in TIn>
{
public ValueTask HandleAsync(TIn item)
ValueTask HandleAsync(TIn item)
{
return this.HandleAsync(item, CancellationToken.None);
}
public ValueTask HandleAsync(TIn item, CancellationToken cancellationToken);
ValueTask HandleAsync(TIn item, CancellationToken cancellationToken);
}
public interface IHandleChat<in TIn, TOut> // TODO: Map this to IHandle<> somehow?
{
public ValueTask<TOut> HandleAsync(TIn item)
ValueTask<TOut> HandleAsync(TIn item)
{
return this.HandleAsync(item, CancellationToken.None);
}
public ValueTask<TOut> HandleAsync(TIn item, CancellationToken cancellationToken);
ValueTask<TOut> HandleAsync(TIn item, CancellationToken cancellationToken);
}
public interface IHandleDefault : IHandleChat<object>
@ -29,10 +29,10 @@ public interface IHandleDefault : IHandleChat<object>
public interface IHandleStream<in TIn, TOut>
{
public IAsyncEnumerable<TOut> StreamAsync(TIn item)
IAsyncEnumerable<TOut> StreamAsync(TIn item)
{
return this.StreamAsync(item, CancellationToken.None);
}
public IAsyncEnumerable<TOut> StreamAsync(TIn item, CancellationToken cancellationToken);
IAsyncEnumerable<TOut> StreamAsync(TIn item, CancellationToken cancellationToken);
}

View File

@ -4,10 +4,7 @@
using System.Collections;
using System.Diagnostics;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Microsoft.AutoGen.AgentChat.GroupChat;
using Microsoft.Extensions.AI;
namespace Microsoft.AutoGen.AgentChat.Abstractions;
@ -25,11 +22,6 @@ public abstract class AgentMessage
/// </summary>
public required string Source { get; set; }
/// <summary>
/// The <see cref="IChatClient"/> usage incurred when producing this message.
/// </summary>
public RequestUsage? ModelUsage { get; set; }
// IMPORTANT NOTE: Unlike the ITypeMarshal<AgentMessage, WireProtocol.AgentMessage> implementation in ProtobufTypeMarshal,
// the .ToWire() call on this is intended to be used for directly converting a concrete message type to its leaf representation.
// In the context of Protobuf these may not be the same due to discriminated union types being real types, as opposed to
@ -124,7 +116,7 @@ public struct MultiModalData
/// <param name="item">The <see cref="AIContent"/> to wrap.</param>
/// <returns>A <see cref="MultiModalData"/> instance wrapping the <paramref name="item"/>.</returns>
/// <exception cref="ArgumentException">
/// Thrown if the <paramref name="item"/> is not a <see cref="TextContent"/> or <see cref="DataContent"/>.
/// Thrown if the <paramref name="item"/> is not a <see cref="TextContent"/> or <see cref="ImageContent"/>.
/// </exception>
public static MultiModalData CheckTypeAndCreate(AIContent item)
{
@ -132,7 +124,7 @@ public struct MultiModalData
{
return new MultiModalData(text);
}
else if (item is DataContent image)
else if (item is ImageContent image)
{
return new MultiModalData(image);
}
@ -163,10 +155,10 @@ public struct MultiModalData
}
/// <summary>
/// Initializes a new instance of the <see cref="MultiModalData"/> with an <see cref="DataContent"/>.
/// Initializes a new instance of the <see cref="MultiModalData"/> with an <see cref="ImageContent"/>.
/// </summary>
/// <param name="image">The image to wrap.</param>
public MultiModalData(DataContent image)
public MultiModalData(ImageContent image)
{
ContentType = Type.Image;
AIContent = image;
@ -254,12 +246,12 @@ public class MultiModalMessage : ChatMessage, IList<AIContent>
}
/// <summary>
/// Adds a range of <see cref="DataContent"/> to the message.
/// Adds a range of <see cref="ImageContent"/> to the message.
/// </summary>
/// <param name="images">The items to add.</param>
public void AddRange(IEnumerable<DataContent> images)
public void AddRange(IEnumerable<ImageContent> images)
{
foreach (DataContent image in images)
foreach (ImageContent image in images)
{
this.Add(image);
}
@ -287,7 +279,7 @@ public class MultiModalMessage : ChatMessage, IList<AIContent>
/// Adds a <see cref="TextContent"/> to the message.
/// </summary>
/// <param name="image">The image to add.</param>
public void Add(DataContent image)
public void Add(ImageContent image)
{
this.Content.Add(new(image));
}
@ -374,7 +366,7 @@ public class MultiModalMessage : ChatMessage, IList<AIContent>
}
/// <inheritdoc cref="IList{ImageContent}.Insert(int, ImageContent)"/>
public void Insert(int index, DataContent image)
public void Insert(int index, ImageContent image)
{
this.Content.Insert(index, new(image));
}
@ -500,11 +492,6 @@ public class FunctionExecutionResult
/// </summary>
public required string Id { get; set; }
/// <summary>
/// The name of the function that was called.
/// </summary>
public required string Name { get; set; }
/// <summary>
/// The result of calling the function.
/// </summary>
@ -610,7 +597,7 @@ public static class CompletionChatMessageExtensions
{
contentBuilder.AppendLine(textContent.Text);
}
else if (content is DataContent)
else if (content is ImageContent)
{
contentBuilder.AppendLine("[Image]");
}
@ -627,129 +614,3 @@ public static class CompletionChatMessageExtensions
};
}
}
public static class MessageSerializationHelpers
{
internal sealed class TypeNode(Type type)
{
public Type Type { get; } = type;
public TypeNode? Parent { get; set; }
public TypeNode Root => this.Parent?.Root ?? this;
public List<TypeNode> Children { get; } = new List<TypeNode>();
public IEnumerable<Type> ChildrenTransitiveClosure
{
get
{
return this.Children.Select(c => c.Type)
.Concat(Children.SelectMany(c => c.ChildrenTransitiveClosure));
}
}
}
internal sealed class TypeTree
{
private static IEnumerable<Type> GetDerivedTypes(Type type)
{
// Across all assemblies
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
// Get all types in the assembly
foreach (var derivedType in assembly.GetTypes().Where(t => type.IsAssignableFrom(t) && t != type))
{
yield return derivedType;
}
}
}
private TypeNode EnsureTypeNode(Type type)
{
if (!this.TypeNodes.TryGetValue(type, out TypeNode? currentNode))
{
currentNode = this.TypeNodes[type] = new TypeNode(type);
}
return currentNode;
}
private void EnsureType(Type type)
{
TypeNode? currentNode = this.EnsureTypeNode(type);
while (currentNode != null &&
currentNode.Parent == null &&
!this.RootTypes.Contains(currentNode.Type))
{
Type parentType = currentNode.Type.BaseType
?? throw new InvalidOperationException("We should never have a non-Root, underived base");
TypeNode parentNode = this.EnsureTypeNode(parentType);
currentNode.Parent = parentNode;
parentNode.Children.Add(currentNode);
currentNode = parentNode;
}
}
public HashSet<Type> RootTypes { get; }
public Dictionary<Type, TypeNode> TypeNodes { get; } = new Dictionary<Type, TypeNode>();
public TypeTree(params Type[] rootTypes)
{
this.RootTypes = new HashSet<Type>();
foreach (var rootType in rootTypes)
{
// Check that there are no other types that this type derives from in the root types
// or vice versa
if (this.RootTypes.Any(t => t.IsAssignableFrom(rootType) || rootType.IsAssignableFrom(t)))
{
throw new ArgumentException($"Root types cannot be derived from each other: {rootType.Name}");
}
this.RootTypes.Add(rootType);
this.EnsureType(rootType);
foreach (var derivedType in GetDerivedTypes(rootType))
{
this.EnsureType(derivedType);
}
}
}
}
internal static readonly TypeTree MessageTypeTree = new(typeof(AgentMessage), typeof(GroupChatEventBase));
internal sealed class MessagesTypeInfoResolver : DefaultJsonTypeInfoResolver
{
public override JsonTypeInfo GetTypeInfo(Type type, JsonSerializerOptions options)
{
JsonTypeInfo baseTypeInfo = base.GetTypeInfo(type, options);
if (MessageTypeTree.TypeNodes.TryGetValue(type, out TypeNode? typeNode) &&
typeNode.Children.Any()) // Only add polymorphism info if there are derived children
{
if (baseTypeInfo.PolymorphismOptions == null)
{
baseTypeInfo.PolymorphismOptions = new JsonPolymorphismOptions();
}
baseTypeInfo.PolymorphismOptions.IgnoreUnrecognizedTypeDiscriminators = true;
baseTypeInfo.PolymorphismOptions.UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FailSerialization;
foreach (Type childType in typeNode.ChildrenTransitiveClosure)
{
if (childType.IsAbstract || childType.IsInterface || childType.IsGenericTypeDefinition)
{
// Can only deserialize concrete, complete types.
continue;
}
baseTypeInfo.PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(childType, childType.FullName ?? childType.Name));
}
}
return baseTypeInfo;
}
}
}

View File

@ -1,73 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ModelContext.cs
using System.Text.Json;
using Microsoft.AutoGen.AgentChat.State;
using Microsoft.AutoGen.Contracts;
using LLMMessage = Microsoft.Extensions.AI.ChatMessage;
namespace Microsoft.AutoGen.AgentChat.Abstractions;
public interface IModelContext : ISaveState
{
public void Add(LLMMessage message);
public void Clear();
public IEnumerable<LLMMessage> Messages { get; }
}
public sealed class ModelContextState : BaseState
{
public List<LLMMessage> Messages { get; set; } = new();
}
public abstract class ModelContextBase : IModelContext
{
protected readonly List<LLMMessage> messages;
public abstract IEnumerable<LLMMessage> Messages { get; }
public ModelContextBase(params IEnumerable<LLMMessage> messages)
{
this.messages = [.. messages];
}
public void Add(LLMMessage message)
{
this.messages.Add(message);
}
public void Clear()
{
this.messages.Clear();
}
public ValueTask<JsonElement> SaveStateAsync()
{
SerializedState state = SerializedState.Create(new ModelContextState { Messages = this.messages });
return ValueTask.FromResult(state.AsJson());
}
public ValueTask LoadStateAsync(JsonElement state)
{
SerializedState serializedState = new(state);
ModelContextState modelContextState = serializedState.As<ModelContextState>();
this.messages.Clear();
this.messages.AddRange(modelContextState.Messages);
return ValueTask.CompletedTask;
}
}
public sealed class UnboundedModelContext : ModelContextBase
{
public UnboundedModelContext(params IEnumerable<LLMMessage> messages) : base(messages)
{
}
public override IEnumerable<LLMMessage> Messages => this.messages;
}
// TODO: Promote ModelContext to AutoGen.Core

View File

@ -59,7 +59,7 @@ public interface ITaskRunner
/// <param name="task">The task definition in text form.</param>
/// <param name="cancellationToken"></param>
/// <returns>The result of running the task.</returns>
public async ValueTask<TaskResult> RunAsync(string task, CancellationToken cancellationToken = default) =>
async ValueTask<TaskResult> RunAsync(string task, CancellationToken cancellationToken = default) =>
await this.RunAsync(ToMessage(task)!, cancellationToken);
/// <summary>
@ -73,7 +73,7 @@ public interface ITaskRunner
/// <param name="cancellationToken"></param>
/// <returns>The result of running the task.</returns>
/// <exception cref="InvalidOperationException">If no response is generated.</exception>
public async ValueTask<TaskResult> RunAsync(ChatMessage task, CancellationToken cancellationToken = default)
async ValueTask<TaskResult> RunAsync(ChatMessage task, CancellationToken cancellationToken = default)
{
await foreach (TaskFrame frame in this.StreamAsync(task, cancellationToken))
{
@ -98,7 +98,7 @@ public interface ITaskRunner
/// <param name="cancellationToken"></param>
/// <returns>A stream of <see cref="TaskFrame"/> containing internal messages and intermediate results followed by
/// the final <see cref="TaskResult"/></returns>
public IAsyncEnumerable<TaskFrame> StreamAsync(string task, CancellationToken cancellationToken = default) =>
IAsyncEnumerable<TaskFrame> StreamAsync(string task, CancellationToken cancellationToken = default) =>
this.StreamAsync(ToMessage(task), cancellationToken);
/// <summary>
@ -113,5 +113,5 @@ public interface ITaskRunner
/// <param name="cancellationToken"></param>
/// <returns>A stream of <see cref="TaskFrame"/> containing internal messages and intermediate results followed by
/// the final <see cref="TaskResult"/></returns>
public IAsyncEnumerable<TaskFrame> StreamAsync(ChatMessage? task, CancellationToken cancellationToken = default);
IAsyncEnumerable<TaskFrame> StreamAsync(ChatMessage? task, CancellationToken cancellationToken = default);
}

View File

@ -3,29 +3,6 @@
namespace Microsoft.AutoGen.AgentChat.Abstractions;
public static class TerminationConditionExtensions
{
/// <summary>
/// Combine this termination condition with another using a logical OR.
/// </summary>
/// <param name="other">Another termination condition.</param>
/// <returns>The combined termination condition, with appropriate short-circuiting.</returns>
public static ITerminationCondition Or(this ITerminationCondition this_, ITerminationCondition other)
{
return new CombinerCondition(CombinerCondition.Or, this_, other);
}
/// <summary>
/// Combine this termination condition with another using a logical AND.
/// </summary>
/// <param name="other">Another termination condition.</param>
/// <returns>The combined termination condition, with appropriate short-circuiting.</returns>
public static ITerminationCondition And(this ITerminationCondition this_, ITerminationCondition other)
{
return new CombinerCondition(CombinerCondition.And, this_, other);
}
}
/// <summary>
/// A stateful condition that determines when a conversation should be terminated.
///
@ -35,15 +12,14 @@ public static class TerminationConditionExtensions
///
/// Once a termination condition has been reached, it must be <see cref="Reset()"/> before it can be used again.
///
/// Termination conditions can be combined using the <see cref="TerminationConditionExtensions.Or"/> and
/// <see cref="TerminationConditionExtensions.And"/> methods.
/// Termination conditions can be combined using the <see cref="Or"/> and <see cref="And"/> methods.
/// </summary>
public interface ITerminationCondition
{
/// <summary>
/// Checks if the termination condition has been reached
/// </summary>
public bool IsTerminated { get; }
bool IsTerminated { get; }
/// <summary>
/// Check if the conversation should be terminated based on the messages received
@ -54,45 +30,31 @@ public interface ITerminationCondition
/// <returns>A <see cref="StopMessage"/> if the conversation should be terminated, or <c>null</c>
/// otherwise.</returns>
/// <exception cref="TerminatedException">If the termination condition has already been reached.</exception>
public ValueTask<StopMessage?> CheckAndUpdateAsync(IList<AgentMessage> messages);
ValueTask<StopMessage?> CheckAndUpdateAsync(IList<AgentMessage> messages);
/// <summary>
/// Resets the termination condition.
/// </summary>
public void Reset();
void Reset();
/// <summary>
/// Combine two termination conditions with another using an associative, short-circuiting OR.
/// Combine this termination condition with another using a logical OR.
/// </summary>
/// <param name="left">
/// The left-hand side termination condition. If this condition is already a disjunction, the RHS condition is added to the list of clauses.
/// </param>
/// <param name="right">
/// The right-hand side termination condition. If the LHS condition is already a disjunction, this condition is added to the list of clauses.
/// </param>
/// <returns>
/// The combined termination condition, with appropriate short-circuiting.
/// </returns>
public static ITerminationCondition operator |(ITerminationCondition left, ITerminationCondition right)
/// <param name="other">Another termination condition.</param>
/// <returns>The combined termination condition, with appropriate short-circuiting.</returns>
ITerminationCondition Or(ITerminationCondition other)
{
return left.Or(right);
return new CombinerCondition(CombinerCondition.Or, this, other);
}
/// <summary>
/// Combine two termination conditions with another using an associative, short-circuiting AND.
/// Combine this termination condition with another using a logical AND.
/// </summary>
/// <param name="left">
/// The left-hand side termination condition. If this condition is already a conjunction, the RHS condition is added to the list of clauses.
/// </param>
/// <param name="right">
/// The right-hand side termination condition. If the LHS condition is already a conjunction, this condition is added to the list of clauses.
/// </param>
/// <returns>
/// The combined termination condition, with appropriate short-circuiting.
/// </returns>
public static ITerminationCondition operator &(ITerminationCondition left, ITerminationCondition right)
/// <param name="other">Another termination condition.</param>
/// <returns>The combined termination condition, with appropriate short-circuiting.</returns>
ITerminationCondition And(ITerminationCondition other)
{
return left.And(right);
return new CombinerCondition(CombinerCondition.And, this, other);
}
}
@ -205,4 +167,38 @@ internal sealed class CombinerCondition : ITerminationCondition
return null;
}
/// <inheritdoc cref="ITerminationCondition.Or" />
/// <remarks>
/// If this condition is already a disjunction, the new condition is added to the list of clauses.
/// </remarks>
ITerminationCondition ITerminationCondition.Or(ITerminationCondition other)
{
if (this.conjunction == Or)
{
this.clauses.Add(other);
return this;
}
else
{
return new CombinerCondition(Or, this, new CombinerCondition(Or, other));
}
}
/// <inheritdoc cref="ITerminationCondition.And" />
/// <remarks>
/// If this condition is already a conjunction, the new condition is added to the list of clauses.
/// </remarks>
ITerminationCondition ITerminationCondition.And(ITerminationCondition other)
{
if (this.conjunction == And)
{
this.clauses.Add(other);
return this;
}
else
{
return new CombinerCondition(And, this, new CombinerCondition(And, other));
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Tools.cs
using System.ComponentModel;
using System.Reflection;
using Microsoft.Extensions.AI;
@ -9,6 +10,37 @@ namespace Microsoft.AutoGen.AgentChat.Abstractions;
// TODO: This likely should live as a "Component" in an Agent-building ClassLib?
// It seems like it could have applicability beyond AgentChat.
public static class ReflectionExtensions
{
public static AIFunctionParameterMetadata ToAIFunctionMetadata(this ParameterInfo pi)
{
return new AIFunctionParameterMetadata(pi.Name!)
{
Description = pi.GetCustomAttribute<DescriptionAttribute>()?.Description,
ParameterType = pi.ParameterType,
HasDefaultValue = pi.HasDefaultValue,
IsRequired = !pi.HasDefaultValue,
DefaultValue = pi.DefaultValue,
// Schema = JSONSchema of type
};
}
public static AIFunctionReturnParameterMetadata ToAIFunctionReturnMetadata(this ParameterInfo rpi)
{
return new AIFunctionReturnParameterMetadata
{
Description = rpi.GetCustomAttribute<DescriptionAttribute>()?.Description,
ParameterType = rpi.ParameterType
//Schema = JSONSchema of type
};
}
}
public class ParameterSchema(string name, Type type, bool isRequired = false, object? defaultValue = default)
{
public string Name { get; } = name;
@ -22,6 +54,15 @@ public class ParameterSchema(string name, Type type, bool isRequired = false, ob
Type parameterType = parameterInfo.ParameterType;
return ParameterSchema<object>.Create(parameterType, parameterInfo.Name!, parameterInfo.HasDefaultValue, parameterInfo.DefaultValue);
}
public static implicit operator ParameterSchema(AIFunctionParameterMetadata parameterMetadata)
{
Type parameterType = parameterMetadata.ParameterType!; // TODO: Deal with missing ParameterTypes
return ParameterSchema<object>.Create(parameterType,
parameterMetadata.Name,
parameterMetadata.IsRequired,
parameterMetadata.DefaultValue);
}
}
// TODO: Can this be obviated by AIFunctionParameter?
@ -41,10 +82,11 @@ public class ParameterSchema<T>(string name, bool isRequired = false, T? default
/// </summary>
public interface ITool
{
public string Name { get; }
public string Description { get; }
string Name { get; }
string Description { get; }
public IEnumerable<ParameterSchema> Parameters { get; }
public Type ReturnType { get; }
// TODO: State serialization
@ -94,15 +136,18 @@ public class AIFunctionTool(AIFunction aiFunction) : ITool
public AIFunction AIFunction { get; } = aiFunction;
/// <inheritdoc cref="ITool.Name" />
public string Name => this.AIFunction.Name;
public string Name => this.AIFunction.Metadata.Name;
/// <inheritdoc cref="ITool.Description" />
public string Description => this.AIFunction.Description;
public string Description => this.AIFunction.Metadata.Description;
/// <inheritdoc cref="ITool.Parameters" />
public IEnumerable<ParameterSchema> Parameters => from rawParameter in this.AIFunction.UnderlyingMethod!.GetParameters()
public IEnumerable<ParameterSchema> Parameters => from rawParameter in this.AIFunction.Metadata.Parameters
select (ParameterSchema)rawParameter;
/// <inheritdoc cref="ITool.ReturnType" />
public Type ReturnType => this.AIFunction.Metadata.ReturnParameter.ParameterType!; // TODO: Deal with missing return types
/// <inheritdoc cref="ITool.ExecuteAsync" />
public Task<object> ExecuteAsync(IEnumerable<object> parameters, CancellationToken cancellationToken = default)
=> this.ExecuteAsync(parameters, cancellationToken);
@ -119,6 +164,23 @@ public class CallableTool(string name, string description, Delegate callable)
{
internal static AIFunction CreateAIFunction(string name, string description, Delegate callable)
{
return AIFunctionFactory.Create(callable, name: name, description: description);
MethodInfo methodInfo = callable.Method;
IEnumerable<AIFunctionParameterMetadata> parameters =
from parameterInfo in methodInfo.GetParameters()
select parameterInfo.ToAIFunctionMetadata();
AIFunctionReturnParameterMetadata returnParameter = methodInfo.ReturnParameter.ToAIFunctionReturnMetadata();
AIFunctionFactoryCreateOptions createOptions = new()
{
Name = name,
Description = description,
Parameters = parameters.ToList(),
ReturnParameter = returnParameter,
// SerializerOptions = TODO: How do we maintain consistency with Python?
};
return AIFunctionFactory.Create(callable, createOptions);
}
}

View File

@ -1,10 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Usage.cs
namespace Microsoft.AutoGen.AgentChat.Abstractions;
public struct RequestUsage
{
public int PromptTokens { get; set; }
public int CompletionTokens { get; set; }
}

View File

@ -1,9 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ChatAgentRouter.cs
using System.Text.Json;
using Microsoft.AutoGen.AgentChat.Abstractions;
using Microsoft.AutoGen.AgentChat.State;
using Microsoft.AutoGen.Contracts;
using Microsoft.AutoGen.Core;
using Microsoft.Extensions.Logging;
@ -26,8 +24,7 @@ internal sealed class ChatAgentRouter : HostableAgentAdapter,
IHandle<GroupChatStart>,
IHandle<GroupChatAgentResponse>,
IHandle<GroupChatRequestPublish>,
IHandle<GroupChatReset>,
ISaveState
IHandle<GroupChatReset>
{
private readonly TopicId parentTopic;
private readonly TopicId outputTopic;
@ -69,6 +66,7 @@ internal sealed class ChatAgentRouter : HostableAgentAdapter,
// place.
await foreach (ChatStreamFrame frame in this.agent.StreamAsync(this.MessageBuffer, messageContext.CancellationToken))
{
// TODO: call publish message
switch (frame.Type)
{
case ChatStreamFrame.FrameType.Response:
@ -96,24 +94,5 @@ internal sealed class ChatAgentRouter : HostableAgentAdapter,
this.MessageBuffer.Clear();
return this.agent.ResetAsync(messageContext.CancellationToken);
}
async ValueTask<JsonElement> ISaveState.SaveStateAsync()
{
ChatAgentContainerState state = new ChatAgentContainerState
{
AgentState = new SerializedState(await this.agent.SaveStateAsync()),
MessageBuffer = this.MessageBuffer
};
return SerializedState.Create(state).AsJson();
}
ValueTask ISaveState.LoadStateAsync(JsonElement state)
{
ChatAgentContainerState parsedState = new SerializedState(state).As<ChatAgentContainerState>();
this.MessageBuffer = parsedState.MessageBuffer;
return this.agent.LoadStateAsync(parsedState.AgentState.AsJson());
}
}

View File

@ -4,9 +4,7 @@
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.AutoGen.AgentChat.Abstractions;
using Microsoft.AutoGen.AgentChat.State;
using Microsoft.AutoGen.Contracts;
using Microsoft.AutoGen.Core;
@ -80,8 +78,7 @@ public abstract class GroupChatBase<TManager> : ITeam where TManager : GroupChat
private GroupChatOptions GroupChatOptions { get; }
private readonly RuntimeLayer runtimeLayer;
private readonly List<AgentMessage> messageThread = new();
private Dictionary<string, AgentChatConfig> Participants { get; } = new();
protected GroupChatBase(List<IChatAgent> participants, ITerminationCondition? terminationCondition = null, int? maxTurns = null)
@ -99,10 +96,9 @@ public abstract class GroupChatBase<TManager> : ITeam where TManager : GroupChat
this.GroupChatOptions.Participants[participant.Name] = new GroupParticipant(config.ParticipantTopicType, participant.Description);
}
this.TeamId = Guid.NewGuid().ToString().ToLowerInvariant();
this.messageThread = new List<AgentMessage>(); // TODO: Allow injecting this
this.runtimeLayer = new RuntimeLayer(this);
this.RunManager = new(this.InitializationLayersInternal);
this.TeamId = Guid.NewGuid().ToString().ToLowerInvariant();
}
public string TeamId
@ -118,7 +114,7 @@ public abstract class GroupChatBase<TManager> : ITeam where TManager : GroupChat
if (Activator.CreateInstance(typeof(TManager), options) is TManager result)
{
return result;
}
};
}
catch (TargetInvocationException tie)
{
@ -132,58 +128,17 @@ public abstract class GroupChatBase<TManager> : ITeam where TManager : GroupChat
throw new Exception("Could not create chat manager; make sure that it contains a ctor() or ctor(GroupChatOptions), or override the CreateChatManager method");
}
private sealed class RuntimeLayer(GroupChatBase<TManager> groupChat) : IRunContextLayer
// TODO: Turn this into an IDisposable-based utility
private int running; // = 0
private bool EnsureSingleRun()
{
public GroupChatBase<TManager> GroupChat { get; } = groupChat;
public InProcessRuntime? Runtime { get; private set; }
public OutputSink? OutputSink { get; private set; }
public Task? InitOnceTask { get; set; }
public Task ShutdownTask { get; set; } = Task.CompletedTask;
public async ValueTask DeinitializeAsync()
{
await this.ShutdownTask;
}
private async Task CreateRuntime()
{
this.Runtime = new InProcessRuntime();
foreach (AgentChatConfig config in this.GroupChat.Participants.Values)
{
await this.Runtime.RegisterChatAgentAsync(config);
}
await this.Runtime.RegisterGroupChatManagerAsync(this.GroupChat.GroupChatOptions, this.GroupChat.TeamId, this.GroupChat.CreateChatManager);
this.OutputSink = new OutputSink();
await this.Runtime.RegisterOutputCollectorAsync(this.OutputSink, this.GroupChat.GroupChatOptions.OutputTopicType);
}
public bool HasRunOnce => this.InitOnceTask != null;
public async ValueTask InitializeAsync()
{
if (this.InitOnceTask == null)
{
this.InitOnceTask = this.CreateRuntime();
}
await this.InitOnceTask;
await this.Runtime!.StartAsync();
}
return Interlocked.CompareExchange(ref running, 1, 0) == 0;
}
private IRunContextLayer[] InitializationLayersInternal =>
[
this.runtimeLayer, ..this.InitializationLayers
];
protected virtual IEnumerable<IRunContextLayer> InitializationLayers => [];
private RunManager RunManager { get; }
private void EndRun()
{
this.running = 0;
}
public IAsyncEnumerable<TaskFrame> StreamAsync(string task, CancellationToken cancellationToken)
{
@ -202,149 +157,79 @@ public abstract class GroupChatBase<TManager> : ITeam where TManager : GroupChat
return this.StreamAsync(taskStart, cancellationToken);
}
private InProcessRuntime? Runtime => this.runtimeLayer.Runtime;
private OutputSink? OutputSink => this.runtimeLayer.OutputSink;
private Task ShutdownTask
public ValueTask ResetAsync(CancellationToken cancel)
{
get => this.runtimeLayer.ShutdownTask;
set => this.runtimeLayer.ShutdownTask = value;
return ValueTask.CompletedTask;
}
private Func<CancellationToken, ValueTask> PrepareStream(ChatMessage task)
{
GroupChatStart taskMessage = new GroupChatStart
{
Messages = [task]
};
return async (CancellationToken cancellationToken) =>
{
AgentId chatManagerId = new AgentId(GroupChatManagerTopicType, this.TeamId);
await this.Runtime!.SendMessageAsync(taskMessage, chatManagerId, cancellationToken: cancellationToken);
this.ShutdownTask = Task.Run(this.Runtime!.RunUntilIdleAsync);
};
}
private async IAsyncEnumerable<TaskFrame> StreamOutput([EnumeratorCancellation] CancellationToken cancellationToken)
{
List<AgentMessage> runMessages = new();
while (true)
{
OutputSink.SinkFrame frame = await this.OutputSink!.WaitForDataAsync(cancellationToken);
runMessages.AddRange(frame.Messages);
foreach (AgentMessage message in frame.Messages)
{
yield return new TaskFrame(message);
}
if (frame.IsTerminal)
{
TaskResult result = new TaskResult(runMessages);
yield return new TaskFrame(result);
break;
}
}
}
public IAsyncEnumerable<TaskFrame> StreamAsync(ChatMessage? task, CancellationToken cancellationToken = default)
public async IAsyncEnumerable<TaskFrame> StreamAsync(ChatMessage? task, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
if (task == null)
{
throw new ArgumentNullException(nameof(task));
}
const string TaskAlreadyRunning = "The task is already running";
return this.RunManager.StreamAsync(
this.StreamOutput,
cancellationToken,
this.PrepareStream(task),
TaskAlreadyRunning);
}
if (!this.EnsureSingleRun())
{
throw new InvalidOperationException("The task is already running.");
}
// TODO: How do we allow the user to configure this?
//AgentsAppBuilder builder = new AgentsAppBuilder().UseInProcessRuntime();
InProcessRuntime runtime = new InProcessRuntime();
foreach (AgentChatConfig config in this.Participants.Values)
{
await runtime.RegisterChatAgentAsync(config);
}
await runtime.RegisterGroupChatManagerAsync(this.GroupChatOptions, this.TeamId, this.CreateChatManager);
OutputSink outputSink = new OutputSink();
await runtime.RegisterOutputCollectorAsync(outputSink, this.GroupChatOptions.OutputTopicType);
await runtime.StartAsync();
Task shutdownTask = Task.CompletedTask;
private async ValueTask ResetInternalAsync(CancellationToken cancel)
{
try
{
foreach (var participant in this.Participants.Values)
// TODO: Protos
GroupChatStart taskMessage = new GroupChatStart
{
await this.Runtime!.SendMessageAsync(
new GroupChatReset(),
new AgentId(participant.ParticipantTopicType, this.TeamId),
cancellationToken: cancel);
Messages = [task]
};
List<AgentMessage> runMessages = new();
AgentId chatManagerId = new AgentId(GroupChatManagerTopicType, this.TeamId);
await runtime.SendMessageAsync(taskMessage, chatManagerId, cancellationToken: cancellationToken);
shutdownTask = Task.Run(runtime.RunUntilIdleAsync);
while (true)
{
OutputSink.SinkFrame frame = await outputSink.WaitForDataAsync(cancellationToken);
runMessages.AddRange(frame.Messages);
foreach (AgentMessage message in frame.Messages)
{
yield return new TaskFrame(message);
}
if (frame.IsTerminal)
{
TaskResult result = new TaskResult(runMessages);
yield return new TaskFrame(result);
break;
}
}
await this.Runtime!.SendMessageAsync(
new GroupChatReset(),
new AgentId(GroupChatManagerTopicType, this.TeamId),
cancellationToken: cancel);
await this.Runtime!.RunUntilIdleAsync();
}
finally
{
this.OutputSink?.Reset();
}
}
this.EndRun();
public ValueTask ResetAsync(CancellationToken cancel)
{
const string TaskAlreadyRunning = "The group chat is currently running. It must be stopped before it can be reset.";
return this.RunManager.RunAsync(
this.ResetInternalAsync,
cancel,
message: TaskAlreadyRunning);
}
public ValueTask<JsonElement> SaveStateAsync()
{
if (!this.runtimeLayer.HasRunOnce)
{
throw new InvalidOperationException("The group chat has not been initialized. It must be run before it can be saved.");
}
const string TaskAlreadyRunning = "The team cannot be saved while it is running.";
return this.RunManager.RunAsync(
SaveStateInternalAsync,
CancellationToken.None, // TODO: Change this API?
message: TaskAlreadyRunning);
async ValueTask<JsonElement> SaveStateInternalAsync(CancellationToken _)
{
TeamState teamState = new()
{
TeamId = this.TeamId,
RuntimeState = await this.Runtime!.SaveStateAsync(),
};
JsonElement result = SerializedState.Create(teamState).AsJson();
await this.Runtime!.StopAsync();
return result;
}
}
public ValueTask LoadStateAsync(JsonElement state)
{
const string TaskAlreadyRunning = "The team cannot be loaded while it is running.";
return this.RunManager.RunAsync(
LoadStateInternalAsync,
CancellationToken.None, // TODO: Change this API?
message: TaskAlreadyRunning);
async ValueTask LoadStateInternalAsync(CancellationToken _)
{
TeamState parsedState = new SerializedState(state).As<TeamState>();
this.TeamId = parsedState.TeamId;
await this.Runtime!.LoadStateAsync(parsedState.RuntimeState.AsJson());
await this.Runtime!.StopAsync();
await shutdownTask;
}
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// GroupChatHandlerRouter.cs
using System.Text.Json;
using Microsoft.AutoGen.Contracts;
using Microsoft.AutoGen.Core;
using Microsoft.Extensions.Logging;
@ -10,17 +9,16 @@ namespace Microsoft.AutoGen.AgentChat.GroupChat;
internal delegate ValueTask MessagePublishServicer(GroupChatEventBase event_, string topicType, CancellationToken cancellation = default);
internal interface IGroupChatHandler : IHandle<GroupChatStart>, IHandle<GroupChatAgentResponse>, IHandle<object>, ISaveState
internal interface IGroupChatHandler : IHandle<GroupChatStart>, IHandle<GroupChatAgentResponse>, IHandle<object>
{
public void AttachMessagePublishServicer(MessagePublishServicer? servicer = null);
public void DetachMessagePublishServicer() => this.AttachMessagePublishServicer(null);
void AttachMessagePublishServicer(MessagePublishServicer? servicer = null);
void DetachMessagePublishServicer() => this.AttachMessagePublishServicer(null);
}
internal sealed class GroupChatHandlerRouter<TManager> : HostableAgentAdapter,
IHandle<GroupChatStart>,
IHandle<GroupChatAgentResponse>,
IHandle<object>,
ISaveState
IHandle<object>
where TManager : GroupChatManagerBase, IGroupChatHandler
{
@ -47,10 +45,4 @@ internal sealed class GroupChatHandlerRouter<TManager> : HostableAgentAdapter,
public ValueTask HandleAsync(object item, MessageContext messageContext)
=> this.ChatManager.HandleAsync(item, messageContext);
ValueTask<JsonElement> ISaveState.SaveStateAsync()
=> this.ChatManager.SaveStateAsync();
ValueTask ISaveState.LoadStateAsync(JsonElement state)
=> this.ChatManager.LoadStateAsync(state);
}

View File

@ -33,7 +33,7 @@ public abstract class GroupChatManagerBase : IGroupChatHandler
protected ITerminationCondition? TerminationCondition => this.options.TerminationCondition;
protected int? MaxTurns => this.options.MaxTurns;
protected int CurrentTurn { get; set; }
private int CurrentTurn { get; set; }
protected List<AgentMessage> MessageThread;
@ -182,6 +182,6 @@ public abstract class GroupChatManagerBase : IGroupChatHandler
public ValueTask HandleAsync(object item, MessageContext messageContext)
{
throw new NotImplementedException();
throw new InvalidOperationException($"Unhandled message in group chat manager: {item.GetType()}");
}
}

View File

@ -11,8 +11,8 @@ namespace Microsoft.AutoGen.AgentChat.Abstractions;
internal interface IOutputCollectionSink
{
public void CollectMessage(AgentMessage message);
public void Terminate(StopMessage message);
void CollectMessage(AgentMessage message);
void Terminate(StopMessage message);
}
internal sealed class OutputSink : IOutputCollectionSink
@ -26,7 +26,7 @@ internal sealed class OutputSink : IOutputCollectionSink
}
private readonly object sync = new();
private SemaphoreSlim semapohre = new SemaphoreSlim(0, 1);
private SemaphoreSlim semapohre = new SemaphoreSlim(1, 1);
private SinkFrame? receivingSinkFrame;
@ -43,12 +43,7 @@ internal sealed class OutputSink : IOutputCollectionSink
frameAction(this.receivingSinkFrame);
}
// TODO: Replace the Semaphore with a TaskSource approach
try
{
semapohre.Release();
}
catch (SemaphoreFullException) { }
semapohre.Release();
}
public void CollectMessage(AgentMessage message)
@ -87,14 +82,6 @@ internal sealed class OutputSink : IOutputCollectionSink
await this.semapohre.WaitAsync(cancellation);
}
}
internal void Reset()
{
lock (this.sync)
{
this.receivingSinkFrame = null;
}
}
}
// TODO: Abstract the core logic of this out into the equivalent of ClosureAgent, because that seems like a

View File

@ -1,17 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// RoundRobinGroupChat.cs
using System.Text.Json;
using Microsoft.AutoGen.AgentChat.Abstractions;
using Microsoft.AutoGen.AgentChat.State;
using Microsoft.AutoGen.Contracts;
namespace Microsoft.AutoGen.AgentChat.GroupChat;
/// <summary>
/// A group chat manager that selects the next speaker in a round-robin fashion.
/// </summary>
public class RoundRobinGroupChatManager : GroupChatManagerBase, ISaveState
public class RoundRobinGroupChatManager : GroupChatManagerBase
{
private readonly List<string> participantTopicTypes;
private int nextSpeakerIndex;
@ -31,29 +28,6 @@ public class RoundRobinGroupChatManager : GroupChatManagerBase, ISaveState
return ValueTask.FromResult(result);
}
ValueTask<JsonElement> ISaveState.SaveStateAsync()
{
RoundRobinManagerState state = new RoundRobinManagerState
{
NextSpeakerIndex = this.nextSpeakerIndex,
CurrentTurn = this.CurrentTurn,
MessageThread = this.MessageThread
};
return ValueTask.FromResult(SerializedState.Create(state).AsJson());
}
ValueTask ISaveState.LoadStateAsync(JsonElement state)
{
RoundRobinManagerState parsedState = new SerializedState(state).As<RoundRobinManagerState>();
this.MessageThread = parsedState.MessageThread;
this.CurrentTurn = parsedState.CurrentTurn;
this.nextSpeakerIndex = parsedState.NextSpeakerIndex;
return ValueTask.CompletedTask;
}
}
/// <summary>

View File

@ -1,297 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// RunContext.cs
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Microsoft.AutoGen.AgentChat.GroupChat;
public abstract class LifecycleObject
{
private int initialized;
private void PrepareInitialize(Action errorAction)
{
if (Interlocked.CompareExchange(ref this.initialized, 1, 0) != 0)
{
errorAction();
}
}
private void PrepareDeinitialize(Action errorAction)
{
if (Interlocked.CompareExchange(ref this.initialized, 0, 1) != 1)
{
errorAction();
}
}
protected bool IsInitialized => Volatile.Read(ref this.initialized) == 1;
protected virtual void OnInitializeError() => throw new InvalidOperationException($"Error initializing: {this.GetType().FullName}; already initialized.");
protected virtual void OnDeinitializeError() => throw new InvalidOperationException($"Error deinitializing: {this.GetType().FullName}; not initialized.");
public ValueTask InitializeAsync()
{
this.PrepareInitialize(this.OnInitializeError);
return this.InitializeCore();
}
public ValueTask DeinitializeAsync()
{
this.PrepareDeinitialize(this.OnDeinitializeError);
return this.DeinitializeCore();
}
protected abstract ValueTask InitializeCore();
protected abstract ValueTask DeinitializeCore();
}
public interface IRunContextLayer
{
public ValueTask InitializeAsync();
public ValueTask DeinitializeAsync();
}
public sealed class RunContextStack : LifecycleObject, IRunContextLayer
{
private Stack<IRunContextLayer> Uninitialized { get; } = new();
private Stack<IRunContextLayer> Initialized { get; } = new();
public RunContextStack(params IEnumerable<IRunContextLayer> contextLayers)
{
this.Uninitialized = new Stack<IRunContextLayer>(contextLayers);
}
// TODO: There is probably a way to have a sound manner by which pushing/popping a layer when initialized
// would be allowed. But this is not necessary for now, so we will keep it simple.
public void PushLayer(IRunContextLayer layer)
{
if (this.IsInitialized)
{
throw new InvalidOperationException("Cannot push a layer while the context is initialized.");
}
this.Uninitialized.Push(layer);
}
public void PopLayer()
{
if (this.IsInitialized)
{
throw new InvalidOperationException("Cannot pop a layer while the context is initialized.");
}
}
private Action? initializeError;
protected override void OnInitializeError()
{
(this.initializeError ?? base.OnInitializeError)();
}
private Action? deinitializeError;
protected override void OnDeinitializeError()
{
(this.deinitializeError ?? base.OnDeinitializeError)();
}
public static IRunContextLayer OverrideErrors(Action? initializeError = null, Action? deinitializeError = null)
{
return new ErrorOverrideLayer(initializeError, deinitializeError);
}
private sealed class ErrorOverrideLayer(Action? initializeError = null, Action? deinitializeError = null)
: IRunContextLayer
{
public RunContextStack? Target { get; set; }
private Action? initializeErrorPrev;
private Action? deinitializeErrorPrev;
public ValueTask InitializeAsync()
{
if (initializeError != null)
{
this.initializeErrorPrev = Interlocked.CompareExchange(ref this.Target!.initializeError, initializeError, null);
}
if (deinitializeError != null)
{
this.deinitializeErrorPrev = Interlocked.CompareExchange(ref this.Target!.deinitializeError, deinitializeError, null);
}
return ValueTask.CompletedTask;
}
public ValueTask DeinitializeAsync()
{
if (this.initializeErrorPrev != null)
{
Interlocked.CompareExchange(ref this.Target!.initializeError, this.initializeErrorPrev, initializeError);
}
if (this.deinitializeErrorPrev != null)
{
Interlocked.CompareExchange(ref this.Target!.deinitializeError, this.deinitializeErrorPrev, deinitializeError);
}
return ValueTask.CompletedTask;
}
}
public ValueTask<IAsyncDisposable> Enter()
{
return RunTicket.Enter(this);
}
protected override async ValueTask InitializeCore()
{
while (this.Uninitialized.Count > 0)
{
IRunContextLayer layer = this.Uninitialized.Pop();
if (layer is ErrorOverrideLayer errorOverrideLayer)
{
errorOverrideLayer.Target = this;
}
await layer.InitializeAsync();
this.Initialized.Push(layer);
}
}
protected override async ValueTask DeinitializeCore()
{
while (this.Initialized.Count > 0)
{
IRunContextLayer layer = this.Initialized.Pop();
await layer.DeinitializeAsync();
this.Uninitialized.Push(layer);
}
}
private sealed class RunTicket : IAsyncDisposable
{
private RunContextStack contextStack;
private int disposed;
private RunTicket(RunContextStack contextStack)
{
Debug.Assert(contextStack.IsInitialized, "The context stack must be initialized.");
this.contextStack = contextStack;
}
public static async ValueTask<IAsyncDisposable> Enter(RunContextStack contextStack)
{
await contextStack.InitializeAsync();
return new RunTicket(contextStack);
}
public ValueTask DisposeAsync()
{
if (Interlocked.CompareExchange(ref this.disposed, 1, 0) == 0)
{
return this.contextStack.DeinitializeAsync();
}
return ValueTask.CompletedTask;
}
}
}
public sealed class RunManager
{
private RunContextStack runContextStack;
public RunManager(params IEnumerable<IRunContextLayer> contextLayers)
{
this.runContextStack = new RunContextStack(contextLayers);
}
private ValueTask<IAsyncDisposable> PrepareRunAsync(string? message = null)
{
if (message != null)
{
IRunContextLayer errorOverride = RunContextStack.OverrideErrors(() => throw new InvalidOperationException(message));
this.runContextStack.PushLayer(errorOverride);
}
return this.runContextStack.Enter();
}
private async ValueTask EndRunAsync(IAsyncDisposable? runDisposable, bool hadMessage)
{
if (runDisposable != null)
{
await runDisposable.DisposeAsync().ConfigureAwait(false);
}
if (hadMessage)
{
this.runContextStack.PopLayer();
}
}
public async ValueTask RunAsync(Func<CancellationToken, ValueTask> asyncAction, CancellationToken cancellation = default, Func<CancellationToken, ValueTask>? prepareAction = null, string? message = null)
{
IAsyncDisposable? runDisposable = null;
try
{
runDisposable = await this.PrepareRunAsync(message).ConfigureAwait(false);
if (prepareAction != null)
{
await prepareAction(cancellation).ConfigureAwait(false);
}
await asyncAction(cancellation).ConfigureAwait(false);
}
finally
{
await this.EndRunAsync(runDisposable, message != null).ConfigureAwait(false);
}
}
public async ValueTask<T> RunAsync<T>(Func<CancellationToken, ValueTask<T>> asyncAction, CancellationToken cancellation = default, Func<CancellationToken, ValueTask>? prepareAction = null, string? message = null)
{
IAsyncDisposable? runDisposable = null;
try
{
runDisposable = await this.PrepareRunAsync(message).ConfigureAwait(false);
if (prepareAction != null)
{
await prepareAction(cancellation).ConfigureAwait(false);
}
return await asyncAction(cancellation).ConfigureAwait(false);
}
finally
{
await this.EndRunAsync(runDisposable, message != null).ConfigureAwait(false);
}
}
public async IAsyncEnumerable<TItem> StreamAsync<TItem>(Func<CancellationToken, IAsyncEnumerable<TItem>> streamAction, [EnumeratorCancellation] CancellationToken cancellation = default, Func<CancellationToken, ValueTask>? prepareAction = null, string? message = null)
{
IAsyncDisposable? runDisposable = null;
try
{
runDisposable = await this.PrepareRunAsync(message).ConfigureAwait(false);
if (prepareAction != null)
{
await prepareAction(cancellation).ConfigureAwait(false);
}
await foreach (TItem item in streamAction(cancellation))
{
yield return item;
}
}
finally
{
await this.EndRunAsync(runDisposable, message != null).ConfigureAwait(false);
}
}
}

View File

@ -1,19 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// BaseState.cs
namespace Microsoft.AutoGen.AgentChat.State;
[AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = false)]
public sealed class StateSerializableAttribute : Attribute
{
public StateSerializableAttribute()
{
}
}
[StateSerializable]
public abstract class BaseState
{
public string Type => this.GetType().FullName!;
public string Version { get; set; } = "1.0.0"; // TODO: More rigorous state versioning?
}

View File

@ -1,23 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ChatAgentContainerState.cs
using Microsoft.AutoGen.AgentChat.Abstractions;
namespace Microsoft.AutoGen.AgentChat.State;
public class ChatAgentContainerState : BaseState
{
public required SerializedState AgentState { get; set; }
public List<ChatMessage> MessageBuffer { get; set; } = new();
}
public class GroupChatManagerStateBase : BaseState
{
public List<AgentMessage> MessageThread { get; set; } = new();
public int CurrentTurn { get; set; }
}
public class RoundRobinManagerState : GroupChatManagerStateBase
{
public int NextSpeakerIndex { get; set; }
}

View File

@ -1,100 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SerializedState.cs
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.AutoGen.AgentChat.Abstractions;
namespace Microsoft.AutoGen.AgentChat.State;
public class SerializedStateConverter : JsonConverter<SerializedState>
{
public override SerializedState Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var json = JsonDocument.ParseValue(ref reader).RootElement;
var state = new SerializedState(json);
return state;
}
public override void Write(Utf8JsonWriter writer, SerializedState value, JsonSerializerOptions options)
{
value.AsJson().WriteTo(writer);
}
}
[JsonConverter(typeof(SerializedStateConverter))]
public class SerializedState
{
private readonly JsonSerializerOptions SerializerOptions = new()
{
Converters =
{
new SerializedStateConverter(),
},
TypeInfoResolver = new MessageSerializationHelpers.MessagesTypeInfoResolver(),
};
public JsonElement AsJson()
{
if (this.jsonValue != null)
{
return this.jsonValue.Value;
}
if (this.deserializedValue == null)
{
throw new InvalidOperationException("State is not initialized.");
}
this.jsonValue = JsonSerializer.SerializeToElement(this.deserializedValue, SerializerOptions);
return this.jsonValue.Value;
}
public static SerializedState Create<T>(T state) where T : notnull
{
return new SerializedState((object)state);
}
public T As<T>()
{
if (this.deserializedValue is T value)
{
return value;
}
if (this.deserializedValue != null)
{
throw new InvalidOperationException($"Cannot convert state of type {this.deserializedValue.GetType()} to {typeof(T)}.");
}
if (this.jsonValue == null)
{
throw new InvalidOperationException("State is not initialized.");
}
T? result = JsonSerializer.Deserialize<T>(this.jsonValue!.Value, SerializerOptions)
?? throw new InvalidOperationException($"Cannot deserialize state to {typeof(T)}.");
this.deserializedValue = result;
return result;
}
private object? deserializedValue;
private JsonElement? jsonValue;
private SerializedState(object state)
{
this.deserializedValue = state;
}
public SerializedState(JsonElement json)
{
this.jsonValue = json;
}
public static implicit operator SerializedState(JsonElement json)
{
return new SerializedState(json);
}
}

View File

@ -1,10 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// TeamState.cs
namespace Microsoft.AutoGen.AgentChat.State;
public sealed class TeamState : BaseState
{
public required string TeamId { get; set; }
public required SerializedState RuntimeState { get; set; }
}

View File

@ -1,53 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ExternalTermination.cs
using Microsoft.AutoGen.AgentChat.Abstractions;
namespace Microsoft.AutoGen.AgentChat.Terminations;
/// <summary>
/// A <see cref="ITerminationCondition"/> that is externally controlled by calling the <see cref="Set"/> method.
/// </summary>
public sealed class ExternalTermination : ITerminationCondition
{
public ExternalTermination()
{
this.TerminationQueued = false;
this.IsTerminated = false;
}
public bool TerminationQueued { get; private set; }
public bool IsTerminated { get; private set; }
/// <summary>
/// Set the termination condition to terminated.
/// </summary>
public void Set()
{
this.TerminationQueued = true;
}
public ValueTask<StopMessage?> CheckAndUpdateAsync(IList<AgentMessage> messages)
{
if (this.IsTerminated)
{
throw new TerminatedException();
}
if (this.TerminationQueued)
{
this.IsTerminated = true;
string message = "External termination requested.";
StopMessage result = new() { Content = message, Source = nameof(ExternalTermination) };
return ValueTask.FromResult<StopMessage?>(result);
}
return ValueTask.FromResult<StopMessage?>(null);
}
public void Reset()
{
this.TerminationQueued = false;
this.IsTerminated = false;
}
}

View File

@ -1,51 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// FunctionCallTermination.cs
using Microsoft.AutoGen.AgentChat.Abstractions;
namespace Microsoft.AutoGen.AgentChat.Terminations;
/// <summary>
/// Terminate the conversation if a <see cref="ToolCallExecutionEvent"/> with a specific name is received.
/// </summary>
public sealed class FunctionCallTermination : ITerminationCondition
{
/// <summary>
/// Initializes a new instance of the <see cref="FunctionCallTermination"/> class.
/// </summary>
/// <param name="functionName">The name of the function to look for in the messages.</param>
public FunctionCallTermination(string functionName)
{
this.FunctionName = functionName;
this.IsTerminated = false;
}
public string FunctionName { get; }
public bool IsTerminated { get; private set; }
public ValueTask<StopMessage?> CheckAndUpdateAsync(IList<AgentMessage> messages)
{
if (this.IsTerminated)
{
throw new TerminatedException();
}
foreach (AgentMessage item in messages)
{
if (item is ToolCallExecutionEvent toolCallEvent && toolCallEvent.Content.Any(execution => execution.Name == this.FunctionName))
{
this.IsTerminated = true;
string message = $"Function '{this.FunctionName}' was executed.";
StopMessage result = new() { Content = message, Source = nameof(FunctionCallTermination) };
return ValueTask.FromResult<StopMessage?>(result);
}
}
return ValueTask.FromResult<StopMessage?>(null);
}
public void Reset()
{
this.IsTerminated = false;
}
}

View File

@ -1,53 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// HandoffTermination.cs
using Microsoft.AutoGen.AgentChat.Abstractions;
namespace Microsoft.AutoGen.AgentChat.Terminations;
/// <summary>
/// Terminate the conversation if a <see cref="HandoffMessage"/> with the given <see cref="HandoffMessage.Target"/>
/// is received.
/// </summary>
public sealed class HandoffTermination : ITerminationCondition
{
/// <summary>
/// Initializes a new instance of the <see cref="HandoffTermination"/> class.
/// </summary>
/// <param name="target">The target of the handoff message.</param>
public HandoffTermination(string target)
{
this.Target = target;
this.IsTerminated = false;
}
public string Target { get; }
public bool IsTerminated { get; private set; }
public ValueTask<StopMessage?> CheckAndUpdateAsync(IList<AgentMessage> messages)
{
if (this.IsTerminated)
{
throw new TerminatedException();
}
foreach (AgentMessage item in messages)
{
if (item is HandoffMessage handoffMessage && handoffMessage.Target == this.Target)
{
this.IsTerminated = true;
string message = $"Handoff to {handoffMessage.Target} from {handoffMessage.Source} detected.";
StopMessage result = new() { Content = message, Source = nameof(HandoffTermination) };
return ValueTask.FromResult<StopMessage?>(result);
}
}
return ValueTask.FromResult<StopMessage?>(null);
}
public void Reset()
{
this.IsTerminated = false;
}
}

View File

@ -1,52 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// MaxMessageTermination.cs
using Microsoft.AutoGen.AgentChat.Abstractions;
namespace Microsoft.AutoGen.AgentChat.Terminations;
/// <summary>
/// Terminate the conversation after a maximum number of messages have been exchanged.
/// </summary>
public sealed class MaxMessageTermination : ITerminationCondition
{
/// <summary>
/// Initializes a new instance of the <see cref="MaxMessageTermination"/> class.
/// </summary>
/// <param name="maxMessages">The maximum number of messages allowed in the conversation.</param>
public MaxMessageTermination(int maxMessages, bool includeAgentEvent = false)
{
this.MaxMessages = maxMessages;
this.MessageCount = 0;
this.IncludeAgentEvent = includeAgentEvent;
}
public int MaxMessages { get; }
public int MessageCount { get; private set; }
public bool IncludeAgentEvent { get; }
public bool IsTerminated => this.MessageCount >= this.MaxMessages;
public ValueTask<StopMessage?> CheckAndUpdateAsync(IList<AgentMessage> messages)
{
if (this.IsTerminated)
{
throw new TerminatedException();
}
this.MessageCount += messages.Where(m => this.IncludeAgentEvent || m is not AgentEvent).Count();
if (this.IsTerminated)
{
StopMessage result = new() { Content = "Max message count reached", Source = nameof(MaxMessageTermination) };
return ValueTask.FromResult<StopMessage?>(result);
}
return ValueTask.FromResult<StopMessage?>(null);
}
public void Reset()
{
this.MessageCount = 0;
}
}

View File

@ -1,50 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SourceMatchTermination.cs
using Microsoft.AutoGen.AgentChat.Abstractions;
namespace Microsoft.AutoGen.AgentChat.Terminations;
/// <summary>
/// Terminate the conversation after a specific source responds.
/// </summary>
public sealed class SourceMatchTermination : ITerminationCondition
{
/// <summary>
/// Initializes a new instance of the <see cref="SourceMatchTermination"/> class.
/// </summary>
/// <param name="sources">List of source names to terminate the conversation.</param>
public SourceMatchTermination(params IEnumerable<string> sources)
{
this.Sources = [.. sources];
}
public HashSet<string> Sources { get; }
public bool IsTerminated { get; private set; }
public ValueTask<StopMessage?> CheckAndUpdateAsync(IList<AgentMessage> messages)
{
if (this.IsTerminated)
{
throw new TerminatedException();
}
foreach (AgentMessage item in messages)
{
if (this.Sources.Contains(item.Source))
{
this.IsTerminated = true;
string message = $"'{item.Source}' answered.";
StopMessage result = new() { Content = message, Source = nameof(SourceMatchTermination) };
return ValueTask.FromResult<StopMessage?>(result);
}
}
return ValueTask.FromResult<StopMessage?>(null);
}
public void Reset()
{
this.IsTerminated = false;
}
}

View File

@ -1,76 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// TextMentionTermination.cs
using Microsoft.AutoGen.AgentChat.Abstractions;
using Microsoft.Extensions.AI;
namespace Microsoft.AutoGen.AgentChat.Terminations;
/// <summary>
/// Terminate the conversation if a specific text is mentioned.
/// </summary>
public sealed class TextMentionTermination : ITerminationCondition
{
/// <summary>
/// Initializes a new instance of the <see cref="TextMentionTermination"/> class.
/// </summary>
/// <param name="targetText">The text to look for in the messages.</param>
/// <param name="sources">Check only the messages of the specified agents for the text to look for.</param>
public TextMentionTermination(string targetText, IEnumerable<string>? sources = null)
{
this.TargetText = targetText;
this.Sources = sources != null ? [.. sources] : null;
this.IsTerminated = false;
}
public string TargetText { get; }
public HashSet<string>? Sources { get; }
public bool IsTerminated { get; private set; }
private bool CheckMultiModalData(MultiModalData data)
{
return data.ContentType == MultiModalData.Type.String &&
((TextContent)data.AIContent).Text.Contains(this.TargetText);
}
public ValueTask<StopMessage?> CheckAndUpdateAsync(IList<AgentMessage> messages)
{
if (this.IsTerminated)
{
throw new TerminatedException();
}
foreach (AgentMessage item in messages)
{
if (this.Sources != null && !this.Sources.Contains(item.Source))
{
continue;
}
bool hasMentions = item switch
{
TextMessage textMessage => textMessage.Content.Contains(this.TargetText),
MultiModalMessage multiModalMessage => multiModalMessage.Content.Any(CheckMultiModalData),
StopMessage stopMessage => stopMessage.Content.Contains(this.TargetText),
ToolCallSummaryMessage toolCallSummaryMessage => toolCallSummaryMessage.Content.Contains(this.TargetText),
_ => false
};
if (hasMentions)
{
this.IsTerminated = true;
StopMessage result = new() { Content = "Text mention received", Source = nameof(TextMentionTermination) };
return ValueTask.FromResult<StopMessage?>(result);
}
}
return ValueTask.FromResult<StopMessage?>(null);
}
public void Reset()
{
this.IsTerminated = false;
}
}

View File

@ -1,63 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// TextMessageTermination.cs
using Microsoft.AutoGen.AgentChat.Abstractions;
namespace Microsoft.AutoGen.AgentChat.Terminations;
/// <summary>
/// Terminate the conversation if a <see cref="TextMessage"/> is received.
///
/// This termination condition checks for TextMessage instances in the message sequence. When a TextMessage is found,
/// it terminates the conversation if either:
///
/// <list type="bullet">
/// <item>No source was specified (terminates on any <see cref="TextMessage"/>)</item>
/// <item>The message source matches the specified source</item>
/// </list>
///
/// </summary>
public sealed class TextMessageTermination : ITerminationCondition
{
/// <summary>
/// Initializes a new instance of the <see cref="TextMessageTermination"/> class.
/// </summary>
/// <param name="source">
/// The source name to match against incoming messages. If <c>null</c>, matches any source.
/// Defaults to <c>null</c>.
/// </param>
public TextMessageTermination(string? source = null)
{
this.Source = source;
this.IsTerminated = false;
}
public string? Source { get; }
public bool IsTerminated { get; private set; }
public ValueTask<StopMessage?> CheckAndUpdateAsync(IList<AgentMessage> messages)
{
if (this.IsTerminated)
{
throw new TerminatedException();
}
foreach (AgentMessage item in messages)
{
if (item is TextMessage textMessage && (this.Source == null || textMessage.Source == this.Source))
{
this.IsTerminated = true;
string message = $"Text message received from '{textMessage.Source}'.";
StopMessage result = new() { Content = message, Source = nameof(TextMessageTermination) };
return ValueTask.FromResult<StopMessage?>(result);
}
}
return ValueTask.FromResult<StopMessage?>(null);
}
public void Reset()
{
this.IsTerminated = false;
}
}

View File

@ -1,59 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// TimeoutTermination.cs
using Microsoft.AutoGen.AgentChat.Abstractions;
namespace Microsoft.AutoGen.AgentChat.Terminations;
/// <summary>
/// Terminate the conversation after the specified duration has passed.
/// </summary>
public sealed class TimeoutTermination : ITerminationCondition
{
/// <summary>
/// Initializes a new instance of the <see cref="TimeoutTermination"/> class.
/// </summary>
/// <param name="timeout">The maximum duration before terminating the conversation.</param>
public TimeoutTermination(TimeSpan timeout)
{
this.Timeout = timeout;
this.StartTime = DateTime.UtcNow;
}
/// <summary>
/// Initializes a new instance of the <see cref="TimeoutTermination"/> class.
/// </summary>
/// <param name="seconds">The maximum duration in seconds before terminating the conversation.</param>
public TimeoutTermination(float seconds) : this(TimeSpan.FromSeconds(seconds))
{
}
public TimeSpan Timeout { get; }
public DateTime StartTime { get; private set; }
public bool IsTerminated { get; private set; }
public ValueTask<StopMessage?> CheckAndUpdateAsync(IList<AgentMessage> messages)
{
if (this.IsTerminated)
{
throw new TerminatedException();
}
if (DateTime.UtcNow - this.StartTime >= this.Timeout)
{
this.IsTerminated = true;
string message = $"Timeout of {this.Timeout.TotalSeconds} seconds reached.";
StopMessage result = new() { Content = message, Source = nameof(TimeoutTermination) };
return ValueTask.FromResult<StopMessage?>(result);
}
return ValueTask.FromResult<StopMessage?>(null);
}
public void Reset()
{
this.IsTerminated = false;
this.StartTime = DateTime.UtcNow;
}
}

View File

@ -1,73 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// TokenUsageTermination.cs
using Microsoft.AutoGen.AgentChat.Abstractions;
namespace Microsoft.AutoGen.AgentChat.Terminations;
/// <summary>
/// Terminate the conversation if the token usage limit is reached.
/// </summary>
public sealed class TokenUsageTermination : ITerminationCondition
{
/// <summary>
/// Initializes a new instance of the <see cref="TokenUsageTermination"/> class.
/// </summary>
/// <param name="maxTotalTokens">The maximum total number of tokens allowed in the conversation.</param>
/// <param name="maxPromptTokens">The maximum number of prompt tokens allowed in the conversation.</param>
/// <param name="maxCompletionTokens">The maximum number of completion tokens allowed in the conversation.</param>
public TokenUsageTermination(int? maxTotalTokens = null, int? maxPromptTokens = null, int? maxCompletionTokens = null)
{
this.MaxTotalTokens = maxTotalTokens;
this.MaxPromptTokens = maxPromptTokens;
this.MaxCompletionTokens = maxCompletionTokens;
this.PromptTokenCount = 0;
this.CompletionTokenCount = 0;
}
public int? MaxTotalTokens { get; }
public int? MaxPromptTokens { get; }
public int? MaxCompletionTokens { get; }
public int TotalTokenCount => this.PromptTokenCount + this.CompletionTokenCount;
public int PromptTokenCount { get; private set; }
public int CompletionTokenCount { get; private set; }
public bool IsTerminated =>
(this.MaxTotalTokens != null && this.TotalTokenCount >= this.MaxTotalTokens) ||
(this.MaxPromptTokens != null && this.PromptTokenCount >= this.MaxPromptTokens) ||
(this.MaxCompletionTokens != null && this.CompletionTokenCount >= this.MaxCompletionTokens);
public ValueTask<StopMessage?> CheckAndUpdateAsync(IList<AgentMessage> messages)
{
if (this.IsTerminated)
{
throw new TerminatedException();
}
foreach (AgentMessage item in messages)
{
if (item.ModelUsage is RequestUsage usage)
{
this.PromptTokenCount += usage.PromptTokens;
this.CompletionTokenCount += usage.CompletionTokens;
}
}
if (this.IsTerminated)
{
string message = $"Token usage limit reached, total token count: {this.TotalTokenCount}, prompt token count: {this.PromptTokenCount}, completion token count: {this.CompletionTokenCount}.";
StopMessage result = new() { Content = message, Source = nameof(TokenUsageTermination) };
return ValueTask.FromResult<StopMessage?>(result);
}
return ValueTask.FromResult<StopMessage?>(null);
}
public void Reset()
{
this.PromptTokenCount = 0;
this.CompletionTokenCount = 0;
}
}

View File

@ -1,31 +0,0 @@
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
!**/.gitignore
!.git/HEAD
!.git/config
!.git/packed-refs
!.git/refs/heads/**
/python

View File

@ -1,34 +0,0 @@
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 5001
# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["dotnet/Directory.Packages.props", "dotnet/"]
COPY ["dotnet/Directory.Build.props", "dotnet/"]
COPY ["dotnet/Directory.Build.targets", "dotnet/"]
COPY ["dotnet/NuGet.config", "dotnet/"]
COPY ["dotnet/src/Microsoft.Autogen.AgentHost/Microsoft.Autogen.AgentHost.csproj", "dotnet/src/Microsoft.Autogen.AgentHost/"]
COPY ["dotnet/src/Microsoft.AutoGen.Runtime.Grpc/Microsoft.AutoGen.RuntimeGateway.Grpc.csproj", "dotnet/src/Microsoft.AutoGen.RuntimeGateway.Grpc/"]
COPY ["dotnet/src/Microsoft.AutoGen.Contracts/Microsoft.AutoGen.Contracts.csproj", "dotnet/src/Microsoft.AutoGen.Contracts/"]
RUN dotnet restore "./dotnet/src/Microsoft.Autogen.AgentHost/Microsoft.Autogen.AgentHost.csproj"
COPY . .
WORKDIR "/src/dotnet/src/Microsoft.Autogen.AgentHost"
RUN dotnet build "./Microsoft.Autogen.AgentHost.csproj" -c $BUILD_CONFIGURATION -o /app/build
# This stage is used to publish the service project to be copied to the final stage
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./Microsoft.Autogen.AgentHost.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
# This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration)
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Microsoft.Autogen.AgentHost.dll"]

View File

@ -6,16 +6,7 @@
<ContainerRepository>autogen-host</ContainerRepository>
<ContainerFamily>alpine</ContainerFamily>
<EnableSdkContainerSupport>true</EnableSdkContainerSupport>
<PackAsTool>true</PackAsTool>
<ToolCommandName>agenthost</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
</PropertyGroup>
<Import Project="$(RepoRoot)/nuget/nuget-package.props" />
</PropertyGroup>
<ItemGroup>
<ContainerEnvironmentVariable Include="ASPNETCORE_HTTP_PORTS" Value="5001" />
<ContainerPort Include="5001" Type="tcp" />

View File

@ -26,19 +26,19 @@ public abstract class InferenceAgent<T>(
{
protected IChatClient ChatClient { get; } = client;
private ILogger<InferenceAgent<T>>? Logger => _logger as ILogger<InferenceAgent<T>>;
private Task<ChatResponse> CompleteAsync(
private Task<ChatCompletion> CompleteAsync(
IList<ChatMessage> chatMessages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
return ChatClient.GetResponseAsync(chatMessages, options, cancellationToken);
return ChatClient.CompleteAsync(chatMessages, options, cancellationToken);
}
private IAsyncEnumerable<ChatResponseUpdate> CompleteStreamingAsync(
private IAsyncEnumerable<StreamingChatCompletionUpdate> CompleteStreamingAsync(
IList<ChatMessage> chatMessages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
return ChatClient.GetStreamingResponseAsync(chatMessages, options, cancellationToken);
return ChatClient.CompleteStreamingAsync(chatMessages, options, cancellationToken);
}
}

View File

@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// IHandleConsole.cs
using Google.Protobuf;
using Microsoft.AutoGen.Contracts;
namespace Microsoft.AutoGen.Agents;
@ -13,12 +14,13 @@ public interface IHandleConsole : IHandle<Output>, IHandle<Input>, IProcessIO
/// <summary>
/// Prototype for Publish Message Async method
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="message"></param>
/// <param name="topic"></param>
/// <param name="messageId"></param>
/// <param name="cancellationToken"></param>
/// <param name="token"></param>
/// <returns>ValueTask</returns>
ValueTask PublishMessageAsync(object message, TopicId topic, string? messageId = null, CancellationToken cancellationToken = default);
ValueTask PublishMessageAsync<T>(T message, TopicId topic, string? messageId, CancellationToken token = default) where T : IMessage;
/// <summary>
/// Receives events of type Output and writes them to the console
@ -37,7 +39,7 @@ public interface IHandleConsole : IHandle<Output>, IHandle<Input>, IProcessIO
{
Route = "console"
};
await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, cancellationToken: CancellationToken.None).ConfigureAwait(false);
await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, token: CancellationToken.None).ConfigureAwait(false);
}
/// <summary>
@ -58,6 +60,6 @@ public interface IHandleConsole : IHandle<Output>, IHandle<Input>, IProcessIO
{
Route = "console"
};
await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, cancellationToken: CancellationToken.None).ConfigureAwait(false);
await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, token: CancellationToken.None).ConfigureAwait(false);
}
}

View File

@ -1,5 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// IHandleFileIO.cs
using Google.Protobuf;
using Microsoft.AutoGen.Contracts;
using Microsoft.Extensions.Logging;
@ -23,12 +25,13 @@ public interface IHandleFileIO : IHandle<Input>, IHandle<Output>, IProcessIO
/// <summary>
/// Prototype for Publish Message Async method
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="message"></param>
/// <param name="topic"></param>
/// <param name="messageId"></param>
/// <param name="cancellationToken"></param>
/// <param name="token"></param>
/// <returns>ValueTask</returns>
ValueTask PublishMessageAsync(object message, TopicId topic, string? messageId = null, CancellationToken cancellationToken = default);
ValueTask PublishMessageAsync<T>(T message, TopicId topic, string? messageId, CancellationToken token = default) where T : IMessage;
async ValueTask IHandle<Input>.HandleAsync(Input item, MessageContext messageContext)
{
@ -42,7 +45,7 @@ public interface IHandleFileIO : IHandle<Input>, IHandle<Output>, IProcessIO
{
Message = errorMessage
};
await PublishMessageAsync(err, new TopicId("IOError"), null, cancellationToken: CancellationToken.None).ConfigureAwait(false);
await PublishMessageAsync(err, new TopicId("IOError"), null, token: CancellationToken.None).ConfigureAwait(false);
return;
}
string content;
@ -55,7 +58,7 @@ public interface IHandleFileIO : IHandle<Input>, IHandle<Output>, IProcessIO
{
Route = Route
};
await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, cancellationToken: CancellationToken.None).ConfigureAwait(false);
await PublishMessageAsync(evt, new TopicId("InputProcessed"), null, token: CancellationToken.None).ConfigureAwait(false);
}
async ValueTask IHandle<Output>.HandleAsync(Output item, MessageContext messageContext)
{
@ -67,6 +70,6 @@ public interface IHandleFileIO : IHandle<Input>, IHandle<Output>, IProcessIO
{
Route = Route
};
await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, cancellationToken: CancellationToken.None).ConfigureAwait(false);
await PublishMessageAsync(evt, new TopicId("OutputWritten"), null, token: CancellationToken.None).ConfigureAwait(false);
}
}

View File

@ -11,10 +11,11 @@
<ItemGroup>
<ProjectReference Include="..\Contracts\Microsoft.AutoGen.Contracts.csproj" />
<ProjectReference Include="..\Core\Microsoft.AutoGen.Core.csproj" />
<ProjectReference Include="..\Extensions\Aspire\Microsoft.AutoGen.Extensions.Aspire.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.SemanticKernel" />
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>

View File

@ -6,7 +6,7 @@ namespace Microsoft.AutoGen.Contracts;
/// <summary>
/// Represents an agent within the runtime that can process messages, maintain state, and be closed when no longer needed.
/// </summary>
public interface IAgent : ISaveState
public interface IAgent : ISaveState<IAgent>
{
/// <summary>
/// Gets the unique identifier of the agent.
@ -42,6 +42,6 @@ public interface IHostableAgent : IAgent
/// Called when the runtime is closing.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public ValueTask CloseAsync();
public ValueTask CloseAsync() => ValueTask.CompletedTask;
}

View File

@ -8,7 +8,7 @@ namespace Microsoft.AutoGen.Contracts;
/// <summary>
/// Defines the runtime environment for agents, managing message sending, subscriptions, agent resolution, and state persistence.
/// </summary>
public interface IAgentRuntime : ISaveState
public interface IAgentRuntime : ISaveState<IAgentRuntime>
{
/// <summary>
/// Sends a message to an agent and gets a response.

View File

@ -9,10 +9,9 @@ namespace Microsoft.AutoGen.Contracts;
/// Defines a contract for saving and loading the state of an object.
/// The state must be JSON serializable.
/// </summary>
public interface ISaveState
/// <typeparam name="T">The type of the object implementing this interface.</typeparam>
public interface ISaveState<T>
{
public static ValueTask<JsonElement> DefaultSaveStateAsync() => new(JsonDocument.Parse("{}").RootElement);
/// <summary>
/// Saves the current state of the object.
/// </summary>
@ -21,7 +20,7 @@ public interface ISaveState
/// containing the saved state. The structure of the state is implementation-defined
/// but must be JSON serializable.
/// </returns>
public virtual ValueTask<JsonElement> SaveStateAsync() => DefaultSaveStateAsync();
public ValueTask<JsonElement> SaveStateAsync();
/// <summary>
/// Loads a previously saved state into the object.
@ -31,6 +30,6 @@ public interface ISaveState
/// is implementation-defined but must be JSON serializable.
/// </param>
/// <returns>A task representing the asynchronous operation.</returns>
public virtual ValueTask LoadStateAsync(JsonElement state) => ValueTask.CompletedTask;
public ValueTask LoadStateAsync(JsonElement state);
}

View File

@ -1,48 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// ISaveStateMixin.cs
using System.Text.Json;
namespace Microsoft.AutoGen.Contracts;
/// <summary>
/// Defines a contract for saving and loading the state of an object.
/// The state must be JSON serializable.
/// </summary>
/// <typeparam name="T">The type of the object implementing this interface.</typeparam>
///
public interface ISaveStateMixin<T> : ISaveState
{
/// <summary>
/// Saves the current state of the object.
/// </summary>
/// <returns>
/// A task representing the asynchronous operation, returning a dictionary
/// containing the saved state. The structure of the state is implementation-defined
/// but must be JSON serializable.
/// </returns>
async ValueTask<JsonElement> ISaveState.SaveStateAsync()
{
var state = await SaveStateImpl();
return JsonSerializer.SerializeToElement(state);
}
/// <summary>
/// Loads a previously saved state into the object.
/// </summary>
/// <param name="state">
/// A dictionary representing the saved state. The structure of the state
/// is implementation-defined but must be JSON serializable.
/// </param>
/// <returns>A task representing the asynchronous operation.</returns>
ValueTask ISaveState.LoadStateAsync(JsonElement state)
{
// Throw if failed to deserialize
var stateObject = JsonSerializer.Deserialize<T>(state) ?? throw new InvalidDataException();
return LoadStateImpl(stateObject);
}
protected ValueTask<T> SaveStateImpl();
protected ValueTask LoadStateImpl(T state);
}

View File

@ -13,7 +13,7 @@ internal static class KVStringParseHelper
/// <summary>
/// The regular expression pattern used to match key-value pairs in the format "key/value".
/// </summary>
private const string KVPairPattern = @"^(?<key>\w+)/(?<value>[\w-]+)$";
private const string KVPairPattern = @"^(?<key>\w+)/(?<value>\w+)$";
/// <summary>
/// The compiled regex used for extracting key-value pairs from a string.

View File

@ -1,38 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// AgentExtensions.cs
using System.Reflection;
using Google.Protobuf;
using Microsoft.AutoGen.Contracts;
using Microsoft.AutoGen.Core.Grpc;
namespace Microsoft.AutoGen.Core;
internal static partial class AgentExtensions
{
private static readonly Type ProtobufIMessage = typeof(IMessage<>);
private static bool IsProtobufType(this Type type)
{
// TODO: Support the non-generic IMessage as well
Type specializedIMessageType = ProtobufIMessage.MakeGenericType(type);
// type T needs to derive from IMessage<T>
return specializedIMessageType.IsAssignableFrom(type);
}
public static void RegisterHandledMessageTypes(this IHostableAgent agent, IProtoSerializationRegistry registry)
{
Type agentRuntimeType = agent.GetType();
MethodInfo[] messageHandlers = agentRuntimeType.GetHandlers();
foreach (MethodInfo handler in messageHandlers)
{
Type messageType = handler.GetParameters().First().ParameterType;
if (messageType.IsProtobufType() && registry.GetSerializer(messageType) == null)
{
registry.RegisterSerializer(messageType);
}
}
}
}

View File

@ -9,7 +9,6 @@ using Microsoft.AutoGen.Protobuf;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace Microsoft.AutoGen.Core.Grpc;
@ -18,7 +17,7 @@ public static class AgentsAppBuilderExtensions
private const string _defaultAgentServiceAddress = "http://localhost:53071";
// TODO: How do we ensure AddGrpcAgentWorker and UseInProcessRuntime are mutually exclusive?
public static AgentsAppBuilder AddGrpcAgentWorker(this AgentsAppBuilder builder, string? agentServiceAddress = null, bool useStrictDeserialiation = false)
public static AgentsAppBuilder AddGrpcAgentWorker(this AgentsAppBuilder builder, string? agentServiceAddress = null)
{
builder.Services.AddGrpcClient<AgentRpc.AgentRpcClient>(options =>
{
@ -66,16 +65,7 @@ public static class AgentsAppBuilderExtensions
});
builder.Services.TryAddSingleton(DistributedContextPropagator.Current);
builder.Services.AddSingleton<IAgentRuntime, GrpcAgentRuntime>(
(services) =>
{
return new GrpcAgentRuntime(
services.GetRequiredService<AgentRpc.AgentRpcClient>(),
services.GetRequiredService<IHostApplicationLifetime>(),
services,
services.GetRequiredService<ILogger<GrpcAgentRuntime>>(),
useStrictDeserialiation);
});
builder.Services.AddSingleton<IAgentRuntime, GrpcAgentRuntime>();
builder.Services.AddHostedService<GrpcAgentRuntime>(services =>
{
return (services.GetRequiredService<IAgentRuntime>() as GrpcAgentRuntime)!;

View File

@ -11,10 +11,9 @@ using Microsoft.Extensions.Logging;
namespace Microsoft.AutoGen.Core.Grpc;
internal sealed class AgentsContainer(IAgentRuntime hostingRuntime, IProtoSerializationRegistry serializationRegistry)
internal sealed class AgentsContainer(IAgentRuntime hostingRuntime)
{
private readonly IAgentRuntime hostingRuntime = hostingRuntime;
private readonly IProtoSerializationRegistry serializationRegistry = serializationRegistry;
private Dictionary<Contracts.AgentId, IHostableAgent> agentInstances = new();
public Dictionary<string, ISubscriptionDefinition> Subscriptions = new();
@ -30,10 +29,6 @@ internal sealed class AgentsContainer(IAgentRuntime hostingRuntime, IProtoSerial
}
agent = await factoryFunc(agentId, this.hostingRuntime);
// Just-in-Time register the message types so we can deserialize them
agent.RegisterHandledMessageTypes(this.serializationRegistry);
this.agentInstances.Add(agentId, agent);
}
@ -90,16 +85,14 @@ public sealed class GrpcAgentRuntime : IHostedService, IAgentRuntime, IMessageSi
public GrpcAgentRuntime(AgentRpc.AgentRpcClient client,
IHostApplicationLifetime hostApplicationLifetime,
IServiceProvider serviceProvider,
ILogger<GrpcAgentRuntime> logger,
bool strictMessageDeserialization = false)
ILogger<GrpcAgentRuntime> logger)
{
this._client = client;
this._logger = logger;
this._shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(hostApplicationLifetime.ApplicationStopping);
this._messageRouter = new GrpcMessageRouter(client, this, _clientId, logger, this._shutdownCts.Token);
this._agentsContainer = new AgentsContainer(this, this.SerializationRegistry);
this._strictMessageDeserialization = strictMessageDeserialization;
this._agentsContainer = new AgentsContainer(this);
this.ServiceProvider = serviceProvider;
}
@ -130,7 +123,6 @@ public sealed class GrpcAgentRuntime : IHostedService, IAgentRuntime, IMessageSi
}
}
private readonly bool _strictMessageDeserialization;
public IProtoSerializationRegistry SerializationRegistry { get; } = new ProtobufSerializationRegistry();
public void Dispose()
@ -239,6 +231,8 @@ public sealed class GrpcAgentRuntime : IHostedService, IAgentRuntime, IMessageSi
var messageId = evt.Id;
var typeName = evt.Attributes[Constants.DATA_SCHEMA_ATTR].CeString;
var serializer = SerializationRegistry.GetSerializer(typeName) ?? throw new Exception();
var message = serializer.Deserialize(evt.ProtoData);
var messageContext = new MessageContext(messageId, cancellationToken)
{
@ -247,10 +241,6 @@ public sealed class GrpcAgentRuntime : IHostedService, IAgentRuntime, IMessageSi
IsRpc = false
};
// We may not have a Serializer registered yet, if this is the first time we are instantiating the agent
IProtobufMessageSerializer? serializer = SerializationRegistry.GetSerializer(typeName);
object? message = serializer?.Deserialize(evt.ProtoData);
// Iterate over subscriptions values to find receiving agents
foreach (var subscription in this._agentsContainer.Subscriptions.Values)
{
@ -258,55 +248,17 @@ public sealed class GrpcAgentRuntime : IHostedService, IAgentRuntime, IMessageSi
{
var recipient = subscription.MapToAgent(topic);
var agent = await this._agentsContainer.EnsureAgentAsync(recipient);
// give the serializer a second chance to have been registered
serializer ??= SerializationRegistry.GetSerializer(typeName);
if (serializer != null)
{
message ??= serializer.Deserialize(evt.ProtoData);
await agent.OnMessageAsync(message, messageContext);
}
else if (_strictMessageDeserialization)
{
throw new Exception($"Could not find a serializer for message of type {typeName}");
}
else
{
_logger.LogWarning($"Could not find a serializer for message of type {typeName}; this is likely due there not yet being an instantiated agent with a contract for it.");
}
await agent.OnMessageAsync(message, messageContext);
}
}
}
public async ValueTask StartAsync(CancellationToken cancellationToken)
public ValueTask StartAsync(CancellationToken cancellationToken)
{
await this._messageRouter.StartAsync(cancellationToken);
if (this._agentsContainer.RegisteredAgentTypes.Count > 0)
{
foreach (var type in this._agentsContainer.RegisteredAgentTypes)
{
await this._client.RegisterAgentAsync(new RegisterAgentTypeRequest
{
Type = type
}, this.CallOptions);
}
}
if (this._agentsContainer.Subscriptions.Count > 0)
{
foreach (var subscription in this._agentsContainer.Subscriptions.Values)
{
await this._client.AddSubscriptionAsync(new AddSubscriptionRequest
{
Subscription = subscription.ToProtobuf()
}, this.CallOptions);
}
}
return this._messageRouter.StartAsync(cancellationToken);
}
Task IHostedService.StartAsync(CancellationToken cancellationToken) => this.StartAsync(cancellationToken).AsTask();
Task IHostedService.StartAsync(CancellationToken cancellationToken) => this._messageRouter.StartAsync(cancellationToken).AsTask();
public Task StopAsync(CancellationToken cancellationToken)
{
@ -392,39 +344,30 @@ public sealed class GrpcAgentRuntime : IHostedService, IAgentRuntime, IMessageSi
{
this._agentsContainer.AddSubscription(subscription);
if (this._messageRouter.IsChannelOpen)
var _ = await this._client.AddSubscriptionAsync(new AddSubscriptionRequest
{
var _ = await this._client.AddSubscriptionAsync(new AddSubscriptionRequest
{
Subscription = subscription.ToProtobuf()
}, this.CallOptions);
}
Subscription = subscription.ToProtobuf()
}, this.CallOptions);
}
public async ValueTask RemoveSubscriptionAsync(string subscriptionId)
{
this._agentsContainer.RemoveSubscriptionAsync(subscriptionId);
if (this._messageRouter.IsChannelOpen)
await this._client.RemoveSubscriptionAsync(new RemoveSubscriptionRequest
{
await this._client.RemoveSubscriptionAsync(new RemoveSubscriptionRequest
{
Id = subscriptionId
}, this.CallOptions);
}
Id = subscriptionId
}, this.CallOptions);
}
public async ValueTask<AgentType> RegisterAgentFactoryAsync(AgentType type, Func<Contracts.AgentId, IAgentRuntime, ValueTask<IHostableAgent>> factoryFunc)
{
this._agentsContainer.RegisterAgentFactory(type, factoryFunc);
if (this._messageRouter.IsChannelOpen)
await this._client.RegisterAgentAsync(new RegisterAgentTypeRequest
{
await this._client.RegisterAgentAsync(new RegisterAgentTypeRequest
{
Type = type,
}, this.CallOptions);
}
Type = type,
}, this.CallOptions);
return type;
}

View File

@ -34,8 +34,6 @@ internal sealed class AutoRestartChannel : IDisposable
_shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(shutdownCancellation);
}
public bool Connected { get => _channel is not null; }
public void EnsureConnected()
{
_logger.LogInformation("Connecting to gRPC endpoint " + Environment.GetEnvironmentVariable("AGENT_HOST"));
@ -43,7 +41,7 @@ internal sealed class AutoRestartChannel : IDisposable
if (this.RecreateChannel(null) == null)
{
throw new Exception("Failed to connect to gRPC endpoint.");
}
};
}
public AsyncDuplexStreamingCall<Message, Message> StreamingCall
@ -117,9 +115,8 @@ internal sealed class GrpcMessageRouter(AgentRpc.AgentRpcClient client,
private readonly CancellationTokenSource _shutdownCts = CancellationTokenSource.CreateLinkedTokenSource(shutdownCancellation);
private readonly IMessageSink<Message> _incomingMessageSink = incomingMessageSink;
// TODO: Enable a way to configure the channel options
private readonly Channel<(Message Message, TaskCompletionSource WriteCompletionSource)> _outboundMessagesChannel
// TODO: Enable a way to configure the channel options
= Channel.CreateBounded<(Message, TaskCompletionSource)>(DefaultChannelOptions);
private readonly AutoRestartChannel _incomingMessageChannel = new AutoRestartChannel(client, clientId, logger, shutdownCancellation);
@ -290,8 +287,6 @@ internal sealed class GrpcMessageRouter(AgentRpc.AgentRpcClient client,
this._incomingMessageChannel.Dispose();
}
public bool IsChannelOpen => this._incomingMessageChannel.Connected;
public void Dispose()
{
_outboundMessagesChannel.Writer.TryComplete();

View File

@ -6,8 +6,6 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<Import Project="$(RepoRoot)/nuget/nuget-package.props" />
<ItemGroup>
<ProjectReference Include="..\Core\Microsoft.AutoGen.Core.csproj" />
<ProjectReference Include="..\Contracts\Microsoft.AutoGen.Contracts.csproj" />

View File

@ -28,7 +28,10 @@ public class ProtobufSerializationRegistry : IProtoSerializationRegistry
public void RegisterSerializer(Type type, IProtobufMessageSerializer serializer)
{
_serializers.TryAdd(TypeNameResolver.ResolveTypeName(type), serializer);
if (_serializers.ContainsKey(TypeNameResolver.ResolveTypeName(type)))
{
throw new InvalidOperationException($"Serializer already registered for {type.FullName}");
}
_serializers[TypeNameResolver.ResolveTypeName(type)] = serializer;
}
}

View File

@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Reflection;
using System.Text.Json;
using Microsoft.AutoGen.Contracts;
using Microsoft.Extensions.Logging;
@ -12,7 +13,7 @@ namespace Microsoft.AutoGen.Core;
/// <summary>
/// Represents the base class for an agent in the AutoGen system.
/// </summary>
public abstract class BaseAgent : IHostableAgent, ISaveState
public abstract class BaseAgent : IAgent, IHostableAgent
{
/// <summary>
/// The activity source for tracing.
@ -92,6 +93,15 @@ public abstract class BaseAgent : IHostableAgent, ISaveState
return null;
}
public virtual ValueTask<JsonElement> SaveStateAsync()
{
return ValueTask.FromResult(JsonDocument.Parse("{}").RootElement);
}
public virtual ValueTask LoadStateAsync(JsonElement state)
{
return ValueTask.CompletedTask;
}
public ValueTask<object?> SendMessageAsync(object message, AgentId recepient, string? messageId = null, CancellationToken cancellationToken = default)
{
return this.Runtime.SendMessageAsync(message, recepient, sender: this.Id, messageId: messageId, cancellationToken: cancellationToken);
@ -102,19 +112,4 @@ public abstract class BaseAgent : IHostableAgent, ISaveState
{
return this.Runtime.PublishMessageAsync(message, topic, sender: this.Id, messageId: messageId, cancellationToken: cancellationToken);
}
//public virtual ValueTask<JsonElement> SaveStateAsync()
//{
// return new ValueTask<JsonElement>(JsonDocument.Parse("{}").RootElement);
//}
//public virtual ValueTask LoadStateAsync(JsonElement _)
//{
// return ValueTask.CompletedTask;
//}
public virtual ValueTask CloseAsync()
{
return ValueTask.CompletedTask;
}
}

View File

@ -14,8 +14,8 @@ public sealed class InProcessRuntime : IAgentRuntime, IHostedService
public bool DeliverToSelf { get; set; } //= false;
internal Dictionary<AgentId, IHostableAgent> agentInstances = new();
private Dictionary<string, ISubscriptionDefinition> subscriptions = new();
private Dictionary<AgentType, Func<AgentId, IAgentRuntime, ValueTask<IHostableAgent>>> agentFactories = new();
Dictionary<string, ISubscriptionDefinition> subscriptions = new();
Dictionary<AgentType, Func<AgentId, IAgentRuntime, ValueTask<IHostableAgent>>> agentFactories = new();
private ValueTask<T> ExecuteTracedAsync<T>(Func<ValueTask<T>> func)
{
@ -43,45 +43,28 @@ public sealed class InProcessRuntime : IAgentRuntime, IHostedService
}
TopicId topic = envelope.Topic.Value;
List<Exception> exceptions = new();
foreach (var subscription in this.subscriptions.Values.Where(subscription => subscription.Matches(topic)))
{
try
AgentId? sender = envelope.Sender;
CancellationTokenSource combinedSource = CancellationTokenSource.CreateLinkedTokenSource(envelope.Cancellation, deliveryToken);
MessageContext messageContext = new(envelope.MessageId, combinedSource.Token)
{
deliveryToken.ThrowIfCancellationRequested();
Sender = sender,
Topic = topic,
IsRpc = false
};
AgentId? sender = envelope.Sender;
CancellationTokenSource combinedSource = CancellationTokenSource.CreateLinkedTokenSource(envelope.Cancellation, deliveryToken);
MessageContext messageContext = new(envelope.MessageId, combinedSource.Token)
{
Sender = sender,
Topic = topic,
IsRpc = false
};
AgentId agentId = subscription.MapToAgent(topic);
if (!this.DeliverToSelf && sender.HasValue && sender == agentId)
{
continue;
}
IHostableAgent agent = await this.EnsureAgentAsync(agentId);
// TODO: Cancellation propagation!
await agent.OnMessageAsync(envelope.Message, messageContext);
}
catch (Exception ex)
AgentId agentId = subscription.MapToAgent(topic);
if (!this.DeliverToSelf && sender.HasValue && sender == agentId)
{
exceptions.Add(ex);
continue;
}
}
if (exceptions.Count > 0)
{
// TODO: Unwrap TargetInvocationException?
throw new AggregateException("One or more exceptions occurred while processing the message.", exceptions);
IHostableAgent agent = await this.EnsureAgentAsync(agentId);
// TODO: Cancellation propagation!
await agent.OnMessageAsync(envelope.Message, messageContext);
}
}
@ -95,7 +78,7 @@ public sealed class InProcessRuntime : IAgentRuntime, IHostedService
this.messageDeliveryQueue.Enqueue(delivery);
return delivery.FutureNoResult;
return ValueTask.CompletedTask;
});
}
@ -121,15 +104,14 @@ public sealed class InProcessRuntime : IAgentRuntime, IHostedService
public ValueTask<object?> SendMessageAsync(object message, AgentId recepient, AgentId? sender = null, string? messageId = null, CancellationToken cancellationToken = default)
{
return this.ExecuteTracedAsync(async () =>
return this.ExecuteTracedAsync(() =>
{
MessageDelivery delivery = new MessageEnvelope(message, messageId, cancellationToken)
.WithSender(sender)
.ForSend(recepient, this.SendMessageServicer);
this.messageDeliveryQueue.Enqueue(delivery);
return await delivery.Future;
return delivery.Future;
});
}
@ -233,9 +215,7 @@ public sealed class InProcessRuntime : IAgentRuntime, IHostedService
var agentState = await this.agentInstances[agentId].SaveStateAsync();
state[agentId.ToString()] = agentState;
}
JsonElement jsonElement = JsonSerializer.SerializeToElement(state);
return jsonElement;
return JsonSerializer.SerializeToElement(state);
}
public ValueTask<AgentType> RegisterAgentFactoryAsync<TAgent>(AgentType type, Func<AgentId, IAgentRuntime, ValueTask<TAgent>> factoryFunc) where TAgent : IHostableAgent
@ -260,7 +240,7 @@ public sealed class InProcessRuntime : IAgentRuntime, IHostedService
return ValueTask.FromResult(new AgentProxy(agentId, this));
}
public ValueTask ProcessNextMessageAsync(CancellationToken cancellation = default)
public ValueTask ProcessNextMessage(CancellationToken cancellation = default)
{
Debug.WriteLine("Processing next message...");
if (this.messageDeliveryQueue.TryDequeue(out MessageDelivery? delivery))
@ -276,34 +256,11 @@ public sealed class InProcessRuntime : IAgentRuntime, IHostedService
private CancellationTokenSource? finishSource;
private async Task RunAsync(CancellationToken cancellation)
{
Dictionary<Guid, Task> pendingTasks = new();
while (!cancellation.IsCancellationRequested && shouldContinue())
{
// Get a unique task id
Guid taskId;
do
{
taskId = Guid.NewGuid();
} while (pendingTasks.ContainsKey(taskId));
// There is potentially a race condition here, but even if we leak a Task, we will
// still catch it on the Finish() pass.
ValueTask processTask = this.ProcessNextMessageAsync(cancellation);
await Task.Yield();
// Check if the task is already completed
if (processTask.IsCompleted)
{
continue;
}
else
{
Task actualTask = processTask.AsTask();
pendingTasks.Add(taskId, actualTask.ContinueWith(t => pendingTasks.Remove(taskId), TaskScheduler.Current));
}
await this.ProcessNextMessage(cancellation);
}
await Task.WhenAll(pendingTasks.Values.Where(t => t is not null).ToArray());
await this.FinishAsync(this.finishSource?.Token ?? CancellationToken.None);
}
@ -343,15 +300,12 @@ public sealed class InProcessRuntime : IAgentRuntime, IHostedService
return ValueTask.CompletedTask;
}
public async Task RunUntilIdleAsync()
public Task RunUntilIdleAsync()
{
Func<bool> oldShouldContinue = this.shouldContinue;
this.shouldContinue = () => !this.messageDeliveryQueue.IsEmpty;
// TODO: Do we want detach semantics?
await this.messageDeliveryTask;
this.shouldContinue = oldShouldContinue;
return this.messageDeliveryTask;
}
private async Task FinishAsync(CancellationToken token)
@ -363,9 +317,6 @@ public sealed class InProcessRuntime : IAgentRuntime, IHostedService
await agent.CloseAsync();
}
}
this.shutdownSource = null;
this.finishSource = null;
}
Task IHostedService.StartAsync(CancellationToken cancellationToken) => this.StartAsync(cancellationToken).AsTask();

View File

@ -1,19 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// MessageDelivery.cs
using System.Reflection;
using Microsoft.AutoGen.Contracts;
namespace Microsoft.AutoGen.Core;
internal sealed class MessageDelivery(MessageEnvelope message, Func<MessageEnvelope, CancellationToken, ValueTask> servicer, IResultSink<object?> resultSink)
internal sealed class MessageDelivery(MessageEnvelope message, Func<MessageEnvelope, CancellationToken, ValueTask> servicer, IResultSink<object?>? resultSink = null)
{
public MessageEnvelope Message { get; } = message;
public Func<MessageEnvelope, CancellationToken, ValueTask> Servicer { get; } = servicer;
public IResultSink<object?> ResultSink { get; } = resultSink;
public IResultSink<object?>? ResultSink { get; } = resultSink;
public ValueTask<object?> Future => this.ResultSink.Future;
public ValueTask FutureNoResult => this.ResultSink.FutureNoResult;
public ValueTask<object?> Future => this.ResultSink != null ? this.ResultSink.Future : ValueTask.FromResult((object?)null);
public ValueTask InvokeAsync(CancellationToken cancellation)
{
@ -50,19 +48,8 @@ internal sealed class MessageEnvelope
ResultSink<object?> resultSink = new ResultSink<object?>();
Func<MessageEnvelope, CancellationToken, ValueTask> boundServicer = async (envelope, cancellation) =>
{
try
{
object? result = await servicer(envelope, cancellation);
resultSink.SetResult(result);
}
catch (TargetInvocationException ex) when (ex.InnerException is OperationCanceledException innerOCEx)
{
resultSink.SetCancelled(innerOCEx);
}
catch (Exception ex)
{
resultSink.SetException(ex);
}
object? result = await servicer(envelope, cancellation);
resultSink.SetResult(result);
};
return new MessageDelivery(this, boundServicer, resultSink);
@ -72,20 +59,6 @@ internal sealed class MessageEnvelope
{
this.Topic = topic;
ResultSink<object?> waitForPublish = new ResultSink<object?>();
Func<MessageEnvelope, CancellationToken, ValueTask> boundServicer = async (envelope, cancellation) =>
{
try
{
await servicer(envelope, cancellation);
waitForPublish.SetResult(null);
}
catch (Exception ex) // Do we want to special-case cancellation, and hoist the exception type like above?
{
waitForPublish.SetException(ex);
}
};
return new MessageDelivery(this, boundServicer, waitForPublish);
return new MessageDelivery(this, servicer);
}
}

View File

@ -5,14 +5,13 @@ using System.Threading.Tasks.Sources;
namespace Microsoft.AutoGen.Core;
internal interface IResultSink<TResult> : IValueTaskSource<TResult>, IValueTaskSource
internal interface IResultSink<TResult> : IValueTaskSource<TResult>
{
public void SetResult(TResult result);
public void SetException(Exception exception);
public void SetCancelled(OperationCanceledException? ocEx = null);
void SetResult(TResult result);
void SetException(Exception exception);
void SetCancelled();
public ValueTask<TResult> Future { get; }
public ValueTask FutureNoResult { get; }
ValueTask<TResult> Future { get; }
}
public sealed class ResultSink<TResult> : IResultSink<TResult>
@ -35,10 +34,10 @@ public sealed class ResultSink<TResult> : IResultSink<TResult>
}
public bool IsCancelled { get; private set; }
public void SetCancelled(OperationCanceledException? ocEx = null)
public void SetCancelled()
{
this.IsCancelled = true;
this.core.SetException(ocEx ?? new OperationCanceledException());
this.core.SetException(new OperationCanceledException());
}
public void SetException(Exception exception)
@ -51,9 +50,5 @@ public sealed class ResultSink<TResult> : IResultSink<TResult>
this.core.SetResult(result);
}
void IValueTaskSource.GetResult(short token) => _ = this.GetResult(token);
public ValueTask<TResult> Future => new(this, this.core.Version);
public ValueTask FutureNoResult => new(this, this.core.Version);
}

View File

@ -41,13 +41,12 @@ public static class ServiceCollectionChatClientExtensions
Func<ChatClientBuilder, ChatClientBuilder>? builder = null)
{
uri ??= new Uri("http://localhost:11434");
services.AddChatClient(service =>
return services.AddChatClient(pipeline =>
{
var httpClient = service.GetService<HttpClient>() ?? new();
return new OllamaChatClient(uri, modelName, httpClient);
builder?.Invoke(pipeline);
var httpClient = pipeline.Services.GetService<HttpClient>() ?? new();
return pipeline.Use(new OllamaChatClient(uri, modelName, httpClient));
});
return services;
}
public static IServiceCollection AddOpenAIChatClient(
this IHostApplicationBuilder hostBuilder,
@ -82,17 +81,16 @@ public static class ServiceCollectionChatClientExtensions
Uri? endpoint = null,
Func<ChatClientBuilder, ChatClientBuilder>? builder = null)
{
services
return services
.AddSingleton(_ => endpoint is null
? new OpenAIClient(apiKey)
: new AzureOpenAIClient(endpoint, new ApiKeyCredential(apiKey)))
.AddChatClient(service =>
.AddChatClient(pipeline =>
{
var openAiClient = service.GetRequiredService<OpenAIClient>();
return openAiClient.AsChatClient(modelOrDeploymentName);
builder?.Invoke(pipeline);
var openAiClient = pipeline.Services.GetRequiredService<OpenAIClient>();
return pipeline.Use(openAiClient.AsChatClient(modelOrDeploymentName));
});
return services;
}
public static IServiceCollection AddAzureChatClient(
this IHostApplicationBuilder hostBuilder,
@ -111,10 +109,12 @@ public static class ServiceCollectionChatClientExtensions
}
var endpoint = $"{serviceName}:Endpoint" ?? throw new InvalidOperationException($"No endpoint was specified for the Azure Inference Chat Client");
var endpointUri = string.IsNullOrEmpty(endpoint) ? null : new Uri(endpoint);
var token = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new InvalidOperationException("No model access token was found in the environment variable AZURE_OPENAI_API_KEY");
var chatClient = new ChatCompletionsClient(endpointUri, new AzureKeyCredential(token)).AsChatClient(modelOrDeploymentName);
hostBuilder.Services.AddChatClient(chatClient);
return hostBuilder.Services;
return hostBuilder.Services.AddChatClient(pipeline =>
{
builder?.Invoke(pipeline);
var token = Environment.GetEnvironmentVariable("GH_TOKEN") ?? throw new InvalidOperationException("No model access token was found in the environment variable GH_TOKEN");
return pipeline.Use(new ChatCompletionsClient(
endpointUri, new AzureKeyCredential(token)).AsChatClient(modelOrDeploymentName));
});
}
}

View File

@ -4,40 +4,12 @@ using System.Collections.Concurrent;
using Microsoft.AutoGen.Protobuf;
namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions;
/// <summary>
/// Stores agent subscription information such as topic and prefix mappings,
/// and maintains an ETag for concurrency checks.
/// </summary>
public class AgentsRegistryState
{
/// <summary>
/// Maps each agent ID to the set of topics they subscribe to.
/// </summary>
public ConcurrentDictionary<string, HashSet<string>> AgentsToTopicsMap { get; set; } = [];
/// <summary>
/// Maps each agent ID to the set of topic prefixes they subscribe to.
/// </summary>
public ConcurrentDictionary<string, HashSet<string>> AgentsToTopicsPrefixMap { get; set; } = [];
/// <summary>
/// Maps each topic name to the set of agent types subscribed to it.
/// </summary>
public ConcurrentDictionary<string, HashSet<string>> TopicToAgentTypesMap { get; set; } = [];
/// <summary>
/// Maps each topic prefix to the set of agent types subscribed to it.
/// </summary>
public ConcurrentDictionary<string, HashSet<string>> TopicPrefixToAgentTypesMap { get; set; } = [];
/// <summary>
/// Stores subscriptions by GUID
/// </summary>
public ConcurrentDictionary<string, HashSet<Subscription>> GuidSubscriptionsMap { get; set; } = [];
/// <summary>
/// The concurrency ETag for identifying the registry's version or state.
/// </summary>
public string Etag { get; set; } = Guid.NewGuid().ToString();
}

View File

@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// IConnection.cs
namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions;
public interface IConnection
{
}

View File

@ -5,45 +5,11 @@ using Microsoft.AutoGen.Protobuf;
namespace Microsoft.AutoGen.RuntimeGateway.Grpc.Abstractions;
/// <summary>
/// Defines the gateway interface for handling RPC requests and subscriptions.
/// Note that all of the request types are generated from the proto file.
/// </summary>
public interface IGateway : IGrainObserver
{
/// <summary>
/// Invokes a request asynchronously.
/// </summary>
/// <param name="request">The RPC request.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the RPC response.</returns>
ValueTask<RpcResponse> InvokeRequestAsync(RpcRequest request);
/// <summary>
/// Registers an agent type asynchronously.
/// </summary>
/// <param name="request">The register agent type request.</param>
/// <param name="context">The server call context.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the register agent type response.</returns>
ValueTask<RegisterAgentTypeResponse> RegisterAgentTypeAsync(RegisterAgentTypeRequest request, ServerCallContext context);
/// <summary>
/// Subscribes to a topic asynchronously.
/// </summary>
/// <param name="request">The add subscription request.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the add subscription response.</returns>
ValueTask<AddSubscriptionResponse> SubscribeAsync(AddSubscriptionRequest request);
/// <summary>
/// Unsubscribes from a topic asynchronously.
/// </summary>
/// <param name="request">The remove subscription request.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the remove subscription response.</returns>
ValueTask<RemoveSubscriptionResponse> UnsubscribeAsync(RemoveSubscriptionRequest request);
/// <summary>
/// Gets the subscriptions asynchronously.
/// </summary>
/// <param name="request">The get subscriptions request.</param>
/// <returns>A task that represents the asynchronous operation. The task result contains the list of subscriptions.</returns>
ValueTask<List<Subscription>> GetSubscriptionsAsync(GetSubscriptionsRequest request);
}

Some files were not shown because too many files have changed in this diff Show More