feat: add AskSage LLM provider integration
Implement AskSageAdapter using the official asksageclient SDK to support AskSage as an LLM provider option. This enables users to leverage AskSage's API with configurable email, API key, and model settings. - Add AskSageAdapter class with async support via thread pool - Update Settings to include asksage_email, asksage_api_key, asksage_model - Extend llm_mode literal to include "asksage" option - Update dependency injection to instantiate AskSageAdapter when configured - Remove completed OPENAI_INTEGRATION_PLAN.md - Update requirements.txt with full dependency list including asksageclient
This commit is contained in:
+6
-1
@@ -6,7 +6,7 @@ from pydantic_settings import BaseSettings
|
||||
class Settings(BaseSettings):
|
||||
"""Application configuration loaded from environment variables."""
|
||||
|
||||
llm_mode: Literal["local", "remote", "openai"] = "local"
|
||||
llm_mode: Literal["local", "remote", "openai", "asksage"] = "local"
|
||||
llm_remote_url: str = ""
|
||||
llm_remote_token: str = ""
|
||||
|
||||
@@ -14,6 +14,11 @@ class Settings(BaseSettings):
|
||||
openai_api_key: str = ""
|
||||
openai_model: str = "gpt-4o-mini"
|
||||
|
||||
# AskSage configuration
|
||||
asksage_email: str = ""
|
||||
asksage_api_key: str = ""
|
||||
asksage_model: str = "gpt-4o"
|
||||
|
||||
class Config:
|
||||
env_file = ".env"
|
||||
env_file_encoding = "utf-8"
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
"""LLM adapter implementations with FastAPI dependency injection support."""
|
||||
|
||||
import asyncio
|
||||
from abc import ABC, abstractmethod
|
||||
from functools import lru_cache
|
||||
from typing import Annotated
|
||||
|
||||
import httpx
|
||||
from asksageclient import AskSageClient
|
||||
from fastapi import Depends
|
||||
from openai import (
|
||||
AsyncOpenAI,
|
||||
@@ -182,6 +184,63 @@ class OpenAIAdapter(LLMAdapter):
|
||||
)
|
||||
|
||||
|
||||
class AskSageAdapter(LLMAdapter):
|
||||
"""AskSage API adapter using the official asksageclient SDK."""
|
||||
|
||||
def __init__(self, email: str, api_key: str, model: str = "gpt-4o"):
|
||||
"""Initialize the AskSage adapter.
|
||||
|
||||
Args:
|
||||
email: AskSage account email
|
||||
api_key: AskSage API key
|
||||
model: Model identifier (e.g., "gpt-4o", "claude-3-opus")
|
||||
"""
|
||||
self.client = AskSageClient(email=email, api_key=api_key)
|
||||
self.model = model
|
||||
|
||||
async def generate(self, conversation_id: str, message: str) -> str:
|
||||
"""Generate a response using the AskSage API.
|
||||
|
||||
Args:
|
||||
conversation_id: The conversation identifier (for future use)
|
||||
message: The user's message
|
||||
|
||||
Returns:
|
||||
The generated response string
|
||||
|
||||
Raises:
|
||||
LLMAuthenticationError: If credentials are invalid
|
||||
LLMConnectionError: If connection fails
|
||||
LLMResponseError: If response content is empty or invalid
|
||||
LLMError: For other API errors
|
||||
"""
|
||||
try:
|
||||
# AskSageClient is synchronous, run in thread pool to avoid blocking
|
||||
response = await asyncio.to_thread(
|
||||
self.client.query,
|
||||
message=message,
|
||||
model=self.model,
|
||||
)
|
||||
|
||||
if not isinstance(response, dict):
|
||||
raise LLMResponseError("AskSage returned invalid response format")
|
||||
|
||||
content = response.get("response")
|
||||
if content is None:
|
||||
raise LLMResponseError("AskSage returned empty response content")
|
||||
return content
|
||||
|
||||
except LLMError:
|
||||
raise
|
||||
except Exception as e:
|
||||
error_msg = str(e).lower()
|
||||
if "auth" in error_msg or "401" in error_msg or "unauthorized" in error_msg:
|
||||
raise LLMAuthenticationError(f"AskSage authentication failed: {e}")
|
||||
if "connect" in error_msg or "timeout" in error_msg:
|
||||
raise LLMConnectionError(f"Could not connect to AskSage: {e}")
|
||||
raise LLMError(f"AskSage API error: {e}")
|
||||
|
||||
|
||||
# --- Dependency Injection Support ---
|
||||
|
||||
|
||||
@@ -225,6 +284,17 @@ def get_adapter(settings: Annotated[Settings, Depends(get_settings)]) -> LLMAdap
|
||||
token=settings.llm_remote_token or None,
|
||||
)
|
||||
|
||||
if settings.llm_mode == "asksage":
|
||||
if not settings.asksage_email or not settings.asksage_api_key:
|
||||
raise LLMConfigurationError(
|
||||
"ASKSAGE_EMAIL and ASKSAGE_API_KEY must be set when LLM_MODE is 'asksage'"
|
||||
)
|
||||
return AskSageAdapter(
|
||||
email=settings.asksage_email,
|
||||
api_key=settings.asksage_api_key,
|
||||
model=settings.asksage_model,
|
||||
)
|
||||
|
||||
return LocalAdapter()
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ class ChatResponse(BaseModel):
|
||||
|
||||
conversation_id: str = Field(..., description="Conversation ID (generated if not provided)")
|
||||
response: str = Field(..., description="The LLM's response")
|
||||
mode: Literal["local", "remote", "openai"] = Field(..., description="Which adapter was used")
|
||||
mode: Literal["local", "remote", "openai", "asksage"] = Field(..., description="Which adapter was used")
|
||||
sources: list = Field(default_factory=list, description="Source references (empty for now)")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user