diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/llm/deepseek.py b/ai-hub/app/core/providers/llm/deepseek.py new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/ai-hub/app/core/providers/llm/deepseek.py @@ -0,0 +1,22 @@ +import logging +from openai import AsyncOpenAI # Use AsyncOpenAI +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class DeepSeekProvider(LLMProvider): + """Provider for the DeepSeek API.""" + def __init__(self, model_name: str, client: AsyncOpenAI): # Type hint with AsyncOpenAI + self.model = model_name + self._client = client + + async def generate_response(self, prompt: str) -> str: + messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] + try: + # This await is now correct for AsyncOpenAI + chat_completion = await self._client.chat.completions.create(model=self.model, messages=messages) + return chat_completion.choices[0].message.content + except Exception as e: + logging.error("DeepSeek Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/llm/deepseek.py b/ai-hub/app/core/providers/llm/deepseek.py new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/ai-hub/app/core/providers/llm/deepseek.py @@ -0,0 +1,22 @@ +import logging +from openai import AsyncOpenAI # Use AsyncOpenAI +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class DeepSeekProvider(LLMProvider): + """Provider for the DeepSeek API.""" + def __init__(self, model_name: str, client: AsyncOpenAI): # Type hint with AsyncOpenAI + self.model = model_name + self._client = client + + async def generate_response(self, prompt: str) -> str: + messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] + try: + # This await is now correct for AsyncOpenAI + chat_completion = await self._client.chat.completions.create(model=self.model, messages=messages) + return chat_completion.choices[0].message.content + except Exception as e: + logging.error("DeepSeek Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/gemini.py b/ai-hub/app/core/providers/llm/gemini.py new file mode 100644 index 0000000..ecbba9c --- /dev/null +++ b/ai-hub/app/core/providers/llm/gemini.py @@ -0,0 +1,26 @@ +import httpx +import logging +import json +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class GeminiProvider(LLMProvider): + """Provider for the Google Gemini API.""" + def __init__(self, api_url: str): + self.url = api_url + + async def generate_response(self, prompt: str) -> str: + payload = {"contents": [{"parts": [{"text": prompt}]}]} + headers = {"Content-Type": "application/json"} + try: + async with httpx.AsyncClient() as client: + response = await client.post(self.url, json=payload, headers=headers) + response.raise_for_status() + # Await the async `json` method + data = response.json() + return data['candidates'][0]['content']['parts'][0]['text'] + except Exception as e: + logging.error("Gemini Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/llm/deepseek.py b/ai-hub/app/core/providers/llm/deepseek.py new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/ai-hub/app/core/providers/llm/deepseek.py @@ -0,0 +1,22 @@ +import logging +from openai import AsyncOpenAI # Use AsyncOpenAI +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class DeepSeekProvider(LLMProvider): + """Provider for the DeepSeek API.""" + def __init__(self, model_name: str, client: AsyncOpenAI): # Type hint with AsyncOpenAI + self.model = model_name + self._client = client + + async def generate_response(self, prompt: str) -> str: + messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] + try: + # This await is now correct for AsyncOpenAI + chat_completion = await self._client.chat.completions.create(model=self.model, messages=messages) + return chat_completion.choices[0].message.content + except Exception as e: + logging.error("DeepSeek Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/gemini.py b/ai-hub/app/core/providers/llm/gemini.py new file mode 100644 index 0000000..ecbba9c --- /dev/null +++ b/ai-hub/app/core/providers/llm/gemini.py @@ -0,0 +1,26 @@ +import httpx +import logging +import json +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class GeminiProvider(LLMProvider): + """Provider for the Google Gemini API.""" + def __init__(self, api_url: str): + self.url = api_url + + async def generate_response(self, prompt: str) -> str: + payload = {"contents": [{"parts": [{"text": prompt}]}]} + headers = {"Content-Type": "application/json"} + try: + async with httpx.AsyncClient() as client: + response = await client.post(self.url, json=payload, headers=headers) + response.raise_for_status() + # Await the async `json` method + data = response.json() + return data['candidates'][0]['content']['parts'][0]['text'] + except Exception as e: + logging.error("Gemini Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/stt/__init__.py b/ai-hub/app/core/providers/stt/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/stt/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/llm/deepseek.py b/ai-hub/app/core/providers/llm/deepseek.py new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/ai-hub/app/core/providers/llm/deepseek.py @@ -0,0 +1,22 @@ +import logging +from openai import AsyncOpenAI # Use AsyncOpenAI +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class DeepSeekProvider(LLMProvider): + """Provider for the DeepSeek API.""" + def __init__(self, model_name: str, client: AsyncOpenAI): # Type hint with AsyncOpenAI + self.model = model_name + self._client = client + + async def generate_response(self, prompt: str) -> str: + messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] + try: + # This await is now correct for AsyncOpenAI + chat_completion = await self._client.chat.completions.create(model=self.model, messages=messages) + return chat_completion.choices[0].message.content + except Exception as e: + logging.error("DeepSeek Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/gemini.py b/ai-hub/app/core/providers/llm/gemini.py new file mode 100644 index 0000000..ecbba9c --- /dev/null +++ b/ai-hub/app/core/providers/llm/gemini.py @@ -0,0 +1,26 @@ +import httpx +import logging +import json +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class GeminiProvider(LLMProvider): + """Provider for the Google Gemini API.""" + def __init__(self, api_url: str): + self.url = api_url + + async def generate_response(self, prompt: str) -> str: + payload = {"contents": [{"parts": [{"text": prompt}]}]} + headers = {"Content-Type": "application/json"} + try: + async with httpx.AsyncClient() as client: + response = await client.post(self.url, json=payload, headers=headers) + response.raise_for_status() + # Await the async `json` method + data = response.json() + return data['candidates'][0]['content']['parts'][0]['text'] + except Exception as e: + logging.error("Gemini Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/stt/__init__.py b/ai-hub/app/core/providers/stt/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/stt/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/tts/__init__.py b/ai-hub/app/core/providers/tts/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/tts/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/llm/deepseek.py b/ai-hub/app/core/providers/llm/deepseek.py new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/ai-hub/app/core/providers/llm/deepseek.py @@ -0,0 +1,22 @@ +import logging +from openai import AsyncOpenAI # Use AsyncOpenAI +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class DeepSeekProvider(LLMProvider): + """Provider for the DeepSeek API.""" + def __init__(self, model_name: str, client: AsyncOpenAI): # Type hint with AsyncOpenAI + self.model = model_name + self._client = client + + async def generate_response(self, prompt: str) -> str: + messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] + try: + # This await is now correct for AsyncOpenAI + chat_completion = await self._client.chat.completions.create(model=self.model, messages=messages) + return chat_completion.choices[0].message.content + except Exception as e: + logging.error("DeepSeek Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/gemini.py b/ai-hub/app/core/providers/llm/gemini.py new file mode 100644 index 0000000..ecbba9c --- /dev/null +++ b/ai-hub/app/core/providers/llm/gemini.py @@ -0,0 +1,26 @@ +import httpx +import logging +import json +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class GeminiProvider(LLMProvider): + """Provider for the Google Gemini API.""" + def __init__(self, api_url: str): + self.url = api_url + + async def generate_response(self, prompt: str) -> str: + payload = {"contents": [{"parts": [{"text": prompt}]}]} + headers = {"Content-Type": "application/json"} + try: + async with httpx.AsyncClient() as client: + response = await client.post(self.url, json=payload, headers=headers) + response.raise_for_status() + # Await the async `json` method + data = response.json() + return data['candidates'][0]['content']['parts'][0]['text'] + except Exception as e: + logging.error("Gemini Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/stt/__init__.py b/ai-hub/app/core/providers/stt/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/stt/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/tts/__init__.py b/ai-hub/app/core/providers/tts/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/tts/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/retrievers/remote_retriever.py b/ai-hub/app/core/retrievers/remote_retriever.py new file mode 100644 index 0000000..2b08885 --- /dev/null +++ b/ai-hub/app/core/retrievers/remote_retriever.py @@ -0,0 +1,42 @@ +from typing import List +import requests +from sqlalchemy.orm import Session +from app.core.retrievers.base_retriever import Retriever + +class RemoteRetriever(Retriever): + """ + A retriever that fetches context from a single remote backend server. + """ + def __init__(self, server_url: str): + """ + Initializes the retriever with the remote server's URL. + + Args: + server_url (str): The URL of the remote backend server. + """ + self.server_url = server_url + self.session = requests.Session() + + def retrieve_context(self, query: str, db: Session) -> List[str]: + """ + Fetches context for a given query from the remote server. + + Args: + query (str): The user's query string. + db (Session): The database session (unused, but required by the + abstract base class). + + Returns: + List[str]: A list of text strings representing the retrieved context. + """ + try: + # Send the query to the remote server + response = self.session.post(self.server_url, json={'query': query}) + response.raise_for_status() # Raise an exception for bad status codes + + # Assuming the server returns a JSON object with a 'context' key + server_context = response.json().get('context', []) + return server_context + except requests.exceptions.RequestException as e: + print(f"Error connecting to the server at {self.server_url}: {e}") + return [] \ No newline at end of file diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/llm/deepseek.py b/ai-hub/app/core/providers/llm/deepseek.py new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/ai-hub/app/core/providers/llm/deepseek.py @@ -0,0 +1,22 @@ +import logging +from openai import AsyncOpenAI # Use AsyncOpenAI +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class DeepSeekProvider(LLMProvider): + """Provider for the DeepSeek API.""" + def __init__(self, model_name: str, client: AsyncOpenAI): # Type hint with AsyncOpenAI + self.model = model_name + self._client = client + + async def generate_response(self, prompt: str) -> str: + messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] + try: + # This await is now correct for AsyncOpenAI + chat_completion = await self._client.chat.completions.create(model=self.model, messages=messages) + return chat_completion.choices[0].message.content + except Exception as e: + logging.error("DeepSeek Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/gemini.py b/ai-hub/app/core/providers/llm/gemini.py new file mode 100644 index 0000000..ecbba9c --- /dev/null +++ b/ai-hub/app/core/providers/llm/gemini.py @@ -0,0 +1,26 @@ +import httpx +import logging +import json +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class GeminiProvider(LLMProvider): + """Provider for the Google Gemini API.""" + def __init__(self, api_url: str): + self.url = api_url + + async def generate_response(self, prompt: str) -> str: + payload = {"contents": [{"parts": [{"text": prompt}]}]} + headers = {"Content-Type": "application/json"} + try: + async with httpx.AsyncClient() as client: + response = await client.post(self.url, json=payload, headers=headers) + response.raise_for_status() + # Await the async `json` method + data = response.json() + return data['candidates'][0]['content']['parts'][0]['text'] + except Exception as e: + logging.error("Gemini Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/stt/__init__.py b/ai-hub/app/core/providers/stt/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/stt/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/tts/__init__.py b/ai-hub/app/core/providers/tts/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/tts/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/retrievers/remote_retriever.py b/ai-hub/app/core/retrievers/remote_retriever.py new file mode 100644 index 0000000..2b08885 --- /dev/null +++ b/ai-hub/app/core/retrievers/remote_retriever.py @@ -0,0 +1,42 @@ +from typing import List +import requests +from sqlalchemy.orm import Session +from app.core.retrievers.base_retriever import Retriever + +class RemoteRetriever(Retriever): + """ + A retriever that fetches context from a single remote backend server. + """ + def __init__(self, server_url: str): + """ + Initializes the retriever with the remote server's URL. + + Args: + server_url (str): The URL of the remote backend server. + """ + self.server_url = server_url + self.session = requests.Session() + + def retrieve_context(self, query: str, db: Session) -> List[str]: + """ + Fetches context for a given query from the remote server. + + Args: + query (str): The user's query string. + db (Session): The database session (unused, but required by the + abstract base class). + + Returns: + List[str]: A list of text strings representing the retrieved context. + """ + try: + # Send the query to the remote server + response = self.session.post(self.server_url, json={'query': query}) + response.raise_for_status() # Raise an exception for bad status codes + + # Assuming the server returns a JSON object with a 'context' key + server_context = response.json().get('context', []) + return server_context + except requests.exceptions.RequestException as e: + print(f"Error connecting to the server at {self.server_url}: {e}") + return [] \ No newline at end of file diff --git a/ai-hub/app/core/services/rag.py b/ai-hub/app/core/services/rag.py index 517278d..e3973da 100644 --- a/ai-hub/app/core/services/rag.py +++ b/ai-hub/app/core/services/rag.py @@ -8,7 +8,7 @@ from app.db import models from app.core.retrievers.faiss_db_retriever import FaissDBRetriever from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import get_llm_provider +from app.core.providers.factory import get_llm_provider from app.core.pipelines.dspy_rag import DSPyLLMProvider, DspyRagPipeline class RAGService: diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/llm/deepseek.py b/ai-hub/app/core/providers/llm/deepseek.py new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/ai-hub/app/core/providers/llm/deepseek.py @@ -0,0 +1,22 @@ +import logging +from openai import AsyncOpenAI # Use AsyncOpenAI +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class DeepSeekProvider(LLMProvider): + """Provider for the DeepSeek API.""" + def __init__(self, model_name: str, client: AsyncOpenAI): # Type hint with AsyncOpenAI + self.model = model_name + self._client = client + + async def generate_response(self, prompt: str) -> str: + messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] + try: + # This await is now correct for AsyncOpenAI + chat_completion = await self._client.chat.completions.create(model=self.model, messages=messages) + return chat_completion.choices[0].message.content + except Exception as e: + logging.error("DeepSeek Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/gemini.py b/ai-hub/app/core/providers/llm/gemini.py new file mode 100644 index 0000000..ecbba9c --- /dev/null +++ b/ai-hub/app/core/providers/llm/gemini.py @@ -0,0 +1,26 @@ +import httpx +import logging +import json +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class GeminiProvider(LLMProvider): + """Provider for the Google Gemini API.""" + def __init__(self, api_url: str): + self.url = api_url + + async def generate_response(self, prompt: str) -> str: + payload = {"contents": [{"parts": [{"text": prompt}]}]} + headers = {"Content-Type": "application/json"} + try: + async with httpx.AsyncClient() as client: + response = await client.post(self.url, json=payload, headers=headers) + response.raise_for_status() + # Await the async `json` method + data = response.json() + return data['candidates'][0]['content']['parts'][0]['text'] + except Exception as e: + logging.error("Gemini Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/stt/__init__.py b/ai-hub/app/core/providers/stt/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/stt/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/tts/__init__.py b/ai-hub/app/core/providers/tts/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/tts/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/retrievers/remote_retriever.py b/ai-hub/app/core/retrievers/remote_retriever.py new file mode 100644 index 0000000..2b08885 --- /dev/null +++ b/ai-hub/app/core/retrievers/remote_retriever.py @@ -0,0 +1,42 @@ +from typing import List +import requests +from sqlalchemy.orm import Session +from app.core.retrievers.base_retriever import Retriever + +class RemoteRetriever(Retriever): + """ + A retriever that fetches context from a single remote backend server. + """ + def __init__(self, server_url: str): + """ + Initializes the retriever with the remote server's URL. + + Args: + server_url (str): The URL of the remote backend server. + """ + self.server_url = server_url + self.session = requests.Session() + + def retrieve_context(self, query: str, db: Session) -> List[str]: + """ + Fetches context for a given query from the remote server. + + Args: + query (str): The user's query string. + db (Session): The database session (unused, but required by the + abstract base class). + + Returns: + List[str]: A list of text strings representing the retrieved context. + """ + try: + # Send the query to the remote server + response = self.session.post(self.server_url, json={'query': query}) + response.raise_for_status() # Raise an exception for bad status codes + + # Assuming the server returns a JSON object with a 'context' key + server_context = response.json().get('context', []) + return server_context + except requests.exceptions.RequestException as e: + print(f"Error connecting to the server at {self.server_url}: {e}") + return [] \ No newline at end of file diff --git a/ai-hub/app/core/services/rag.py b/ai-hub/app/core/services/rag.py index 517278d..e3973da 100644 --- a/ai-hub/app/core/services/rag.py +++ b/ai-hub/app/core/services/rag.py @@ -8,7 +8,7 @@ from app.db import models from app.core.retrievers.faiss_db_retriever import FaissDBRetriever from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import get_llm_provider +from app.core.providers.factory import get_llm_provider from app.core.pipelines.dspy_rag import DSPyLLMProvider, DspyRagPipeline class RAGService: diff --git a/ai-hub/requirements.txt b/ai-hub/requirements.txt index 62e3168..4be17cd 100644 --- a/ai-hub/requirements.txt +++ b/ai-hub/requirements.txt @@ -11,7 +11,7 @@ pytest-asyncio pytest-tornasync pytest-trio +pytest-mock numpy faiss-cpu -dspy -dspy-ai \ No newline at end of file +dspy \ No newline at end of file diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/llm/deepseek.py b/ai-hub/app/core/providers/llm/deepseek.py new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/ai-hub/app/core/providers/llm/deepseek.py @@ -0,0 +1,22 @@ +import logging +from openai import AsyncOpenAI # Use AsyncOpenAI +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class DeepSeekProvider(LLMProvider): + """Provider for the DeepSeek API.""" + def __init__(self, model_name: str, client: AsyncOpenAI): # Type hint with AsyncOpenAI + self.model = model_name + self._client = client + + async def generate_response(self, prompt: str) -> str: + messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] + try: + # This await is now correct for AsyncOpenAI + chat_completion = await self._client.chat.completions.create(model=self.model, messages=messages) + return chat_completion.choices[0].message.content + except Exception as e: + logging.error("DeepSeek Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/gemini.py b/ai-hub/app/core/providers/llm/gemini.py new file mode 100644 index 0000000..ecbba9c --- /dev/null +++ b/ai-hub/app/core/providers/llm/gemini.py @@ -0,0 +1,26 @@ +import httpx +import logging +import json +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class GeminiProvider(LLMProvider): + """Provider for the Google Gemini API.""" + def __init__(self, api_url: str): + self.url = api_url + + async def generate_response(self, prompt: str) -> str: + payload = {"contents": [{"parts": [{"text": prompt}]}]} + headers = {"Content-Type": "application/json"} + try: + async with httpx.AsyncClient() as client: + response = await client.post(self.url, json=payload, headers=headers) + response.raise_for_status() + # Await the async `json` method + data = response.json() + return data['candidates'][0]['content']['parts'][0]['text'] + except Exception as e: + logging.error("Gemini Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/stt/__init__.py b/ai-hub/app/core/providers/stt/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/stt/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/tts/__init__.py b/ai-hub/app/core/providers/tts/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/tts/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/retrievers/remote_retriever.py b/ai-hub/app/core/retrievers/remote_retriever.py new file mode 100644 index 0000000..2b08885 --- /dev/null +++ b/ai-hub/app/core/retrievers/remote_retriever.py @@ -0,0 +1,42 @@ +from typing import List +import requests +from sqlalchemy.orm import Session +from app.core.retrievers.base_retriever import Retriever + +class RemoteRetriever(Retriever): + """ + A retriever that fetches context from a single remote backend server. + """ + def __init__(self, server_url: str): + """ + Initializes the retriever with the remote server's URL. + + Args: + server_url (str): The URL of the remote backend server. + """ + self.server_url = server_url + self.session = requests.Session() + + def retrieve_context(self, query: str, db: Session) -> List[str]: + """ + Fetches context for a given query from the remote server. + + Args: + query (str): The user's query string. + db (Session): The database session (unused, but required by the + abstract base class). + + Returns: + List[str]: A list of text strings representing the retrieved context. + """ + try: + # Send the query to the remote server + response = self.session.post(self.server_url, json={'query': query}) + response.raise_for_status() # Raise an exception for bad status codes + + # Assuming the server returns a JSON object with a 'context' key + server_context = response.json().get('context', []) + return server_context + except requests.exceptions.RequestException as e: + print(f"Error connecting to the server at {self.server_url}: {e}") + return [] \ No newline at end of file diff --git a/ai-hub/app/core/services/rag.py b/ai-hub/app/core/services/rag.py index 517278d..e3973da 100644 --- a/ai-hub/app/core/services/rag.py +++ b/ai-hub/app/core/services/rag.py @@ -8,7 +8,7 @@ from app.db import models from app.core.retrievers.faiss_db_retriever import FaissDBRetriever from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import get_llm_provider +from app.core.providers.factory import get_llm_provider from app.core.pipelines.dspy_rag import DSPyLLMProvider, DspyRagPipeline class RAGService: diff --git a/ai-hub/requirements.txt b/ai-hub/requirements.txt index 62e3168..4be17cd 100644 --- a/ai-hub/requirements.txt +++ b/ai-hub/requirements.txt @@ -11,7 +11,7 @@ pytest-asyncio pytest-tornasync pytest-trio +pytest-mock numpy faiss-cpu -dspy -dspy-ai \ No newline at end of file +dspy \ No newline at end of file diff --git a/ai-hub/tests/core/providers/llm/test_llm_providers.py b/ai-hub/tests/core/providers/llm/test_llm_providers.py new file mode 100644 index 0000000..38ef108 --- /dev/null +++ b/ai-hub/tests/core/providers/llm/test_llm_providers.py @@ -0,0 +1,81 @@ +import pytest +from unittest.mock import AsyncMock, patch, MagicMock + +from app.core.providers.base import LLMProvider +from app.core.providers.llm.deepseek import DeepSeekProvider +from app.core.providers.llm.gemini import GeminiProvider +from openai import OpenAI +import httpx + +# --- Fixtures --- + +@pytest.fixture +def mock_deepseek_client(): + """Provides a mock OpenAI client with a mocked chat.completions.create method.""" + mock_client = MagicMock(spec=OpenAI) + + mock_response = MagicMock() + mock_response.choices = [MagicMock()] + mock_response.choices[0].message.content = "This is a mocked DeepSeek response." + + mock_client.chat.completions.create = AsyncMock(return_value=mock_response) + + return mock_client + +# --- DeepSeek Provider Tests --- + +@pytest.mark.asyncio +async def test_deepseek_provider_generates_response(mock_deepseek_client): + """Tests that DeepSeekProvider returns the expected response.""" + provider = DeepSeekProvider(model_name="deepseek-chat", client=mock_deepseek_client) + + assert isinstance(provider, LLMProvider) + + response = await provider.generate_response("Test prompt") + + mock_deepseek_client.chat.completions.create.assert_awaited_with( + model="deepseek-chat", + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Test prompt"} + ] + ) + assert response == "This is a mocked DeepSeek response." + +# --- Gemini Provider Tests --- +@pytest.mark.asyncio +async def test_gemini_provider_generates_response(mocker): + """Tests that GeminiProvider returns the expected response.""" + # Mock the response object from the post call + mock_response = MagicMock() + mock_response.raise_for_status.return_value = None + mock_response.json = MagicMock(return_value={ + 'candidates': [{'content': {'parts': [{'text': 'This is a mocked Gemini response.'}]}}] + }) + + # Create a mock for the AsyncClient instance + mock_client_instance = MagicMock(spec=httpx.AsyncClient) + mock_client_instance.post = AsyncMock(return_value=mock_response) + + # Create a mock for the AsyncClient class itself + mock_async_client_class = MagicMock(spec=httpx.AsyncClient) + + # Mock the behavior of `async with` + mock_async_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client_instance) + mock_async_client_class.return_value.__aexit__ = AsyncMock(return_value=None) + + # Patch the AsyncClient class + mocker.patch('httpx.AsyncClient', new=mock_async_client_class) + + provider = GeminiProvider(api_url="http://mock-gemini.com") + assert isinstance(provider, LLMProvider) + + response = await provider.generate_response("Test prompt") + + mock_client_instance.post.assert_awaited_with( + "http://mock-gemini.com", + json={"contents": [{"parts": [{"text": "Test prompt"}]}]}, + headers={"Content-Type": "application/json"} + ) + + assert response == "This is a mocked Gemini response." diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/llm/deepseek.py b/ai-hub/app/core/providers/llm/deepseek.py new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/ai-hub/app/core/providers/llm/deepseek.py @@ -0,0 +1,22 @@ +import logging +from openai import AsyncOpenAI # Use AsyncOpenAI +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class DeepSeekProvider(LLMProvider): + """Provider for the DeepSeek API.""" + def __init__(self, model_name: str, client: AsyncOpenAI): # Type hint with AsyncOpenAI + self.model = model_name + self._client = client + + async def generate_response(self, prompt: str) -> str: + messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] + try: + # This await is now correct for AsyncOpenAI + chat_completion = await self._client.chat.completions.create(model=self.model, messages=messages) + return chat_completion.choices[0].message.content + except Exception as e: + logging.error("DeepSeek Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/gemini.py b/ai-hub/app/core/providers/llm/gemini.py new file mode 100644 index 0000000..ecbba9c --- /dev/null +++ b/ai-hub/app/core/providers/llm/gemini.py @@ -0,0 +1,26 @@ +import httpx +import logging +import json +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class GeminiProvider(LLMProvider): + """Provider for the Google Gemini API.""" + def __init__(self, api_url: str): + self.url = api_url + + async def generate_response(self, prompt: str) -> str: + payload = {"contents": [{"parts": [{"text": prompt}]}]} + headers = {"Content-Type": "application/json"} + try: + async with httpx.AsyncClient() as client: + response = await client.post(self.url, json=payload, headers=headers) + response.raise_for_status() + # Await the async `json` method + data = response.json() + return data['candidates'][0]['content']['parts'][0]['text'] + except Exception as e: + logging.error("Gemini Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/stt/__init__.py b/ai-hub/app/core/providers/stt/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/stt/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/tts/__init__.py b/ai-hub/app/core/providers/tts/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/tts/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/retrievers/remote_retriever.py b/ai-hub/app/core/retrievers/remote_retriever.py new file mode 100644 index 0000000..2b08885 --- /dev/null +++ b/ai-hub/app/core/retrievers/remote_retriever.py @@ -0,0 +1,42 @@ +from typing import List +import requests +from sqlalchemy.orm import Session +from app.core.retrievers.base_retriever import Retriever + +class RemoteRetriever(Retriever): + """ + A retriever that fetches context from a single remote backend server. + """ + def __init__(self, server_url: str): + """ + Initializes the retriever with the remote server's URL. + + Args: + server_url (str): The URL of the remote backend server. + """ + self.server_url = server_url + self.session = requests.Session() + + def retrieve_context(self, query: str, db: Session) -> List[str]: + """ + Fetches context for a given query from the remote server. + + Args: + query (str): The user's query string. + db (Session): The database session (unused, but required by the + abstract base class). + + Returns: + List[str]: A list of text strings representing the retrieved context. + """ + try: + # Send the query to the remote server + response = self.session.post(self.server_url, json={'query': query}) + response.raise_for_status() # Raise an exception for bad status codes + + # Assuming the server returns a JSON object with a 'context' key + server_context = response.json().get('context', []) + return server_context + except requests.exceptions.RequestException as e: + print(f"Error connecting to the server at {self.server_url}: {e}") + return [] \ No newline at end of file diff --git a/ai-hub/app/core/services/rag.py b/ai-hub/app/core/services/rag.py index 517278d..e3973da 100644 --- a/ai-hub/app/core/services/rag.py +++ b/ai-hub/app/core/services/rag.py @@ -8,7 +8,7 @@ from app.db import models from app.core.retrievers.faiss_db_retriever import FaissDBRetriever from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import get_llm_provider +from app.core.providers.factory import get_llm_provider from app.core.pipelines.dspy_rag import DSPyLLMProvider, DspyRagPipeline class RAGService: diff --git a/ai-hub/requirements.txt b/ai-hub/requirements.txt index 62e3168..4be17cd 100644 --- a/ai-hub/requirements.txt +++ b/ai-hub/requirements.txt @@ -11,7 +11,7 @@ pytest-asyncio pytest-tornasync pytest-trio +pytest-mock numpy faiss-cpu -dspy -dspy-ai \ No newline at end of file +dspy \ No newline at end of file diff --git a/ai-hub/tests/core/providers/llm/test_llm_providers.py b/ai-hub/tests/core/providers/llm/test_llm_providers.py new file mode 100644 index 0000000..38ef108 --- /dev/null +++ b/ai-hub/tests/core/providers/llm/test_llm_providers.py @@ -0,0 +1,81 @@ +import pytest +from unittest.mock import AsyncMock, patch, MagicMock + +from app.core.providers.base import LLMProvider +from app.core.providers.llm.deepseek import DeepSeekProvider +from app.core.providers.llm.gemini import GeminiProvider +from openai import OpenAI +import httpx + +# --- Fixtures --- + +@pytest.fixture +def mock_deepseek_client(): + """Provides a mock OpenAI client with a mocked chat.completions.create method.""" + mock_client = MagicMock(spec=OpenAI) + + mock_response = MagicMock() + mock_response.choices = [MagicMock()] + mock_response.choices[0].message.content = "This is a mocked DeepSeek response." + + mock_client.chat.completions.create = AsyncMock(return_value=mock_response) + + return mock_client + +# --- DeepSeek Provider Tests --- + +@pytest.mark.asyncio +async def test_deepseek_provider_generates_response(mock_deepseek_client): + """Tests that DeepSeekProvider returns the expected response.""" + provider = DeepSeekProvider(model_name="deepseek-chat", client=mock_deepseek_client) + + assert isinstance(provider, LLMProvider) + + response = await provider.generate_response("Test prompt") + + mock_deepseek_client.chat.completions.create.assert_awaited_with( + model="deepseek-chat", + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Test prompt"} + ] + ) + assert response == "This is a mocked DeepSeek response." + +# --- Gemini Provider Tests --- +@pytest.mark.asyncio +async def test_gemini_provider_generates_response(mocker): + """Tests that GeminiProvider returns the expected response.""" + # Mock the response object from the post call + mock_response = MagicMock() + mock_response.raise_for_status.return_value = None + mock_response.json = MagicMock(return_value={ + 'candidates': [{'content': {'parts': [{'text': 'This is a mocked Gemini response.'}]}}] + }) + + # Create a mock for the AsyncClient instance + mock_client_instance = MagicMock(spec=httpx.AsyncClient) + mock_client_instance.post = AsyncMock(return_value=mock_response) + + # Create a mock for the AsyncClient class itself + mock_async_client_class = MagicMock(spec=httpx.AsyncClient) + + # Mock the behavior of `async with` + mock_async_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client_instance) + mock_async_client_class.return_value.__aexit__ = AsyncMock(return_value=None) + + # Patch the AsyncClient class + mocker.patch('httpx.AsyncClient', new=mock_async_client_class) + + provider = GeminiProvider(api_url="http://mock-gemini.com") + assert isinstance(provider, LLMProvider) + + response = await provider.generate_response("Test prompt") + + mock_client_instance.post.assert_awaited_with( + "http://mock-gemini.com", + json={"contents": [{"parts": [{"text": "Test prompt"}]}]}, + headers={"Content-Type": "application/json"} + ) + + assert response == "This is a mocked Gemini response." diff --git a/ai-hub/tests/core/providers/test_factory.py b/ai-hub/tests/core/providers/test_factory.py new file mode 100644 index 0000000..58d4c85 --- /dev/null +++ b/ai-hub/tests/core/providers/test_factory.py @@ -0,0 +1,19 @@ +import pytest +from app.core.providers.factory import get_llm_provider +from app.core.providers.llm.deepseek import DeepSeekProvider +from app.core.providers.llm.gemini import GeminiProvider + +def test_get_llm_provider_returns_deepseek_provider(): + """Tests that the factory returns a DeepSeekProvider instance.""" + provider = get_llm_provider("deepseek") + assert isinstance(provider, DeepSeekProvider) + +def test_get_llm_provider_returns_gemini_provider(): + """Tests that the factory returns a GeminiProvider instance.""" + provider = get_llm_provider("gemini") + assert isinstance(provider, GeminiProvider) + +def test_get_llm_provider_raises_error_for_unsupported_provider(): + """Tests that the factory raises an error for an unsupported provider name.""" + with pytest.raises(ValueError, match="Unsupported model provider: 'unknown'"): + get_llm_provider("unknown") \ No newline at end of file diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/llm/deepseek.py b/ai-hub/app/core/providers/llm/deepseek.py new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/ai-hub/app/core/providers/llm/deepseek.py @@ -0,0 +1,22 @@ +import logging +from openai import AsyncOpenAI # Use AsyncOpenAI +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class DeepSeekProvider(LLMProvider): + """Provider for the DeepSeek API.""" + def __init__(self, model_name: str, client: AsyncOpenAI): # Type hint with AsyncOpenAI + self.model = model_name + self._client = client + + async def generate_response(self, prompt: str) -> str: + messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] + try: + # This await is now correct for AsyncOpenAI + chat_completion = await self._client.chat.completions.create(model=self.model, messages=messages) + return chat_completion.choices[0].message.content + except Exception as e: + logging.error("DeepSeek Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/gemini.py b/ai-hub/app/core/providers/llm/gemini.py new file mode 100644 index 0000000..ecbba9c --- /dev/null +++ b/ai-hub/app/core/providers/llm/gemini.py @@ -0,0 +1,26 @@ +import httpx +import logging +import json +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class GeminiProvider(LLMProvider): + """Provider for the Google Gemini API.""" + def __init__(self, api_url: str): + self.url = api_url + + async def generate_response(self, prompt: str) -> str: + payload = {"contents": [{"parts": [{"text": prompt}]}]} + headers = {"Content-Type": "application/json"} + try: + async with httpx.AsyncClient() as client: + response = await client.post(self.url, json=payload, headers=headers) + response.raise_for_status() + # Await the async `json` method + data = response.json() + return data['candidates'][0]['content']['parts'][0]['text'] + except Exception as e: + logging.error("Gemini Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/stt/__init__.py b/ai-hub/app/core/providers/stt/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/stt/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/tts/__init__.py b/ai-hub/app/core/providers/tts/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/tts/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/retrievers/remote_retriever.py b/ai-hub/app/core/retrievers/remote_retriever.py new file mode 100644 index 0000000..2b08885 --- /dev/null +++ b/ai-hub/app/core/retrievers/remote_retriever.py @@ -0,0 +1,42 @@ +from typing import List +import requests +from sqlalchemy.orm import Session +from app.core.retrievers.base_retriever import Retriever + +class RemoteRetriever(Retriever): + """ + A retriever that fetches context from a single remote backend server. + """ + def __init__(self, server_url: str): + """ + Initializes the retriever with the remote server's URL. + + Args: + server_url (str): The URL of the remote backend server. + """ + self.server_url = server_url + self.session = requests.Session() + + def retrieve_context(self, query: str, db: Session) -> List[str]: + """ + Fetches context for a given query from the remote server. + + Args: + query (str): The user's query string. + db (Session): The database session (unused, but required by the + abstract base class). + + Returns: + List[str]: A list of text strings representing the retrieved context. + """ + try: + # Send the query to the remote server + response = self.session.post(self.server_url, json={'query': query}) + response.raise_for_status() # Raise an exception for bad status codes + + # Assuming the server returns a JSON object with a 'context' key + server_context = response.json().get('context', []) + return server_context + except requests.exceptions.RequestException as e: + print(f"Error connecting to the server at {self.server_url}: {e}") + return [] \ No newline at end of file diff --git a/ai-hub/app/core/services/rag.py b/ai-hub/app/core/services/rag.py index 517278d..e3973da 100644 --- a/ai-hub/app/core/services/rag.py +++ b/ai-hub/app/core/services/rag.py @@ -8,7 +8,7 @@ from app.db import models from app.core.retrievers.faiss_db_retriever import FaissDBRetriever from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import get_llm_provider +from app.core.providers.factory import get_llm_provider from app.core.pipelines.dspy_rag import DSPyLLMProvider, DspyRagPipeline class RAGService: diff --git a/ai-hub/requirements.txt b/ai-hub/requirements.txt index 62e3168..4be17cd 100644 --- a/ai-hub/requirements.txt +++ b/ai-hub/requirements.txt @@ -11,7 +11,7 @@ pytest-asyncio pytest-tornasync pytest-trio +pytest-mock numpy faiss-cpu -dspy -dspy-ai \ No newline at end of file +dspy \ No newline at end of file diff --git a/ai-hub/tests/core/providers/llm/test_llm_providers.py b/ai-hub/tests/core/providers/llm/test_llm_providers.py new file mode 100644 index 0000000..38ef108 --- /dev/null +++ b/ai-hub/tests/core/providers/llm/test_llm_providers.py @@ -0,0 +1,81 @@ +import pytest +from unittest.mock import AsyncMock, patch, MagicMock + +from app.core.providers.base import LLMProvider +from app.core.providers.llm.deepseek import DeepSeekProvider +from app.core.providers.llm.gemini import GeminiProvider +from openai import OpenAI +import httpx + +# --- Fixtures --- + +@pytest.fixture +def mock_deepseek_client(): + """Provides a mock OpenAI client with a mocked chat.completions.create method.""" + mock_client = MagicMock(spec=OpenAI) + + mock_response = MagicMock() + mock_response.choices = [MagicMock()] + mock_response.choices[0].message.content = "This is a mocked DeepSeek response." + + mock_client.chat.completions.create = AsyncMock(return_value=mock_response) + + return mock_client + +# --- DeepSeek Provider Tests --- + +@pytest.mark.asyncio +async def test_deepseek_provider_generates_response(mock_deepseek_client): + """Tests that DeepSeekProvider returns the expected response.""" + provider = DeepSeekProvider(model_name="deepseek-chat", client=mock_deepseek_client) + + assert isinstance(provider, LLMProvider) + + response = await provider.generate_response("Test prompt") + + mock_deepseek_client.chat.completions.create.assert_awaited_with( + model="deepseek-chat", + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Test prompt"} + ] + ) + assert response == "This is a mocked DeepSeek response." + +# --- Gemini Provider Tests --- +@pytest.mark.asyncio +async def test_gemini_provider_generates_response(mocker): + """Tests that GeminiProvider returns the expected response.""" + # Mock the response object from the post call + mock_response = MagicMock() + mock_response.raise_for_status.return_value = None + mock_response.json = MagicMock(return_value={ + 'candidates': [{'content': {'parts': [{'text': 'This is a mocked Gemini response.'}]}}] + }) + + # Create a mock for the AsyncClient instance + mock_client_instance = MagicMock(spec=httpx.AsyncClient) + mock_client_instance.post = AsyncMock(return_value=mock_response) + + # Create a mock for the AsyncClient class itself + mock_async_client_class = MagicMock(spec=httpx.AsyncClient) + + # Mock the behavior of `async with` + mock_async_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client_instance) + mock_async_client_class.return_value.__aexit__ = AsyncMock(return_value=None) + + # Patch the AsyncClient class + mocker.patch('httpx.AsyncClient', new=mock_async_client_class) + + provider = GeminiProvider(api_url="http://mock-gemini.com") + assert isinstance(provider, LLMProvider) + + response = await provider.generate_response("Test prompt") + + mock_client_instance.post.assert_awaited_with( + "http://mock-gemini.com", + json={"contents": [{"parts": [{"text": "Test prompt"}]}]}, + headers={"Content-Type": "application/json"} + ) + + assert response == "This is a mocked Gemini response." diff --git a/ai-hub/tests/core/providers/test_factory.py b/ai-hub/tests/core/providers/test_factory.py new file mode 100644 index 0000000..58d4c85 --- /dev/null +++ b/ai-hub/tests/core/providers/test_factory.py @@ -0,0 +1,19 @@ +import pytest +from app.core.providers.factory import get_llm_provider +from app.core.providers.llm.deepseek import DeepSeekProvider +from app.core.providers.llm.gemini import GeminiProvider + +def test_get_llm_provider_returns_deepseek_provider(): + """Tests that the factory returns a DeepSeekProvider instance.""" + provider = get_llm_provider("deepseek") + assert isinstance(provider, DeepSeekProvider) + +def test_get_llm_provider_returns_gemini_provider(): + """Tests that the factory returns a GeminiProvider instance.""" + provider = get_llm_provider("gemini") + assert isinstance(provider, GeminiProvider) + +def test_get_llm_provider_raises_error_for_unsupported_provider(): + """Tests that the factory raises an error for an unsupported provider name.""" + with pytest.raises(ValueError, match="Unsupported model provider: 'unknown'"): + get_llm_provider("unknown") \ No newline at end of file diff --git a/ai-hub/tests/core/services/test_rag.py b/ai-hub/tests/core/services/test_rag.py index c431c4e..06a133f 100644 --- a/ai-hub/tests/core/services/test_rag.py +++ b/ai-hub/tests/core/services/test_rag.py @@ -14,7 +14,7 @@ from app.core.retrievers.faiss_db_retriever import FaissDBRetriever, Retriever from app.core.retrievers.base_retriever import Retriever from app.core.pipelines.dspy_rag import DspyRagPipeline -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider @pytest.fixture def rag_service(): diff --git a/ai-hub/app/core/__init__.py b/ai-hub/app/core/__init__.py deleted file mode 100644 index 3fbb1fd..0000000 --- a/ai-hub/app/core/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# This file can be left empty. diff --git a/ai-hub/app/core/llm_providers.py b/ai-hub/app/core/llm_providers.py index b22a670..09364b3 100644 --- a/ai-hub/app/core/llm_providers.py +++ b/ai-hub/app/core/llm_providers.py @@ -1,69 +1,69 @@ -import httpx -import logging -import json -from abc import ABC, abstractmethod -from openai import OpenAI -from typing import final -from app.config import settings # <-- Import the centralized settings +# import httpx +# import logging +# import json +# from abc import ABC, abstractmethod +# from openai import OpenAI +# from typing import final +# from app.config import settings # <-- Import the centralized settings -# --- 1. Initialize API Clients from Central Config --- -# All environment variable access is now gone from this file. -deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") -GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" +# # --- 1. Initialize API Clients from Central Config --- +# # All environment variable access is now gone from this file. +# deepseek_client = OpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +# GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" -# --- 2. Provider Interface and Implementations (Unchanged) --- -class LLMProvider(ABC): - """Abstract base class ('Interface') for all LLM providers.""" - @abstractmethod - async def generate_response(self, prompt: str) -> str: - """Generates a response from the LLM.""" - pass +# # --- 2. Provider Interface and Implementations (Unchanged) --- +# class LLMProvider(ABC): +# """Abstract base class ('Interface') for all LLM providers.""" +# @abstractmethod +# async def generate_response(self, prompt: str) -> str: +# """Generates a response from the LLM.""" +# pass -@final -class DeepSeekProvider(LLMProvider): - """Provider for the DeepSeek API.""" - def __init__(self, model_name: str): - self.model = model_name +# @final +# class DeepSeekProvider(LLMProvider): +# """Provider for the DeepSeek API.""" +# def __init__(self, model_name: str): +# self.model = model_name - async def generate_response(self, prompt: str) -> str: - messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] - try: - chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) - return chat_completion.choices[0].message.content - except Exception as e: - logging.error("DeepSeek Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] +# try: +# chat_completion = deepseek_client.chat.completions.create(model=self.model, messages=messages) +# return chat_completion.choices[0].message.content +# except Exception as e: +# logging.error("DeepSeek Provider Error", exc_info=True) +# raise -@final -class GeminiProvider(LLMProvider): - """Provider for the Google Gemini API.""" - def __init__(self, api_url: str): - self.url = api_url +# @final +# class GeminiProvider(LLMProvider): +# """Provider for the Google Gemini API.""" +# def __init__(self, api_url: str): +# self.url = api_url - async def generate_response(self, prompt: str) -> str: - payload = {"contents": [{"parts": [{"text": prompt}]}]} - headers = {"Content-Type": "application/json"} - try: - async with httpx.AsyncClient() as client: - response = await client.post(self.url, json=payload, headers=headers) - response.raise_for_status() - data = response.json() - return data['candidates'][0]['content']['parts'][0]['text'] - except Exception as e: - logging.error("Gemini Provider Error", exc_info=True) - raise +# async def generate_response(self, prompt: str) -> str: +# payload = {"contents": [{"parts": [{"text": prompt}]}]} +# headers = {"Content-Type": "application/json"} +# try: +# async with httpx.AsyncClient() as client: +# response = await client.post(self.url, json=payload, headers=headers) +# response.raise_for_status() +# data = response.json() +# return data['candidates'][0]['content']['parts'][0]['text'] +# except Exception as e: +# logging.error("Gemini Provider Error", exc_info=True) +# raise -# --- 3. The Factory Function --- -# The dictionary of providers is now built using values from the settings object. -_providers = { - "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), - "gemini": GeminiProvider(api_url=GEMINI_URL) -} +# # --- 3. The Factory Function --- +# # The dictionary of providers is now built using values from the settings object. +# _providers = { +# "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME), +# "gemini": GeminiProvider(api_url=GEMINI_URL) +# } -def get_llm_provider(model_name: str) -> LLMProvider: - """Factory function to get the appropriate, pre-configured LLM provider.""" - provider = _providers.get(model_name) - if not provider: - raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") - return provider \ No newline at end of file +# def get_llm_provider(model_name: str) -> LLMProvider: +# """Factory function to get the appropriate, pre-configured LLM provider.""" +# provider = _providers.get(model_name) +# if not provider: +# raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_providers.keys())}") +# return provider \ No newline at end of file diff --git a/ai-hub/app/core/pipelines/dspy_rag.py b/ai-hub/app/core/pipelines/dspy_rag.py index 88d1915..2a76510 100644 --- a/ai-hub/app/core/pipelines/dspy_rag.py +++ b/ai-hub/app/core/pipelines/dspy_rag.py @@ -6,7 +6,7 @@ from app.db import models from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider class DSPyLLMProvider(dspy.BaseLM): diff --git a/ai-hub/app/core/providers/__init__.py b/ai-hub/app/core/providers/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/base.py b/ai-hub/app/core/providers/base.py new file mode 100644 index 0000000..b49ad2c --- /dev/null +++ b/ai-hub/app/core/providers/base.py @@ -0,0 +1,23 @@ +from abc import ABC, abstractmethod +from typing import AsyncGenerator + +class LLMProvider(ABC): + """Abstract base class for all LLM providers.""" + @abstractmethod + async def generate_response(self, prompt: str) -> str: + """Generates a response from the LLM.""" + pass + +class TTSProvider(ABC): + """Abstract base class for all Text-to-Speech providers.""" + @abstractmethod + async def generate_speech(self, text: str) -> AsyncGenerator[bytes, None]: + """Generates speech from text and streams the audio data.""" + pass + +class STTProvider(ABC): + """Abstract base class for all Speech-to-Text providers.""" + @abstractmethod + async def transcribe_audio(self, audio_data: bytes) -> str: + """Transcribes audio data into text.""" + pass \ No newline at end of file diff --git a/ai-hub/app/core/providers/factory.py b/ai-hub/app/core/providers/factory.py new file mode 100644 index 0000000..5912909 --- /dev/null +++ b/ai-hub/app/core/providers/factory.py @@ -0,0 +1,23 @@ +from app.config import settings +from .base import LLMProvider +from .llm.deepseek import DeepSeekProvider +from .llm.gemini import GeminiProvider +from openai import AsyncOpenAI + +# --- 1. Initialize API Clients from Central Config --- +deepseek_client = AsyncOpenAI(api_key=settings.DEEPSEEK_API_KEY, base_url="https://api.deepseek.com") +GEMINI_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{settings.GEMINI_MODEL_NAME}:generateContent?key={settings.GEMINI_API_KEY}" + +# --- 2. The Factory Dictionaries --- +_llm_providers = { + "deepseek": DeepSeekProvider(model_name=settings.DEEPSEEK_MODEL_NAME, client=deepseek_client), + "gemini": GeminiProvider(api_url=GEMINI_URL) +} + +# --- 3. The Factory Function --- +def get_llm_provider(model_name: str) -> LLMProvider: + """Factory function to get the appropriate, pre-configured LLM provider.""" + provider = _llm_providers.get(model_name) + if not provider: + raise ValueError(f"Unsupported model provider: '{model_name}'. Supported providers are: {list(_llm_providers.keys())}") + return provider \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/__init__.py b/ai-hub/app/core/providers/llm/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/llm/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/llm/deepseek.py b/ai-hub/app/core/providers/llm/deepseek.py new file mode 100644 index 0000000..2fde32c --- /dev/null +++ b/ai-hub/app/core/providers/llm/deepseek.py @@ -0,0 +1,22 @@ +import logging +from openai import AsyncOpenAI # Use AsyncOpenAI +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class DeepSeekProvider(LLMProvider): + """Provider for the DeepSeek API.""" + def __init__(self, model_name: str, client: AsyncOpenAI): # Type hint with AsyncOpenAI + self.model = model_name + self._client = client + + async def generate_response(self, prompt: str) -> str: + messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}] + try: + # This await is now correct for AsyncOpenAI + chat_completion = await self._client.chat.completions.create(model=self.model, messages=messages) + return chat_completion.choices[0].message.content + except Exception as e: + logging.error("DeepSeek Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/llm/gemini.py b/ai-hub/app/core/providers/llm/gemini.py new file mode 100644 index 0000000..ecbba9c --- /dev/null +++ b/ai-hub/app/core/providers/llm/gemini.py @@ -0,0 +1,26 @@ +import httpx +import logging +import json +from typing import final +from app.core.providers.base import LLMProvider +from app.config import settings + +@final +class GeminiProvider(LLMProvider): + """Provider for the Google Gemini API.""" + def __init__(self, api_url: str): + self.url = api_url + + async def generate_response(self, prompt: str) -> str: + payload = {"contents": [{"parts": [{"text": prompt}]}]} + headers = {"Content-Type": "application/json"} + try: + async with httpx.AsyncClient() as client: + response = await client.post(self.url, json=payload, headers=headers) + response.raise_for_status() + # Await the async `json` method + data = response.json() + return data['candidates'][0]['content']['parts'][0]['text'] + except Exception as e: + logging.error("Gemini Provider Error", exc_info=True) + raise \ No newline at end of file diff --git a/ai-hub/app/core/providers/stt/__init__.py b/ai-hub/app/core/providers/stt/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/stt/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/providers/tts/__init__.py b/ai-hub/app/core/providers/tts/__init__.py new file mode 100644 index 0000000..3fbb1fd --- /dev/null +++ b/ai-hub/app/core/providers/tts/__init__.py @@ -0,0 +1 @@ +# This file can be left empty. diff --git a/ai-hub/app/core/retrievers/remote_retriever.py b/ai-hub/app/core/retrievers/remote_retriever.py new file mode 100644 index 0000000..2b08885 --- /dev/null +++ b/ai-hub/app/core/retrievers/remote_retriever.py @@ -0,0 +1,42 @@ +from typing import List +import requests +from sqlalchemy.orm import Session +from app.core.retrievers.base_retriever import Retriever + +class RemoteRetriever(Retriever): + """ + A retriever that fetches context from a single remote backend server. + """ + def __init__(self, server_url: str): + """ + Initializes the retriever with the remote server's URL. + + Args: + server_url (str): The URL of the remote backend server. + """ + self.server_url = server_url + self.session = requests.Session() + + def retrieve_context(self, query: str, db: Session) -> List[str]: + """ + Fetches context for a given query from the remote server. + + Args: + query (str): The user's query string. + db (Session): The database session (unused, but required by the + abstract base class). + + Returns: + List[str]: A list of text strings representing the retrieved context. + """ + try: + # Send the query to the remote server + response = self.session.post(self.server_url, json={'query': query}) + response.raise_for_status() # Raise an exception for bad status codes + + # Assuming the server returns a JSON object with a 'context' key + server_context = response.json().get('context', []) + return server_context + except requests.exceptions.RequestException as e: + print(f"Error connecting to the server at {self.server_url}: {e}") + return [] \ No newline at end of file diff --git a/ai-hub/app/core/services/rag.py b/ai-hub/app/core/services/rag.py index 517278d..e3973da 100644 --- a/ai-hub/app/core/services/rag.py +++ b/ai-hub/app/core/services/rag.py @@ -8,7 +8,7 @@ from app.db import models from app.core.retrievers.faiss_db_retriever import FaissDBRetriever from app.core.retrievers.base_retriever import Retriever -from app.core.llm_providers import get_llm_provider +from app.core.providers.factory import get_llm_provider from app.core.pipelines.dspy_rag import DSPyLLMProvider, DspyRagPipeline class RAGService: diff --git a/ai-hub/requirements.txt b/ai-hub/requirements.txt index 62e3168..4be17cd 100644 --- a/ai-hub/requirements.txt +++ b/ai-hub/requirements.txt @@ -11,7 +11,7 @@ pytest-asyncio pytest-tornasync pytest-trio +pytest-mock numpy faiss-cpu -dspy -dspy-ai \ No newline at end of file +dspy \ No newline at end of file diff --git a/ai-hub/tests/core/providers/llm/test_llm_providers.py b/ai-hub/tests/core/providers/llm/test_llm_providers.py new file mode 100644 index 0000000..38ef108 --- /dev/null +++ b/ai-hub/tests/core/providers/llm/test_llm_providers.py @@ -0,0 +1,81 @@ +import pytest +from unittest.mock import AsyncMock, patch, MagicMock + +from app.core.providers.base import LLMProvider +from app.core.providers.llm.deepseek import DeepSeekProvider +from app.core.providers.llm.gemini import GeminiProvider +from openai import OpenAI +import httpx + +# --- Fixtures --- + +@pytest.fixture +def mock_deepseek_client(): + """Provides a mock OpenAI client with a mocked chat.completions.create method.""" + mock_client = MagicMock(spec=OpenAI) + + mock_response = MagicMock() + mock_response.choices = [MagicMock()] + mock_response.choices[0].message.content = "This is a mocked DeepSeek response." + + mock_client.chat.completions.create = AsyncMock(return_value=mock_response) + + return mock_client + +# --- DeepSeek Provider Tests --- + +@pytest.mark.asyncio +async def test_deepseek_provider_generates_response(mock_deepseek_client): + """Tests that DeepSeekProvider returns the expected response.""" + provider = DeepSeekProvider(model_name="deepseek-chat", client=mock_deepseek_client) + + assert isinstance(provider, LLMProvider) + + response = await provider.generate_response("Test prompt") + + mock_deepseek_client.chat.completions.create.assert_awaited_with( + model="deepseek-chat", + messages=[ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Test prompt"} + ] + ) + assert response == "This is a mocked DeepSeek response." + +# --- Gemini Provider Tests --- +@pytest.mark.asyncio +async def test_gemini_provider_generates_response(mocker): + """Tests that GeminiProvider returns the expected response.""" + # Mock the response object from the post call + mock_response = MagicMock() + mock_response.raise_for_status.return_value = None + mock_response.json = MagicMock(return_value={ + 'candidates': [{'content': {'parts': [{'text': 'This is a mocked Gemini response.'}]}}] + }) + + # Create a mock for the AsyncClient instance + mock_client_instance = MagicMock(spec=httpx.AsyncClient) + mock_client_instance.post = AsyncMock(return_value=mock_response) + + # Create a mock for the AsyncClient class itself + mock_async_client_class = MagicMock(spec=httpx.AsyncClient) + + # Mock the behavior of `async with` + mock_async_client_class.return_value.__aenter__ = AsyncMock(return_value=mock_client_instance) + mock_async_client_class.return_value.__aexit__ = AsyncMock(return_value=None) + + # Patch the AsyncClient class + mocker.patch('httpx.AsyncClient', new=mock_async_client_class) + + provider = GeminiProvider(api_url="http://mock-gemini.com") + assert isinstance(provider, LLMProvider) + + response = await provider.generate_response("Test prompt") + + mock_client_instance.post.assert_awaited_with( + "http://mock-gemini.com", + json={"contents": [{"parts": [{"text": "Test prompt"}]}]}, + headers={"Content-Type": "application/json"} + ) + + assert response == "This is a mocked Gemini response." diff --git a/ai-hub/tests/core/providers/test_factory.py b/ai-hub/tests/core/providers/test_factory.py new file mode 100644 index 0000000..58d4c85 --- /dev/null +++ b/ai-hub/tests/core/providers/test_factory.py @@ -0,0 +1,19 @@ +import pytest +from app.core.providers.factory import get_llm_provider +from app.core.providers.llm.deepseek import DeepSeekProvider +from app.core.providers.llm.gemini import GeminiProvider + +def test_get_llm_provider_returns_deepseek_provider(): + """Tests that the factory returns a DeepSeekProvider instance.""" + provider = get_llm_provider("deepseek") + assert isinstance(provider, DeepSeekProvider) + +def test_get_llm_provider_returns_gemini_provider(): + """Tests that the factory returns a GeminiProvider instance.""" + provider = get_llm_provider("gemini") + assert isinstance(provider, GeminiProvider) + +def test_get_llm_provider_raises_error_for_unsupported_provider(): + """Tests that the factory raises an error for an unsupported provider name.""" + with pytest.raises(ValueError, match="Unsupported model provider: 'unknown'"): + get_llm_provider("unknown") \ No newline at end of file diff --git a/ai-hub/tests/core/services/test_rag.py b/ai-hub/tests/core/services/test_rag.py index c431c4e..06a133f 100644 --- a/ai-hub/tests/core/services/test_rag.py +++ b/ai-hub/tests/core/services/test_rag.py @@ -14,7 +14,7 @@ from app.core.retrievers.faiss_db_retriever import FaissDBRetriever, Retriever from app.core.retrievers.base_retriever import Retriever from app.core.pipelines.dspy_rag import DspyRagPipeline -from app.core.llm_providers import LLMProvider +from app.core.providers.base import LLMProvider @pytest.fixture def rag_service(): diff --git a/ai-hub/tests/core/test_services.py b/ai-hub/tests/core/test_services.py deleted file mode 100644 index a59a6d4..0000000 --- a/ai-hub/tests/core/test_services.py +++ /dev/null @@ -1,320 +0,0 @@ -# import pytest -# import asyncio -# from unittest.mock import patch, MagicMock, AsyncMock -# from sqlalchemy.orm import Session -# from sqlalchemy.exc import SQLAlchemyError -# from typing import List -# from datetime import datetime -# import dspy - -# # Import the service and its dependencies -# from app.core.services import RAGService -# from app.db import models -# from app.core.vector_store.faiss_store import FaissVectorStore -# from app.core.vector_store.embedder.mock import MockEmbedder -# # Import FaissDBRetriever and a mock WebRetriever for testing different cases -# from app.core.retrievers import FaissDBRetriever, Retriever -# from app.core.pipelines.dspy_rag import DspyRagPipeline, DSPyLLMProvider -# from app.core.llm_providers import LLMProvider - - -# @pytest.fixture -# def rag_service(): -# """ -# Pytest fixture to create a RAGService instance with mocked dependencies. -# It includes a mock FaissDBRetriever and a mock generic Retriever to test -# conditional loading. -# """ -# # Create a mock embedder to be attached to the vector store mock -# mock_embedder = MagicMock(spec=MockEmbedder) -# mock_vector_store = MagicMock(spec=FaissVectorStore) -# mock_vector_store.embedder = mock_embedder # Explicitly set the embedder attribute - -# mock_faiss_retriever = MagicMock(spec=FaissDBRetriever) -# mock_web_retriever = MagicMock(spec=Retriever) -# return RAGService( -# vector_store=mock_vector_store, -# retrievers=[mock_web_retriever, mock_faiss_retriever] -# ) - -# # --- Session Management Tests --- - -# def test_create_session(rag_service: RAGService): -# """Tests that the create_session method correctly creates a new session.""" -# mock_db = MagicMock(spec=Session) - -# rag_service.create_session(db=mock_db, user_id="test_user", model="gemini") - -# mock_db.add.assert_called_once() -# added_object = mock_db.add.call_args[0][0] -# assert isinstance(added_object, models.Session) -# assert added_object.user_id == "test_user" -# assert added_object.model_name == "gemini" - -# @patch('app.core.services.get_llm_provider') -# @patch('app.core.services.DspyRagPipeline') -# @patch('dspy.configure') -# def test_chat_with_rag_success(mock_configure, mock_dspy_pipeline, mock_get_llm_provider, rag_service: RAGService): -# """ -# Tests the full orchestration of a chat message within a session using the default model -# and with the retriever loading parameter explicitly set to False. -# """ -# # --- Arrange --- -# mock_db = MagicMock(spec=Session) -# mock_session = models.Session(id=42, model_name="deepseek", messages=[]) -# mock_db.query.return_value.options.return_value.filter.return_value.first.return_value = mock_session - -# mock_llm_provider = MagicMock(spec=LLMProvider) -# mock_get_llm_provider.return_value = mock_llm_provider -# mock_pipeline_instance = MagicMock(spec=DspyRagPipeline) -# mock_pipeline_instance.forward = AsyncMock(return_value="Final RAG response") -# mock_dspy_pipeline.return_value = mock_pipeline_instance - -# # --- Act --- -# answer, model_name = asyncio.run( -# rag_service.chat_with_rag( -# db=mock_db, -# session_id=42, -# prompt="Test prompt", -# model="deepseek", -# load_faiss_retriever=False # Explicitly pass the default value -# ) -# ) - -# # --- Assert --- -# mock_db.query.assert_called_once_with(models.Session) -# assert mock_db.add.call_count == 2 -# mock_get_llm_provider.assert_called_once_with("deepseek") - -# # Assert that DspyRagPipeline was initialized with an empty list of retrievers -# mock_dspy_pipeline.assert_called_once_with(retrievers=[]) - -# mock_pipeline_instance.forward.assert_called_once_with( -# question="Test prompt", -# history=mock_session.messages, -# db=mock_db -# ) - -# assert answer == "Final RAG response" -# assert model_name == "deepseek" - - -# @patch('app.core.services.get_llm_provider') -# @patch('app.core.services.DspyRagPipeline') -# @patch('dspy.configure') -# def test_chat_with_rag_model_switch(mock_configure, mock_dspy_pipeline, mock_get_llm_provider, rag_service: RAGService): -# """ -# Tests that chat_with_rag correctly switches the model based on the 'model' argument, -# while still using the default retriever setting. -# """ -# # --- Arrange --- -# mock_db = MagicMock(spec=Session) -# mock_session = models.Session(id=43, model_name="deepseek", messages=[]) -# mock_db.query.return_value.options.return_value.filter.return_value.first.return_value = mock_session - -# mock_llm_provider = MagicMock(spec=LLMProvider) -# mock_get_llm_provider.return_value = mock_llm_provider -# mock_pipeline_instance = MagicMock(spec=DspyRagPipeline) -# mock_pipeline_instance.forward = AsyncMock(return_value="Final RAG response from Gemini") -# mock_dspy_pipeline.return_value = mock_pipeline_instance - -# # --- Act --- -# answer, model_name = asyncio.run( -# rag_service.chat_with_rag( -# db=mock_db, -# session_id=43, -# prompt="Test prompt for Gemini", -# model="gemini", -# load_faiss_retriever=False # Explicitly pass the default value -# ) -# ) - -# # --- Assert --- -# mock_db.query.assert_called_once_with(models.Session) -# assert mock_db.add.call_count == 2 -# mock_get_llm_provider.assert_called_once_with("gemini") - -# # Assert that DspyRagPipeline was initialized with an empty list of retrievers -# mock_dspy_pipeline.assert_called_once_with(retrievers=[]) - -# mock_pipeline_instance.forward.assert_called_once_with( -# question="Test prompt for Gemini", -# history=mock_session.messages, -# db=mock_db -# ) - -# assert answer == "Final RAG response from Gemini" -# assert model_name == "gemini" - - -# @patch('app.core.services.get_llm_provider') -# @patch('app.core.services.DspyRagPipeline') -# @patch('dspy.configure') -# def test_chat_with_rag_with_faiss_retriever(mock_configure, mock_dspy_pipeline, mock_get_llm_provider, rag_service: RAGService): -# """ -# Tests that the chat_with_rag method correctly initializes the DspyRagPipeline -# with the FaissDBRetriever when `load_faiss_retriever` is True. -# """ -# # --- Arrange --- -# mock_db = MagicMock(spec=Session) -# mock_session = models.Session(id=44, model_name="deepseek", messages=[]) -# mock_db.query.return_value.options.return_value.filter.return_value.first.return_value = mock_session - -# mock_llm_provider = MagicMock(spec=LLMProvider) -# mock_get_llm_provider.return_value = mock_llm_provider -# mock_pipeline_instance = MagicMock(spec=DspyRagPipeline) -# mock_pipeline_instance.forward = AsyncMock(return_value="Response with FAISS context") -# mock_dspy_pipeline.return_value = mock_pipeline_instance - -# # --- Act --- -# # Explicitly enable the FAISS retriever -# answer, model_name = asyncio.run( -# rag_service.chat_with_rag( -# db=mock_db, -# session_id=44, -# prompt="Test prompt with FAISS", -# model="deepseek", -# load_faiss_retriever=True -# ) -# ) - -# # --- Assert --- -# # The crucial part is to verify that the pipeline was called with the correct retriever -# expected_retrievers = [rag_service.faiss_retriever] -# mock_dspy_pipeline.assert_called_once_with(retrievers=expected_retrievers) - -# mock_pipeline_instance.forward.assert_called_once_with( -# question="Test prompt with FAISS", -# history=mock_session.messages, -# db=mock_db -# ) - -# assert answer == "Response with FAISS context" -# assert model_name == "deepseek" - - -# def test_get_message_history_success(rag_service: RAGService): -# """Tests successfully retrieving message history for an existing session.""" -# # Arrange -# mock_db = MagicMock(spec=Session) -# # Ensure mocked messages have created_at for sorting -# mock_session = models.Session(id=1, messages=[ -# models.Message(created_at=datetime(2023, 1, 1, 10, 0, 0)), -# models.Message(created_at=datetime(2023, 1, 1, 10, 1, 0)) -# ]) -# mock_db.query.return_value.options.return_value.filter.return_value.first.return_value = mock_session - -# # Act -# messages = rag_service.get_message_history(db=mock_db, session_id=1) - -# # Assert -# assert len(messages) == 2 -# assert messages[0].created_at < messages[1].created_at # Verify sorting -# mock_db.query.assert_called_once_with(models.Session) - -# def test_get_message_history_not_found(rag_service: RAGService): -# """Tests retrieving history for a non-existent session.""" -# # Arrange -# mock_db = MagicMock(spec=Session) -# mock_db.query.return_value.options.return_value.filter.return_value.first.return_value = None - -# # Act -# messages = rag_service.get_message_history(db=mock_db, session_id=999) - -# # Assert -# assert messages is None - -# # --- Document Management Tests --- - -# @patch('app.db.models.VectorMetadata') -# @patch('app.db.models.Document') -# @patch('app.core.vector_store.faiss_store.FaissVectorStore') -# def test_rag_service_add_document_success(mock_vector_store, mock_document_model, mock_vector_metadata_model): -# """ -# Test the RAGService.add_document method for a successful run. -# Verifies that the method correctly calls db.add(), db.commit(), and the vector store. -# """ -# # Setup mocks -# mock_db = MagicMock(spec=Session) -# mock_new_document_instance = MagicMock() -# mock_document_model.return_value = mock_new_document_instance -# mock_new_document_instance.id = 1 -# mock_new_document_instance.text = "Test text." -# mock_new_document_instance.title = "Test Title" - -# mock_vector_store_instance = mock_vector_store.return_value -# # Fix: Manually set the embedder on the mock vector store instance -# mock_vector_store_instance.embedder = MagicMock(spec=MockEmbedder) -# mock_vector_store_instance.add_document.return_value = 123 - -# # Instantiate the service correctly -# rag_service = RAGService( -# vector_store=mock_vector_store_instance, -# retrievers=[] -# ) - -# doc_data = { -# "title": "Test Title", -# "text": "Test text.", -# "source_url": "http://test.com" -# } - -# # Call the method under test -# document_id = rag_service.add_document(db=mock_db, doc_data=doc_data) - -# # Assertions -# assert document_id == 1 - -# from unittest.mock import call -# expected_calls = [ -# call(mock_new_document_instance), -# call(mock_vector_metadata_model.return_value) -# ] -# mock_db.add.assert_has_calls(expected_calls) - -# mock_db.commit.assert_called() -# mock_db.refresh.assert_called_with(mock_new_document_instance) -# mock_vector_store_instance.add_document.assert_called_once_with("Test text.") - -# # Assert that VectorMetadata was instantiated with the correct arguments -# mock_vector_metadata_model.assert_called_once_with( -# document_id=mock_new_document_instance.id, -# faiss_index=mock_vector_store_instance.add_document.return_value, -# embedding_model="mock_embedder" # This now passes because the mock embedder is of type MockEmbedder -# ) - -# @patch('app.core.vector_store.faiss_store.FaissVectorStore') -# def test_rag_service_add_document_error_handling(mock_vector_store): -# """ -# Test the RAGService.add_document method's error handling. -# Verifies that the transaction is rolled back on an exception. -# """ -# # Setup mocks -# mock_db = MagicMock(spec=Session) - -# # Configure the mock db.add to raise the specific SQLAlchemyError. -# mock_db.add.side_effect = SQLAlchemyError("Database error") - -# mock_vector_store_instance = mock_vector_store.return_value - -# # Instantiate the service correctly -# rag_service = RAGService( -# vector_store=mock_vector_store_instance, -# retrievers=[] -# ) - -# doc_data = { -# "title": "Test Title", -# "text": "Test text.", -# "source_url": "http://test.com" -# } - -# # Call the method under test and expect an exception -# with pytest.raises(SQLAlchemyError, match="Database error"): -# rag_service.add_document(db=mock_db, doc_data=doc_data) - -# # Assertions -# mock_db.add.assert_called_once() -# mock_db.commit.assert_not_called() -# mock_db.rollback.assert_called_once() -