Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
169 changes: 169 additions & 0 deletions backend/app/alembic/versions/048_create_llm_chain_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
"""Create llm_chain table

Revision ID: 048
Revises: 047
Create Date: 2026-02-20 00:00:00.000000

"""

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects.postgresql import JSONB

revision = "048"
down_revision = "047"
branch_labels = None
depends_on = None


def upgrade() -> None:
# 1. Create llm_chain table
op.create_table(
"llm_chain",
sa.Column(
"id",
sa.Uuid(),
nullable=False,
comment="Unique identifier for the LLM chain record",
),
sa.Column(
"job_id",
sa.Uuid(),
nullable=False,
comment="Reference to the parent job (status tracked in job table)",
),
sa.Column(
"project_id",
sa.Integer(),
nullable=False,
comment="Reference to the project this LLM call belongs to",
),
sa.Column(
"organization_id",
sa.Integer(),
nullable=False,
comment="Reference to the organization this LLM call belongs to",
),
sa.Column(
"status",
sa.String(),
nullable=False,
server_default="pending",
comment="Chain execution status (pending, running, failed, completed)",
),
sa.Column(
"error",
sa.Text(),
nullable=True,
comment="Error message if the chain execution failed",
),
sa.Column(
"block_sequences",
JSONB(),
nullable=True,
comment="Ordered list of llm_call UUIDs as blocks complete",
),
sa.Column(
"total_blocks",
sa.Integer(),
nullable=False,
comment="Total number of blocks to execute",
),
sa.Column(
"number_of_blocks_processed",
sa.Integer(),
nullable=False,
server_default="0",
comment="Number of blocks processed so far (used for tracking progress)",
),
sa.Column(
"input",
sa.String(),
nullable=False,
comment="First block user's input - text string, binary data, or file path for multimodal",
),
sa.Column(
"output",
JSONB(),
nullable=True,
comment="Last block's final output (set on chain completion)",
),
sa.Column(
"configs",
JSONB(),
nullable=True,
comment="Ordered list of block configs as submitted in the request",
),
sa.Column(
"total_usage",
JSONB(),
nullable=True,
comment="Aggregated token usage: {input_tokens, output_tokens, total_tokens}",
),
sa.Column(
"metadata",
JSONB(),
nullable=True,
comment="Future-proof extensibility catch-all",
),
sa.Column(
"inserted_at",
sa.DateTime(),
nullable=False,
comment="Timestamp when the chain record was created",
),
sa.Column(
"updated_at",
sa.DateTime(),
nullable=False,
comment="Timestamp when the chain record was last updated",
),
sa.PrimaryKeyConstraint("id"),
sa.ForeignKeyConstraint(["job_id"], ["job.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(["project_id"], ["project.id"], ondelete="CASCADE"),
sa.ForeignKeyConstraint(
["organization_id"], ["organization.id"], ondelete="CASCADE"
),
)

op.create_index(
"idx_llm_chain_job_id",
"llm_chain",
["job_id"],
)

# 2. Add chain_id FK column to llm_call table
op.add_column(
"llm_call",
sa.Column(
"chain_id",
sa.Uuid(),
nullable=True,
comment="Reference to the parent chain (NULL for standalone /llm/call requests)",
),
)
op.create_foreign_key(
"fk_llm_call_chain_id",
"llm_call",
"llm_chain",
["chain_id"],
["id"],
ondelete="SET NULL",
)
op.create_index(
"idx_llm_call_chain_id",
"llm_call",
["chain_id"],
postgresql_where=sa.text("chain_id IS NOT NULL"),
)

op.execute("ALTER TYPE jobtype ADD VALUE IF NOT EXISTS 'LLM_CHAIN'")


def downgrade() -> None:
op.drop_index("idx_llm_call_chain_id", table_name="llm_call")
op.drop_constraint("fk_llm_call_chain_id", "llm_call", type_="foreignkey")
op.drop_column("llm_call", "chain_id")

op.drop_index("idx_llm_chain_job_id", table_name="llm_chain")
op.drop_table("llm_chain")
61 changes: 61 additions & 0 deletions backend/app/api/docs/llm/llm_chain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
Execute a chain of LLM calls sequentially, where each block's output becomes the next block's input.

This endpoint initiates an asynchronous LLM chain job. The request is queued
for processing, and results are delivered via the callback URL when complete.

### Key Parameters

**`query`** (required) - Initial query input for the first block in the chain:
- `input` (required): User question/prompt/query — accepts a plain string, a structured input object (`text`, `audio`, `image`, `pdf`), or a list of structured inputs
- `conversation` (optional, object): Conversation configuration
- `id` (optional, string): Existing conversation ID to continue
- `auto_create` (optional, boolean, default false): Create new conversation if no ID provided
- **Note**: Cannot specify both `id` and `auto_create=true`


**`blocks`** (required, array, min 1 block) - Ordered list of blocks to execute sequentially. Each block contains:

- `config` (required) - Configuration for this block's LLM call (just choose one mode):

- **Mode 1: Stored Configuration**
- `id` (UUID): Configuration ID
- `version` (integer >= 1): Version number
- **Both required together**
- **Note**: When using stored configuration, do not include the `blob` field in the request body

- **Mode 2: Ad-hoc Configuration**
- `blob` (object): Complete configuration object
- `completion` (required, object): Completion configuration
- `provider` (required, string): Kaapi providers (`openai`, `google`, `sarvamai`) — params are validated and mapped internally. Native providers (`openai-native`, `google-native`, `sarvamai-native`) — params are passed through as-is
- `type` (required, string): Completion type — `"text"`, `"stt"`, or `"tts"`
- `params` (required, object): Parameters structure depends on provider and type (see schema for detailed structure)
- `input_guardrails` (optional, array): Guardrails applied to validate/sanitize input before the LLM call
- `output_guardrails` (optional, array): Guardrails applied to validate/sanitize output after the LLM call
- `prompt_template` (optional, object): Template for text interpolation
- `template` (required, string): Template string with `{{input}}` placeholder — replaced with the block's input before execution
- **Note**
- When using ad-hoc configuration, do not include `id` and `version` fields
- When using the Kaapi abstraction, parameters that are not supported by the selected provider or model are automatically suppressed. If any parameters are ignored, a list of warnings is included in the metadata.warnings.
- **Recommendation**: Use stored configs (Mode 1) for production; use ad-hoc configs only for testing/validation
- **Schema**: Check the API schema or examples below for the complete parameter structure for each provider type

- `include_provider_raw_response` (optional, boolean, default false):
- When true, includes the unmodified raw response from the LLM provider for this block

- `intermediate_callback` (optional, boolean, default false):
- When true, sends an intermediate callback after this block completes with the block's response, usage, and position in the chain

**`callback_url`** (optional, HTTPS URL):
- Webhook endpoint to receive the final response and intermediate callbacks
- Must be a valid HTTPS URL
- If not provided, response is only accessible through job status
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

"Accessible through job status" is misleading — the endpoint currently does not return a job_id.

Without a job_id in the response, callers have no identifier to query for job status when callback_url is omitted. This documentation claim is inaccurate until the route returns a job ID (see the comment in llm_chain.py).

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

In `@backend/app/api/docs/llm/llm_chain.md` at line 48, The docs line "If not
provided, response is only accessible through job status" is inaccurate because
the LLM chain creation endpoint in llm_chain.py does not return a job_id; either
remove or reword that sentence in llm_chain.md to reflect current behavior
(e.g., state that no job identifier is returned and callers cannot poll for
status) or alternatively update the endpoint implementation in llm_chain.py to
include and return a job_id (generate/attach the Job id from the job creation
flow and include it in the JSON response) so the documentation is correct.


**`request_metadata`** (optional, object):
- Custom JSON metadata
- Passed through unchanged in the response

### Note
- If any block fails, the chain stops immediately — no subsequent blocks are executed
- `warnings` list is automatically added in response metadata when using Kaapi configs if any parameters are suppressed or adjusted (e.g., temperature on reasoning models)

---
2 changes: 2 additions & 0 deletions backend/app/api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
login,
languages,
llm,
llm_chain,
organization,
openai_conversation,
project,
Expand Down Expand Up @@ -41,6 +42,7 @@
api_router.include_router(evaluations.router)
api_router.include_router(languages.router)
api_router.include_router(llm.router)
api_router.include_router(llm_chain.router)
api_router.include_router(login.router)
api_router.include_router(onboarding.router)
api_router.include_router(openai_conversation.router)
Expand Down
62 changes: 62 additions & 0 deletions backend/app/api/routes/llm_chain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import logging

from fastapi import APIRouter, Depends
from app.api.deps import AuthContextDep, SessionDep
from app.api.permissions import Permission, require_permission
from app.models import LLMChainRequest, LLMChainResponse, Message
from app.services.llm.jobs import start_chain_job
from app.utils import APIResponse, validate_callback_url, load_description

logger = logging.getLogger(__name__)

router = APIRouter(tags=["LLM"])
llm_callback_router = APIRouter()


@llm_callback_router.post(
"{$callback_url}",
Comment on lines +16 to +17
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Feb 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, find and read the file
find . -name "llm_chain.py" -path "*/backend/app/api/*" | head -5

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 112


🏁 Script executed:

# Also check file structure to understand the path
find . -type f -name "llm_chain.py"

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 144


🏁 Script executed:

# Let me also search for the specific file with less strict matching
fd "llm_chain\.py$"

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 140


🏁 Script executed:

cat -n backend/app/api/routes/llm_chain.py

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 2400


🏁 Script executed:

# Check if the description files exist
find . -name "llm_call.md" -o -name "llm_chain.md" | grep -E "(llm_call|llm_chain)"

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 113


🏁 Script executed:

# Search for where load_description is defined to understand its usage
rg "def load_description" -A 5

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 403


🏁 Script executed:

# Check the LLMChainRequest model to verify callback_url optionality
rg "class LLMChainRequest" -A 20

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 1424


🏁 Script executed:

# Check if llm_chain.md exists
find . -name "llm_chain.md"

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 57


🏁 Script executed:

# Let's also check what description files exist in the llm directory
ls -la backend/app/api/docs/llm/

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 249


🌐 Web query:

OpenAPI 3.0 callback runtime expression syntax specification

💡 Result:

In OpenAPI 3.0.x, the URL template key inside a Callback Object is a runtime expression (“key expression”) evaluated at runtime to produce the callback URL (e.g., $request.body#/url). (spec.openapis.org)

Runtime expression syntax (ABNF)

expression = ( "$url" / "$method" / "$statusCode"
             / "$request." source / "$response." source )
source = ( header-reference / query-reference / path-reference / body-reference )
header-reference = "header." token
query-reference  = "query." name
path-reference   = "path." name
body-reference   = "body" ["#" json-pointer ]
json-pointer     = *( "/" reference-token )
reference-token  = *( unescaped / escaped )
escaped          = "~" ( "0" / "1" )   ; "~0" => "~", "~1" => "/"

(spec.openapis.org)

Common callback/key examples

Embedding into strings (typical for callback URLs)

Runtime expressions can be embedded in a string by wrapping them in {} (for example, a callback key like {$request.body#/callbackUrl}). (spec.openapis.org)

Citations:


🏁 Script executed:

# Check the entire llm_chain function to see all return paths
sed -n '39,62p' backend/app/api/routes/llm_chain.py

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 750


🏁 Script executed:

# Verify what the llm_call.md content says and if it's appropriate for llm_chain endpoint
cat backend/app/api/docs/llm/llm_call.md | head -20

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 981


🏁 Script executed:

# Check if there are any return type hints elsewhere in the file or similar endpoints
rg "@router.post" -A 10 backend/app/api/routes/ | head -50

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 2876


🏁 Script executed:

# Check what happens in start_chain_job when callback_url is None
rg "def start_chain_job" -A 20 backend/app/services/

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 1519


🏁 Script executed:

# Also check the llm_callback_notification function return type more carefully
sed -n '20,29p' backend/app/api/routes/llm_chain.py

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 484


🏁 Script executed:

# Check response_model to infer what return type should be
sed -n '32,38p' backend/app/api/routes/llm_chain.py

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 320


🏁 Script executed:

# Check if there are other endpoints with similar callback patterns to understand the intended usage
rg "callback" -i backend/app/api/routes/ -l

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 284


🏁 Script executed:

# Let me also check if execute_chain_job uses the callback_url
rg "def execute_chain_job" -A 30 backend/app/services/

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 2098


🏁 Script executed:

# Check what happens when callback_url is None in the job execution
rg "callback_url" backend/app/services/llm/jobs.py

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 845


🏁 Script executed:

# Final verification: confirm the exact issue exists in the code
sed -n '15,19p' backend/app/api/routes/llm_chain.py

Repository: ProjectTech4DevAI/kaapi-backend

Length of output: 158


Fix invalid OpenAPI runtime expression — {$callback_url} does not conform to spec.

OpenAPI 3.0 defines runtime expressions using the ABNF grammar: expression = ( "$url" / "$method" / "$statusCode" / "$request." source / "$response." source ). The token $callback_url is not a recognized form; valid patterns require $request.body, $response.body, or similar qualified sources.

Proposed fix
-    "{$callback_url}",
+    "{$request.body#/callback_url}",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/routes/llm_chain.py` around lines 16 - 17, The route
decorator uses an invalid OpenAPI runtime expression "{$callback_url}"; update
the expression to a valid OpenAPI runtime expression that points to where the
callback URL actually lives (e.g. request body or query). Replace
"{$callback_url}" in the llm_callback_router.post decorator with a compliant
expression such as "{$request.body#/callback_url}" or
"{$request.query.callback_url}" depending on where the callback_url is provided,
ensuring the symbol llm_callback_router.post is updated accordingly.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

same "{$callback_url}" pattern is used in the existing /llm/call endpoint, its just a swagger documentation convention for showing the callback schemes, it would not going to affect the runtime. I know for consistency it needed, but both the llm/call and llm/chain need to be fixed if this is the case. which can be fixed in next pr

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the claim that the same {$callback_url} pattern is used in the existing /llm/call endpoint
rg -n "{.*callback.*}" backend/app/api/routes/ -A 2 -B 2

Length of output: 89


🏁 Script executed:

#!/bin/bash
# Verify the claim that the same {$callback_url} pattern is used in the existing /llm/call endpoint
rg -n '\{\$callback' backend/app/api/routes/ -A 2 -B 2

Length of output: 1128


@vprashrex, you're absolutely correct! I verified that the same {$callback_url} pattern is used consistently across multiple existing endpoints:

  • backend/app/api/routes/llm.py (line 19)
  • backend/app/api/routes/documents.py (line 51)
  • backend/app/api/routes/collections.py (line 46)
  • backend/app/api/routes/llm_chain.py (line 17)

Your suggestion to fix all callback endpoints together in a future PR makes sense for consistency. Would you like me to create a tracking issue for this, so it's not forgotten?

name="llm_chain_callback",
)
def llm_callback_notification(body: APIResponse[LLMChainResponse]):
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Feb 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing return type hints on both route handlers — coding guideline violation.

llm_callback_notification and llm_chain both lack return type annotations.

🐛 Proposed fix
-def llm_callback_notification(body: APIResponse[LLMChainResponse]):
+def llm_callback_notification(body: APIResponse[LLMChainResponse]) -> None:
-def llm_chain(
-    _current_user: AuthContextDep, _session: SessionDep, request: LLMChainRequest
-):
+def llm_chain(
+    _current_user: AuthContextDep, _session: SessionDep, request: LLMChainRequest
+) -> APIResponse[Message]:

As per coding guidelines: "Always add type hints to all function parameters and return values in Python code."

Also applies to: 39-41

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

In `@backend/app/api/routes/llm_chain.py` at line 20, Both route handlers missing
return type annotations: add explicit return type hints for
llm_callback_notification and llm_chain to match what they actually return
(e.g., APIResponse[LLMChainResponse], fastapi.Response,
starlette.responses.JSONResponse, or None). Update the function signatures
(llm_callback_notification(body: APIResponse[LLMChainResponse]) and
llm_chain(...)) to include the correct -> <ReturnType> annotation, ensuring the
declared type aligns with the handler’s actual return values.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

the existing /llm/call handlers (llm_callback_notification, llm_call) also don't have return type annotations following same pattern for the /llm/chain as well. and also this is standard FastAPI convention no fix needed

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the existing /llm/call route handlers to verify the user's claim
fd -e py -x cat {} \; | grep -A 10 "def llm_call\|def llm_callback_notification" backend/app/api/routes/ || true

# Let's specifically look for the llm.py route file (for /llm/call endpoints)
find backend/app/api/routes -name "llm.py" -type f -exec cat {} \;

Length of output: 1907


@vprashrex, you're absolutely right — I verified that the existing /llm/call handlers (llm_callback_notification and llm_call in backend/app/api/routes/llm.py) also omit return type annotations, so your new handlers are indeed consistent with the established pattern in this codebase. I understand your decision to maintain that consistency.

For context: while FastAPI does allow omitting return types and will infer them in many cases, explicit return type hints are generally recommended because they improve OpenAPI schema generation and IDE support. That said, maintaining consistency within a codebase is also valuable, and you're applying the same pattern used elsewhere.

"""
Callback endpoint specification for LLM chain completion.

The callback will receive:
- On success: APIResponse with success=True and data containing LLMChainResponse
- On failure: APIResponse with success=False and error message
- metadata field will always be included if provided in the request
"""
...


@router.post(
"/llm/chain",
description=load_description("llm/llm_chain.md"),
response_model=APIResponse[Message],
callbacks=llm_callback_router.routes,
dependencies=[Depends(require_permission(Permission.REQUIRE_PROJECT))],
)
def llm_chain(
_current_user: AuthContextDep, _session: SessionDep, request: LLMChainRequest
):
"""
Endpoint to initiate an LLM chain as a background job.
"""
project_id = _current_user.project_.id
organization_id = _current_user.organization_.id

if request.callback_url:
validate_callback_url(str(request.callback_url))

start_chain_job(
db=_session,
request=request,
project_id=project_id,
organization_id=organization_id,
)

return APIResponse.success_response(
data=Message(
message="Your response is being generated and will be delivered via callback."
),
)
Comment on lines +48 to +62
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Feb 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unconditional "callback" success message when callback_url is optional — results are silently lost.

LLMChainRequest.callback_url is HttpUrl | None with default=None, so it's entirely valid to submit a chain request without a callback URL. When that happens the endpoint still responds "Your response is being generated and will be delivered via callback.", which is incorrect: there is no delivery mechanism and the caller has no way to retrieve the result.

Consider either (a) requiring callback_url to be non-null, or (b) returning a synchronous result / job-ID for polling when no callback URL is supplied, and adjusting the response message accordingly.

🐛 Minimal guard (option a — require callback_url)
+from fastapi import HTTPException, status

 if request.callback_url:
     validate_callback_url(str(request.callback_url))
+else:
+    raise HTTPException(
+        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
+        detail="callback_url is required for LLM chain execution.",
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if request.callback_url:
validate_callback_url(str(request.callback_url))
start_chain_job(
db=_session,
request=request,
project_id=project_id,
organization_id=organization_id,
)
return APIResponse.success_response(
data=Message(
message="Your response is being generated and will be delivered via callback."
),
)
from fastapi import HTTPException, status
if request.callback_url:
validate_callback_url(str(request.callback_url))
else:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
detail="callback_url is required for LLM chain execution.",
)
start_chain_job(
db=_session,
request=request,
project_id=project_id,
organization_id=organization_id,
)
return APIResponse.success_response(
data=Message(
message="Your response is being generated and will be delivered via callback."
),
)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/api/routes/llm_chain.py` around lines 48 - 62, The endpoint
currently treats request.callback_url as optional but always returns "will be
delivered via callback", which is incorrect when LLMChainRequest.callback_url is
None; either enforce a non-null callback by validating request.callback_url and
raising/returning a 400 before calling start_chain_job (use
validate_callback_url and LLMChainRequest.callback_url to gate behavior), or
change the flow when callback_url is None to return a synchronous result or a
job identifier for polling (have start_chain_job return a job_id/status and
update the APIResponse.success_response payload and Message accordingly); ensure
the response text matches the chosen path and do not claim callback delivery
when no callback_url was provided.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

That's the design choice. which was introduced for the /llm/call. and same way I incorporate that for the /llm/chain as well, to make it consistent. if it need to be fixed, will create new pr for fixing this.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the /llm/call endpoint to verify if it has the same pattern
# Look for the llm_call endpoint and check if callback_url is optional and if the message mentions callback delivery

# Find the file with llm_call endpoint
fd -t f "llm_call.py" backend/app/api/routes/ -x cat {}

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Also check the LLMCallRequest model to see if callback_url is optional
rg -A 5 -B 5 "class LLMCallRequest" --type=py

Length of output: 743


🏁 Script executed:

#!/bin/bash
# Search for llm_call route definition more broadly
rg -A 30 "@router.post.*\/llm\/call" --type=py

Length of output: 105


🏁 Script executed:

#!/bin/bash
# List all route files to understand the structure
fd -t f ".py" backend/app/api/routes/ 

Length of output: 1443


🏁 Script executed:

#!/bin/bash
# Check LLMCallRequest for callback_url field
rg -A 15 "class LLMCallRequest" backend/app/models/llm/request.py

Length of output: 607


🏁 Script executed:

#!/bin/bash
# Check the llm.py route file for the /llm/call endpoint
cat backend/app/api/routes/llm.py

Length of output: 1861


🏁 Script executed:

#!/bin/bash
# Check for callback_url in LLMCallRequest
rg "callback_url" backend/app/models/llm/request.py -A 2 -B 2

Length of output: 274


@vprashrex, thanks for the clarification! I verified and you're absolutely right — /llm/call has the identical pattern (optional callback_url with the same unconditional "via callback" message), so maintaining consistency makes sense.

Since this affects both endpoints and you're open to fixing it in a separate PR, would you like me to create a GitHub issue to track this improvement? I can document both /llm/call and /llm/chain in the issue so it's captured for future work.

2 changes: 2 additions & 0 deletions backend/app/crud/llm.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def create_llm_call(
*,
request: LLMCallRequest,
job_id: UUID,
chain_id: UUID | None = None,
project_id: int,
organization_id: int,
resolved_config: ConfigBlob,
Expand Down Expand Up @@ -128,6 +129,7 @@ def create_llm_call(
job_id=job_id,
project_id=project_id,
organization_id=organization_id,
chain_id=chain_id,
input=serialize_input(request.query.input),
input_type=input_type,
output_type=output_type,
Expand Down
Loading