diff --git a/README.md b/README.md
index fc49e78a0..dad5ebd68 100644
--- a/README.md
+++ b/README.md
@@ -1143,8 +1143,6 @@ main:
Whenever julep encounters a _user-defined function_, it pauses, giving control back to the client and waits for the client to run the function call and give the results back to julep.
-> [!TIP] > **Example cookbook**: [cookbooks/13-Error_Handling_and_Recovery.py](https://github.com/julep-ai/julep/blob/dev/cookbooks/13-Error_Handling_and_Recovery.py)
-
### `system` tools
Built-in tools that can be used to call the julep APIs themselves, like triggering a task execution, appending to a metadata field, etc.
@@ -1224,7 +1222,7 @@ Additional operations available for some resources:
Note: The availability of these operations may vary depending on the specific resource and implementation details.
-> [!TIP] > **Example cookbook**: [cookbooks/10-Document_Management_and_Search.py](https://github.com/julep-ai/julep/blob/dev/cookbooks/10-Document_Management_and_Search.py)
+> [!TIP] > **Example cookbook**: [cookbooks/06-browser-use.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/06-browser-use.ipynb)
### Built-in `integrations`
@@ -1232,7 +1230,7 @@ Julep comes with a number of built-in integrations (as described in the section
See [Integrations](#integrations) for details on the available integrations.
-> [!TIP] > **Example cookbook**: [cookbooks/01-Website_Crawler_using_Spider.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/01-Website_Crawler_using_Spider.ipynb)
+> [!TIP] > **Example cookbook**: [cookbooks/01-website-crawler.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/01-website-crawler.ipynb)
### Direct `api_calls`
@@ -1290,7 +1288,7 @@ output:
-**Example cookbook**: [cookbooks/03-SmartResearcher_With_WebSearch.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/03-SmartResearcher_With_WebSearch.ipynb)
+**Example cookbook**: [cookbooks/02-sarcastic-news-headline-generator.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/02-sarcastic-news-headline-generator.ipynb)
|
@@ -1313,6 +1311,11 @@ output:
+
+
+**Example cookbook**: [cookbooks/06-browser-use.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/06-browser-use.ipynb)
+
+ |
Email |
@@ -1364,7 +1367,7 @@ output:
-**Example cookbook**: [cookbooks/01-Website_Crawler_using_Spider.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/01-Website_Crawler_using_Spider.ipynb)
+**Example cookbook**: [cookbooks/01-website-crawler.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/01-website-crawler.ipynb)
|
@@ -1387,7 +1390,7 @@ output:
-**Example cookbook**: [cookbooks/04-TripPlanner_With_Weather_And_WikiInfo.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/04-TripPlanner_With_Weather_And_WikiInfo.ipynb)
+**Example cookbook**: [cookbooks/03-trip-planning-assistant.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/03-trip-planning-assistant.ipynb)
|
@@ -1409,10 +1412,87 @@ output:
-**Example cookbook**: [cookbooks/04-TripPlanner_With_Weather_And_WikiInfo.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/04-TripPlanner_With_Weather_And_WikiInfo.ipynb)
+**Example cookbook**: [cookbooks/03-trip-planning-assistant.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/03-trip-planning-assistant.ipynb)
+
+ |
+
+
+
+ FFmpeg |
+
+
+```yaml
+arguments:
+ cmd: string # The FFmpeg command to execute
+ file: string # The base64 encoded file to process
+
+output:
+ fileoutput: string # The output file from the FFmpeg command in base64 encoding
+ result: boolean # Whether the FFmpeg command was executed successfully
+ mime_type: string # The MIME type of the output file
+```
|
+
+
+
+ Llama Parse |
+
+
+```yaml
+setup:
+ llamaparse_api_key: string # The API key for Llama Parse
+
+arguments:
+ file: string # The base64 encoded file to parse
+ filename: string # The filename of the file
+
+output:
+ documents: list # The parsed data from the document
+```
+
+ |
+
+
+
+
+ Cloudinary |
+
+
+```yaml
+
+method: media_upload | media_edit # The method to use for the Cloudinary integration
+
+setup:
+ cloudinary_cloud_name: string # Your Cloudinary cloud name
+ cloudinary_api_key: string # Your Cloudinary API key
+ cloudinary_api_secret: string # Your Cloudinary API secret
+
+arguments:
+ file: string # The URL of the file upload. Only available for media_upload method.
+ upload_params: dict # Additional parameters for the upload. Only available for media_upload method.
+ public_id: string # The public ID for the file. For media_edit method it is MANDATORY. For media_upload method it is optional.
+ transformation: list[dict] # The transformations to apply to the file
+ return_base64: boolean # Whether to return the file in base64 encoding
+
+output:
+ url: string # The URL of the uploaded file. Only available for media_upload method.
+ meta_data: dict # Additional metadata from the upload response. Only available for media_upload method.
+ public_id: string # The public ID of the uploaded file. Only available for media_upload method.
+ transformed_url: string # The transformed URL. Only available for media_edit method.
+ base64: string # The base64 encoded file. Only available for media_edit method.
+```
+
+ |
+
+
+
+**Example cookbook**: [cookbooks/05-video-processing-with-natural-language.ipynb](https://github.com/julep-ai/julep/blob/dev/cookbooks/05-video-processing-with-natural-language.ipynb)
+
+ |
+
+
For more details, refer to our [Integrations Documentation](#integrations).
diff --git a/agents-api/agents_api/activities/task_steps/prompt_step.py b/agents-api/agents_api/activities/task_steps/prompt_step.py
index bf2b413ae..f365fa286 100644
--- a/agents-api/agents_api/activities/task_steps/prompt_step.py
+++ b/agents-api/agents_api/activities/task_steps/prompt_step.py
@@ -167,10 +167,25 @@ async def prompt_step(context: StepContext) -> StepOutcome:
},
}
formatted_tools.append(tool)
-
+ # For non-Claude models, we don't need to send tools
+ # FIXME: Enable formatted_tools once format-tools PR is merged.
if not is_claude_model:
formatted_tools = None
+ # HOTFIX: for groq calls, litellm expects tool_calls_id not to be in the messages
+ # FIXME: This is a temporary fix. We need to update the agent-api to use the new tool calling format
+ # FIXME: Enable formatted_tools once format-tools PR is merged.
+ is_groq_model = agent_model.lower().startswith("llama-3.1")
+ if is_groq_model:
+ prompt = [
+ {
+ k: v
+ for k, v in message.items()
+ if k not in ["tool_calls", "tool_call_id", "user", "continue_", "name"]
+ }
+ for message in prompt
+ ]
+
# Use litellm for other models
completion_data: dict = {
"model": agent_model,
diff --git a/agents-api/agents_api/routers/sessions/chat.py b/agents-api/agents_api/routers/sessions/chat.py
index f4cc7420e..830273a09 100644
--- a/agents-api/agents_api/routers/sessions/chat.py
+++ b/agents-api/agents_api/routers/sessions/chat.py
@@ -168,11 +168,24 @@ async def chat(
}
formatted_tools.append(tool)
- # If not using Claude model,
-
+ # If not using Claude model
+ # FIXME: Enable formatted_tools once format-tools PR is merged.
if not is_claude_model:
formatted_tools = None
+ # HOTFIX: for groq calls, litellm expects tool_calls_id not to be in the messages
+ # FIXME: This is a temporary fix. We need to update the agent-api to use the new tool calling format
+ is_groq_model = settings["model"].lower().startswith("llama-3.1")
+ if is_groq_model:
+ messages = [
+ {
+ k: v
+ for k, v in message.items()
+ if k not in ["tool_calls", "tool_call_id", "user", "continue_", "name"]
+ }
+ for message in messages
+ ]
+
# Use litellm for other models
model_response = await litellm.acompletion(
messages=messages,
diff --git a/integrations-service/integrations/models/models.py b/integrations-service/integrations/models/models.py
deleted file mode 100644
index 8d0119e53..000000000
--- a/integrations-service/integrations/models/models.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from typing import Literal
-
-from pydantic import BaseModel
-
-
-class IntegrationExecutionResponse(BaseModel):
- result: str
- """
- The result of the integration execution
- """
-
-
-class IntegrationDef(BaseModel):
- provider: (
- Literal[
- "dummy",
- "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
- """
diff --git a/integrations-service/integrations/utils/execute_integration.py b/integrations-service/integrations/utils/execute_integration.py
index f98f1eec8..5fd298344 100644
--- a/integrations-service/integrations/utils/execute_integration.py
+++ b/integrations-service/integrations/utils/execute_integration.py
@@ -21,17 +21,13 @@ async def execute_integration(
setup: ExecutionSetup | None = None,
arguments: ExecutionArguments,
) -> ExecutionResponse:
- provider_obj: BaseProvider | None = getattr(available_providers, provider, None)
-
- if not provider_obj:
+ provider_obj = getattr(available_providers, provider, None)
+ if not provider_obj or not isinstance(provider_obj, BaseProvider):
raise HTTPException(status_code=400, detail=f"Unknown provider: {provider}")
- assert isinstance(provider_obj, BaseProvider)
-
- if method is None:
- method = provider_obj.methods[0].method
-
- elif method not in [method.method for method in provider_obj.methods]:
+ method = method or provider_obj.methods[0].method
+ method_config = next((m for m in provider_obj.methods if m.method == method), None)
+ if not method_config:
raise HTTPException(
status_code=400, detail=f"Unknown method: {method} for provider: {provider}"
)
@@ -41,28 +37,22 @@ async def execute_integration(
package="integrations",
)
- execution_function = getattr(provider_module, method)
-
- setup_obj = setup
-
- if setup is not None:
- setup_class = provider_obj.setup
-
- if setup_class and not isinstance(setup, setup_class):
- setup_obj = setup_class(**setup.model_dump())
-
- arguments_class = next(
- m for m in provider_obj.methods if m.method == method
- ).arguments
+ if (
+ setup is not None
+ and provider_obj.setup
+ and not isinstance(setup, provider_obj.setup)
+ ):
+ setup = provider_obj.setup(**setup.model_dump())
+
+ arguments = (
+ method_config.arguments(**arguments.model_dump())
+ if not isinstance(arguments, method_config.arguments)
+ else arguments
+ )
- if not isinstance(arguments, arguments_class):
- parsed_arguments = arguments_class(**arguments.model_dump())
- else:
- parsed_arguments = arguments
try:
- if setup_obj:
- return await execution_function(setup=setup_obj, arguments=parsed_arguments)
- else:
- return await execution_function(arguments=parsed_arguments)
+ return await getattr(provider_module, method)(
+ **({"setup": setup} if setup else {}), arguments=arguments
+ )
except BaseException as e:
return ExecutionError(error=str(e))
diff --git a/integrations-service/integrations/utils/integrations/browserbase.py b/integrations-service/integrations/utils/integrations/browserbase.py
index a3fd1f2f8..c59e69138 100644
--- a/integrations-service/integrations/utils/integrations/browserbase.py
+++ b/integrations-service/integrations/utils/integrations/browserbase.py
@@ -38,10 +38,14 @@
def get_browserbase_client(setup: BrowserbaseSetup) -> Browserbase:
- if setup.api_key == "DEMO_API_KEY":
- setup.api_key = browserbase_api_key
- if setup.project_id == "DEMO_PROJECT_ID":
- setup.project_id = browserbase_project_id
+ setup.api_key = (
+ browserbase_api_key if setup.api_key == "DEMO_API_KEY" else setup.api_key
+ )
+ setup.project_id = (
+ browserbase_project_id
+ if setup.project_id == "DEMO_PROJECT_ID"
+ else setup.project_id
+ )
return Browserbase(
api_key=setup.api_key,
diff --git a/integrations-service/integrations/utils/integrations/llama_parse.py b/integrations-service/integrations/utils/integrations/llama_parse.py
index 5d15debe5..509b6dfb4 100644
--- a/integrations-service/integrations/utils/integrations/llama_parse.py
+++ b/integrations-service/integrations/utils/integrations/llama_parse.py
@@ -26,23 +26,23 @@ async def parse(
assert isinstance(setup, LlamaParseSetup), "Invalid setup"
assert isinstance(arguments, LlamaParseFetchArguments), "Invalid arguments"
- if setup.llamaparse_api_key == "DEMO_API_KEY":
- setup.llamaparse_api_key = llama_api_key
+ # Use walrus operator to simplify assignment and condition
+ if (api_key := setup.llamaparse_api_key) == "DEMO_API_KEY":
+ api_key = llama_api_key
parser = LlamaParse(
- api_key=setup.llamaparse_api_key,
+ api_key=api_key, # Use the local variable instead
result_type=arguments.result_format,
num_workers=arguments.num_workers,
language=arguments.language,
)
- # Decode base64 file content
- file_content = base64.b64decode(arguments.file)
- extra_info = {
- "file_name": arguments.filename if arguments.filename else str(uuid.uuid4())
- }
+ # Simplify filename assignment using or operator
+ extra_info = {"file_name": arguments.filename or str(uuid.uuid4())}
- # Parse the document
- documents = await parser.aload_data(file_content, extra_info=extra_info)
+ # Parse the document (decode inline)
+ documents = await parser.aload_data(
+ base64.b64decode(arguments.file), extra_info=extra_info
+ )
return LlamaParseFetchOutput(documents=documents)
diff --git a/integrations-service/integrations/utils/integrations/spider.py b/integrations-service/integrations/utils/integrations/spider.py
index baac7c5e6..a8403b3cf 100644
--- a/integrations-service/integrations/utils/integrations/spider.py
+++ b/integrations-service/integrations/utils/integrations/spider.py
@@ -1,3 +1,7 @@
+import asyncio
+from functools import lru_cache
+from typing import Any, Dict
+
from beartype import beartype
from langchain_community.document_loaders import SpiderLoader
from tenacity import retry, stop_after_attempt, wait_exponential
@@ -7,6 +11,11 @@
from ...models import SpiderFetchOutput
+# Spider client instances
+def get_spider_client(api_key: str, **kwargs) -> SpiderLoader:
+ return SpiderLoader(api_key=api_key, **kwargs)
+
+
@beartype
@retry(
wait=wait_exponential(multiplier=1, min=4, max=10),
@@ -23,21 +32,18 @@ async def crawl(
assert isinstance(setup, SpiderSetup), "Invalid setup"
assert isinstance(arguments, SpiderFetchArguments), "Invalid arguments"
- url = arguments.url
-
- if not url:
- raise ValueError("URL parameter is required for spider")
-
- if setup.spider_api_key == "DEMO_API_KEY":
- setup.spider_api_key = spider_api_key
+ api_key = (
+ setup.spider_api_key
+ if setup.spider_api_key != "DEMO_API_KEY"
+ else spider_api_key
+ )
- spider_loader = SpiderLoader(
- api_key=setup.spider_api_key,
- url=str(url),
+ spider_loader = get_spider_client(
+ api_key=api_key,
+ url=str(arguments.url),
mode=arguments.mode,
params=arguments.params,
)
- documents = spider_loader.load()
-
+ documents = await asyncio.to_thread(spider_loader.load)
return SpiderFetchOutput(documents=documents)
diff --git a/integrations-service/integrations/web.py b/integrations-service/integrations/web.py
index a1c77f130..905d9abc1 100644
--- a/integrations-service/integrations/web.py
+++ b/integrations-service/integrations/web.py
@@ -7,17 +7,29 @@
import uvloop
from fastapi import FastAPI, Request, status
from fastapi.exceptions import HTTPException, RequestValidationError
+from fastapi.middleware.gzip import GZipMiddleware
from fastapi.responses import JSONResponse
from .routers.execution.router import router as execution_router
from .routers.integrations.router import router as integrations_router
-app: FastAPI = FastAPI()
+app: FastAPI = FastAPI(
+ title="Integrations Service",
+ docs_url="/docs",
+ redoc_url="/redoc",
+ openapi_url="/openapi.json",
+ default_response_class=JSONResponse,
+)
+
+# Add GZIP compression
+app.add_middleware(GZipMiddleware, minimum_size=1000)
# Add routers
app.include_router(integrations_router)
app.include_router(execution_router)
+# Optimize event loop policy
+asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
logger: logging.Logger = logging.getLogger(__name__)
@@ -84,6 +96,3 @@ def main(
workers=workers,
reload=reload,
)
-
-
-asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())