optimizely-opal.opal-tools-sdk 0.1.4.dev0__py3-none-any.whl → 0.1.10.dev0__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.
@@ -2,6 +2,19 @@ from .service import ToolsService
2
2
  from .decorators import tool
3
3
  from .auth import requires_auth
4
4
  from .logging import register_logger_factory
5
+ from .models import AuthData, AuthRequirement, Credentials, Environment, IslandConfig, IslandResponse
5
6
 
6
7
  __version__ = "0.1.0"
7
- __all__ = ["ToolsService", "tool", "requires_auth", "register_logger_factory"]
8
+ __all__ = [
9
+ "ToolsService",
10
+ "tool",
11
+ "requires_auth",
12
+ "register_logger_factory",
13
+ # Models
14
+ "AuthData",
15
+ "AuthRequirement",
16
+ "Credentials",
17
+ "Environment",
18
+ "IslandConfig",
19
+ "IslandResponse",
20
+ ]
@@ -62,25 +62,48 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
62
62
  else:
63
63
  field_type = str
64
64
 
65
+ # Check if the field is Optional (Union with None)
66
+ # Optional[X] is equivalent to Union[X, None]
67
+ type_args = getattr(field_type, '__args__', ())
68
+ is_optional = get_origin(field_type) is Union and type(None) in type_args
69
+
70
+ # Extract the actual type from Optional[T]
71
+ if is_optional and type_args:
72
+ # Get the non-None type from Union[T, None]
73
+ field_type = next(
74
+ (arg for arg in type_args if arg is not type(None)),
75
+ field_type,
76
+ )
77
+
65
78
  # Map Python type to Parameter type
66
79
  param_type = ParameterType.string
67
- if field_type == int:
80
+ if field_type is int:
68
81
  param_type = ParameterType.integer
69
- elif field_type == float:
82
+ elif field_type is float:
70
83
  param_type = ParameterType.number
71
- elif field_type == bool:
84
+ elif field_type is bool:
72
85
  param_type = ParameterType.boolean
73
- elif get_origin(field_type) is list:
86
+ elif field_type is list or get_origin(field_type) is list:
74
87
  param_type = ParameterType.list
75
- elif get_origin(field_type) is dict:
88
+ elif field_type is dict or get_origin(field_type) is dict:
76
89
  param_type = ParameterType.dictionary
77
90
 
78
91
  # Determine if required
79
92
  field_info_extra = getattr(field_info, "json_schema_extra") or {}
80
93
  if "required" in field_info_extra:
81
94
  required = field_info_extra["required"]
95
+ # If the field is typed as Optional, it's not required (check this FIRST)
96
+ elif is_optional:
97
+ required = False
98
+ # Check for Pydantic v2 is_required() method
99
+ elif hasattr(field_info, 'is_required'):
100
+ required = field_info.is_required()
101
+ # Fall back to checking if default is ... (Pydantic v1/v2 compatibility)
102
+ elif hasattr(field_info, 'default'):
103
+ required = field_info.default is ...
82
104
  else:
83
- required = field_info.default is ... if hasattr(field_info, 'default') else True
105
+ # If no default attribute at all, assume required
106
+ required = True
84
107
 
85
108
  # Get description
86
109
  description_text = ""
opal_tools_sdk/models.py CHANGED
@@ -1,9 +1,13 @@
1
1
  from enum import Enum
2
- from typing import List, Dict, Any, Optional
2
+ from typing import List, Dict, Any, Optional, Literal, TypedDict
3
3
  from dataclasses import dataclass
4
4
 
5
+ from pydantic import BaseModel, Field
6
+
7
+
5
8
  class ParameterType(str, Enum):
6
9
  """Types of parameters supported by Opal tools."""
10
+
7
11
  string = "string"
8
12
  integer = "integer"
9
13
  number = "number"
@@ -11,9 +15,11 @@ class ParameterType(str, Enum):
11
15
  list = "array" # Changed to match main service expectation
12
16
  dictionary = "object" # Standard JSON schema type
13
17
 
18
+
14
19
  @dataclass
15
20
  class Parameter:
16
21
  """Parameter definition for an Opal tool."""
22
+
17
23
  name: str
18
24
  param_type: ParameterType
19
25
  description: str
@@ -21,31 +27,49 @@ class Parameter:
21
27
 
22
28
  def to_dict(self) -> Dict[str, Any]:
