Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding declarative HTTP tools to autogen ext #5181

Merged
merged 46 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
57cbb2e
initial commit for declarative-mcp-tools
EItanya Jan 24, 2025
dd00059
update without local deps
EItanya Jan 24, 2025
db763d5
Merge branch 'main' of https://github.com/microsoft/autogen into decl…
EItanya Jan 25, 2025
9c4a00b
remove MCP and add more http options
EItanya Jan 25, 2025
95fc65e
unit tests are working
EItanya Jan 25, 2025
3c1a8ce
Merge branch 'main' into declarative-mcp-tools
EItanya Jan 25, 2025
7b644e2
works again with path params stuff
EItanya Jan 25, 2025
873ac5c
add unit tests for the new path params stuff
EItanya Jan 25, 2025
dfb9b50
unit tests now fully passing with params, example works with httpbin …
EItanya Jan 25, 2025
7395c0a
update docs for args
EItanya Jan 25, 2025
98b0519
revert autogen studio
EItanya Jan 25, 2025
4644bc8
formatting
EItanya Jan 26, 2025
1773520
Merge branch 'main' into declarative-mcp-tools
EItanya Jan 27, 2025
64c08a6
Merge branch 'main' into declarative-mcp-tools
EItanya Jan 27, 2025
65b7c72
Merge branch 'main' into declarative-mcp-tools
EItanya Jan 28, 2025
4f9901e
update deps
EItanya Feb 1, 2025
b55ac3e
merge with main
EItanya Feb 1, 2025
e6b14df
add respose_type and tests
EItanya Feb 1, 2025
09f653f
run check
EItanya Feb 1, 2025
f572696
fix pyright checks
EItanya Feb 1, 2025
c6a1c38
make mypy happy
EItanya Feb 1, 2025
e6d1728
Merge branch 'main' into declarative-mcp-tools
EItanya Feb 3, 2025
2e611c1
move the graphrag tests into a subdir to not leak conftest.py
EItanya Feb 3, 2025
c35d6a2
Merge branch 'main' into declarative-mcp-tools
EItanya Feb 3, 2025
ade7e65
Merge branch 'main' into declarative-mcp-tools
victordibia Feb 4, 2025
3272eb3
Merge branch 'main' into declarative-mcp-tools
victordibia Feb 4, 2025
d6e176a
PR comments
EItanya Feb 4, 2025
ad30c83
Merge branch 'main' into declarative-mcp-tools
EItanya Feb 4, 2025
d25e119
udpate uv.lock
EItanya Feb 4, 2025
0a51544
Merge branch 'main' into declarative-mcp-tools
ekzhu Feb 4, 2025
58718a8
Merge branch 'main' of https://github.com/microsoft/autogen into decl…
EItanya Feb 6, 2025
286a628
mypy finally passing
EItanya Feb 6, 2025
cade3e6
Merge branch 'main' of https://github.com/microsoft/autogen into decl…
EItanya Feb 6, 2025
59f2139
Merge branch 'main' into declarative-mcp-tools
ekzhu Feb 7, 2025
a3a175e
Merge branch 'main' into declarative-mcp-tools
ekzhu Feb 7, 2025
220ad3e
Merge branch 'main' into declarative-mcp-tools
ekzhu Feb 8, 2025
2b48ea1
Merge branch 'main' into declarative-mcp-tools
ekzhu Feb 8, 2025
4ad329e
merge with main
EItanya Feb 9, 2025
07c526f
Merge branch 'declarative-mcp-tools' of github.com:EItanya/autogen in…
EItanya Feb 9, 2025
6ccbcf1
ignore excluded mypy error
EItanya Feb 9, 2025
405f3f6
fix import
EItanya Feb 9, 2025
7853cfb
Merge branch 'main' into declarative-mcp-tools
EItanya Feb 10, 2025
bbae288
Merge branch 'main' into declarative-mcp-tools
EItanya Feb 10, 2025
053c424
Merge branch 'main' into declarative-mcp-tools
ekzhu Feb 10, 2025
03b14f9
format
ekzhu Feb 10, 2025
0dea967
doc: update API reference and add documentation for http tool
ekzhu Feb 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions python/packages/autogen-core/docs/src/reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,17 @@ python/autogen_ext.agents.web_surfer
python/autogen_ext.agents.file_surfer
python/autogen_ext.agents.video_surfer
python/autogen_ext.agents.video_surfer.tools
python/autogen_ext.auth.azure
python/autogen_ext.teams.magentic_one
python/autogen_ext.models.cache
python/autogen_ext.models.openai
python/autogen_ext.models.replay
python/autogen_ext.models.azure
python/autogen_ext.models.semantic_kernel
python/autogen_ext.tools.code_execution
python/autogen_ext.tools.graphrag
python/autogen_ext.tools.http
python/autogen_ext.tools.langchain
python/autogen_ext.tools.mcp
python/autogen_ext.tools.graphrag
python/autogen_ext.tools.code_execution
python/autogen_ext.tools.semantic_kernel
python/autogen_ext.code_executors.local
python/autogen_ext.code_executors.docker
Expand All @@ -65,4 +65,5 @@ python/autogen_ext.code_executors.azure
python/autogen_ext.cache_store.diskcache
python/autogen_ext.cache_store.redis
python/autogen_ext.runtimes.grpc
python/autogen_ext.auth.azure
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
autogen\_ext.tools.http
=======================


