-
Notifications
You must be signed in to change notification settings - Fork 7
Implement Unified LLM API v1 to Simplify LLM Integrations and Enable Multi-Provider Extensibility #413
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Implement Unified LLM API v1 to Simplify LLM Integrations and Enable Multi-Provider Extensibility #413
Changes from all commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
d46e028
intial commit v1 llm
avirajsingh7 e08fdcd
resolve using registry
avirajsingh7 95be54b
resolve using sqlModel
avirajsingh7 a7f63d8
Implement from llm request
avirajsingh7 e76e2c8
remove md
avirajsingh7 2941118
rename OpenAISpec to OpenAIResponseSpec
avirajsingh7 a8b9577
Enhanced OpenAISpec Configuration
avirajsingh7 2d191ee
Define intial unified api contract
avirajsingh7 6e359d5
Use flexible json for config
avirajsingh7 bb74ab6
Handle callback
avirajsingh7 b6a3fd9
Refactor LLM provider architecture: remove factory pattern, introduce…
avirajsingh7 86f6855
remove spec
avirajsingh7 47f4e25
Refactor LLM provider modules: remove unnecessary docstrings, simplif…
avirajsingh7 9780d6b
Add support for including raw LLM provider response in API calls and …
avirajsingh7 1cf2787
Refactor LLM API and provider modules: update response handling in ll…
avirajsingh7 e87a6bc
Enhance documentation in QueryParams and CompletionConfig classes, an…
avirajsingh7 2982a37
Add LLM callback endpoint and improve job error handling in LLM services
avirajsingh7 46d9a53
Refactor conversation handling in LLM request and OpenAI provider: ad…
avirajsingh7 9867739
Update OpenAI package version to 1.100.0 in pyproject.toml and uv.lock
avirajsingh7 3e7b015
Refactor LLM response models: rename Diagnostics to Usage, update LLM…
avirajsingh7 935f7cd
Add llm/jobs.py tests and update callback_url type to HttpUrl
avirajsingh7 a4a1d71
Tests for registry.py
avirajsingh7 3c8f1e8
Add tests for OpenAIProvider and enhance mock_openai_response to supp…
avirajsingh7 5c02fc5
precommit
avirajsingh7 aba203f
Rename include_provider_response to include_provider_raw_response in …
avirajsingh7 1917f3c
Add request_metadata field to LLMCallRequest and include it in callba…
avirajsingh7 e827679
Refactor LLM response models and update OpenAIProvider to include con…
avirajsingh7 3b7fe54
Update LLM response handling to use nested response structure and adj…
avirajsingh7 cebc310
precommit
avirajsingh7 9a93cc8
Merge branch 'main' into feature/unified_v1
avirajsingh7 fb4959a
Fix request_data serialization in start_job to use JSON mode
avirajsingh7 6ade78c
resolve comments
avirajsingh7 97de9a8
keep init
avirajsingh7 39e68e5
Enhance ConversationConfig validation and update request_metadata des…
avirajsingh7 365ab0d
Refactor LLM provider registry to use class-based structure and updat…
avirajsingh7 83f7303
Improve error handling in LLM job execution and OpenAI provider; upda…
avirajsingh7 cffbb3d
precommit
avirajsingh7 a16e99d
fix openai sdk version
avirajsingh7 ff80246
dependecies
avirajsingh7 c887c31
Merge remote-tracking branch 'origin/main' into feature/unified_v1
avirajsingh7 12f5300
pre commit
avirajsingh7 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
26 changes: 26 additions & 0 deletions
26
backend/app/alembic/versions/219033c644de_add_llm_im_jobs_table.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| """Add LLM in jobs table | ||
|
|
||
| Revision ID: 219033c644de | ||
| Revises: e7c68e43ce6f | ||
| Create Date: 2025-10-17 15:38:33.565674 | ||
|
|
||
| """ | ||
| from alembic import op | ||
| import sqlalchemy as sa | ||
|
|
||
|
|
||
| # revision identifiers, used by Alembic. | ||
| revision = "219033c644de" | ||
| down_revision = "e7c68e43ce6f" | ||
| branch_labels = None | ||
| depends_on = None | ||
|
|
||
|
|
||
| def upgrade(): | ||
| op.execute("ALTER TYPE jobtype ADD VALUE IF NOT EXISTS 'LLM_API'") | ||
|
|
||
|
|
||
| def downgrade(): | ||
| # Enum value removal requires manual intervention if 'LLM_API' is in use. | ||
| # If rollback is necessary, run SQL manually to recreate the enum without 'LLM_API'. | ||
| pass | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ | |
| documents, | ||
| doc_transformation_job, | ||
| login, | ||
| llm, | ||
| organization, | ||
| openai_conversation, | ||
| project, | ||
|
|
@@ -31,6 +32,7 @@ | |
| api_router.include_router(credentials.router) | ||
| api_router.include_router(documents.router) | ||
| api_router.include_router(doc_transformation_job.router) | ||
| api_router.include_router(llm.router) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only curious if keeping the router name as "llm" could be confusing possibly, since we are taking care of the details of the service that an llm provider is providing, and exactly doing anything to the llm itself |
||
| api_router.include_router(login.router) | ||
| api_router.include_router(onboarding.router) | ||
| api_router.include_router(openai_conversation.router) | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| import logging | ||
|
|
||
| from fastapi import APIRouter | ||
|
|
||
| from app.api.deps import AuthContextDep, SessionDep | ||
| from app.models import LLMCallRequest, LLMCallResponse, Message | ||
| from app.services.llm.jobs import start_job | ||
| from app.utils import APIResponse | ||
|
|
||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| router = APIRouter(tags=["LLM"]) | ||
| llm_callback_router = APIRouter() | ||
|
|
||
|
|
||
| @llm_callback_router.post( | ||
| "{$callback_url}", | ||
| name="llm_callback", | ||
| ) | ||
| def llm_callback_notification(body: APIResponse[LLMCallResponse]): | ||
avirajsingh7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| """ | ||
| Callback endpoint specification for LLM call completion. | ||
| The callback will receive: | ||
| - On success: APIResponse with success=True and data containing LLMCallResponse | ||
| - On failure: APIResponse with success=False and error message | ||
| - metadata field will always be included if provided in the request | ||
| """ | ||
| ... | ||
|
|
||
|
|
||
| @router.post( | ||
| "/llm/call", | ||
| response_model=APIResponse[Message], | ||
| callbacks=llm_callback_router.routes, | ||
| ) | ||
| async def llm_call( | ||
| _current_user: AuthContextDep, _session: SessionDep, request: LLMCallRequest | ||
| ): | ||
| """ | ||
| Endpoint to initiate an LLM call as a background job. | ||
| """ | ||
| project_id = _current_user.project.id | ||
| organization_id = _current_user.organization.id | ||
|
|
||
| start_job( | ||
| db=_session, | ||
| request=request, | ||
| project_id=project_id, | ||
| organization_id=organization_id, | ||
| ) | ||
|
|
||
| return APIResponse.success_response( | ||
| data=Message( | ||
| message=f"Your response is being generated and will be delivered via callback." | ||
| ), | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| from app.models.llm.request import LLMCallRequest, CompletionConfig, QueryParams | ||
| from app.models.llm.response import LLMCallResponse, LLMResponse, LLMOutput, Usage |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| from typing import Any, Literal | ||
|
|
||
| from sqlmodel import Field, SQLModel | ||
| from pydantic import model_validator, HttpUrl | ||
|
|
||
|
|
||
| class ConversationConfig(SQLModel): | ||
| id: str | None = Field( | ||
| default=None, | ||
| description=( | ||
| "Identifier for an existing conversation. " | ||
| "Used to retrieve the previous message context and continue the chat. " | ||
| "If not provided and `auto_create` is True, a new conversation will be created." | ||
| ), | ||
| ) | ||
| auto_create: bool = Field( | ||
| default=False, | ||
| description=( | ||
| "Only if True and no `id` is provided, a new conversation will be created automatically." | ||
| ), | ||
| ) | ||
|
|
||
| @model_validator(mode="after") | ||
| def validate_conversation_logic(self): | ||
avirajsingh7 marked this conversation as resolved.
Show resolved
Hide resolved
nishika26 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if self.id and self.auto_create: | ||
| raise ValueError( | ||
| "Cannot specify both 'id' and 'auto_create=True'. " | ||
| "Use 'id' to continue an existing conversation, or set 'auto_create=True' to create a new one." | ||
| ) | ||
| return self | ||
|
|
||
|
|
||
| # Query Parameters (dynamic per request) | ||
| class QueryParams(SQLModel): | ||
| """Query-specific parameters for each LLM call.""" | ||
|
|
||
| input: str = Field( | ||
| ..., | ||
| min_length=1, | ||
| description="User input question/query/prompt, used to generate a response.", | ||
| ) | ||
| conversation: ConversationConfig | None = Field( | ||
| default=None, | ||
| description="Conversation control configuration for context handling.", | ||
| ) | ||
|
|
||
|
|
||
| class CompletionConfig(SQLModel): | ||
| """Completion configuration with provider and parameters.""" | ||
|
|
||
| provider: Literal["openai"] = Field( | ||
| default="openai", description="LLM provider to use" | ||
| ) | ||
| params: dict[str, Any] = Field( | ||
| ..., | ||
| description="Provider-specific parameters (schema varies by provider), should exactly match the provider's endpoint params structure", | ||
| ) | ||
|
|
||
|
|
||
| class LLMCallConfig(SQLModel): | ||
| """Complete configuration for LLM call including all processing stages.""" | ||
|
|
||
| completion: CompletionConfig = Field(..., description="Completion configuration") | ||
| # Future additions: | ||
| # classifier: ClassifierConfig | None = None | ||
| # pre_filter: PreFilterConfig | None = None | ||
|
|
||
|
|
||
| class LLMCallRequest(SQLModel): | ||
| """User-facing API request for LLM completion.""" | ||
|
|
||
| query: QueryParams = Field(..., description="Query-specific parameters") | ||
| config: LLMCallConfig = Field(..., description="Configuration for the LLM call") | ||
| callback_url: HttpUrl | None = Field( | ||
| default=None, description="Webhook URL for async response delivery" | ||
| ) | ||
| include_provider_raw_response: bool = Field( | ||
| default=False, | ||
| description="Whether to include the raw LLM provider response in the output", | ||
| ) | ||
| request_metadata: dict[str, Any] | None = Field( | ||
| default=None, | ||
| description=( | ||
| "Client-provided metadata passed through unchanged in the response. " | ||
| "Use this to correlate responses with requests or track request state. " | ||
| "The exact dictionary provided here will be returned in the response metadata field." | ||
| ), | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| """ | ||
| LLM response models. | ||
|
|
||
| This module contains structured response models for LLM API calls. | ||
| """ | ||
| from sqlmodel import SQLModel, Field | ||
|
|
||
|
|
||
| class Usage(SQLModel): | ||
| input_tokens: int | ||
| output_tokens: int | ||
| total_tokens: int | ||
|
|
||
|
|
||
| class LLMOutput(SQLModel): | ||
| """Standardized output format for LLM responses.""" | ||
|
|
||
| text: str = Field(..., description="Primary text content of the LLM response.") | ||
|
|
||
|
|
||
| class LLMResponse(SQLModel): | ||
| """Normalized response format independent of provider.""" | ||
|
|
||
| provider_response_id: str = Field( | ||
| ..., description="Unique response ID provided by the LLM provider." | ||
| ) | ||
| conversation_id: str | None = Field( | ||
| default=None, description="Conversation or thread ID for context (if any)." | ||
| ) | ||
| provider: str = Field( | ||
avirajsingh7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ..., description="Name of the LLM provider (e.g., openai, anthropic)." | ||
| ) | ||
| model: str = Field( | ||
| ..., description="Model used by the provider (e.g., gpt-4-turbo)." | ||
| ) | ||
| output: LLMOutput = Field( | ||
| ..., | ||
| description="Structured output containing text and optional additional data.", | ||
| ) | ||
|
|
||
|
|
||
| class LLMCallResponse(SQLModel): | ||
| """Top-level response schema for an LLM API call.""" | ||
|
|
||
| response: LLMResponse = Field( | ||
| ..., description="Normalized, structured LLM response." | ||
| ) | ||
| usage: Usage = Field(..., description="Token usage and cost information.") | ||
| provider_raw_response: dict[str, object] | None = Field( | ||
| default=None, | ||
| description="Unmodified raw response from the LLM provider.", | ||
| ) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Providers | ||
| from app.services.llm.providers import ( | ||
| BaseProvider, | ||
| OpenAIProvider, | ||
| ) | ||
| from app.services.llm.providers import ( | ||
| LLMProvider, | ||
| get_llm_provider, | ||
| ) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.