Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion backend/app/api/docs/credentials/create.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Credentials are encrypted and stored securely for provider integrations (OpenAI,
- **LLM:** openai, sarvamai, google(gemini)
- **Observability:** langfuse
- **Audio:** elevenlabs
- **Miscellaneous** webhook_secret

### Examples:

Expand Down Expand Up @@ -40,7 +41,19 @@ Credentials are encrypted and stored securely for provider integrations (OpenAI,
"public_key": "pk-lf-....",
"secret_key": "sk-lf-...",
"host": "https://cloud.langfuse.com"
}
},
"webhook_secret": {
"webhook_secret: "webhook_secret"
},
}
}
```
#### For registering Webhook Secret
```json
{
"credential":{
"webhook_secret":"your-webhook-secret"
}

}
Comment on lines +45 to +58
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the webhook credential examples.

The multi-provider JSON is invalid ("webhook_secret: ... is missing the closing key quote), and the standalone example uses a different shape than the provider-object format. This will mislead API users.

📝 Proposed fix
     "langfuse": {
       "public_key": "pk-lf-....",
       "secret_key": "sk-lf-...",
       "host": "https://cloud.langfuse.com"
     },
     "webhook_secret": {
-      "webhook_secret: "webhook_secret"
-    },
+      "webhook_secret": "your-webhook-secret"
+    }
   }
 }

For registering Webhook Secret

{
-  "credential":{
-    "webhook_secret":"your-webhook-secret"
+  "credential": {
+    "webhook_secret": {
+      "webhook_secret": "your-webhook-secret"
+    }
  }
-
}
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @backend/app/api/docs/credentials/create.md around lines 45 - 58, The
examples for webhook credentials are invalid and inconsistent: fix the
multi-provider JSON by closing the missing quote on the "webhook_secret" key and
nesting the provider object under "credential" so it matches the provider-object
shape (i.e., "credential": { "webhook_secret": { "webhook_secret": "..." } }),
and update the standalone "For registering Webhook Secret" example to use the
same object shape (credential -> webhook_secret -> webhook_secret) so both
examples are valid JSON and consistent; key symbols to locate are the
"credential" object and the "webhook_secret" key in the shown examples.


</details>

<!-- fingerprinting:phantom:medusa:ibis -->

<!-- This is an auto-generated comment by CodeRabbit -->

```
1 change: 1 addition & 0 deletions backend/app/api/docs/credentials/update.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ The `credential` field accepts **two formats** (both work the same):
- **LLM:** openai, sarvamai, google(gemini)
- **Observability:** langfuse
- **Audio:** elevenlabs
- **Miscellaneous** webhook_secret
1 change: 1 addition & 0 deletions backend/app/api/docs/credentials/update_by_org_project.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ The `credential` field accepts **two formats** (both work the same):
- **LLM:** openai, sarvamai, google(gemini)
- **Observability:** langfuse
- **Audio:** elevenlabs
- **Miscellaneous** webhook_secret
4 changes: 4 additions & 0 deletions backend/app/core/providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class Provider(str, Enum):
GOOGLE = "google"
SARVAMAI = "sarvamai"
ELEVENLABS = "elevenlabs"
WEBHOOK_SECRET = "webhook_secret"


@dataclass
Expand Down Expand Up @@ -42,6 +43,9 @@ class ProviderConfig:
Provider.ELEVENLABS: ProviderConfig(
required_fields=["api_key"], sensitive_fields=["api_key"]
),
Provider.WEBHOOK_SECRET: ProviderConfig(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update the credentials endpoint doc ..

required_fields=["webhook_secret"], sensitive_fields=["webhook_secret"]
),
Comment on lines +46 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update credential tests that hardcode the provider set.

Adding Provider.WEBHOOK_SECRET here expands the configured provider list; backend/app/tests/crud/test_credentials.py still asserts exactly {"openai", "langfuse"} and len(...) == 2, so those tests should be updated to include webhook_secret or derive expectations from the provider config.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/core/providers.py` around lines 46 - 48, The tests in
test_credentials.py hardcode the expected provider set and length (currently
asserting {"openai","langfuse"} and len==2), which breaks now that
Provider.WEBHOOK_SECRET is added; update those assertions to include
"webhook_secret" (matching Provider.WEBHOOK_SECRET) or, better, compute expected
providers dynamically from the Provider/ProviderConfig registry so the test no
longer hardcodes the set/length (e.g., derive expectation from the Provider enum
or the providers mapping used in providers.py).

}