23
29
  """Convert to dictionary for the discovery endpoint."""
24
- return {
25
- "name": self.name,
26
- "type": self.param_type.value,
27
- "description": self.description,
28
- "required": self.required
29
- }
30
+ return {"name": self.name, "type": self.param_type.value, "description": self.description, "required": self.required}
31
+
30
32
 
31
33
  @dataclass
32
34
  class AuthRequirement:
33
35
  """Authentication requirements for an Opal tool."""
36
+
34
37
  provider: str # e.g., "google", "microsoft"
35
38
  scope_bundle: str # e.g., "calendar", "drive"
36
39
  required: bool = True
37
40
 
38
41
  def to_dict(self) -> Dict[str, Any]:
39
42
  """Convert to dictionary for the discovery endpoint."""
40
- return {
41
- "provider": self.provider,
42
- "scope_bundle": self.scope_bundle,
43
- "required": self.required
44
- }
43
+ return {"provider": self.provider, "scope_bundle": self.scope_bundle, "required": self.required}
44
+
45
+
46
+ class Credentials(TypedDict):
47
+ """AuthData credentials."""
48
+
49
+ access_token: str
50
+ org_sso_id: Optional[str]
51
+ customer_id: str
52
+ instance_id: str
53
+ product_sku: str
54
+
55
+
56
+ class AuthData(TypedDict):
57
+ """Authentication data for an Opal tool."""
58
+
59
+ provider: str
60
+ credentials: Credentials
61
+
62
+
63
+ class Environment(TypedDict):
64
+ """Execution environment for an Opal tool. Interactive will provide interaction islands, while headless will not."""
65
+
66
+ execution_mode: Literal["headless", "interactive"]
67
+
45
68
 
46
69
  @dataclass
47
70
  class Function:
48
71
  """Function definition for an Opal tool."""
72
+
49
73
  name: str
50
74
  description: str
51
75
  parameters: List[Parameter]
@@ -60,10 +84,46 @@ class Function:
60
84
  "description": self.description,
61
85
  "parameters": [p.to_dict() for p in self.parameters],
62
86
  "endpoint": self.endpoint,
63
- "http_method": self.http_method
87
+ "http_method": self.http_method,
64
88
  }
65
89
 
66
90
  if self.auth_requirements:
67
91
  result["auth_requirements"] = [auth.to_dict() for auth in self.auth_requirements]
68
92
 
69
93
  return result
94
+
95
+
96
+ # Interaction island related classes
97
+ class IslandConfig(BaseModel):
98
+ class Field(BaseModel):
99
+ name: str
100
+ label: str
101
+ type: Literal["string", "boolean", "json"]
102
+ value: str = Field(default="")
103
+ hidden: bool = Field(default=False)
104
+ options: list[str] = Field(default=[])
105
+
106
+ class Action(BaseModel):
107
+ name: str
108
+ label: str
109
+ type: str
110
+ endpoint: str
111
+ operation: str = Field(default="create")
112
+
113
+ fields: list[Field]
114
+ actions: list[Action]
115
+ type: Optional[str] = None
116
+ icon: Optional[str] = None
117
+
118
+
119
+ class IslandResponse(BaseModel):
120
+ class ResponseConfig(BaseModel):
121
+ islands: list[IslandConfig]
122
+
123
+ type: Literal["island"]
124
+ config: ResponseConfig
125
+ message: Optional[str] = None
126
+
127
+ @classmethod
128
+ def create(cls, islands: list[IslandConfig], message: Optional[str] = None):
129
+ return cls(type="island", config=cls.ResponseConfig(islands=islands), message=message)
opal_tools_sdk/service.py CHANGED
@@ -1,15 +1,16 @@
1
- from typing import Dict, List, Any, Callable, Type, Optional, get_type_hints
1
+ from typing import Dict, List, Any, Callable, Optional, get_type_hints
2
2
  import inspect
3
3
  import logging
4
- from fastapi import FastAPI, APIRouter, Depends, Header, HTTPException, Request
4
+ from fastapi import FastAPI, APIRouter, HTTPException, Request
5
5
  from fastapi.routing import APIRoute
6
- from pydantic import BaseModel, create_model
6
+ from pydantic import BaseModel, ValidationError
7
7
 
8
- from .models import Function, Parameter, ParameterType, AuthRequirement
8
+ from .models import Function, Parameter, AuthRequirement
9
9
  from . import _registry
