-
Notifications
You must be signed in to change notification settings - Fork 904
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(integration-service): Add integrations service (#520)
Introduces a new integration service with FastAPI, Docker setup, and support for multiple integrations like DALL-E and Wikipedia. - Integration Service: + Adds integrations-service with Docker setup in docker-compose.yml and Dockerfile. + FastAPI application in web.py with routers for execution and integration management. - Models: + Defines models for DalleImageGenerator, DuckDuckGoSearch, HackerNews, Weather, and Wikipedia in models directory. + IntegrationExecutionRequest and IntegrationExecutionResponse for handling execution requests. - Utilities: + Implements execute_integration in execute_integration.py to handle integration execution logic. + Integration utilities for DALL-E, DuckDuckGo, Hacker News, Weather, and Wikipedia in utils/integrations. - Configuration: + Adds pyproject.toml for dependency management with Poetry. + Adds pytype.toml for type checking configuration.
- Loading branch information
1 parent
a4151d0
commit d8072a0
Showing
33 changed files
with
2,688 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
FROM python:3.11-slim | ||
|
||
WORKDIR /app | ||
|
||
# Install Poetry | ||
RUN pip install poetry | ||
|
||
# Copy only requirements to cache them in docker layer | ||
COPY pyproject.toml poetry.lock* /app/ | ||
|
||
# Project initialization: | ||
RUN poetry config virtualenvs.create false \ | ||
&& poetry install --no-interaction --no-ansi | ||
|
||
# Copy project | ||
COPY . ./ | ||
|
||
# Run the application | ||
CMD ["python", "-m", "integrations.web", "--host", "0.0.0.0", "--port", "8000"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
name: julep-integrations | ||
|
||
# Shared environment variables | ||
x--shared-environment: &shared-environment | ||
OPENAI_API_KEY: ${OPENAI_API_KEY} | ||
|
||
services: | ||
integrations: | ||
environment: | ||
<<: *shared-environment | ||
|
||
build: . | ||
ports: | ||
- "8000:8000" | ||
|
||
develop: | ||
watch: | ||
- action: sync+restart | ||
path: ./ | ||
target: /app/ | ||
ignore: | ||
- ./**/*.pyc | ||
- action: rebuild | ||
path: poetry.lock | ||
- action: rebuild | ||
path: Dockerfile |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
from .dalle_image_generator import ( | ||
DalleImageGeneratorArguments, | ||
DalleImageGeneratorSetup, | ||
) | ||
from .duckduckgo_search import DuckDuckGoSearchExecutionArguments | ||
from .hacker_news import HackerNewsExecutionArguments | ||
|
||
# TODO: Move these models somewhere else | ||
from .models import ( | ||
ExecuteIntegrationArguments, | ||
ExecuteIntegrationSetup, | ||
IntegrationDef, | ||
IntegrationExecutionRequest, | ||
IntegrationExecutionResponse, | ||
) | ||
from .weather import WeatherExecutionArguments, WeatherExecutionSetup | ||
from .wikipedia import WikipediaExecutionArguments |
9 changes: 9 additions & 0 deletions
9
integrations-service/integrations/models/dalle_image_generator.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from pydantic import BaseModel, Field | ||
|
||
|
||
class DalleImageGeneratorSetup(BaseModel): | ||
api_key: str = Field(str, description="The API key for DALL-E") | ||
|
||
|
||
class DalleImageGeneratorArguments(BaseModel): | ||
prompt: str = Field(str, description="The image generation prompt") |
5 changes: 5 additions & 0 deletions
5
integrations-service/integrations/models/duckduckgo_search.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from pydantic import BaseModel, Field | ||
|
||
|
||
class DuckDuckGoSearchExecutionArguments(BaseModel): | ||
query: str = Field(..., description="The search query string") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from pydantic import BaseModel, Field | ||
|
||
|
||
class HackerNewsExecutionArguments(BaseModel): | ||
url: str = Field(..., description="The URL of the Hacker News thread to fetch") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
from typing import Literal, Union | ||
|
||
from pydantic import BaseModel | ||
|
||
from .dalle_image_generator import ( | ||
DalleImageGeneratorArguments, | ||
DalleImageGeneratorSetup, | ||
) | ||
from .duckduckgo_search import DuckDuckGoSearchExecutionArguments | ||
from .hacker_news import HackerNewsExecutionArguments | ||
from .weather import WeatherExecutionArguments, WeatherExecutionSetup | ||
from .wikipedia import WikipediaExecutionArguments | ||
|
||
ExecuteIntegrationArguments = Union[ | ||
WikipediaExecutionArguments, | ||
DuckDuckGoSearchExecutionArguments, | ||
DalleImageGeneratorArguments, | ||
WeatherExecutionArguments, | ||
HackerNewsExecutionArguments, | ||
] | ||
|
||
ExecuteIntegrationSetup = Union[ | ||
DalleImageGeneratorSetup, | ||
WeatherExecutionSetup, | ||
] | ||
|
||
|
||
class IntegrationExecutionRequest(BaseModel): | ||
setup: ExecuteIntegrationSetup | None = None | ||
""" | ||
The setup parameters the integration accepts (such as API keys) | ||
""" | ||
arguments: ExecuteIntegrationArguments | ||
""" | ||
The arguments to pass to the integration | ||
""" | ||
|
||
|
||
class IntegrationExecutionResponse(BaseModel): | ||
result: str | ||
""" | ||
The result of the integration execution | ||
""" | ||
|
||
|
||
class IntegrationDef(BaseModel): | ||
provider: ( | ||
Literal[ | ||
"dummy", | ||
"dalle_image_generator", | ||
"duckduckgo_search", | ||
"hacker_news", | ||
"weather", | ||
"wikipedia", | ||
"twitter", | ||
"web_base", | ||
"requests", | ||
"gmail", | ||
"tts_query", | ||
] | ||
| None | ||
) = None | ||
""" | ||
The provider of the integration | ||
""" | ||
method: str | None = None | ||
""" | ||
The specific method of the integration to call | ||
""" | ||
description: str | None = None | ||
""" | ||
Optional description of the integration | ||
""" | ||
setup: dict | None = None | ||
""" | ||
The setup parameters the integration accepts | ||
""" | ||
arguments: dict | None = None | ||
""" | ||
The arguments to pre-apply to the integration call | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
from pydantic import BaseModel, Field | ||
|
||
|
||
class WeatherExecutionSetup(BaseModel): | ||
openweathermap_api_key: str = Field( | ||
..., description="The location for which to fetch weather data" | ||
) | ||
|
||
|
||
class WeatherExecutionArguments(BaseModel): | ||
location: str = Field( | ||
..., description="The location for which to fetch weather data" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
from pydantic import BaseModel, Field | ||
|
||
|
||
class WikipediaExecutionArguments(BaseModel): | ||
query: str = Field(..., description="The search query string") | ||
load_max_docs: int = Field(2, description="Maximum number of documents to load") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .execution.router import router as execution_router | ||
from .integrations.router import router as integrations_router |
1 change: 1 addition & 0 deletions
1
integrations-service/integrations/routers/execution/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .execute import execute |
19 changes: 19 additions & 0 deletions
19
integrations-service/integrations/routers/execution/execute.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from fastapi import Body, HTTPException, Path | ||
|
||
from ...models import IntegrationExecutionRequest, IntegrationExecutionResponse | ||
from ...utils.execute_integration import execute_integration | ||
from .router import router | ||
|
||
|
||
@router.post("/execute/{provider}", tags=["execution"]) | ||
async def execute( | ||
provider: str = Path(..., description="The integration provider"), | ||
request: IntegrationExecutionRequest = Body( | ||
..., description="The integration execution request" | ||
), | ||
) -> IntegrationExecutionResponse: | ||
try: | ||
result = await execute_integration(provider, request.setup, request.arguments) | ||
return IntegrationExecutionResponse(result=result) | ||
except ValueError as e: | ||
raise HTTPException(status_code=400, detail=str(e)) |
3 changes: 3 additions & 0 deletions
3
integrations-service/integrations/routers/execution/router.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from fastapi import APIRouter | ||
|
||
router: APIRouter = APIRouter() |
2 changes: 2 additions & 0 deletions
2
integrations-service/integrations/routers/integrations/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
from .get_integration_tool import get_integration_tool | ||
from .get_integrations import get_integrations |
40 changes: 40 additions & 0 deletions
40
integrations-service/integrations/routers/integrations/get_integration_tool.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
from typing import Optional | ||
|
||
from fastapi import HTTPException | ||
|
||
from ...models.models import IntegrationDef | ||
from .get_integrations import get_integrations | ||
from .router import router | ||
|
||
|
||
def convert_to_openai_tool(integration: IntegrationDef) -> dict: | ||
return { | ||
"type": "function", | ||
"function": { | ||
"name": integration.provider, | ||
"description": integration.description, | ||
"parameters": { | ||
"type": "object", | ||
"properties": integration.arguments, | ||
"required": [ | ||
k | ||
for k, v in integration.arguments.items() | ||
if v.get("required", False) | ||
], | ||
}, | ||
}, | ||
} | ||
|
||
|
||
@router.get("/integrations/{provider}/tool", tags=["integration_tool"]) | ||
@router.get("/integrations/{provider}/{method}/tool", tags=["integration_tool"]) | ||
async def get_integration_tool(provider: str, method: Optional[str] = None): | ||
integrations = await get_integrations() | ||
|
||
for integration in integrations: | ||
if integration.provider == provider and ( | ||
method is None or integration.method == method | ||
): | ||
return convert_to_openai_tool(integration) | ||
|
||
raise HTTPException(status_code=404, detail="Integration not found") |
82 changes: 82 additions & 0 deletions
82
integrations-service/integrations/routers/integrations/get_integrations.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import importlib | ||
import inspect | ||
import os | ||
from typing import Any, List | ||
|
||
from pydantic import BaseModel | ||
|
||
from ...models.models import IntegrationDef | ||
from ...utils import integrations | ||
from .router import router | ||
|
||
|
||
def create_integration_def(module: Any) -> IntegrationDef: | ||
module_parts = module.__name__.split(".") | ||
if len(module_parts) > 4: # Nested integration | ||
provider = module_parts[-2] | ||
method = module_parts[-1] | ||
else: # Top-level integration | ||
provider = module_parts[-1] | ||
method = None | ||
|
||
# Find the first function in the module | ||
function_name = next( | ||
name | ||
for name, obj in inspect.getmembers(module) | ||
if inspect.isfunction(obj) and not name.startswith("_") | ||
) | ||
function = getattr(module, function_name) | ||
signature = inspect.signature(function) | ||
|
||
# Get the Pydantic model for the parameters | ||
params_model = next(iter(signature.parameters.values())).annotation | ||
|
||
# Check if the params_model is a Pydantic model | ||
if issubclass(params_model, BaseModel): | ||
arguments = {} | ||
for field_name, field in params_model.model_fields.items(): | ||
field_type = field.annotation | ||
arguments[field_name] = { | ||
"type": field_type.__name__.lower(), | ||
"description": field.description, | ||
} | ||
else: | ||
# Fallback to a dictionary if it's not a Pydantic model | ||
arguments = { | ||
param.name: {"type": str(param.annotation.__name__).lower()} | ||
for param in signature.parameters.values() | ||
if param.name != "parameters" | ||
} | ||
|
||
return IntegrationDef( | ||
provider=provider, | ||
method=method, | ||
description=function.__doc__.strip() if function.__doc__ else None, | ||
arguments=arguments, | ||
) | ||
|
||
|
||
@router.get("/integrations", tags=["integrations"]) | ||
async def get_integrations() -> List[IntegrationDef]: | ||
integration_defs = [] | ||
integrations_dir = os.path.dirname(integrations.__file__) | ||
|
||
for item in os.listdir(integrations_dir): | ||
item_path = os.path.join(integrations_dir, item) | ||
|
||
if os.path.isdir(item_path): | ||
# This is a toolkit | ||
for file in os.listdir(item_path): | ||
if file.endswith(".py") and not file.startswith("__"): | ||
module = importlib.import_module( | ||
f"...utils.integrations.{item}.{file[:-3]}", package=__package__ | ||
) | ||
integration_defs.append(create_integration_def(module)) | ||
elif item.endswith(".py") and not item.startswith("__"): | ||
# This is a single-file tool | ||
module = importlib.import_module( | ||
f"...utils.integrations.{item[:-3]}", package=__package__ | ||
) | ||
integration_defs.append(create_integration_def(module)) | ||
|
||
return integration_defs |
3 changes: 3 additions & 0 deletions
3
integrations-service/integrations/routers/integrations/router.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
from fastapi import APIRouter | ||
|
||
router: APIRouter = APIRouter() |
26 changes: 26 additions & 0 deletions
26
integrations-service/integrations/utils/execute_integration.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from ..models import ExecuteIntegrationArguments, ExecuteIntegrationSetup | ||
from .integrations.dalle_image_generator import dalle_image_generator | ||
from .integrations.duckduckgo_search import duckduckgo_search | ||
from .integrations.hacker_news import hacker_news | ||
from .integrations.weather import weather | ||
from .integrations.wikipedia import wikipedia | ||
|
||
|
||
async def execute_integration( | ||
provider: str, | ||
setup: ExecuteIntegrationSetup | None, | ||
arguments: ExecuteIntegrationArguments, | ||
) -> str: | ||
match provider: | ||
case "duckduckgo_search": | ||
return await duckduckgo_search(arguments=arguments) | ||
case "dalle_image_generator": | ||
return await dalle_image_generator(setup=setup, arguments=arguments) | ||
case "wikipedia": | ||
return await wikipedia(arguments=arguments) | ||
case "weather": | ||
return await weather(setup=setup, arguments=arguments) | ||
case "hacker_news": | ||
return await hacker_news(arguments=arguments) | ||
case _: | ||
raise ValueError(f"Unknown integration: {provider}") |
5 changes: 5 additions & 0 deletions
5
integrations-service/integrations/utils/integrations/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
from .dalle_image_generator import dalle_image_generator | ||
from .duckduckgo_search import duckduckgo_search | ||
from .hacker_news import hacker_news | ||
from .weather import weather | ||
from .wikipedia import wikipedia |
Oops, something went wrong.