mirror of
https://github.com/ComposioHQ/composio.git
synced 2026-09-22 11:46:35 +08:00
2e42d75259
## Summary
Agentic providers honor `schema_config={"skip_defaults": True}` for
`args_schema` (defaults stripped, fields become required) but were
**not** applying the same `skip_default` to the function
`__signature__`, so the wrapped tool's signature and `args_schema`
disagreed about which defaulted params were optional. The sibling
`llamaindex` provider already passed it to both.
**Supersedes #3727.** This is the polished version, now that the ag2
migration (#3729) landed and `composio-autogen` imports again — so the
autogen case uses a real import instead of the earlier source-load stub,
and the parity tests run in CI.
## Changes
- **langchain / langgraph** (original commit by @anxkhn): pass
`skip_default=self.skip_default` to
`get_signature_format_from_schema_params`.
- **autogen**: same one-line alignment. autogen builds **only**
`__signature__` (no `args_schema`), so it never had the *mismatch* — but
its signature still ignored `skip_defaults`. Aligned for consistency;
verified on the signature alone.
- **tests**: `TestAgenticSkipDefaultsParity` guards each provider with
`pytest.importorskip`; adds real-import autogen regression tests (no
stub).
- **CI + nox**: install `providers/autogen` in the unit-test job
(`py.test.yml`) and the nox `tst` session so the autogen parity tests
actually run, plus a **hard `import composio_autogen` smoke check** in
CI.
## Why the CI import guard (not just the tests)
The `importorskip`-guarded unit tests **skip** when a provider can't be
imported — which is the correct behavior for optional providers, but
means they'd *silently skip* if autogen broke on a fresh install
(exactly the failure that #3729 just fixed). The extra `python -c
"import composio_autogen"` in the import step is a **hard** check that
fails loudly, so a future dependency breakage can't slip through as a
green build.
`providers/langchain` + `providers/autogen` co-install cleanly
(verified: 59 packages, ag2 0.14.0, no conflict). langgraph continues to
skip in CI — its code path is identical to langchain's, which runs.
## Type of change
- [x] Bug fix
## How Has This Been Tested?
- Base venv (langchain only): langchain parity **passes**, langgraph +
autogen **skip** (no error).
- CI-mirror (langchain + autogen, editable): **4 passed, 2 skipped
(langgraph)** — autogen tests actually run and pass.
- Regression proof, each provider: reverting only the provider line
fails the skip-defaults case with `assert 5 is Parameter.empty`;
restoring passes. Verified for langchain **and** autogen.
- `ruff check`, `ruff format --check`, `mypy`, and full
`tests/test_provider.py` (29 passed, 4 skipped) → clean. `py.test.yml`
parses.
## Checklist
- [x] I ran linters/tests locally and they passed
- [x] I added tests: langchain/langgraph parity + regression, and
autogen parity that runs in CI
- [ ] Changeset: not applicable — Python-only change.
## Credits
Original fix by @anxkhn (#3713 → #3727), preserved as the first commit
and co-authored on the follow-up.
---------
Co-authored-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
120 lines
4.0 KiB
Python
120 lines
4.0 KiB
Python
import hashlib
|
|
import types
|
|
import typing as t
|
|
from inspect import Signature
|
|
|
|
import autogen
|
|
from autogen.agentchat.conversable_agent import ConversableAgent
|
|
from autogen_core.tools import FunctionTool
|
|
|
|
from composio.client.types import Tool
|
|
from composio.core.provider import AgenticProvider
|
|
from composio.core.provider.agentic import AgenticProviderExecuteFn
|
|
from composio.utils.shared import (
|
|
get_signature_format_from_schema_params,
|
|
normalize_tool_arguments,
|
|
reinstate_reserved_python_keywords,
|
|
substitute_reserved_python_keywords,
|
|
)
|
|
|
|
|
|
class AutogenProvider(
|
|
AgenticProvider[FunctionTool, list[FunctionTool]],
|
|
name="autogen",
|
|
):
|
|
"""
|
|
Composio toolset for Autogen framework.
|
|
"""
|
|
|
|
def _process_function_name_for_registration(
|
|
self,
|
|
input_string: str,
|
|
max_allowed_length: int = 64,
|
|
num_hash_char: int = 10,
|
|
):
|
|
"""
|
|
Process function name for proxy registration under given character length limitation.
|
|
"""
|
|
hash_hex = hashlib.sha256(input_string.encode(encoding="utf-8")).hexdigest()
|
|
hash_chars_to_attach = hash_hex[:10]
|
|
num_input_str_char = max_allowed_length - (num_hash_char + 1)
|
|
input_str_to_attach = input_string[-num_input_str_char:]
|
|
processed_name = input_str_to_attach + "_" + hash_chars_to_attach
|
|
return processed_name
|
|
|
|
def register_tools(
|
|
self,
|
|
caller: ConversableAgent,
|
|
executor: ConversableAgent,
|
|
tools: t.List[FunctionTool],
|
|
) -> None:
|
|
"""
|
|
Register tools to the proxy agents.
|
|
|
|
:param executor: Executor agent.
|
|
:param caller: Caller agent.
|
|
:param tools: List of tools to register.
|
|
"""
|
|
for tool in tools:
|
|
autogen.agentchat.register_function(
|
|
f=tool._func,
|
|
caller=caller,
|
|
executor=executor,
|
|
name=tool.name,
|
|
description=tool.description,
|
|
)
|
|
|
|
def wrap_tool(
|
|
self,
|
|
tool: Tool,
|
|
execute_tool: AgenticProviderExecuteFn,
|
|
) -> FunctionTool:
|
|
"""Wraps a composio tool as an Autogen FunctionTool."""
|
|
schema_params, keywords = substitute_reserved_python_keywords(
|
|
schema=tool.input_parameters
|
|
)
|
|
|
|
def execute_action(**kwargs: t.Any) -> t.Dict:
|
|
"""Placeholder function for executing action."""
|
|
kwargs = reinstate_reserved_python_keywords(
|
|
request=kwargs, keywords=keywords
|
|
)
|
|
# Normalize defensively so a stringified payload is coerced to a dict (issue #2406).
|
|
return execute_tool(
|
|
slug=tool.slug, arguments=normalize_tool_arguments(kwargs)
|
|
)
|
|
|
|
# Create function with proper signature
|
|
function = types.FunctionType(
|
|
code=execute_action.__code__,
|
|
globals=globals(),
|
|
closure=execute_action.__closure__,
|
|
name=self._process_function_name_for_registration(input_string=tool.slug),
|
|
)
|
|
|
|
# Set signature and annotations
|
|
params = get_signature_format_from_schema_params(
|
|
schema_params=schema_params,
|
|
skip_default=self.skip_default,
|
|
)
|
|
function.__doc__ = tool.description
|
|
setattr(function, "__signature__", Signature(parameters=params))
|
|
setattr(
|
|
function,
|
|
"__annotations__",
|
|
{p.name: p.annotation for p in params} | {"return": t.Dict[str, t.Any]},
|
|
)
|
|
return FunctionTool(
|
|
func=function,
|
|
description=tool.description,
|
|
name=self._process_function_name_for_registration(input_string=tool.slug),
|
|
)
|
|
|
|
def wrap_tools(
|
|
self,
|
|
tools: t.Sequence[Tool],
|
|
execute_tool: AgenticProviderExecuteFn,
|
|
) -> list[FunctionTool]:
|
|
"""Wraps array of composio tools as an Autogen FunctionTool."""
|
|
return [self.wrap_tool(tool=tool, execute_tool=execute_tool) for tool in tools]
|