10
10
 
11
11
  logger = logging.getLogger(__name__)
12
12
 
13
+
13
14
  class ToolsService:
14
15
  """Main class for managing Opal tools."""
15
16
 
@@ -33,15 +34,12 @@ class ToolsService:
33
34
  routes = []
34
35
  for route in app.routes:
35
36
  if isinstance(route, APIRoute):
36
- routes.append({
37
- "path": route.path,
38
- "name": route.name,
39
- "methods": route.methods
40
- })
37
+ routes.append({"path": route.path, "name": route.name, "methods": route.methods})
41
38
  return {"routes": routes}
42
39
 
43
40
  def _init_routes(self) -> None:
44
41
  """Initialize the discovery endpoint."""
42
+
45
43
  @self.router.get("/discovery")
46
44
  async def discovery() -> Dict[str, Any]:
47
45
  """Return the discovery information for this tools service."""
@@ -66,21 +64,23 @@ class ToolsService:
66
64
  # Auth requirements should always be a list
67
65
  if isinstance(handler.__auth_requirements__, list):
68
66
  for req in handler.__auth_requirements__:
69
- auth_requirements.append(AuthRequirement(
70
- provider=req.get("provider", ""),
71
- scope_bundle=req.get("scope_bundle", ""),
72
- required=req.get("required", True)
73
- ))
67
+ auth_requirements.append(
68
+ AuthRequirement(
69
+ provider=req.get("provider", ""), scope_bundle=req.get("scope_bundle", ""), required=req.get("required", True)
70
+ )
71
+ )
74
72
 
75
73
  return auth_requirements
76
74
 
