mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
added exawebsets and answers moved it over to new credentails system
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import SecretStr
|
||||
|
||||
from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput
|
||||
from backend.integrations.providers import ProviderName
|
||||
|
||||
ExaCredentials = APIKeyCredentials
|
||||
ExaCredentialsInput = CredentialsMetaInput[
|
||||
Literal[ProviderName.EXA],
|
||||
Literal["api_key"],
|
||||
]
|
||||
|
||||
TEST_CREDENTIALS = APIKeyCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="exa",
|
||||
api_key=SecretStr("mock-exa-api-key"),
|
||||
title="Mock Exa API key",
|
||||
expires_at=None,
|
||||
)
|
||||
|
||||
TEST_CREDENTIALS_INPUT = {
|
||||
"provider": TEST_CREDENTIALS.provider,
|
||||
"id": TEST_CREDENTIALS.id,
|
||||
"type": TEST_CREDENTIALS.type,
|
||||
"title": TEST_CREDENTIALS.title,
|
||||
}
|
||||
|
||||
|
||||
def ExaCredentialsField() -> ExaCredentialsInput:
|
||||
"""Creates an Exa credentials input on a block."""
|
||||
return CredentialsField(description="The Exa integration requires an API Key.")
|
||||
131
autogpt_platform/backend/backend/blocks/exa/answers.py
Normal file
131
autogpt_platform/backend/backend/blocks/exa/answers.py
Normal file
@@ -0,0 +1,131 @@
|
||||
from backend.sdk import (
|
||||
APIKeyCredentials,
|
||||
Block,
|
||||
BlockCategory,
|
||||
BlockOutput,
|
||||
BlockSchema,
|
||||
Boolean,
|
||||
CredentialsField,
|
||||
CredentialsMetaInput,
|
||||
Dict,
|
||||
List,
|
||||
SchemaField,
|
||||
SecretStr,
|
||||
Settings,
|
||||
String,
|
||||
default_credentials,
|
||||
provider,
|
||||
requests,
|
||||
)
|
||||
|
||||
settings = Settings()
|
||||
|
||||
|
||||
@provider("exa")
|
||||
@default_credentials(
|
||||
APIKeyCredentials(
|
||||
id="01234567-89ab-cdef-0123-456789abcdef",
|
||||
provider="exa",
|
||||
api_key=SecretStr(settings.secrets.exa_api_key),
|
||||
title="Use Credits for Exa",
|
||||
expires_at=None,
|
||||
)
|
||||
)
|
||||
class ExaAnswerBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: CredentialsMetaInput = CredentialsField(
|
||||
provider="exa",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Exa integration requires an API Key.",
|
||||
)
|
||||
query: String = SchemaField(
|
||||
description="The question or query to answer",
|
||||
placeholder="What is the latest valuation of SpaceX?",
|
||||
)
|
||||
stream: Boolean = SchemaField(
|
||||
default=False,
|
||||
description="If true, the response is returned as a server-sent events (SSE) stream",
|
||||
advanced=True,
|
||||
)
|
||||
text: Boolean = SchemaField(
|
||||
default=False,
|
||||
description="If true, the response includes full text content in the search results",
|
||||
advanced=True,
|
||||
)
|
||||
model: String = SchemaField(
|
||||
default="exa",
|
||||
description="The search model to use (exa or exa-pro)",
|
||||
placeholder="exa",
|
||||
advanced=True,
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
answer: String = SchemaField(
|
||||
description="The generated answer based on search results",
|
||||
)
|
||||
citations: List[Dict] = SchemaField(
|
||||
description="Search results used to generate the answer",
|
||||
default_factory=list,
|
||||
)
|
||||
cost_dollars: Dict = SchemaField(
|
||||
description="Cost breakdown of the request",
|
||||
default_factory=dict,
|
||||
)
|
||||
error: String = SchemaField(
|
||||
description="Error message if the request failed",
|
||||
default="",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="f8e7d6c5-b4a3-5c2d-9e1f-3a7b8c9d4e6f",
|
||||
description="Get an LLM answer to a question informed by Exa search results",
|
||||
categories={BlockCategory.SEARCH, BlockCategory.AI},
|
||||
input_schema=ExaAnswerBlock.Input,
|
||||
output_schema=ExaAnswerBlock.Output,
|
||||
test_input={
|
||||
"query": "What is the capital of France?",
|
||||
"text": False,
|
||||
"stream": False,
|
||||
"model": "exa",
|
||||
},
|
||||
test_output=[
|
||||
("answer", "Paris"),
|
||||
("citations", []),
|
||||
("cost_dollars", {}),
|
||||
],
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
url = "https://api.exa.ai/answer"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": credentials.api_key.get_secret_value(),
|
||||
}
|
||||
|
||||
# Build the payload
|
||||
payload = {
|
||||
"query": input_data.query,
|
||||
"stream": input_data.stream,
|
||||
"text": input_data.text,
|
||||
"model": input_data.model,
|
||||
}
|
||||
|
||||
try:
|
||||
# Note: This endpoint doesn't support streaming in our block implementation
|
||||
# If stream=True is requested, we still make a regular request
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
yield "answer", data.get("answer", "")
|
||||
yield "citations", data.get("citations", [])
|
||||
yield "cost_dollars", data.get("costDollars", {})
|
||||
|
||||
except Exception as e:
|
||||
yield "error", str(e)
|
||||
yield "answer", ""
|
||||
yield "citations", []
|
||||
yield "cost_dollars", {}
|
||||
@@ -1,57 +1,46 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.blocks.exa._auth import (
|
||||
ExaCredentials,
|
||||
ExaCredentialsField,
|
||||
ExaCredentialsInput,
|
||||
from backend.sdk import (
|
||||
APIKeyCredentials,
|
||||
Block,
|
||||
BlockCategory,
|
||||
BlockOutput,
|
||||
BlockSchema,
|
||||
CredentialsField,
|
||||
CredentialsMetaInput,
|
||||
List,
|
||||
SchemaField,
|
||||
String,
|
||||
provider,
|
||||
requests,
|
||||
)
|
||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||
from backend.data.model import SchemaField
|
||||
from backend.util.request import requests
|
||||
|
||||
|
||||
class ContentRetrievalSettings(BaseModel):
|
||||
text: dict = SchemaField(
|
||||
description="Text content settings",
|
||||
default={"maxCharacters": 1000, "includeHtmlTags": False},
|
||||
advanced=True,
|
||||
)
|
||||
highlights: dict = SchemaField(
|
||||
description="Highlight settings",
|
||||
default={
|
||||
"numSentences": 3,
|
||||
"highlightsPerUrl": 3,
|
||||
"query": "",
|
||||
},
|
||||
advanced=True,
|
||||
)
|
||||
summary: dict = SchemaField(
|
||||
description="Summary settings",
|
||||
default={"query": ""},
|
||||
advanced=True,
|
||||
)
|
||||
|
||||
from .helpers import ContentSettings
|
||||
|
||||
|
||||
@provider("exa")
|
||||
class ExaContentsBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: ExaCredentialsInput = ExaCredentialsField()
|
||||
ids: List[str] = SchemaField(
|
||||
credentials: CredentialsMetaInput = CredentialsField(
|
||||
provider="exa",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Exa integration requires an API Key.",
|
||||
)
|
||||
ids: List[String] = SchemaField(
|
||||
description="Array of document IDs obtained from searches",
|
||||
)
|
||||
contents: ContentRetrievalSettings = SchemaField(
|
||||
contents: ContentSettings = SchemaField(
|
||||
description="Content retrieval settings",
|
||||
default=ContentRetrievalSettings(),
|
||||
default=ContentSettings(),
|
||||
advanced=True,
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
results: list = SchemaField(
|
||||
results: List = SchemaField(
|
||||
description="List of document contents",
|
||||
default_factory=list,
|
||||
)
|
||||
error: str = SchemaField(description="Error message if the request failed")
|
||||
error: String = SchemaField(
|
||||
description="Error message if the request failed", default=""
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
@@ -63,7 +52,7 @@ class ExaContentsBlock(Block):
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: ExaCredentials, **kwargs
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
url = "https://api.exa.ai/contents"
|
||||
headers = {
|
||||
@@ -71,11 +60,24 @@ class ExaContentsBlock(Block):
|
||||
"x-api-key": credentials.api_key.get_secret_value(),
|
||||
}
|
||||
|
||||
# Convert ContentSettings to API format
|
||||
contents_dict = input_data.contents.model_dump()
|
||||
payload = {
|
||||
"ids": input_data.ids,
|
||||
"text": input_data.contents.text,
|
||||
"highlights": input_data.contents.highlights,
|
||||
"summary": input_data.contents.summary,
|
||||
"text": {
|
||||
"maxCharacters": contents_dict["text"]["max_characters"],
|
||||
"includeHtmlTags": contents_dict["text"]["include_html_tags"],
|
||||
},
|
||||
"highlights": {
|
||||
"numSentences": contents_dict["highlights"]["num_sentences"],
|
||||
"highlightsPerUrl": contents_dict["highlights"]["highlights_per_url"],
|
||||
"query": contents_dict["summary"][
|
||||
"query"
|
||||
], # Note: query comes from summary
|
||||
},
|
||||
"summary": {
|
||||
"query": contents_dict["summary"]["query"],
|
||||
},
|
||||
}
|
||||
|
||||
try:
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from backend.data.model import SchemaField
|
||||
from backend.sdk import BaseModel, Boolean, Integer, List, Optional, SchemaField, String
|
||||
|
||||
|
||||
class TextSettings(BaseModel):
|
||||
max_characters: int = SchemaField(
|
||||
max_characters: Integer = SchemaField(
|
||||
default=1000,
|
||||
description="Maximum number of characters to return",
|
||||
placeholder="1000",
|
||||
)
|
||||
include_html_tags: bool = SchemaField(
|
||||
include_html_tags: Boolean = SchemaField(
|
||||
default=False,
|
||||
description="Whether to include HTML tags in the text",
|
||||
placeholder="False",
|
||||
@@ -19,12 +15,12 @@ class TextSettings(BaseModel):
|
||||
|
||||
|
||||
class HighlightSettings(BaseModel):
|
||||
num_sentences: int = SchemaField(
|
||||
num_sentences: Integer = SchemaField(
|
||||
default=3,
|
||||
description="Number of sentences per highlight",
|
||||
placeholder="3",
|
||||
)
|
||||
highlights_per_url: int = SchemaField(
|
||||
highlights_per_url: Integer = SchemaField(
|
||||
default=3,
|
||||
description="Number of highlights per URL",
|
||||
placeholder="3",
|
||||
@@ -32,7 +28,7 @@ class HighlightSettings(BaseModel):
|
||||
|
||||
|
||||
class SummarySettings(BaseModel):
|
||||
query: Optional[str] = SchemaField(
|
||||
query: Optional[String] = SchemaField(
|
||||
default="",
|
||||
description="Query string for summarization",
|
||||
placeholder="Enter query",
|
||||
@@ -52,3 +48,83 @@ class ContentSettings(BaseModel):
|
||||
default=SummarySettings(),
|
||||
description="Summary settings",
|
||||
)
|
||||
|
||||
|
||||
# Websets Models
|
||||
class WebsetEntitySettings(BaseModel):
|
||||
type: Optional[String] = SchemaField(
|
||||
default=None,
|
||||
description="Entity type (e.g., 'company', 'person')",
|
||||
placeholder="company",
|
||||
)
|
||||
|
||||
|
||||
class WebsetCriterion(BaseModel):
|
||||
description: String = SchemaField(
|
||||
description="Description of the criterion",
|
||||
placeholder="Must be based in the US",
|
||||
)
|
||||
success_rate: Optional[Integer] = SchemaField(
|
||||
default=None,
|
||||
description="Success rate percentage",
|
||||
ge=0,
|
||||
le=100,
|
||||
)
|
||||
|
||||
|
||||
class WebsetSearchConfig(BaseModel):
|
||||
query: String = SchemaField(
|
||||
description="Search query",
|
||||
placeholder="Marketing agencies based in the US",
|
||||
)
|
||||
count: Integer = SchemaField(
|
||||
default=10,
|
||||
description="Number of results to return",
|
||||
ge=1,
|
||||
le=100,
|
||||
)
|
||||
entity: Optional[WebsetEntitySettings] = SchemaField(
|
||||
default=None,
|
||||
description="Entity settings for the search",
|
||||
)
|
||||
criteria: Optional[List[WebsetCriterion]] = SchemaField(
|
||||
default=None,
|
||||
description="Search criteria",
|
||||
)
|
||||
behavior: Optional[String] = SchemaField(
|
||||
default="override",
|
||||
description="Behavior when updating results ('override' or 'append')",
|
||||
placeholder="override",
|
||||
)
|
||||
|
||||
|
||||
class EnrichmentOption(BaseModel):
|
||||
label: String = SchemaField(
|
||||
description="Label for the enrichment option",
|
||||
placeholder="Option 1",
|
||||
)
|
||||
|
||||
|
||||
class WebsetEnrichmentConfig(BaseModel):
|
||||
title: String = SchemaField(
|
||||
description="Title of the enrichment",
|
||||
placeholder="Company Details",
|
||||
)
|
||||
description: String = SchemaField(
|
||||
description="Description of what this enrichment does",
|
||||
placeholder="Extract company information",
|
||||
)
|
||||
format: String = SchemaField(
|
||||
default="text",
|
||||
description="Format of the enrichment result",
|
||||
placeholder="text",
|
||||
)
|
||||
instructions: Optional[String] = SchemaField(
|
||||
default=None,
|
||||
description="Instructions for the enrichment",
|
||||
placeholder="Extract key company metrics",
|
||||
)
|
||||
options: Optional[List[EnrichmentOption]] = SchemaField(
|
||||
default=None,
|
||||
description="Options for the enrichment",
|
||||
)
|
||||
|
||||
@@ -1,46 +1,59 @@
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from backend.blocks.exa._auth import (
|
||||
ExaCredentials,
|
||||
ExaCredentialsField,
|
||||
ExaCredentialsInput,
|
||||
from backend.sdk import (
|
||||
APIKeyCredentials,
|
||||
Block,
|
||||
BlockCategory,
|
||||
BlockOutput,
|
||||
BlockSchema,
|
||||
Boolean,
|
||||
CredentialsField,
|
||||
CredentialsMetaInput,
|
||||
Integer,
|
||||
List,
|
||||
SchemaField,
|
||||
String,
|
||||
provider,
|
||||
requests,
|
||||
)
|
||||
from backend.blocks.exa.helpers import ContentSettings
|
||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||
from backend.data.model import SchemaField
|
||||
from backend.util.request import requests
|
||||
|
||||
from .helpers import ContentSettings
|
||||
|
||||
|
||||
@provider("exa")
|
||||
class ExaSearchBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: ExaCredentialsInput = ExaCredentialsField()
|
||||
query: str = SchemaField(description="The search query")
|
||||
use_auto_prompt: bool = SchemaField(
|
||||
credentials: CredentialsMetaInput = CredentialsField(
|
||||
provider="exa",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Exa integration requires an API Key.",
|
||||
)
|
||||
query: String = SchemaField(description="The search query")
|
||||
use_auto_prompt: Boolean = SchemaField(
|
||||
description="Whether to use autoprompt",
|
||||
default=True,
|
||||
advanced=True,
|
||||
)
|
||||
type: str = SchemaField(
|
||||
type: String = SchemaField(
|
||||
description="Type of search",
|
||||
default="",
|
||||
advanced=True,
|
||||
)
|
||||
category: str = SchemaField(
|
||||
category: String = SchemaField(
|
||||
description="Category to search within",
|
||||
default="",
|
||||
advanced=True,
|
||||
)
|
||||
number_of_results: int = SchemaField(
|
||||
number_of_results: Integer = SchemaField(
|
||||
description="Number of results to return",
|
||||
default=10,
|
||||
advanced=True,
|
||||
)
|
||||
include_domains: List[str] = SchemaField(
|
||||
include_domains: List[String] = SchemaField(
|
||||
description="Domains to include in search",
|
||||
default_factory=list,
|
||||
)
|
||||
exclude_domains: List[str] = SchemaField(
|
||||
exclude_domains: List[String] = SchemaField(
|
||||
description="Domains to exclude from search",
|
||||
default_factory=list,
|
||||
advanced=True,
|
||||
@@ -57,12 +70,12 @@ class ExaSearchBlock(Block):
|
||||
end_published_date: datetime = SchemaField(
|
||||
description="End date for published content",
|
||||
)
|
||||
include_text: List[str] = SchemaField(
|
||||
include_text: List[String] = SchemaField(
|
||||
description="Text patterns to include",
|
||||
default_factory=list,
|
||||
advanced=True,
|
||||
)
|
||||
exclude_text: List[str] = SchemaField(
|
||||
exclude_text: List[String] = SchemaField(
|
||||
description="Text patterns to exclude",
|
||||
default_factory=list,
|
||||
advanced=True,
|
||||
@@ -74,12 +87,13 @@ class ExaSearchBlock(Block):
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
results: list = SchemaField(
|
||||
results: List = SchemaField(
|
||||
description="List of search results",
|
||||
default_factory=list,
|
||||
)
|
||||
error: str = SchemaField(
|
||||
error: String = SchemaField(
|
||||
description="Error message if the request failed",
|
||||
default="",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
@@ -92,7 +106,7 @@ class ExaSearchBlock(Block):
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: ExaCredentials, **kwargs
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
url = "https://api.exa.ai/search"
|
||||
headers = {
|
||||
@@ -104,7 +118,7 @@ class ExaSearchBlock(Block):
|
||||
"query": input_data.query,
|
||||
"useAutoprompt": input_data.use_auto_prompt,
|
||||
"numResults": input_data.number_of_results,
|
||||
"contents": input_data.contents.dict(),
|
||||
"contents": input_data.contents.model_dump(),
|
||||
}
|
||||
|
||||
date_field_mapping = {
|
||||
|
||||
@@ -1,35 +1,47 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, List
|
||||
|
||||
from backend.blocks.exa._auth import (
|
||||
ExaCredentials,
|
||||
ExaCredentialsField,
|
||||
ExaCredentialsInput,
|
||||
from backend.sdk import (
|
||||
Any,
|
||||
APIKeyCredentials,
|
||||
Block,
|
||||
BlockCategory,
|
||||
BlockOutput,
|
||||
BlockSchema,
|
||||
CredentialsField,
|
||||
CredentialsMetaInput,
|
||||
Integer,
|
||||
List,
|
||||
SchemaField,
|
||||
String,
|
||||
provider,
|
||||
requests,
|
||||
)
|
||||
from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema
|
||||
from backend.data.model import SchemaField
|
||||
from backend.util.request import requests
|
||||
|
||||
from .helpers import ContentSettings
|
||||
|
||||
|
||||
@provider("exa")
|
||||
class ExaFindSimilarBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: ExaCredentialsInput = ExaCredentialsField()
|
||||
url: str = SchemaField(
|
||||
credentials: CredentialsMetaInput = CredentialsField(
|
||||
provider="exa",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Exa integration requires an API Key.",
|
||||
)
|
||||
url: String = SchemaField(
|
||||
description="The url for which you would like to find similar links"
|
||||
)
|
||||
number_of_results: int = SchemaField(
|
||||
number_of_results: Integer = SchemaField(
|
||||
description="Number of results to return",
|
||||
default=10,
|
||||
advanced=True,
|
||||
)
|
||||
include_domains: List[str] = SchemaField(
|
||||
include_domains: List[String] = SchemaField(
|
||||
description="Domains to include in search",
|
||||
default_factory=list,
|
||||
advanced=True,
|
||||
)
|
||||
exclude_domains: List[str] = SchemaField(
|
||||
exclude_domains: List[String] = SchemaField(
|
||||
description="Domains to exclude from search",
|
||||
default_factory=list,
|
||||
advanced=True,
|
||||
@@ -46,12 +58,12 @@ class ExaFindSimilarBlock(Block):
|
||||
end_published_date: datetime = SchemaField(
|
||||
description="End date for published content",
|
||||
)
|
||||
include_text: List[str] = SchemaField(
|
||||
include_text: List[String] = SchemaField(
|
||||
description="Text patterns to include (max 1 string, up to 5 words)",
|
||||
default_factory=list,
|
||||
advanced=True,
|
||||
)
|
||||
exclude_text: List[str] = SchemaField(
|
||||
exclude_text: List[String] = SchemaField(
|
||||
description="Text patterns to exclude (max 1 string, up to 5 words)",
|
||||
default_factory=list,
|
||||
advanced=True,
|
||||
@@ -67,7 +79,9 @@ class ExaFindSimilarBlock(Block):
|
||||
description="List of similar documents with title, URL, published date, author, and score",
|
||||
default_factory=list,
|
||||
)
|
||||
error: str = SchemaField(description="Error message if the request failed")
|
||||
error: String = SchemaField(
|
||||
description="Error message if the request failed", default=""
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
@@ -79,7 +93,7 @@ class ExaFindSimilarBlock(Block):
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: ExaCredentials, **kwargs
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
url = "https://api.exa.ai/findSimilar"
|
||||
headers = {
|
||||
@@ -90,7 +104,7 @@ class ExaFindSimilarBlock(Block):
|
||||
payload = {
|
||||
"url": input_data.url,
|
||||
"numResults": input_data.number_of_results,
|
||||
"contents": input_data.contents.dict(),
|
||||
"contents": input_data.contents.model_dump(),
|
||||
}
|
||||
|
||||
optional_field_mapping = {
|
||||
|
||||
523
autogpt_platform/backend/backend/blocks/exa/websets.py
Normal file
523
autogpt_platform/backend/backend/blocks/exa/websets.py
Normal file
@@ -0,0 +1,523 @@
|
||||
from backend.sdk import (
|
||||
Any,
|
||||
APIKeyCredentials,
|
||||
Block,
|
||||
BlockCategory,
|
||||
BlockOutput,
|
||||
BlockSchema,
|
||||
Boolean,
|
||||
CredentialsField,
|
||||
CredentialsMetaInput,
|
||||
Dict,
|
||||
Integer,
|
||||
List,
|
||||
Optional,
|
||||
SchemaField,
|
||||
String,
|
||||
provider,
|
||||
requests,
|
||||
)
|
||||
|
||||
from .helpers import WebsetEnrichmentConfig, WebsetSearchConfig
|
||||
|
||||
|
||||
@provider("exa")
|
||||
class ExaCreateWebsetBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: CredentialsMetaInput = CredentialsField(
|
||||
provider="exa",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Exa integration requires an API Key.",
|
||||
)
|
||||
search: WebsetSearchConfig = SchemaField(
|
||||
description="Initial search configuration for the Webset",
|
||||
)
|
||||
enrichments: Optional[List[WebsetEnrichmentConfig]] = SchemaField(
|
||||
default=None,
|
||||
description="Enrichments to apply to Webset items",
|
||||
advanced=True,
|
||||
)
|
||||
external_id: Optional[String] = SchemaField(
|
||||
default=None,
|
||||
description="External identifier for the webset",
|
||||
placeholder="my-webset-123",
|
||||
advanced=True,
|
||||
)
|
||||
metadata: Optional[Dict] = SchemaField(
|
||||
default=None,
|
||||
description="Key-value pairs to associate with this webset",
|
||||
advanced=True,
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
webset_id: String = SchemaField(
|
||||
description="The unique identifier for the created webset",
|
||||
)
|
||||
status: String = SchemaField(
|
||||
description="The status of the webset",
|
||||
)
|
||||
external_id: Optional[String] = SchemaField(
|
||||
description="The external identifier for the webset",
|
||||
default=None,
|
||||
)
|
||||
created_at: String = SchemaField(
|
||||
description="The date and time the webset was created",
|
||||
)
|
||||
error: String = SchemaField(
|
||||
description="Error message if the request failed",
|
||||
default="",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="a7c3b1d4-9e2f-4c5a-8f1b-3e6d7a9c2b5e",
|
||||
description="Create a new Exa Webset for persistent web search collections",
|
||||
categories={BlockCategory.SEARCH},
|
||||
input_schema=ExaCreateWebsetBlock.Input,
|
||||
output_schema=ExaCreateWebsetBlock.Output,
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
url = "https://api.exa.ai/websets/v0/websets"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": credentials.api_key.get_secret_value(),
|
||||
}
|
||||
|
||||
# Build the payload
|
||||
payload: dict[str, Any] = {
|
||||
"search": input_data.search.model_dump(exclude_none=True),
|
||||
}
|
||||
|
||||
# Convert enrichments to API format
|
||||
if input_data.enrichments:
|
||||
enrichments_data = []
|
||||
for enrichment in input_data.enrichments:
|
||||
enrichments_data.append(enrichment.model_dump(exclude_none=True))
|
||||
payload["enrichments"] = enrichments_data
|
||||
|
||||
if input_data.external_id:
|
||||
payload["externalId"] = input_data.external_id
|
||||
|
||||
if input_data.metadata:
|
||||
payload["metadata"] = input_data.metadata
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
yield "webset_id", data.get("id", "")
|
||||
yield "status", data.get("status", "")
|
||||
yield "external_id", data.get("externalId")
|
||||
yield "created_at", data.get("createdAt", "")
|
||||
|
||||
except Exception as e:
|
||||
yield "error", str(e)
|
||||
yield "webset_id", ""
|
||||
yield "status", ""
|
||||
yield "created_at", ""
|
||||
|
||||
|
||||
@provider("exa")
|
||||
class ExaUpdateWebsetBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: CredentialsMetaInput = CredentialsField(
|
||||
provider="exa",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Exa integration requires an API Key.",
|
||||
)
|
||||
webset_id: String = SchemaField(
|
||||
description="The ID or external ID of the Webset to update",
|
||||
placeholder="webset-id-or-external-id",
|
||||
)
|
||||
metadata: Optional[Dict] = SchemaField(
|
||||
default=None,
|
||||
description="Key-value pairs to associate with this webset (set to null to clear)",
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
webset_id: String = SchemaField(
|
||||
description="The unique identifier for the webset",
|
||||
)
|
||||
status: String = SchemaField(
|
||||
description="The status of the webset",
|
||||
)
|
||||
external_id: Optional[String] = SchemaField(
|
||||
description="The external identifier for the webset",
|
||||
default=None,
|
||||
)
|
||||
metadata: Dict = SchemaField(
|
||||
description="Updated metadata for the webset",
|
||||
default_factory=dict,
|
||||
)
|
||||
updated_at: String = SchemaField(
|
||||
description="The date and time the webset was updated",
|
||||
)
|
||||
error: String = SchemaField(
|
||||
description="Error message if the request failed",
|
||||
default="",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="c9e5d3f6-2a4b-6e7c-1f3d-5a8b9c4e7d2f",
|
||||
description="Update metadata for an existing Webset",
|
||||
categories={BlockCategory.SEARCH},
|
||||
input_schema=ExaUpdateWebsetBlock.Input,
|
||||
output_schema=ExaUpdateWebsetBlock.Output,
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
url = f"https://api.exa.ai/websets/v0/websets/{input_data.webset_id}"
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"x-api-key": credentials.api_key.get_secret_value(),
|
||||
}
|
||||
|
||||
# Build the payload
|
||||
payload = {}
|
||||
if input_data.metadata is not None:
|
||||
payload["metadata"] = input_data.metadata
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=headers, json=payload)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
yield "webset_id", data.get("id", "")
|
||||
yield "status", data.get("status", "")
|
||||
yield "external_id", data.get("externalId")
|
||||
yield "metadata", data.get("metadata", {})
|
||||
yield "updated_at", data.get("updatedAt", "")
|
||||
|
||||
except Exception as e:
|
||||
yield "error", str(e)
|
||||
yield "webset_id", ""
|
||||
yield "status", ""
|
||||
yield "metadata", {}
|
||||
yield "updated_at", ""
|
||||
|
||||
|
||||
@provider("exa")
|
||||
class ExaListWebsetsBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: CredentialsMetaInput = CredentialsField(
|
||||
provider="exa",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Exa integration requires an API Key.",
|
||||
)
|
||||
cursor: Optional[String] = SchemaField(
|
||||
default=None,
|
||||
description="Cursor for pagination through results",
|
||||
advanced=True,
|
||||
)
|
||||
limit: Integer = SchemaField(
|
||||
default=25,
|
||||
description="Number of websets to return (1-100)",
|
||||
ge=1,
|
||||
le=100,
|
||||
advanced=True,
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
websets: List = SchemaField(
|
||||
description="List of websets",
|
||||
default_factory=list,
|
||||
)
|
||||
has_more: Boolean = SchemaField(
|
||||
description="Whether there are more results to paginate through",
|
||||
default=False,
|
||||
)
|
||||
next_cursor: Optional[String] = SchemaField(
|
||||
description="Cursor for the next page of results",
|
||||
default=None,
|
||||
)
|
||||
error: String = SchemaField(
|
||||
description="Error message if the request failed",
|
||||
default="",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="f3h8g6i9-5d7e-9b1f-4c6g-8d2f3h7i1a5c",
|
||||
description="List all Websets with pagination support",
|
||||
categories={BlockCategory.SEARCH},
|
||||
input_schema=ExaListWebsetsBlock.Input,
|
||||
output_schema=ExaListWebsetsBlock.Output,
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
url = "https://api.exa.ai/websets/v0/websets"
|
||||
headers = {
|
||||
"x-api-key": credentials.api_key.get_secret_value(),
|
||||
}
|
||||
|
||||
params: dict[str, Any] = {
|
||||
"limit": input_data.limit,
|
||||
}
|
||||
if input_data.cursor:
|
||||
params["cursor"] = input_data.cursor
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, params=params)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
yield "websets", data.get("data", [])
|
||||
yield "has_more", data.get("hasMore", False)
|
||||
yield "next_cursor", data.get("nextCursor")
|
||||
|
||||
except Exception as e:
|
||||
yield "error", str(e)
|
||||
yield "websets", []
|
||||
yield "has_more", False
|
||||
|
||||
|
||||
@provider("exa")
|
||||
class ExaGetWebsetBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: CredentialsMetaInput = CredentialsField(
|
||||
provider="exa",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Exa integration requires an API Key.",
|
||||
)
|
||||
webset_id: String = SchemaField(
|
||||
description="The ID or external ID of the Webset to retrieve",
|
||||
placeholder="webset-id-or-external-id",
|
||||
)
|
||||
expand_items: Boolean = SchemaField(
|
||||
default=False,
|
||||
description="Include items in the response",
|
||||
advanced=True,
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
webset_id: String = SchemaField(
|
||||
description="The unique identifier for the webset",
|
||||
)
|
||||
status: String = SchemaField(
|
||||
description="The status of the webset",
|
||||
)
|
||||
external_id: Optional[String] = SchemaField(
|
||||
description="The external identifier for the webset",
|
||||
default=None,
|
||||
)
|
||||
searches: List[Dict] = SchemaField(
|
||||
description="The searches performed on the webset",
|
||||
default_factory=list,
|
||||
)
|
||||
enrichments: List[Dict] = SchemaField(
|
||||
description="The enrichments applied to the webset",
|
||||
default_factory=list,
|
||||
)
|
||||
monitors: List[Dict] = SchemaField(
|
||||
description="The monitors for the webset",
|
||||
default_factory=list,
|
||||
)
|
||||
items: Optional[List[Dict]] = SchemaField(
|
||||
description="The items in the webset (if expand_items is true)",
|
||||
default=None,
|
||||
)
|
||||
metadata: Dict = SchemaField(
|
||||
description="Key-value pairs associated with the webset",
|
||||
default_factory=dict,
|
||||
)
|
||||
created_at: String = SchemaField(
|
||||
description="The date and time the webset was created",
|
||||
)
|
||||
updated_at: String = SchemaField(
|
||||
description="The date and time the webset was last updated",
|
||||
)
|
||||
error: String = SchemaField(
|
||||
description="Error message if the request failed",
|
||||
default="",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="b8d4c2e5-1f3a-5d6b-9e2c-4f7a8b3d6c9f",
|
||||
description="Retrieve a Webset by ID or external ID",
|
||||
categories={BlockCategory.SEARCH},
|
||||
input_schema=ExaGetWebsetBlock.Input,
|
||||
output_schema=ExaGetWebsetBlock.Output,
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
url = f"https://api.exa.ai/websets/v0/websets/{input_data.webset_id}"
|
||||
headers = {
|
||||
"x-api-key": credentials.api_key.get_secret_value(),
|
||||
}
|
||||
|
||||
params = {}
|
||||
if input_data.expand_items:
|
||||
params["expand[]"] = "items"
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, params=params)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
yield "webset_id", data.get("id", "")
|
||||
yield "status", data.get("status", "")
|
||||
yield "external_id", data.get("externalId")
|
||||
yield "searches", data.get("searches", [])
|
||||
yield "enrichments", data.get("enrichments", [])
|
||||
yield "monitors", data.get("monitors", [])
|
||||
yield "items", data.get("items")
|
||||
yield "metadata", data.get("metadata", {})
|
||||
yield "created_at", data.get("createdAt", "")
|
||||
yield "updated_at", data.get("updatedAt", "")
|
||||
|
||||
except Exception as e:
|
||||
yield "error", str(e)
|
||||
yield "webset_id", ""
|
||||
yield "status", ""
|
||||
yield "searches", []
|
||||
yield "enrichments", []
|
||||
yield "monitors", []
|
||||
yield "metadata", {}
|
||||
yield "created_at", ""
|
||||
yield "updated_at", ""
|
||||
|
||||
|
||||
@provider("exa")
|
||||
class ExaDeleteWebsetBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: CredentialsMetaInput = CredentialsField(
|
||||
provider="exa",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Exa integration requires an API Key.",
|
||||
)
|
||||
webset_id: String = SchemaField(
|
||||
description="The ID or external ID of the Webset to delete",
|
||||
placeholder="webset-id-or-external-id",
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
webset_id: String = SchemaField(
|
||||
description="The unique identifier for the deleted webset",
|
||||
)
|
||||
external_id: Optional[String] = SchemaField(
|
||||
description="The external identifier for the deleted webset",
|
||||
default=None,
|
||||
)
|
||||
status: String = SchemaField(
|
||||
description="The status of the deleted webset",
|
||||
)
|
||||
success: String = SchemaField(
|
||||
description="Whether the deletion was successful",
|
||||
default="true",
|
||||
)
|
||||
error: String = SchemaField(
|
||||
description="Error message if the request failed",
|
||||
default="",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="d1f6e4g7-3b5c-7f8d-2a4e-6b9c1d5f8e3a",
|
||||
description="Delete a Webset and all its items",
|
||||
categories={BlockCategory.SEARCH},
|
||||
input_schema=ExaDeleteWebsetBlock.Input,
|
||||
output_schema=ExaDeleteWebsetBlock.Output,
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
url = f"https://api.exa.ai/websets/v0/websets/{input_data.webset_id}"
|
||||
headers = {
|
||||
"x-api-key": credentials.api_key.get_secret_value(),
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.delete(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
yield "webset_id", data.get("id", "")
|
||||
yield "external_id", data.get("externalId")
|
||||
yield "status", data.get("status", "")
|
||||
yield "success", "true"
|
||||
|
||||
except Exception as e:
|
||||
yield "error", str(e)
|
||||
yield "webset_id", ""
|
||||
yield "status", ""
|
||||
yield "success", "false"
|
||||
|
||||
|
||||
@provider("exa")
|
||||
class ExaCancelWebsetBlock(Block):
|
||||
class Input(BlockSchema):
|
||||
credentials: CredentialsMetaInput = CredentialsField(
|
||||
provider="exa",
|
||||
supported_credential_types={"api_key"},
|
||||
description="The Exa integration requires an API Key.",
|
||||
)
|
||||
webset_id: String = SchemaField(
|
||||
description="The ID or external ID of the Webset to cancel",
|
||||
placeholder="webset-id-or-external-id",
|
||||
)
|
||||
|
||||
class Output(BlockSchema):
|
||||
webset_id: String = SchemaField(
|
||||
description="The unique identifier for the webset",
|
||||
)
|
||||
status: String = SchemaField(
|
||||
description="The status of the webset after cancellation",
|
||||
)
|
||||
external_id: Optional[String] = SchemaField(
|
||||
description="The external identifier for the webset",
|
||||
default=None,
|
||||
)
|
||||
success: String = SchemaField(
|
||||
description="Whether the cancellation was successful",
|
||||
default="true",
|
||||
)
|
||||
error: String = SchemaField(
|
||||
description="Error message if the request failed",
|
||||
default="",
|
||||
)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
id="e2g7f5h8-4c6d-8a9e-3b5f-7c1d2e6g9f4b",
|
||||
description="Cancel all operations being performed on a Webset",
|
||||
categories={BlockCategory.SEARCH},
|
||||
input_schema=ExaCancelWebsetBlock.Input,
|
||||
output_schema=ExaCancelWebsetBlock.Output,
|
||||
)
|
||||
|
||||
def run(
|
||||
self, input_data: Input, *, credentials: APIKeyCredentials, **kwargs
|
||||
) -> BlockOutput:
|
||||
url = f"https://api.exa.ai/websets/v0/websets/{input_data.webset_id}/cancel"
|
||||
headers = {
|
||||
"x-api-key": credentials.api_key.get_secret_value(),
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(url, headers=headers)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
yield "webset_id", data.get("id", "")
|
||||
yield "status", data.get("status", "")
|
||||
yield "external_id", data.get("externalId")
|
||||
yield "success", "true"
|
||||
|
||||
except Exception as e:
|
||||
yield "error", str(e)
|
||||
yield "webset_id", ""
|
||||
yield "status", ""
|
||||
yield "success", "false"
|
||||
@@ -1,6 +1,6 @@
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Annotated, Awaitable, Literal
|
||||
from typing import TYPE_CHECKING, Annotated, Awaitable, Dict, List, Literal
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query, Request
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -20,6 +20,7 @@ from backend.integrations.creds_manager import IntegrationCredentialsManager
|
||||
from backend.integrations.oauth import HANDLERS_BY_NAME
|
||||
from backend.integrations.providers import ProviderName
|
||||
from backend.integrations.webhooks import get_webhook_manager
|
||||
from backend.sdk.auto_registry import get_registry
|
||||
from backend.util.exceptions import NeedConfirmation, NotFoundError
|
||||
from backend.util.settings import Settings
|
||||
|
||||
@@ -416,3 +417,89 @@ def _get_provider_oauth_handler(
|
||||
client_secret=client_secret,
|
||||
redirect_uri=f"{frontend_base_url}/auth/integrations/oauth_callback",
|
||||
)
|
||||
|
||||
|
||||
# === PROVIDER DISCOVERY ENDPOINTS ===
|
||||
|
||||
|
||||
@router.get("/providers", response_model=List[str])
|
||||
async def list_providers() -> List[str]:
|
||||
"""
|
||||
Get a list of all available provider names.
|
||||
|
||||
Returns both statically defined providers (from ProviderName enum)
|
||||
and dynamically registered providers (from SDK decorators).
|
||||
"""
|
||||
# Get static providers from enum
|
||||
static_providers = [member.value for member in ProviderName]
|
||||
|
||||
# Get dynamic providers from registry
|
||||
registry = get_registry()
|
||||
dynamic_providers = list(registry.providers)
|
||||
|
||||
# Combine and deduplicate
|
||||
all_providers = list(set(static_providers + dynamic_providers))
|
||||
all_providers.sort()
|
||||
|
||||
logger.info(f"Returning {len(all_providers)} providers")
|
||||
return all_providers
|
||||
|
||||
|
||||
class ProviderDetails(BaseModel):
|
||||
name: str
|
||||
source: Literal["static", "dynamic", "both"]
|
||||
has_oauth: bool
|
||||
has_webhooks: bool
|
||||
supported_credential_types: List[CredentialsType] = Field(default_factory=list)
|
||||
|
||||
|
||||
@router.get("/providers/details", response_model=Dict[str, ProviderDetails])
|
||||
async def get_providers_details() -> Dict[str, ProviderDetails]:
|
||||
"""
|
||||
Get detailed information about all providers.
|
||||
|
||||
Returns a dictionary mapping provider names to their details,
|
||||
including supported credential types and other metadata.
|
||||
"""
|
||||
registry = get_registry()
|
||||
|
||||
# Build provider details
|
||||
provider_details: Dict[str, ProviderDetails] = {}
|
||||
|
||||
# Add static providers
|
||||
for member in ProviderName:
|
||||
provider_details[member.value] = ProviderDetails(
|
||||
name=member.value,
|
||||
source="static",
|
||||
has_oauth=member.value in registry.oauth_handlers,
|
||||
has_webhooks=member.value in registry.webhook_managers,
|
||||
)
|
||||
|
||||
# Add/update with dynamic providers
|
||||
for provider in registry.providers:
|
||||
if provider not in provider_details:
|
||||
provider_details[provider] = ProviderDetails(
|
||||
name=provider,
|
||||
source="dynamic",
|
||||
has_oauth=provider in registry.oauth_handlers,
|
||||
has_webhooks=provider in registry.webhook_managers,
|
||||
)
|
||||
else:
|
||||
provider_details[provider].source = "both"
|
||||
provider_details[provider].has_oauth = provider in registry.oauth_handlers
|
||||
provider_details[provider].has_webhooks = (
|
||||
provider in registry.webhook_managers
|
||||
)
|
||||
|
||||
# Determine supported credential types for each provider
|
||||
# This is a simplified version - in reality, you might want to inspect
|
||||
# the blocks or credentials to determine this more accurately
|
||||
for provider_name, details in provider_details.items():
|
||||
credential_types = []
|
||||
if details.has_oauth:
|
||||
credential_types.append("oauth2")
|
||||
# Most providers support API keys
|
||||
credential_types.append("api_key")
|
||||
details.supported_credential_types = credential_types
|
||||
|
||||
return provider_details
|
||||
|
||||
@@ -62,9 +62,36 @@ async def lifespan_context(app: fastapi.FastAPI):
|
||||
try:
|
||||
from backend.sdk.auto_registry import setup_auto_registration
|
||||
|
||||
setup_auto_registration()
|
||||
logger.info("Starting SDK auto-registration system...")
|
||||
registry = setup_auto_registration()
|
||||
|
||||
# Log successful registration
|
||||
logger.info("Auto-registration completed successfully:")
|
||||
logger.info(f" - {len(registry.block_costs)} block costs registered")
|
||||
logger.info(
|
||||
f" - {len(registry.default_credentials)} default credentials registered"
|
||||
)
|
||||
logger.info(f" - {len(registry.oauth_handlers)} OAuth handlers registered")
|
||||
logger.info(f" - {len(registry.webhook_managers)} webhook managers registered")
|
||||
logger.info(f" - {len(registry.providers)} providers registered")
|
||||
|
||||
# Log specific credential providers for debugging
|
||||
credential_providers = [
|
||||
getattr(cred, "provider", "unknown")
|
||||
for cred in registry.default_credentials
|
||||
]
|
||||
if credential_providers:
|
||||
logger.info(
|
||||
f" - Default credential providers: {', '.join(credential_providers)}"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Auto-registration setup failed: {e}")
|
||||
logger.error(f"Auto-registration setup failed: {e}")
|
||||
import traceback
|
||||
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
# Don't let this failure prevent startup, but make it very visible
|
||||
raise
|
||||
|
||||
await backend.data.user.migrate_and_encrypt_user_integrations()
|
||||
await backend.data.graph.fix_llm_provider_credentials()
|
||||
|
||||
Reference in New Issue
Block a user