.. automodule:: autogen_ext.tools.http
:members:
:undoc-members:
:show-inheritance:
5 changes: 5 additions & 0 deletions python/packages/autogen-ext/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,11 @@ semantic-kernel-dapr = [
"semantic-kernel[dapr]>=1.17.1",
]

http-tool = [
"httpx>=0.27.0",
"json-schema-to-pydantic>=0.2.0"
]

semantic-kernel-all = [
"semantic-kernel[google,hugging_face,mistralai,ollama,onnx,anthropic,usearch,pandas,aws,dapr]>=1.17.1",
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from ._http_tool import HttpTool

__all__ = ["HttpTool"]
233 changes: 233 additions & 0 deletions python/packages/autogen-ext/src/autogen_ext/tools/http/_http_tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
import re
from typing import Any, Literal, Optional, Type

import httpx
from autogen_core import CancellationToken, Component
from autogen_core.tools import BaseTool
from json_schema_to_pydantic import create_model
from pydantic import BaseModel, Field
from typing_extensions import Self


class HttpToolConfig(BaseModel):
name: str
"""
The name of the tool.
"""
description: Optional[str]
"""
A description of the tool.
"""
scheme: Literal["http", "https"] = "http"
"""
The scheme to use for the request.
"""
host: str
"""
The URL to send the request to.
"""
port: int
"""
The port to send the request to.
"""
path: str = Field(default="/")
"""
The path to send the request to. defaults to "/"
The path can accept parameters, e.g. "/{param1}/{param2}".
These parameters will be templated from the inputs args, any additional parameters will be added as query parameters or the body of the request.
"""
method: Optional[Literal["GET", "POST", "PUT", "DELETE", "PATCH"]] = "POST"
"""
The HTTP method to use, will default to POST if not provided.
"""
headers: Optional[dict[str, Any]]
victordibia marked this conversation as resolved.
Show resolved Hide resolved
"""
A dictionary of headers to send with the request.
"""
json_schema: dict[str, Any]
victordibia marked this conversation as resolved.
Show resolved Hide resolved
"""
A JSON Schema object defining the expected parameters for the tool.
Path parameters MUST also be included in the json_schema. They must also MUST be set to string
"""
return_type: Optional[Literal["text", "json"]] = "text"
"""
The type of response to return from the tool.
"""


class HttpTool(BaseTool[BaseModel, Any], Component[HttpToolConfig]):
"""A wrapper for using an HTTP server as a tool.
Args:
name (str): The name of the tool.
description (str, optional): A description of the tool.
scheme (str): The scheme to use for the request. Must be either "http" or "https".
host (str): The host to send the request to.
port (int): The port to send the request to.
path (str, optional): The path to send the request to. Defaults to "/".
Can include path parameters like "/{param1}/{param2}" which will be templated from input args.
method (str, optional): The HTTP method to use, will default to POST if not provided.
Must be one of "GET", "POST", "PUT", "DELETE", "PATCH".
headers (dict[str, Any], optional): A dictionary of headers to send with the request.
json_schema (dict[str, Any]): A JSON Schema object defining the expected parameters for the tool.
Path parameters must also be included in the schema and must be strings.
return_type (Literal["text", "json"], optional): The type of response to return from the tool.
Defaults to "text".
ekzhu marked this conversation as resolved.
Show resolved Hide resolved
.. note::
This tool requires the :code:`http-tool` extra for the :code:`autogen-ext` package.
To install:
.. code-block:: bash
pip install -U "autogen-agentchat" "autogen-ext[http-tool]"
Example:
Simple use case::
import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.messages import TextMessage
from autogen_core import CancellationToken
from autogen_ext.models.openai import OpenAIChatCompletionClient
from autogen_ext.tools.http import HttpTool
# Define a JSON schema for a base64 decode tool
base64_schema = {
"type": "object",
"properties": {
"value": {"type": "string", "description": "The base64 value to decode"},
},
"required": ["value"],
}
# Create an HTTP tool for the httpbin API
base64_tool = HttpTool(
name="base64_decode",
description="base64 decode a value",
scheme="https",
host="httpbin.org",
port=443,
path="/base64/{value}",
method="GET",
json_schema=base64_schema,
)
async def main():
# Create an assistant with the base64 tool
model = OpenAIChatCompletionClient(model="gpt-4")
assistant = AssistantAgent("base64_assistant", model_client=model, tools=[base64_tool])
# The assistant can now use the base64 tool to decode the string
response = await assistant.on_messages(
[TextMessage(content="Can you base64 decode the value 'YWJjZGU=', please?", source="user")],
CancellationToken(),
)
print(response.chat_message.content)
asyncio.run(main())
"""

component_type = "tool"
component_provider_override = "autogen_ext.tools.http.HttpTool"
component_config_schema = HttpToolConfig

def __init__(
self,
name: str,
host: str,
port: int,
json_schema: dict[str, Any],
headers: Optional[dict[str, Any]] = None,
description: str = "HTTP tool",
path: str = "/",
scheme: Literal["http", "https"] = "http",
method: Literal["GET", "POST", "PUT", "DELETE", "PATCH"] = "POST",
return_type: Literal["text", "json"] = "text",
) -> None:
self.server_params = HttpToolConfig(
name=name,
description=description,
host=host,
port=port,
path=path,
scheme=scheme,
method=method,
headers=headers,
json_schema=json_schema,
return_type=return_type,
)

# Use regex to find all path parameters, we will need those later to template the path
path_params = {match.group(1) for match in re.finditer(r"{([^}]*)}", path)}
self._path_params = path_params

# Create the input model from the modified schema
input_model = create_model(json_schema)

# Use Any as return type since HTTP responses can vary
base_return_type: Type[Any] = object

super().__init__(input_model, base_return_type, name, description)

def _to_config(self) -> HttpToolConfig:
copied_config = self.server_params.model_copy()
return copied_config

@classmethod
def _from_config(cls, config: HttpToolConfig) -> Self:
copied_config = config.model_copy().model_dump()
return cls(**copied_config)

async def run(self, args: BaseModel, cancellation_token: CancellationToken) -> Any:
"""Execute the HTTP tool with the given arguments.
Args:
args: The validated input arguments
cancellation_token: Token for cancelling the operation
Returns:
The response body from the HTTP call in JSON format
Raises:
Exception: If tool execution fails
"""

model_dump = args.model_dump()
path_params = {k: v for k, v in model_dump.items() if k in self._path_params}
# Remove path params from the model dump
for k in self._path_params:
model_dump.pop(k)

path = self.server_params.path.format(**path_params)

url = httpx.URL(
scheme=self.server_params.scheme,
host=self.server_params.host,
port=self.server_params.port,
path=path,
)
async with httpx.AsyncClient() as client:
match self.server_params.method:
case "GET":
response = await client.get(url, params=model_dump)
case "PUT":
response = await client.put(url, json=model_dump)
case "DELETE":
response = await client.delete(url, params=model_dump)
case "PATCH":
response = await client.patch(url, json=model_dump)
case _: # Default case POST
response = await client.post(url, json=model_dump)

match self.server_params.return_type:
case "text":
return response.text
case "json":
return response.json()
case _:
raise ValueError(f"Invalid return type: {self.server_params.return_type}")
Empty file.
5 changes: 3 additions & 2 deletions python/packages/autogen-ext/tests/test_worker_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
MessageType,
NoopAgent,
)
from protos.serialization_test_pb2 import ProtoMessage

from .protos.serialization_test_pb2 import ProtoMessage


@pytest.mark.grpc
Expand Down Expand Up @@ -423,7 +424,7 @@ def __init__(self) -> None:
self.received_messages: list[Any] = []

@event
async def on_new_message(self, message: ProtoMessage, ctx: MessageContext) -> None:
async def on_new_message(self, message: ProtoMessage, ctx: MessageContext) -> None: # type: ignore
self.num_calls += 1
self.received_messages.append(message)

Expand Down
Empty file.
Empty file.
Empty file.
Loading
Loading