77
- def register_tool(self,
78
- name: str,
79
- description: str,
80
- handler: Callable,
81
- parameters: List[Parameter],
82
- endpoint: str,
83
- auth_requirements: Optional[List[AuthRequirement]] = None) -> None:
75
+ def register_tool(
76
+ self,
77
+ name: str,
78
+ description: str,
79
+ handler: Callable,
80
+ parameters: List[Parameter],
81
+ endpoint: str,
82
+ auth_requirements: Optional[List[AuthRequirement]] = None,
83
+ ) -> None:
84
84
  """Register a tool function.
85
85
 
86
86
  Args:
@@ -101,11 +101,7 @@ class ToolsService:
101
101
  final_auth_requirements = auth_requirements if auth_requirements else handler_auth_requirements
102
102
 
103
103
  function = Function(
104
- name=name,
105
- description=description,
106
- parameters=parameters,
107
- endpoint=endpoint,
108
- auth_requirements=final_auth_requirements
104
+ name=name, description=description, parameters=parameters, endpoint=endpoint, auth_requirements=final_auth_requirements
109
105
  )
110
106
 
111
107
  self.functions.append(function)
@@ -144,7 +140,6 @@ class ToolsService:
144
140
  param_name = list(sig.parameters.keys())[0]
145
141
  param_type = get_type_hints(handler).get(param_name)
146
142
 
147
-
148
143
  args = []
149
144
  kwargs = {}
150
145
  if param_type:
@@ -168,8 +163,12 @@ class ToolsService:
168
163
 
169
164
  logger.info(f"Tool {name} returned: {result}")
170
165
  return result
166
+ except ValidationError as e:
167
+ logger.warning(f"Invalid parameters predicted by LLM for tool {name}: {str(e)}")
168
+ raise HTTPException(status_code=400, detail=str(e))
171
169
  except Exception as e:
172
170
  import traceback
171
+
173
172
  logger.error(f"Error in tool {name}: {str(e)}")
174
173
  logger.error(traceback.format_exc())
175
174
  raise HTTPException(status_code=500, detail=str(e))
@@ -0,0 +1,255 @@
1
+ Metadata-Version: 2.4
2
+ Name: optimizely-opal.opal-tools-sdk
3
+ Version: 0.1.10.dev0
4
+ Summary: SDK for creating Opal-compatible tools services
5
+ Home-page: https://github.com/optimizely/opal-tools-sdk
6
+ Author: Optimizely
7
+ Author-email: Optimizely <opal-team@optimizely.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/optimizely/opal-tools-sdk
10
+ Project-URL: Bug Tracker, https://github.com/optimizely/opal-tools-sdk/issues
11
+ Keywords: opal,tools,sdk,ai,llm
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: fastapi>=0.100.0
19
+ Requires-Dist: pydantic>=2.0.0
20
+ Requires-Dist: httpx>=0.24.1
21
+ Dynamic: author
22
+ Dynamic: home-page
23
+ Dynamic: requires-python
24
+
25
+ # Opal Tools SDK for Python
26
+
27
+ This SDK simplifies the creation of tools services compatible with the Opal Tools Management Service.
28
+
29
+ ## Features
30
+
31
+ - Easy definition of tool functions with decorators
32
+ - Automatic generation of discovery endpoints
33
+ - Parameter validation and type checking
34
+ - Authentication helpers
35
+ - FastAPI integration
36
+ - Island components for interactive UI responses
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ pip install optimizely-opal.opal-tools-sdk
42
+ ```
43
+
44
+ Note: While the package is installed as `optimizely-opal.opal-tools-sdk`, you'll still import it in your code as `opal_tools_sdk`:
45
+
46
+ ```python
47
+ # Import using the package name
48
+ from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
49
+ ```
50
+
51
+ ## Usage
52
+
53
+ ```python
54
+ from opal_tools_sdk import ToolsService, tool
55
+ from pydantic import BaseModel
56
+ from fastapi import FastAPI
57
+
58
+ app = FastAPI()
59
+ tools_service = ToolsService(app)
60
+
61
+ class WeatherParameters(BaseModel):
62
+ location: str
63
+ units: str = "metric"
64
+
65
+ @tool("get_weather", "Gets current weather for a location")
66
+ async def get_weather(parameters: WeatherParameters):
67
+ # Implementation...
68
+ return {"temperature": 22, "condition": "sunny"}
69
+
70
+ # Discovery endpoint is automatically created at /discovery
71
+ ```
72
+
73
+ ## Authentication
74
+
75
+ The SDK provides two ways to require authentication for your tools:
76
+
77
+ ### 1. Using the `@requires_auth` decorator
78
+
79
+ ```python
80
+ from opal_tools_sdk import ToolsService, tool, AuthData
81
+ from opal_tools_sdk.auth import requires_auth
82
+ from pydantic import BaseModel
83
+ from fastapi import FastAPI
84
+ from typing import Optional
85
+
86
+ app = FastAPI()
87
+ tools_service = ToolsService(app)
88
+
89
+ class CalendarParameters(BaseModel):
90
+ date: str
91
+ timezone: str = "UTC"
92
+
93
+ # Single authentication requirement
94
+ @requires_auth(provider="google", scope_bundle="calendar", required=True)
95
+ @tool("get_calendar_events", "Gets calendar events for a date")
96
+ async def get_calendar_events(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
97
+ # The auth_data parameter contains authentication information
98
+ if auth_data:
99
+ token = auth_data["credentials"]["access_token"]
100
+
101
+ # Use the token to make authenticated requests
102
+ # ...
103
+
104
+ return {"events": ["Meeting at 10:00", "Lunch at 12:00"]}
105
+
106
+ # Multiple authentication requirements (tool can work with either provider)
107
+ @requires_auth(provider="google", scope_bundle="calendar", required=True)
108
+ @requires_auth(provider="microsoft", scope_bundle="outlook", required=True)
109
+ @tool("get_calendar_availability", "Check calendar availability")
110
+ async def get_calendar_availability(parameters: CalendarParameters, auth_data: Optional[AuthData] = None):
111
+ provider = ""
112
+ token = ""
113
+
114
+ if auth_data:
115
+ provider = auth_data["provider"]
116
+ token = auth_data["credentials"]["access_token"]
117
+
118
+ if provider == "google":
119
+ # Use Google Calendar API
120
+ pass
121
+ elif provider == "microsoft":
122
+ # Use Microsoft Outlook API
123
+ pass
124
+
125
+ return {"available": True, "provider_used": provider}
126
+ ```
127
+
128
+ ### 2. Specifying auth requirements in the `@tool` decorator
129
+
130
+ ```python
131
+ @tool(
132
+ "get_email",
133
+ "Gets emails from the user's inbox",
134
+ auth_requirements=[
135
+ {"provider": "google", "scope_bundle": "gmail", "required": True}
136
+ ]
137
+ )
138
+ async def get_email(parameters: EmailParameters, auth_data: Optional[AuthData] = None):
139
+ # Implementation...
140
+ return {"emails": ["Email 1", "Email 2"]}
141
+ ```
142
+
143
+ ## Island Components
144
+
145
+ The SDK includes Island components for creating interactive UI responses that allow users to input data and trigger actions.
146
+
147
+ ### Weather Tool with Interactive Island
148
+
149
+ ```python
150
+ from opal_tools_sdk import ToolsService, tool, IslandResponse, IslandConfig
151
+ from pydantic import BaseModel
152
+ from fastapi import FastAPI
153
+
154
+ app = FastAPI()
155
+ tools_service = ToolsService(app)
156
+
157
+ class WeatherParameters(BaseModel):
158
+ location: str
159
+ units: str = "metric"
160
+
161
+ @tool("get_weather", "Gets current weather for a location")
162
+ async def get_weather(parameters: WeatherParameters):
163
+ # Get weather data (implementation details omitted)
164
+ weather_data = {"temperature": 22, "condition": "sunny", "humidity": 65}
165
+
166
+ # Create an interactive island for weather settings
167
+ island = IslandConfig(
168
+ fields=[
169
+ IslandConfig.Field(
170
+ name="location",
171
+ label="Location",
172
+ type="string",
173
+ value=parameters.location
174
+ ),
175
+ IslandConfig.Field(
176
+ name="units",
177
+ label="Temperature Units",
178
+ type="string",
179
+ value=parameters.units,
180
+ options=["metric", "imperial", "kelvin"]
181
+ ),
182
+ IslandConfig.Field(
183
+ name="current_temp",
184
+ label="Current Temperature",
185
+ type="string",
186
+ value=f"{weather_data['temperature']}°{'C' if parameters.units == 'metric' else 'F'}"
187
+ )
188
+ ],
189
+ actions=[
190
+ IslandConfig.Action(
191
+ name="refresh_weather",
192
+ label="Refresh Weather",
193
+ type="button",
194
+ endpoint="/tools/get_weather",
195
+ operation="update"
196
+ )
197
+ ]
198
+ )
199
+
200
+ return IslandResponse.create([island])
201
+ ```
202
+
203
+ ### Island Components
204
+
205
+ #### IslandConfig.Field
206
+ Fields represent data inputs in the UI:
207
+ - `name`: Programmatic field identifier
208
+ - `label`: Human-readable label
209
+ - `type`: Field type (`"string"`, `"boolean"`, `"json"`)
210
+ - `value`: Current field value (optional)
211
+ - `hidden`: Whether to hide from user (optional, default: False)
212
+ - `options`: Available options for selection (optional)
213
+
214
+ #### IslandConfig.Action
215
+ Actions represent buttons or operations:
216
+ - `name`: Programmatic action identifier
217
+ - `label`: Human-readable button label
218
+ - `type`: UI element type (typically `"button"`)
219
+ - `endpoint`: API endpoint to call
220
+ - `operation`: Operation type (default: `"create"`)
221
+
222
+ #### IslandConfig
223
+ Contains the complete island configuration:
224
+ - `fields`: List of IslandConfig.Field objects
225
+ - `actions`: List of IslandConfig.Action objects
226
+ - `type`: Island type for UI rendering (optional, default: `None`)
227
+ - `icon`: Icon to display in the island (optional, default: `None`)
228
+
229
+ #### IslandResponse
230
+ The response wrapper for islands:
231
+ - Use `IslandResponse.create([islands])` to create responses
232
+ - Supports multiple islands per response
233
+
234
+ ## Type Definitions
235
+
236
+ The SDK provides several TypedDict and dataclass definitions for better type safety:
237
+
238
+ ### Authentication Types
239
+ - `AuthData`: TypedDict containing provider and credentials information
240
+ - `Credentials`: TypedDict with access_token, org_sso_id, customer_id, instance_id, and product_sku
241
+ - `AuthRequirement`: Dataclass for specifying authentication requirements
242
+
243
+ ### Execution Environment
244
+ - `Environment`: TypedDict specifying execution mode (`"headless"` or `"interactive"`)
245
+
246
+ ### Parameter Types
247
+ - `ParameterType`: Enum for supported parameter types (string, integer, number, boolean, list, dictionary)
248
+ - `Parameter`: Dataclass for tool parameter definitions
249
+ - `Function`: Dataclass for complete tool function definitions
250
+
251
+ These types are automatically imported when you import from `opal_tools_sdk` and provide better IDE support and type checking.
252
+
253
+ ## Documentation
254
+
255
+ See full documentation for more examples and configuration options.
@@ -0,0 +1,11 @@
1
+ opal_tools_sdk/__init__.py,sha256=0B0c53fyCRMuI_zOfwiePzQk5ion1BSKCqGLVsY0TVk,495
2
+ opal_tools_sdk/_registry.py,sha256=YE3eD4kcS09QDe4RccBYAzXPo9znEU7fblrsB-g3o-Y,67
3
+ opal_tools_sdk/auth.py,sha256=9aMiZv6n6_iu7hQA0sKg4hgNr5DzYFFuP0SWUoZf_Vw,1520
4
+ opal_tools_sdk/decorators.py,sha256=DuSDb1Dd1DSIlOC0ZBS4iE8Go3O93xqtPS43S5HwKBw,6526
5
+ opal_tools_sdk/logging.py,sha256=krvkhdql2GWh0aCWj5gfqyn5plsyLJ773WzIirK7tkU,1325
6
+ opal_tools_sdk/models.py,sha256=X25qtorUkQKQAd2zHBJjlBSGeIo0gIJVJ7d0QJsPPko,3546
7
+ opal_tools_sdk/service.py,sha256=a5ZC-J1GF5y1-FdWCXdilCy7p5hG63MTTn6frBBCo4Y,6939
8
+ optimizely_opal_opal_tools_sdk-0.1.10.dev0.dist-info/METADATA,sha256=AJEYAccTQUy-Tk9YgB_iZuJmp5LpqvCf0-ZkeiVPIBo,8221
9
+ optimizely_opal_opal_tools_sdk-0.1.10.dev0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
+ optimizely_opal_opal_tools_sdk-0.1.10.dev0.dist-info/top_level.txt,sha256=nCJ5PxF0rgoV6yNJvvuUaZPx4D3EWkl7gpu-6xafH1E,15
11
+ optimizely_opal_opal_tools_sdk-0.1.10.dev0.dist-info/RECORD,,
@@ -1,138 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: optimizely-opal.opal-tools-sdk
3
- Version: 0.1.4.dev0
4
- Summary: SDK for creating Opal-compatible tools services
5
- Home-page: https://github.com/optimizely/opal-tools-sdk
6
- Author: Optimizely
7
- Author-email: Optimizely <opal-team@optimizely.com>
8
- License: MIT
9
- Project-URL: Homepage, https://github.com/optimizely/opal-tools-sdk
10
- Project-URL: Bug Tracker, https://github.com/optimizely/opal-tools-sdk/issues
11
- Keywords: opal,tools,sdk,ai,llm
12
- Classifier: Development Status :: 3 - Alpha
13
- Classifier: Intended Audience :: Developers
14
- Classifier: Programming Language :: Python :: 3.10
15
- Classifier: License :: OSI Approved :: MIT License
16
- Requires-Python: >=3.10
17
- Description-Content-Type: text/markdown
18
- Requires-Dist: fastapi>=0.100.0
19
- Requires-Dist: pydantic>=2.0.0
20
- Requires-Dist: httpx>=0.24.1
21
- Dynamic: author
22
- Dynamic: home-page
23
- Dynamic: requires-python
24
-
25
- # Opal Tools SDK for Python
26
-
27
- This SDK simplifies the creation of tools services compatible with the Opal Tools Management Service.
28
-
29
- ## Features
30
-
31
- - Easy definition of tool functions with decorators
32
- - Automatic generation of discovery endpoints
33
- - Parameter validation and type checking
34
- - Authentication helpers
35
- - FastAPI integration
36
-
37
- ## Installation
38
-
39
- ```bash
40
- pip install optimizely-opal.opal-tools-sdk
41
- ```
42
-
43
- Note: While the package is installed as `optimizely-opal.opal-tools-sdk`, you'll still import it in your code as `opal_tools_sdk`:
44
-
45
- ```python
46
- # Import using the package name
47
- from opal_tools_sdk import ToolsService, tool
48
- ```
49
-
50
- ## Usage
51
-
52
- ```python
53
- from opal_tools_sdk import ToolsService, tool
54
- from pydantic import BaseModel
55
- from fastapi import FastAPI
56
-
57
- app = FastAPI()
58
- tools_service = ToolsService(app)
59
-
60
- class WeatherParameters(BaseModel):
61
- location: str
62
- units: str = "metric"
63
-
64
- @tool("get_weather", "Gets current weather for a location")
65
- async def get_weather(parameters: WeatherParameters):
66
- # Implementation...
67
- return {"temperature": 22, "condition": "sunny"}
68
-
69
- # Discovery endpoint is automatically created at /discovery
70
- ```
71
-
72
- ## Authentication
73
-
74
- The SDK provides two ways to require authentication for your tools:
75
-
76
- ### 1. Using the `@requires_auth` decorator
77
-
78
- ```python
79
- from opal_tools_sdk import ToolsService, tool
80
- from opal_tools_sdk.auth import requires_auth
81
- from pydantic import BaseModel
82
- from fastapi import FastAPI
83
-
84
- app = FastAPI()
85
- tools_service = ToolsService(app)
86
-
87
- class CalendarParameters(BaseModel):
88
- date: str
89
- timezone: str = "UTC"
90
-
91
- # Single authentication requirement
92
- @requires_auth(provider="google", scope_bundle="calendar", required=True)
93
- @tool("get_calendar_events", "Gets calendar events for a date")
94
- async def get_calendar_events(parameters: CalendarParameters, auth_data=None):
95
- # The auth_data parameter contains authentication information
96
- token = auth_data.get("credentials", {}).get("token", "")
97
-
98
- # Use the token to make authenticated requests
99
- # ...
100
-
101
- return {"events": ["Meeting at 10:00", "Lunch at 12:00"]}
102
-
103
- # Multiple authentication requirements (tool can work with either provider)
104
- @requires_auth(provider="google", scope_bundle="calendar", required=True)
105
- @requires_auth(provider="microsoft", scope_bundle="outlook", required=True)
106
- @tool("get_calendar_availability", "Check calendar availability")
107
- async def get_calendar_availability(parameters: CalendarParameters, auth_data=None):
108
- provider = auth_data.get("provider", "")
109
- token = auth_data.get("credentials", {}).get("token", "")
110
-
111
- if provider == "google":
112
- # Use Google Calendar API
113
- pass
114
- elif provider == "microsoft":
115
- # Use Microsoft Outlook API
116
- pass
117
-
118
- return {"available": True, "provider_used": provider}
119
- ```
120
-
121
- ### 2. Specifying auth requirements in the `@tool` decorator
122
-
123
- ```python
124
- @tool(
125
- "get_email",
126
- "Gets emails from the user's inbox",
127
- auth_requirements=[
128
- {"provider": "google", "scope_bundle": "gmail", "required": True}
129
- ]
130
- )
131
- async def get_email(parameters: EmailParameters, auth_data=None):
132
- # Implementation...
133
- return {"emails": ["Email 1", "Email 2"]}
134
- ```
135
-
136
- ## Documentation
137
-
138
- See full documentation for more examples and configuration options.
@@ -1,11 +0,0 @@
1
- opal_tools_sdk/__init__.py,sha256=riYZkv0C1Pgwd3SpE9Zwxs3a1eXa26wrGfY1N1Tdjb4,242
2
- opal_tools_sdk/_registry.py,sha256=YE3eD4kcS09QDe4RccBYAzXPo9znEU7fblrsB-g3o-Y,67
3
- opal_tools_sdk/auth.py,sha256=9aMiZv6n6_iu7hQA0sKg4hgNr5DzYFFuP0SWUoZf_Vw,1520
4
- opal_tools_sdk/decorators.py,sha256=iHal7RElOSCWjqKHIK-Hc4GKOsjuGyUg0_lsQKVQShg,5315
5
- opal_tools_sdk/logging.py,sha256=krvkhdql2GWh0aCWj5gfqyn5plsyLJ773WzIirK7tkU,1325
6
- opal_tools_sdk/models.py,sha256=lVumBjEWbKtkRBzwXt7m5uagqinJ6Y96K9a5Uw39yaM,2092
7
- opal_tools_sdk/service.py,sha256=wVYe40zEZc1ynIkvowD9APU8BFHQ5fdy3j8qlsdZB1U,6965
8
- optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info/METADATA,sha256=3uy9B3LYQM6kJj-D_mtfrTYqCmpogXeKT-cMWE8IySU,4115
9
- optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
10
- optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info/top_level.txt,sha256=nCJ5PxF0rgoV6yNJvvuUaZPx4D3EWkl7gpu-6xafH1E,15
11
- optimizely_opal_opal_tools_sdk-0.1.4.dev0.dist-info/RECORD,,