optimizely-opal.opal-tools-sdk 0.1.3.dev0__py3-none-any.whl → 0.1.5.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.
Potentially problematic release.
This version of optimizely-opal.opal-tools-sdk might be problematic. Click here for more details.
- opal_tools_sdk/__init__.py +15 -1
- opal_tools_sdk/decorators.py +6 -5
- opal_tools_sdk/logging.py +37 -0
- opal_tools_sdk/models.py +70 -13
- opal_tools_sdk/service.py +10 -10
- optimizely_opal_opal_tools_sdk-0.1.5.dev0.dist-info/METADATA +253 -0
- optimizely_opal_opal_tools_sdk-0.1.5.dev0.dist-info/RECORD +11 -0
- optimizely_opal_opal_tools_sdk-0.1.3.dev0.dist-info/METADATA +0 -138
- optimizely_opal_opal_tools_sdk-0.1.3.dev0.dist-info/RECORD +0 -10
- {optimizely_opal_opal_tools_sdk-0.1.3.dev0.dist-info → optimizely_opal_opal_tools_sdk-0.1.5.dev0.dist-info}/WHEEL +0 -0
- {optimizely_opal_opal_tools_sdk-0.1.3.dev0.dist-info → optimizely_opal_opal_tools_sdk-0.1.5.dev0.dist-info}/top_level.txt +0 -0
opal_tools_sdk/__init__.py
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
from .service import ToolsService
|
|
2
2
|
from .decorators import tool
|
|
3
3
|
from .auth import requires_auth
|
|
4
|
+
from .logging import register_logger_factory
|
|
5
|
+
from .models import AuthData, AuthRequirement, Credentials, Environment, IslandConfig, IslandResponse
|
|
4
6
|
|
|
5
7
|
__version__ = "0.1.0"
|
|
6
|
-
__all__ = [
|
|
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
|
+
]
|
opal_tools_sdk/decorators.py
CHANGED
|
@@ -6,8 +6,9 @@ from fastapi import APIRouter, Depends, Header, HTTPException
|
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
8
|
from .models import Parameter, ParameterType, AuthRequirement
|
|
9
|
+
from .logging import get_logger
|
|
9
10
|
|
|
10
|
-
logger =
|
|
11
|
+
logger = get_logger(__name__)
|
|
11
12
|
|
|
12
13
|
def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str, Any]]] = None):
|
|
13
14
|
"""Decorator to register a function as an Opal tool.
|
|
@@ -95,9 +96,9 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
|
|
|
95
96
|
required=required
|
|
96
97
|
))
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
logger.info(f"Registered parameter: {field_name} of type {param_type.value}, required: {required}")
|
|
99
100
|
else:
|
|
100
|
-
|
|
101
|
+
logger.warning(f"Warning: No parameter model found for {name}")
|
|
101
102
|
|
|
102
103
|
endpoint = f"/tools/{name}"
|
|
103
104
|
|
|
@@ -112,10 +113,10 @@ def tool(name: str, description: str, auth_requirements: Optional[List[Dict[str,
|
|
|
112
113
|
required=auth_req.get("required", True)
|
|
113
114
|
))
|
|
114
115
|
|
|
115
|
-
|
|
116
|
+
logger.info(f"Registering tool {name} with endpoint {endpoint}")
|
|
116
117
|
|
|
117
118
|
if not _registry.services:
|
|
118
|
-
|
|
119
|
+
logger.warning("No services registered in registry! Make sure to create ToolsService before decorating functions.")
|
|
119
120
|
|
|
120
121
|
for service in _registry.services:
|
|
121
122
|
service.register_tool(
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Optional, Callable
|
|
4
|
+
|
|
5
|
+
# Type alias for a logger factory function
|
|
6
|
+
type LoggerFactory = Callable[[Optional[str]], logging.Logger]
|
|
7
|
+
|
|
8
|
+
# Internal variable to hold a custom logger factory
|
|
9
|
+
_custom_logger_factory: Optional[LoggerFactory] = None
|
|
10
|
+
|
|
11
|
+
def register_logger_factory(factory: LoggerFactory):
|
|
12
|
+
"""
|
|
13
|
+
Register a custom logger factory function. This function should accept a name (str or None)
|
|
14
|
+
and return a logger instance (e.g., structlog or standard logger).
|
|
15
|
+
"""
|
|
16
|
+
global _custom_logger_factory
|
|
17
|
+
_custom_logger_factory = factory
|
|
18
|
+
|
|
19
|
+
def get_logger(name: str = None) -> logging.Logger:
|
|
20
|
+
"""
|
|
21
|
+
Returns a logger configured to output to the console, or uses a registered custom logger factory.
|
|
22
|
+
If no name is provided, uses the root logger.
|
|
23
|
+
"""
|
|
24
|
+
if _custom_logger_factory is not None:
|
|
25
|
+
return _custom_logger_factory(name)
|
|
26
|
+
logger = logging.getLogger(name)
|
|
27
|
+
if not logger.handlers:
|
|
28
|
+
handler = logging.StreamHandler(sys.stdout)
|
|
29
|
+
formatter = logging.Formatter(
|
|
30
|
+
fmt='%(asctime)s %(levelname)s [%(name)s] %(message)s',
|
|
31
|
+
datefmt='%Y-%m-%d %H:%M:%S'
|
|
32
|
+
)
|
|
33
|
+
handler.setFormatter(formatter)
|
|
34
|
+
logger.addHandler(handler)
|
|
35
|
+
logger.setLevel(logging.INFO)
|
|
36
|
+
logger.propagate = False
|
|
37
|
+
return logger
|
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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,43 @@ 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
|
+
|
|
116
|
+
|
|
117
|
+
class IslandResponse(BaseModel):
|
|
118
|
+
class ResponseConfig(BaseModel):
|
|
119
|
+
islands: list[IslandConfig]
|
|
120
|
+
|
|
121
|
+
type: Literal["island"]
|
|
122
|
+
config: ResponseConfig
|
|
123
|
+
|
|
124
|
+
@classmethod
|
|
125
|
+
def create(cls, islands: list[IslandConfig]):
|
|
126
|
+
return cls(type="island", config=cls.ResponseConfig(islands=islands))
|
opal_tools_sdk/service.py
CHANGED
|
@@ -8,7 +8,7 @@ from pydantic import BaseModel, create_model
|
|
|
8
8
|
from .models import Function, Parameter, ParameterType, AuthRequirement
|
|
9
9
|
from . import _registry
|
|
10
10
|
|
|
11
|
-
logger = logging.getLogger(
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
12
|
|
|
13
13
|
class ToolsService:
|
|
14
14
|
"""Main class for managing Opal tools."""
|
|
@@ -91,7 +91,7 @@ class ToolsService:
|
|
|
91
91
|
endpoint: API endpoint for the tool
|
|
92
92
|
auth_requirements: List of authentication requirements (optional)
|
|
93
93
|
"""
|
|
94
|
-
|
|
94
|
+
logger.info(f"Registering tool: {name} with endpoint: {endpoint}")
|
|
95
95
|
|
|
96
96
|
# Extract auth requirements from handler if decorated with @requires_auth
|
|
97
97
|
handler_auth_requirements = self._extract_auth_requirements(handler)
|
|
@@ -116,7 +116,7 @@ class ToolsService:
|
|
|
116
116
|
try:
|
|
117
117
|
# Parse JSON body
|
|
118
118
|
body = await request.json()
|
|
119
|
-
|
|
119
|
+
logger.info(f"Received request for {endpoint} with body: {body}")
|
|
120
120
|
|
|
121
121
|
# Parameters should be in the "parameters" key according to the spec
|
|
122
122
|
# This matches how the tools-mgmt-service calls tools
|
|
@@ -124,20 +124,20 @@ class ToolsService:
|
|
|
124
124
|
params = body["parameters"]
|
|
125
125
|
else:
|
|
126
126
|
# For backward compatibility with direct test calls
|
|
127
|
-
|
|
127
|
+
logger.warning(f"Warning: 'parameters' key not found in request body. Using body directly.")
|
|
128
128
|
params = body
|
|
129
129
|
|
|
130
130
|
# Extract auth data if available
|
|
131
131
|
auth_data = body.get("auth")
|
|
132
132
|
if auth_data:
|
|
133
|
-
|
|
133
|
+
logger.info(f"Auth data provided for provider: {auth_data.get('provider', 'unknown')}")
|
|
134
134
|
|
|
135
135
|
# Extract environment data if available
|
|
136
136
|
environment = body.get("environment", {})
|
|
137
137
|
if environment:
|
|
138
|
-
|
|
138
|
+
logger.info(f"Environment data provided: {environment}")
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
logger.info(f"Extracted parameters: {params}")
|
|
141
141
|
|
|
142
142
|
# Get the parameter model from handler's signature
|
|
143
143
|
sig = inspect.signature(handler)
|
|
@@ -166,12 +166,12 @@ class ToolsService:
|
|
|
166
166
|
|
|
167
167
|
result = await handler(*args, **kwargs)
|
|
168
168
|
|
|
169
|
-
|
|
169
|
+
logger.info(f"Tool {name} returned: {result}")
|
|
170
170
|
return result
|
|
171
171
|
except Exception as e:
|
|
172
172
|
import traceback
|
|
173
|
-
|
|
174
|
-
|
|
173
|
+
logger.error(f"Error in tool {name}: {str(e)}")
|
|
174
|
+
logger.error(traceback.format_exc())
|
|
175
175
|
raise HTTPException(status_code=500, detail=str(e))
|
|
176
176
|
|
|
177
177
|
# Update the route function name and docstring
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: optimizely-opal.opal-tools-sdk
|
|
3
|
+
Version: 0.1.5.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
|
+
|
|
227
|
+
#### IslandResponse
|
|
228
|
+
The response wrapper for islands:
|
|
229
|
+
- Use `IslandResponse.create([islands])` to create responses
|
|
230
|
+
- Supports multiple islands per response
|
|
231
|
+
|
|
232
|
+
## Type Definitions
|
|
233
|
+
|
|
234
|
+
The SDK provides several TypedDict and dataclass definitions for better type safety:
|
|
235
|
+
|
|
236
|
+
### Authentication Types
|
|
237
|
+
- `AuthData`: TypedDict containing provider and credentials information
|
|
238
|
+
- `Credentials`: TypedDict with access_token, org_sso_id, customer_id, instance_id, and product_sku
|
|
239
|
+
- `AuthRequirement`: Dataclass for specifying authentication requirements
|
|
240
|
+
|
|
241
|
+
### Execution Environment
|
|
242
|
+
- `Environment`: TypedDict specifying execution mode (`"headless"` or `"interactive"`)
|
|
243
|
+
|
|
244
|
+
### Parameter Types
|
|
245
|
+
- `ParameterType`: Enum for supported parameter types (string, integer, number, boolean, list, dictionary)
|
|
246
|
+
- `Parameter`: Dataclass for tool parameter definitions
|
|
247
|
+
- `Function`: Dataclass for complete tool function definitions
|
|
248
|
+
|
|
249
|
+
These types are automatically imported when you import from `opal_tools_sdk` and provide better IDE support and type checking.
|
|
250
|
+
|
|
251
|
+
## Documentation
|
|
252
|
+
|
|
253
|
+
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=iHal7RElOSCWjqKHIK-Hc4GKOsjuGyUg0_lsQKVQShg,5315
|
|
5
|
+
opal_tools_sdk/logging.py,sha256=krvkhdql2GWh0aCWj5gfqyn5plsyLJ773WzIirK7tkU,1325
|
|
6
|
+
opal_tools_sdk/models.py,sha256=YESFXdIPIFoVqP6OXtswXzHQ1tYMFVSnb8rFVe9cFj4,3403
|
|
7
|
+
opal_tools_sdk/service.py,sha256=wVYe40zEZc1ynIkvowD9APU8BFHQ5fdy3j8qlsdZB1U,6965
|
|
8
|
+
optimizely_opal_opal_tools_sdk-0.1.5.dev0.dist-info/METADATA,sha256=KS830xd4wFliLcbfJBuWSxj49pIH_AfajJJOrm_UG0Q,8085
|
|
9
|
+
optimizely_opal_opal_tools_sdk-0.1.5.dev0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
10
|
+
optimizely_opal_opal_tools_sdk-0.1.5.dev0.dist-info/top_level.txt,sha256=nCJ5PxF0rgoV6yNJvvuUaZPx4D3EWkl7gpu-6xafH1E,15
|
|
11
|
+
optimizely_opal_opal_tools_sdk-0.1.5.dev0.dist-info/RECORD,,
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: optimizely-opal.opal-tools-sdk
|
|
3
|
-
Version: 0.1.3.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,10 +0,0 @@
|
|
|
1
|
-
opal_tools_sdk/__init__.py,sha256=Eu141xM3QVq1O_BLGZS9m_asU1b_KaOApFtwvaYll6E,170
|
|
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=EE5gjpLRENSZv8cz7wHIIfTCvBh7WqvBaiQtDfMaZig,5268
|
|
5
|
-
opal_tools_sdk/models.py,sha256=lVumBjEWbKtkRBzwXt7m5uagqinJ6Y96K9a5Uw39yaM,2092
|
|
6
|
-
opal_tools_sdk/service.py,sha256=KBqdBHYJ5wgxky0mSv0NJPbbzVhq4YSafgaTbsndTrE,6914
|
|
7
|
-
optimizely_opal_opal_tools_sdk-0.1.3.dev0.dist-info/METADATA,sha256=1pXFL2AInL1gJGBgEwCA5xKu8EoH5pyu0TH_m3y9G8U,4115
|
|
8
|
-
optimizely_opal_opal_tools_sdk-0.1.3.dev0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
9
|
-
optimizely_opal_opal_tools_sdk-0.1.3.dev0.dist-info/top_level.txt,sha256=nCJ5PxF0rgoV6yNJvvuUaZPx4D3EWkl7gpu-6xafH1E,15
|
|
10
|
-
optimizely_opal_opal_tools_sdk-0.1.3.dev0.dist-info/RECORD,,
|
|
File without changes
|