glean-agent-toolkit 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- glean/agent_toolkit/__init__.py +30 -0
- glean/agent_toolkit/adapters/__init__.py +24 -0
- glean/agent_toolkit/adapters/adk.py +81 -0
- glean/agent_toolkit/adapters/base.py +29 -0
- glean/agent_toolkit/adapters/crewai.py +208 -0
- glean/agent_toolkit/adapters/langchain.py +159 -0
- glean/agent_toolkit/adapters/openai.py +150 -0
- glean/agent_toolkit/decorators.py +271 -0
- glean/agent_toolkit/py.typed +1 -0
- glean/agent_toolkit/registry.py +50 -0
- glean/agent_toolkit/spec.py +51 -0
- glean/agent_toolkit/tools/__init__.py +43 -0
- glean/agent_toolkit/tools/_common.py +36 -0
- glean/agent_toolkit/tools/ai_web_search.py +40 -0
- glean/agent_toolkit/tools/calendar_search.py +18 -0
- glean/agent_toolkit/tools/code_search.py +29 -0
- glean/agent_toolkit/tools/employee_search.py +33 -0
- glean/agent_toolkit/tools/glean_search.py +26 -0
- glean/agent_toolkit/tools/gmail_search.py +23 -0
- glean/agent_toolkit/tools/outlook_search.py +23 -0
- glean/agent_toolkit/tools/web_search.py +38 -0
- glean_agent_toolkit-0.2.0.dist-info/METADATA +320 -0
- glean_agent_toolkit-0.2.0.dist-info/RECORD +25 -0
- glean_agent_toolkit-0.2.0.dist-info/WHEEL +4 -0
- glean_agent_toolkit-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Glean Agent Toolkit.
|
|
3
|
+
|
|
4
|
+
Universal Tool/Action Toolkit for Glean agent frameworks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
9
|
+
|
|
10
|
+
from glean.agent_toolkit.decorators import tool_spec
|
|
11
|
+
from glean.agent_toolkit.registry import get_registry
|
|
12
|
+
from glean.agent_toolkit.spec import ToolSpec
|
|
13
|
+
|
|
14
|
+
from . import adapters
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"tool_spec",
|
|
18
|
+
"get_registry",
|
|
19
|
+
"ToolSpec",
|
|
20
|
+
"adapters",
|
|
21
|
+
"__version__",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
__version__ = version("glean-agent-toolkit")
|
|
26
|
+
except PackageNotFoundError: # pragma: no cover – package not installed
|
|
27
|
+
__version__ = "0.0.0"
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
logger.addHandler(logging.NullHandler())
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Adapters for converting tool specifications to framework-specific formats."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
from glean.agent_toolkit.adapters.adk import ADKAdapter
|
|
6
|
+
from glean.agent_toolkit.adapters.base import BaseAdapter
|
|
7
|
+
from glean.agent_toolkit.adapters.crewai import CrewAIAdapter
|
|
8
|
+
from glean.agent_toolkit.adapters.langchain import LangChainAdapter
|
|
9
|
+
from glean.agent_toolkit.adapters.openai import (
|
|
10
|
+
OpenAIAdapter,
|
|
11
|
+
OpenAIFunctionDef,
|
|
12
|
+
OpenAIToolDef,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"BaseAdapter",
|
|
17
|
+
"ADKAdapter",
|
|
18
|
+
"CrewAIAdapter",
|
|
19
|
+
"LangChainAdapter",
|
|
20
|
+
"OpenAIAdapter",
|
|
21
|
+
"OpenAIToolDef",
|
|
22
|
+
"OpenAIFunctionDef",
|
|
23
|
+
"BaseModel",
|
|
24
|
+
]
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Google ADK adapter for converting tool specifications."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import TYPE_CHECKING, Any, TypeAlias
|
|
5
|
+
|
|
6
|
+
from glean.agent_toolkit.adapters.base import BaseAdapter
|
|
7
|
+
from glean.agent_toolkit.spec import ToolSpec
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from google.adk.tools import FunctionTool as _RealAdkFunctionTool
|
|
11
|
+
else:
|
|
12
|
+
_RealAdkFunctionTool = Any # type: ignore
|
|
13
|
+
|
|
14
|
+
HAS_ADK: bool
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _FallbackAdkFunctionTool:
|
|
18
|
+
"""Fallback for google.adk.tools.FunctionTool.
|
|
19
|
+
|
|
20
|
+
This lightweight stand-in mimics the public attributes accessed by tests
|
|
21
|
+
(``name``, ``description``, ``schema`` and ``func``). It purposefully keeps
|
|
22
|
+
the same runtime surface as the real ADK ``FunctionTool`` so that unit
|
|
23
|
+
tests exercising the adapter behave consistently even when the dependency
|
|
24
|
+
is missing.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
name: str
|
|
28
|
+
description: str | None
|
|
29
|
+
func: Callable[..., Any]
|
|
30
|
+
schema: dict[str, Any] | None
|
|
31
|
+
|
|
32
|
+
def __init__(self, func: Callable[..., Any]) -> None: # noqa: D401 – keep signature minimal
|
|
33
|
+
self.func = func
|
|
34
|
+
self.name = func.__name__
|
|
35
|
+
self.description = func.__doc__
|
|
36
|
+
self.schema = None # Set later by the adapter
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
try:
|
|
40
|
+
from google.adk.tools import FunctionTool as _RuntimeAdkFunctionTool
|
|
41
|
+
|
|
42
|
+
HAS_ADK = True
|
|
43
|
+
except ImportError: # pragma: no cover
|
|
44
|
+
_RuntimeAdkFunctionTool = _FallbackAdkFunctionTool # type: ignore
|
|
45
|
+
HAS_ADK = False
|
|
46
|
+
|
|
47
|
+
# Single alias used for typing and at runtime
|
|
48
|
+
AdkFunctionTool: TypeAlias = _RealAdkFunctionTool | _FallbackAdkFunctionTool
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ADKAdapter(BaseAdapter["AdkFunctionTool"]):
|
|
52
|
+
"""Adapter for Google ADK tools."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, tool_spec: ToolSpec) -> None:
|
|
55
|
+
"""Initialize the adapter.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
tool_spec: The tool specification
|
|
59
|
+
"""
|
|
60
|
+
super().__init__(tool_spec)
|
|
61
|
+
if not HAS_ADK:
|
|
62
|
+
raise ImportError(
|
|
63
|
+
"Google Agent Development Kit (ADK) is required for ADK adapter. "
|
|
64
|
+
"Install it with `pip install agent_toolkit[adk]` or `pip install google-adk`."
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def to_tool(self) -> "AdkFunctionTool":
|
|
68
|
+
"""Convert to Google ADK FunctionTool format.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
An ADK FunctionTool instance
|
|
72
|
+
"""
|
|
73
|
+
func = self.tool_spec.function
|
|
74
|
+
if not func.__doc__:
|
|
75
|
+
func.__doc__ = self.tool_spec.description
|
|
76
|
+
|
|
77
|
+
tool = _RuntimeAdkFunctionTool(func=func) # type: ignore[arg-type]
|
|
78
|
+
|
|
79
|
+
setattr(tool, "schema", self.tool_spec.input_schema)
|
|
80
|
+
|
|
81
|
+
return tool
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Base adapter class for converting tool specifications to framework-specific formats."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Generic, TypeVar
|
|
5
|
+
|
|
6
|
+
from glean.agent_toolkit.spec import ToolSpec
|
|
7
|
+
|
|
8
|
+
T = TypeVar("T")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseAdapter(Generic[T], ABC):
|
|
12
|
+
"""Base adapter for converting ToolSpec to framework-specific formats."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, tool_spec: ToolSpec) -> None:
|
|
15
|
+
"""Initialize the adapter.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
tool_spec: The tool specification
|
|
19
|
+
"""
|
|
20
|
+
self.tool_spec = tool_spec
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def to_tool(self) -> T:
|
|
24
|
+
"""Convert to framework-specific tool format.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
The framework-specific representation of the tool
|
|
28
|
+
"""
|
|
29
|
+
pass
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""CrewAI adapter for converting tool specifications."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Union, cast
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from glean.agent_toolkit.adapters.base import BaseAdapter
|
|
9
|
+
from glean.agent_toolkit.spec import ToolSpec
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from crewai.tools import BaseTool as CrewBaseTool
|
|
13
|
+
else:
|
|
14
|
+
CrewBaseTool = Any # type: ignore # noqa: N816
|
|
15
|
+
|
|
16
|
+
from pydantic import Field as PydanticField # type: ignore
|
|
17
|
+
from pydantic import create_model as pydantic_create_model
|
|
18
|
+
|
|
19
|
+
HAS_CREWAI: bool
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _FallbackCrewBaseTool:
|
|
23
|
+
"""Fallback for crewai.tools.BaseTool."""
|
|
24
|
+
|
|
25
|
+
name: str
|
|
26
|
+
description: str
|
|
27
|
+
|
|
28
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D107
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def _run(self, *args: Any, **kwargs: Any) -> Any: # noqa: D401
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _fallback_field(*args: Any, **kwargs: Any) -> Any: # noqa: N802
|
|
36
|
+
"""Fallback for pydantic.Field."""
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _fallback_create_model(*args: Any, **kwargs: Any) -> Any:
|
|
41
|
+
"""Fallback for pydantic.create_model."""
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
from crewai.tools import BaseTool as _ActualCrewBaseTool # type: ignore
|
|
47
|
+
from pydantic import Field as _ActualField # type: ignore
|
|
48
|
+
from pydantic import create_model as _actual_create_model
|
|
49
|
+
|
|
50
|
+
BaseTool = _ActualCrewBaseTool
|
|
51
|
+
Field = _ActualField
|
|
52
|
+
create_model = _actual_create_model
|
|
53
|
+
HAS_CREWAI = True
|
|
54
|
+
except ImportError: # pragma: no cover
|
|
55
|
+
BaseTool = _FallbackCrewBaseTool # type: ignore[assignment]
|
|
56
|
+
Field = _fallback_field # type: ignore[assignment]
|
|
57
|
+
create_model = _fallback_create_model # type: ignore[assignment]
|
|
58
|
+
HAS_CREWAI = False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class GleanCrewAITool(BaseTool): # type: ignore[misc]
|
|
62
|
+
"""CrewAI tool implementation for Glean tools."""
|
|
63
|
+
|
|
64
|
+
name: str # CrewAI BaseTool requires name and description
|
|
65
|
+
description: str # CrewAI BaseTool requires name and description
|
|
66
|
+
# Reuse BaseTool's built-in placeholder default for ``args_schema`` so that
|
|
67
|
+
# CrewAI can lazily infer a schema when one isn't supplied. We intentionally
|
|
68
|
+
# *do not* override the attribute here to avoid accidentally setting it to
|
|
69
|
+
# ``None`` and breaking CrewAI's internal description generation logic.
|
|
70
|
+
|
|
71
|
+
_function: Callable[..., Any]
|
|
72
|
+
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
name: str,
|
|
76
|
+
description: str,
|
|
77
|
+
function: Callable[..., Any],
|
|
78
|
+
args_schema: type[BaseModel] | None = None,
|
|
79
|
+
) -> None:
|
|
80
|
+
"""Initialize the tool.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
name: The name of the tool
|
|
84
|
+
description: A description of the tool
|
|
85
|
+
function: The function to call when the tool is invoked
|
|
86
|
+
args_schema: Optional schema for the arguments
|
|
87
|
+
"""
|
|
88
|
+
super().__init__(name=name, description=description)
|
|
89
|
+
|
|
90
|
+
self._function = function
|
|
91
|
+
|
|
92
|
+
# Only override ``args_schema`` when we actually created one; otherwise
|
|
93
|
+
# leave the placeholder so CrewAI can lazily generate a schema.
|
|
94
|
+
if args_schema is not None:
|
|
95
|
+
self.args_schema = args_schema
|
|
96
|
+
|
|
97
|
+
object.__setattr__(self, "_tool_spec_ref", None)
|
|
98
|
+
|
|
99
|
+
def _run(self, **kwargs: Any) -> Any:
|
|
100
|
+
"""Run the tool with the given arguments.
|
|
101
|
+
|
|
102
|
+
Args:
|
|
103
|
+
**kwargs: The arguments to pass to the function
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
The result of calling the function
|
|
107
|
+
"""
|
|
108
|
+
return self._function(**kwargs)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
CrewAIToolType = CrewBaseTool | BaseTool # type: ignore[valid-type]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class CrewAIAdapter(BaseAdapter[CrewAIToolType]):
|
|
115
|
+
"""Adapter for CrewAI tools."""
|
|
116
|
+
|
|
117
|
+
def __init__(self, tool_spec: ToolSpec) -> None:
|
|
118
|
+
"""Initialize the adapter.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
tool_spec: The tool specification
|
|
122
|
+
"""
|
|
123
|
+
super().__init__(tool_spec)
|
|
124
|
+
if not HAS_CREWAI:
|
|
125
|
+
raise ImportError(
|
|
126
|
+
"CrewAI package is required for CrewAI adapter. "
|
|
127
|
+
"Install it with `pip install agent_toolkit[crewai]`. "
|
|
128
|
+
"Note: CrewAI requires Python 3.10 or higher."
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
def to_tool(self) -> CrewAIToolType:
|
|
132
|
+
"""Convert to CrewAI tool format.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
A CrewAI BaseTool instance
|
|
136
|
+
"""
|
|
137
|
+
# Create and configure the tool
|
|
138
|
+
created_args_schema = self._create_args_schema()
|
|
139
|
+
|
|
140
|
+
tool = GleanCrewAITool(
|
|
141
|
+
name=self.tool_spec.name,
|
|
142
|
+
description=self.tool_spec.description,
|
|
143
|
+
function=self.tool_spec.function,
|
|
144
|
+
args_schema=created_args_schema,
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Store the tool_spec reference for testing
|
|
148
|
+
object.__setattr__(tool, "_tool_spec_ref", self.tool_spec)
|
|
149
|
+
|
|
150
|
+
return tool
|
|
151
|
+
|
|
152
|
+
def _create_args_schema(self) -> type[BaseModel] | None:
|
|
153
|
+
"""Create a Pydantic model for the arguments schema.
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
A Pydantic model class or None if no properties
|
|
157
|
+
"""
|
|
158
|
+
json_schema = self.tool_spec.input_schema
|
|
159
|
+
|
|
160
|
+
props = json_schema.get("properties", {})
|
|
161
|
+
required = json_schema.get("required", [])
|
|
162
|
+
|
|
163
|
+
if not props:
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
field_defs: dict[str, tuple[type, Any]] = {}
|
|
167
|
+
for name, schema in props.items():
|
|
168
|
+
field_type = self._get_field_type(schema)
|
|
169
|
+
is_required = name in required
|
|
170
|
+
description = schema.get("description", "")
|
|
171
|
+
if is_required:
|
|
172
|
+
field_defs[name] = (field_type, PydanticField(..., description=description))
|
|
173
|
+
else:
|
|
174
|
+
field_defs[name] = (field_type, PydanticField(None, description=description))
|
|
175
|
+
|
|
176
|
+
model_name = f"{self.tool_spec.name}ArgsSchema"
|
|
177
|
+
model = pydantic_create_model(model_name, **field_defs) # type: ignore
|
|
178
|
+
|
|
179
|
+
return cast(type[BaseModel], model)
|
|
180
|
+
|
|
181
|
+
def _get_field_type(self, schema: dict[str, Any]) -> type:
|
|
182
|
+
"""Determine the Python type from JSON schema property.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
schema: JSON schema property definition
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Appropriate Python type
|
|
189
|
+
"""
|
|
190
|
+
if "enum" in schema:
|
|
191
|
+
return str
|
|
192
|
+
|
|
193
|
+
schema_type = schema.get("type", "string")
|
|
194
|
+
|
|
195
|
+
if schema_type == "string":
|
|
196
|
+
return str
|
|
197
|
+
elif schema_type == "integer":
|
|
198
|
+
return int
|
|
199
|
+
elif schema_type == "number":
|
|
200
|
+
return float
|
|
201
|
+
elif schema_type == "boolean":
|
|
202
|
+
return bool
|
|
203
|
+
elif schema_type == "array":
|
|
204
|
+
return list
|
|
205
|
+
elif schema_type == "object":
|
|
206
|
+
return dict
|
|
207
|
+
else:
|
|
208
|
+
return str
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""LangChain adapter for converting tool specifications."""
|
|
2
|
+
|
|
3
|
+
from datetime import date, datetime
|
|
4
|
+
from typing import TYPE_CHECKING, Any, TypeAlias, Union, cast
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from glean.agent_toolkit.adapters.base import BaseAdapter
|
|
9
|
+
from glean.agent_toolkit.spec import ToolSpec
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from langchain.tools import Tool as LangchainTool # pragma: no cover
|
|
13
|
+
else:
|
|
14
|
+
LangchainTool = Any # type: ignore # noqa: N816
|
|
15
|
+
|
|
16
|
+
from pydantic import Field as PydanticField # type: ignore
|
|
17
|
+
from pydantic import create_model as pydantic_create_model
|
|
18
|
+
|
|
19
|
+
ToolClass: Any = object
|
|
20
|
+
Field: Any = object
|
|
21
|
+
create_model = pydantic_create_model
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _FallbackLangchainTool:
|
|
25
|
+
"""Fallback for langchain.tools.Tool."""
|
|
26
|
+
|
|
27
|
+
name: str
|
|
28
|
+
description: str
|
|
29
|
+
func: Any
|
|
30
|
+
args_schema: Any
|
|
31
|
+
|
|
32
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D107
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _fallback_pydantic_field(*args: Any, **kwargs: Any) -> Any: # noqa: N802
|
|
37
|
+
"""Fallback for pydantic.Field."""
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _fallback_pydantic_create_model(*args: Any, **kwargs: Any) -> Any:
|
|
42
|
+
"""Fallback for pydantic.create_model."""
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from langchain.tools import Tool as _ActualLangchainToolImport # type: ignore
|
|
48
|
+
from pydantic import Field as _ActualPydanticFieldImport # type: ignore
|
|
49
|
+
from pydantic import create_model as _actual_pydantic_create_model_import
|
|
50
|
+
|
|
51
|
+
ToolClass = _ActualLangchainToolImport
|
|
52
|
+
Field = _ActualPydanticFieldImport
|
|
53
|
+
create_model = _actual_pydantic_create_model_import
|
|
54
|
+
HAS_LANGCHAIN = True
|
|
55
|
+
except ImportError: # pragma: no cover
|
|
56
|
+
ToolClass = _FallbackLangchainTool # type: ignore[assignment]
|
|
57
|
+
Field = _fallback_pydantic_field
|
|
58
|
+
create_model = _fallback_pydantic_create_model
|
|
59
|
+
HAS_LANGCHAIN = False
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if TYPE_CHECKING:
|
|
63
|
+
LangChainToolType: TypeAlias = "LangchainTool"
|
|
64
|
+
else:
|
|
65
|
+
from typing import Any as LangChainToolType # type: ignore
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class LangChainAdapter(BaseAdapter[LangChainToolType]):
|
|
69
|
+
"""Adapter for LangChain tools."""
|
|
70
|
+
|
|
71
|
+
def __init__(self, tool_spec: ToolSpec) -> None:
|
|
72
|
+
"""Initialize the adapter.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
tool_spec: The tool specification
|
|
76
|
+
"""
|
|
77
|
+
super().__init__(tool_spec)
|
|
78
|
+
if not HAS_LANGCHAIN:
|
|
79
|
+
raise ImportError(
|
|
80
|
+
"LangChain package is required for LangChain adapter. "
|
|
81
|
+
"Install it with `pip install agent_toolkit[langchain]`."
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def to_tool(self) -> Any:
|
|
85
|
+
"""Convert to LangChain tool format.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
LangChain Tool instance
|
|
89
|
+
"""
|
|
90
|
+
return ToolClass(
|
|
91
|
+
name=self.tool_spec.name,
|
|
92
|
+
description=self.tool_spec.description,
|
|
93
|
+
func=self.tool_spec.function,
|
|
94
|
+
args_schema=self._create_args_schema(),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def _create_args_schema(self) -> type[BaseModel] | None:
|
|
98
|
+
"""Create a Pydantic model for the arguments schema.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
A Pydantic model class or None if no properties
|
|
102
|
+
"""
|
|
103
|
+
json_schema = self.tool_spec.input_schema
|
|
104
|
+
|
|
105
|
+
props = json_schema.get("properties", {})
|
|
106
|
+
required = json_schema.get("required", [])
|
|
107
|
+
|
|
108
|
+
if not props:
|
|
109
|
+
return None
|
|
110
|
+
|
|
111
|
+
field_defs: dict[str, tuple[type, Any]] = {}
|
|
112
|
+
|
|
113
|
+
for name, schema in props.items():
|
|
114
|
+
field_type = self._get_field_type(schema)
|
|
115
|
+
is_required = name in required
|
|
116
|
+
|
|
117
|
+
description = schema.get("description", "")
|
|
118
|
+
|
|
119
|
+
if is_required:
|
|
120
|
+
field_defs[name] = (field_type, Field(..., description=description))
|
|
121
|
+
else:
|
|
122
|
+
field_defs[name] = (field_type, Field(None, description=description))
|
|
123
|
+
|
|
124
|
+
model = create_model(f"{self.tool_spec.name}Schema", **field_defs) # type: ignore
|
|
125
|
+
return cast(type[BaseModel], model)
|
|
126
|
+
|
|
127
|
+
def _get_field_type(self, schema: dict[str, Any]) -> type:
|
|
128
|
+
"""Determine the Python type from JSON schema property.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
schema: JSON schema property definition
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Appropriate Python type
|
|
135
|
+
"""
|
|
136
|
+
if "enum" in schema:
|
|
137
|
+
return str
|
|
138
|
+
|
|
139
|
+
schema_type = schema.get("type", "string")
|
|
140
|
+
schema_format = schema.get("format", "")
|
|
141
|
+
|
|
142
|
+
if schema_type == "string":
|
|
143
|
+
if schema_format == "date-time":
|
|
144
|
+
return datetime
|
|
145
|
+
elif schema_format == "date":
|
|
146
|
+
return date
|
|
147
|
+
return str
|
|
148
|
+
elif schema_type == "integer":
|
|
149
|
+
return int
|
|
150
|
+
elif schema_type == "number":
|
|
151
|
+
return float
|
|
152
|
+
elif schema_type == "boolean":
|
|
153
|
+
return bool
|
|
154
|
+
elif schema_type == "array":
|
|
155
|
+
return list
|
|
156
|
+
elif schema_type == "object":
|
|
157
|
+
return dict
|
|
158
|
+
else:
|
|
159
|
+
return str
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""OpenAI adapter for converting tool specifications."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from collections.abc import Callable
|
|
5
|
+
from typing import TYPE_CHECKING, Any, TypeAlias, TypedDict, Union
|
|
6
|
+
|
|
7
|
+
from glean.agent_toolkit.adapters.base import BaseAdapter
|
|
8
|
+
from glean.agent_toolkit.spec import ToolSpec
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from agents.tool import FunctionTool as _RealOpenAIFunctionTool
|
|
12
|
+
else:
|
|
13
|
+
_RealOpenAIFunctionTool = Any # type: ignore
|
|
14
|
+
|
|
15
|
+
HAS_OPENAI: bool
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _FallbackOpenAIFunctionTool:
|
|
19
|
+
"""Fallback for agents.tool.FunctionTool."""
|
|
20
|
+
|
|
21
|
+
name: str
|
|
22
|
+
description: str
|
|
23
|
+
params_json_schema: Any
|
|
24
|
+
on_invoke_tool: Any
|
|
25
|
+
|
|
26
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D107
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
import openai # noqa: F401 # type: ignore
|
|
32
|
+
from agents.tool import FunctionTool as _RuntimeFunctionTool # type: ignore
|
|
33
|
+
|
|
34
|
+
_RuntimeOpenAIFunctionTool = _RuntimeFunctionTool # type: ignore
|
|
35
|
+
HAS_OPENAI = True
|
|
36
|
+
except ImportError: # pragma: no cover
|
|
37
|
+
_RuntimeOpenAIFunctionTool = _FallbackOpenAIFunctionTool # type: ignore
|
|
38
|
+
HAS_OPENAI = False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
OpenAIFunctionTool: TypeAlias = _RealOpenAIFunctionTool | _FallbackOpenAIFunctionTool
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
OpenAIToolType = dict[str, Any] | OpenAIFunctionTool
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class OpenAIFunctionDef(TypedDict):
|
|
48
|
+
"""Type definition for OpenAI function definition."""
|
|
49
|
+
|
|
50
|
+
name: str
|
|
51
|
+
description: str
|
|
52
|
+
parameters: dict[str, Any]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class OpenAIToolDef(TypedDict):
|
|
56
|
+
"""Type definition for OpenAI tool definition."""
|
|
57
|
+
|
|
58
|
+
type: str
|
|
59
|
+
function: OpenAIFunctionDef
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class OpenAIAdapter(BaseAdapter[OpenAIToolType]):
|
|
63
|
+
"""Adapter for OpenAI tools."""
|
|
64
|
+
|
|
65
|
+
def __init__(self, tool_spec: ToolSpec) -> None:
|
|
66
|
+
"""Initialize the adapter.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
tool_spec: The tool specification
|
|
70
|
+
"""
|
|
71
|
+
super().__init__(tool_spec)
|
|
72
|
+
if not HAS_OPENAI:
|
|
73
|
+
raise ImportError(
|
|
74
|
+
"OpenAI package is required for OpenAI adapter. "
|
|
75
|
+
"Install it with `pip install agent_toolkit[openai]`."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def to_tool(self) -> Any:
|
|
79
|
+
"""Convert to OpenAI tool format.
|
|
80
|
+
|
|
81
|
+
This method tries to use the OpenAI Agents SDK if available,
|
|
82
|
+
falling back to the standard OpenAI function calling format if not.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
OpenAI tool specification or Agents SDK FunctionTool
|
|
86
|
+
"""
|
|
87
|
+
if HAS_OPENAI and _RuntimeOpenAIFunctionTool is not _FallbackOpenAIFunctionTool:
|
|
88
|
+
return self.to_agents_tool()
|
|
89
|
+
else:
|
|
90
|
+
return self.to_standard_tool()
|
|
91
|
+
|
|
92
|
+
def to_standard_tool(self) -> OpenAIToolDef:
|
|
93
|
+
"""Convert to standard OpenAI function tool format.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
OpenAI function calling specification
|
|
97
|
+
"""
|
|
98
|
+
params_schema = (
|
|
99
|
+
self.tool_spec.input_schema
|
|
100
|
+
if self.tool_spec.input_schema
|
|
101
|
+
else {"type": "object", "properties": {}}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
"type": "function",
|
|
106
|
+
"function": {
|
|
107
|
+
"name": self.tool_spec.name,
|
|
108
|
+
"description": self.tool_spec.description,
|
|
109
|
+
"parameters": params_schema,
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
def to_agents_tool(self) -> OpenAIFunctionTool:
|
|
114
|
+
"""Convert to OpenAI Agents SDK FunctionTool.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
An OpenAI Agents SDK FunctionTool
|
|
118
|
+
"""
|
|
119
|
+
original_func = self.tool_spec.function
|
|
120
|
+
|
|
121
|
+
async def on_invoke_tool(ctx: Any, input_str: str) -> Any:
|
|
122
|
+
"""Function that invokes the tool with parameters."""
|
|
123
|
+
try:
|
|
124
|
+
params = json.loads(input_str) if input_str else {}
|
|
125
|
+
result = original_func(**params)
|
|
126
|
+
return result
|
|
127
|
+
except Exception as e:
|
|
128
|
+
return f"Error executing tool: {str(e)}"
|
|
129
|
+
|
|
130
|
+
params_json_schema_dict = (
|
|
131
|
+
self.tool_spec.input_schema
|
|
132
|
+
if self.tool_spec.input_schema
|
|
133
|
+
else {"type": "object", "properties": {}}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
return _RuntimeOpenAIFunctionTool(
|
|
137
|
+
name=self.tool_spec.name,
|
|
138
|
+
description=self.tool_spec.description,
|
|
139
|
+
params_json_schema=params_json_schema_dict,
|
|
140
|
+
on_invoke_tool=on_invoke_tool,
|
|
141
|
+
strict_json_schema=True,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def to_callable(self) -> Callable:
|
|
145
|
+
"""Get the callable for OpenAI function calling.
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The callable function
|
|
149
|
+
"""
|
|
150
|
+
return self.tool_spec.function
|