Expand Down
16 changes: 13 additions & 3 deletions backend/app/services/collections/create_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
)
from app.services.collections.providers.registry import get_llm_provider
from app.celery.utils import start_create_collection_job
from app.utils import send_callback, APIResponse
from app.utils import send_callback, get_webhook_secret, APIResponse


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -274,7 +274,12 @@ def execute_job(
)

if creation_request.callback_url:
send_callback(creation_request.callback_url, success_payload)
webhook_secret = get_webhook_secret(project_id, organization_id)
send_callback(
str(creation_request.callback_url),
success_payload,
webhook_secret=webhook_secret,
)

except Exception as err:
span.record_exception(err)
Expand Down Expand Up @@ -303,5 +308,10 @@ def execute_job(

if creation_request and creation_request.callback_url and collection_job:
failure_payload = build_failure_payload(collection_job, str(err))
send_callback(creation_request.callback_url, failure_payload)
webhook_secret = get_webhook_secret(project_id, organization_id)
send_callback(
str(creation_request.callback_url),
failure_payload,
webhook_secret=webhook_secret,
)
raise
39 changes: 26 additions & 13 deletions backend/app/services/collections/delete_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from app.services.collections.providers.registry import get_llm_provider
from app.celery.utils import start_delete_collection_job
from app.core.telemetry import log_context
from app.utils import send_callback, APIResponse
from app.utils import send_callback, get_webhook_secret, APIResponse


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -104,6 +104,7 @@ def build_failure_payload(

def _mark_job_failed_and_callback(
*,
organization_id: int,
project_id: int,
collection_id: UUID,
job_id: UUID,
Expand Down Expand Up @@ -146,7 +147,8 @@ def _mark_job_failed_and_callback(
collection_id=collection_id,
error_message=str(err),
)
send_callback(callback_url, failure_payload)
webhook_secret = get_webhook_secret(project_id, organization_id)
send_callback(callback_url, failure_payload, webhook_secret=webhook_secret)


def execute_job(
Expand All @@ -162,8 +164,11 @@ def execute_job(

deletion_request = DeletionRequest(**request)

collection_id = UUID(collection_id)
collection_uuid = UUID(collection_id)
job_uuid = UUID(job_id)
callback_url = (
str(deletion_request.callback_url) if deletion_request.callback_url else None
)

collection_job = None

Expand All @@ -172,12 +177,12 @@ def execute_job(
lifecycle="collection.delete.execute_job",
action="delete",
collection_job_id=job_id,
collection_id=collection_id,
collection_id=str(collection_uuid),
task_id=task_id,
project_id=project_id,
organization_id=organization_id,
), tracer.start_as_current_span("collections.delete.execute_job") as span:
span.set_attribute("collection.id", str(collection_id))
span.set_attribute("collection.id", str(collection_uuid))
span.set_attribute("collection.job_id", str(job_uuid))
span.set_attribute("kaapi.project_id", project_id)
span.set_attribute("kaapi.organization_id", organization_id)
Expand All @@ -194,7 +199,9 @@ def execute_job(
),
)

collection = CollectionCrud(session, project_id).read_one(collection_id)
collection = CollectionCrud(session, project_id).read_one(
collection_uuid
)
span.set_attribute("collection.provider", str(collection.provider))

provider = get_llm_provider(
Expand All @@ -208,7 +215,7 @@ def execute_job(
provider.delete(collection)

with Session(engine) as session:
CollectionCrud(session, project_id).delete_by_id(collection_id)
CollectionCrud(session, project_id).delete_by_id(collection_uuid)

collection_job_crud = CollectionJobCrud(session, project_id)
collection_job_crud.update(
Expand All @@ -223,24 +230,30 @@ def execute_job(
logger.info(
"[delete_collection.execute_job] Collection deleted successfully | "
"{'collection_id': '%s', 'job_id': '%s'}",
str(collection_id),
str(collection_uuid),
str(job_uuid),
)
if deletion_request.callback_url and collection_job:
if callback_url and collection_job:
success_payload = build_success_payload(
collection_job=collection_job,
collection_id=collection_id,
collection_id=collection_uuid,
)
webhook_secret = get_webhook_secret(project_id, organization_id)
send_callback(
callback_url,
success_payload,
webhook_secret=webhook_secret,
)
send_callback(deletion_request.callback_url, success_payload)

except Exception as err:
span.record_exception(err)
span.set_status(trace.Status(trace.StatusCode.ERROR, str(err)))
_mark_job_failed_and_callback(
organization_id=organization_id,
project_id=project_id,
collection_id=collection_id,
collection_id=collection_uuid,
job_id=job_uuid,
err=err,
callback_url=deletion_request.callback_url,
callback_url=callback_url,
)
raise
16 changes: 13 additions & 3 deletions backend/app/services/doctransform/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
DocTransformationJobPublic,
TransformedDocumentPublic,
DocTransformationJob,
Project,
)
from app.core.cloud import get_cloud_storage
from app.celery.utils import start_doctransform_job
from app.utils import send_callback, APIResponse
from app.utils import send_callback, get_webhook_secret, APIResponse
from app.services.doctransform.registry import convert_document, FORMAT_TO_EXTENSION
from app.core.db import engine

Expand Down Expand Up @@ -117,11 +118,18 @@ def execute_job(
tmp_dir: Path | None = None

job_for_payload = None # keep latest job snapshot for payloads
webhook_secret: str | None = None

try:
job_uuid = UUID(job_id)
source_uuid = UUID(source_document_id)

if callback_url:
with Session(engine) as db:
project = db.get(Project, project_id)
if project is not None:
webhook_secret = get_webhook_secret(project_id, project.organization_id)

logger.info(
"[doc_transform.execute_job] started | job_id=%s | transformer=%s | target=%s | project_id=%s",
job_uuid,
Expand Down Expand Up @@ -222,7 +230,7 @@ def execute_job(
)

if callback_url:
send_callback(callback_url, success_payload)
send_callback(callback_url, success_payload, webhook_secret=webhook_secret)

except Exception as e:
logger.error(
Expand Down Expand Up @@ -251,7 +259,9 @@ def execute_job(
if callback_url and job_for_payload:
try:
failure_payload = build_failure_payload(job_for_payload, str(e))
send_callback(callback_url, failure_payload)
send_callback(
callback_url, failure_payload, webhook_secret=webhook_secret
)
except Exception as cb_error:
logger.error(
"[doc_transform.execute_job] callback failed | job_id=%s | error=%s",
Expand Down
10 changes: 9 additions & 1 deletion backend/app/services/llm/chain/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from app.models.llm.response import IntermediateChainResponse, LLMChainResponse
from app.services.llm.chain.chain import ChainContext, LLMChain
from app.services.llm.chain.types import BlockResult
from app.utils import APIResponse, send_callback
from app.utils import APIResponse, get_webhook_secret, send_callback

logger = logging.getLogger(__name__)

Expand All @@ -31,6 +31,7 @@ def __init__(
self._chain = chain
self._context = context
self._request = request
self._webhook_secret: str | None = None

def run(self) -> dict:
"""Execute the full chain lifecycle. Returns serialized APIResponse."""
Expand Down Expand Up @@ -60,6 +61,10 @@ def _setup(self) -> None:
status=ChainStatus.RUNNING,
)

self._webhook_secret = get_webhook_secret(
self._context.project_id, self._context.organization_id
)

def _teardown(self, result: BlockResult) -> dict:
"""Finalize chain record, send callback, and update job status."""

Expand All @@ -76,6 +81,7 @@ def _teardown(self, result: BlockResult) -> dict:
send_callback(
callback_url=str(self._request.callback_url),
data=callback_response.model_dump(),
webhook_secret=self._webhook_secret,
)
with Session(engine) as session:
JobCrud(session).update(
Expand Down Expand Up @@ -107,6 +113,7 @@ def _handle_error(self, error: str) -> dict:
send_callback(
callback_url=str(self._request.callback_url),
data=callback_response.model_dump(),
webhook_secret=self._webhook_secret,
)

with Session(engine) as session:
Expand Down Expand Up @@ -166,6 +173,7 @@ def _send_intermediate_callback(
send_callback(
callback_url=str(self._request.callback_url),
data=callback_data.model_dump(),
webhook_secret=self._webhook_secret,
)
logger.info(
f"[_send_intermediate_callback] Sent intermediate callback | "
Expand Down
Loading
Loading