Compare commits

...

1 Commits

4 changed files with 216 additions and 50 deletions

View File

@ -30,9 +30,26 @@ CHROMA_SERVER_PORT=8002
CHROMA_COLLECTION_NAME=rag_collection
# ============================================
# Ollama 配置
# LLM 配置 (用于文本生成)
# ============================================
# LLM provider: ollama, openai 等
LLM_PROVIDER=ollama
LLM_BASE_URL=http://localhost:11434/v1
LLM_MODEL=qwen3:8b
LLM_API_KEY= # 如使用openai等需要API Key的服务
# ============================================
# Embedding 配置 (用于向量检索)
# ============================================
# Embedding provider: ollama, openai 等
EMBEDDING_PROVIDER=ollama
EMBEDDING_BASE_URL=http://localhost:11434
EMBEDDING_MODEL=qwen3-embedding:0.6b
EMBEDDING_API_KEY= # 如使用openai等需要API Key的服务
# ============================================
# 兼容旧版本配置 (已废弃,仍可用但推荐使用上面的配置)
# ============================================
# OLLAMA_BASE_URL: Ollama 服务地址
OLLAMA_BASE_URL=http://localhost:11434
OLLAMA_MODEL=qwen3:8b
OLLAMA_EMBEDDING_MODEL=qwen3-embedding:0.6b

View File

@ -138,13 +138,24 @@ class Settings(BaseSettings):
CHROMA_DB_PATH: str = "./chroma_db" # Only used for PersistentClient mode
CHROMA_COLLECTION_NAME: str = "rag_collection"
# Ollama Settings
# Configure OLLAMA_BASE_URL in .env file based on your deployment
# - Local: http://localhost:11434
# - Remote: http://192.168.1.100:11434
# LLM Settings
# LLM provider: "ollama", "vllm", "openai", "deepseek" etc.
LLM_PROVIDER: str = "ollama"
LLM_BASE_URL: str = "http://localhost:11434"
LLM_MODEL: str = "qwen3:8b"
LLM_API_KEY: Optional[str] = None
# Embedding Settings
# Embedding provider: "ollama", "vllm", "openai" etc.
EMBEDDING_PROVIDER: str = "ollama"
EMBEDDING_BASE_URL: str = "http://localhost:11434"
EMBEDDING_MODEL: str = "qwen3-embedding:0.6b"
EMBEDDING_API_KEY: Optional[str] = None
# Legacy Ollama Settings (for backward compatibility)
OLLAMA_BASE_URL: str = "http://localhost:11434"
OLLAMA_MODEL: str = "qwen3:1.7b" # LLM model for text generation
OLLAMA_EMBEDDING_MODEL: str = "qwen3-embedding:0.6b" # Embedding model for vectorization
OLLAMA_MODEL: str = "qwen3:8b"
OLLAMA_EMBEDDING_MODEL: str = "qwen3-embedding:0.6b"
# RAG Settings
EMBEDDING_DIMENSION: int = 768
@ -184,6 +195,71 @@ class Settings(BaseSettings):
extra="ignore" # Ignore extra fields in .env file that are not defined in Settings
)
def get_llm_config(self) -> Dict[str, Any]:
"""
Get LLM configuration with backward compatibility for legacy OLLAMA_* settings.
Returns:
Dictionary with provider, base_url, model, and api_key
"""
if self.LLM_PROVIDER == "ollama":
return {
"provider": "ollama",
"base_url": self.LLM_BASE_URL or self.OLLAMA_BASE_URL,
"model": self.LLM_MODEL or self.OLLAMA_MODEL,
"api_key": self.LLM_API_KEY
}
elif self.LLM_PROVIDER == "openai":
return {
"provider": "openai",
"base_url": self.LLM_BASE_URL or "https://api.openai.com/v1",
"model": self.LLM_MODEL,
"api_key": self.LLM_API_KEY
}
else:
return {
"provider": self.LLM_PROVIDER,
"base_url": self.LLM_BASE_URL,
"model": self.LLM_MODEL,
"api_key": self.LLM_API_KEY
}
def get_embedding_config(self) -> Dict[str, Any]:
"""
Get Embedding configuration with backward compatibility for legacy OLLAMA_* settings.
Returns:
Dictionary with provider, base_url, model, and api_key
"""
if self.EMBEDDING_PROVIDER == "ollama":
return {
"provider": "ollama",
"base_url": self.EMBEDDING_BASE_URL or self.OLLAMA_BASE_URL,
"model": self.EMBEDDING_MODEL or self.OLLAMA_EMBEDDING_MODEL,
"api_key": self.EMBEDDING_API_KEY
}
elif self.EMBEDDING_PROVIDER == "vllm":
return {
"provider": "vllm",
"base_url": self.EMBEDDING_BASE_URL,
"model": self.EMBEDDING_MODEL,
"api_key": self.EMBEDDING_API_KEY
}
elif self.EMBEDDING_PROVIDER == "openai":
return {
"provider": "openai",
"base_url": self.EMBEDDING_BASE_URL or "https://api.openai.com/v1",
"model": self.EMBEDDING_MODEL,
"api_key": self.EMBEDDING_API_KEY
}
else:
return {
"provider": self.EMBEDDING_PROVIDER,
"base_url": self.EMBEDDING_BASE_URL,
"model": self.EMBEDDING_MODEL,
"api_key": self.EMBEDDING_API_KEY
}
def get_data_sources(self) -> List[BaseDataSourceConfig]:
"""
Get list of data source configurations

View File

@ -5,6 +5,7 @@ from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.response_synthesizers import ResponseMode
from llama_index.core.base.response.schema import StreamingResponse
from llama_index.llms.ollama import Ollama
from llama_index.llms.openai import OpenAI
from llama_index.core import PromptTemplate
from typing import AsyncIterator, Optional, Tuple
import asyncio
@ -82,42 +83,71 @@ class RAGEngine:
"""Main RAG engine for query processing"""
@staticmethod
def check_ollama_connection() -> Tuple[bool, str]:
def check_llm_connection(provider: str, base_url: str, model: str, api_key: Optional[str] = None) -> Tuple[bool, str]:
"""
Check if Ollama server is accessible and connection can be established
Check if LLM server is accessible and connection can be established
Args:
provider: LLM provider (ollama, vllm, openai)
base_url: Base URL for the LLM service
model: Model name
api_key: Optional API key for authentication
Returns:
Tuple of (is_connected: bool, error_message: str)
If connected, error_message will be empty string
"""
try:
logger.info(f"Checking Ollama connection to {settings.OLLAMA_BASE_URL}...")
headers = {}
if api_key:
headers["Authorization"] = f"Bearer {api_key}"
# Test connection by calling Ollama API
with httpx.Client(timeout=10.0) as client:
response = client.get(f"{settings.OLLAMA_BASE_URL}/api/tags")
if response.status_code == 200:
models = response.json().get("models", [])
model_names = [m.get("name", "unknown") for m in models]
logger.info(f"✓ Ollama connection successful (found {len(models)} model(s): {', '.join(model_names[:3])}{'...' if len(model_names) > 3 else ''})")
if provider == "ollama":
logger.info(f"Checking Ollama connection to {base_url}...")
with httpx.Client(timeout=10.0) as client:
response = client.get(f"{base_url}/api/tags")
if response.status_code == 200:
models = response.json().get("models", [])
model_names = [m.get("name", "unknown") for m in models]
logger.info(f"✓ Ollama connection successful (found {len(models)} model(s): {', '.join(model_names[:3])}{'...' if len(model_names) > 3 else ''})")
return True, ""
else:
error_message = f"Ollama API returned status {response.status_code}: {response.text}"
logger.error(f"✗ Ollama connection failed: {error_message}")
return False, error_message
elif provider == "openai":
logger.info(f"Checking OpenAI-compatible API connection to {base_url}...")
if not api_key:
logger.warning("OpenAI provider requires API key, skipping connection check")
return True, ""
else:
error_message = f"Ollama API returned status {response.status_code}: {response.text}"
logger.error(f"✗ Ollama connection failed: {error_message}")
return False, error_message
with httpx.Client(timeout=10.0, headers=headers) as client:
response = client.get(f"{base_url}/models")
if response.status_code == 200:
models = response.json().get("data", [])
model_names = [m.get("id", "unknown") for m in models]
logger.info(f"✓ OpenAI-compatible API connection successful (found {len(models)} model(s): {', '.join(model_names[:3])}{'...' if len(model_names) > 3 else ''})")
return True, ""
else:
error_message = f"OpenAI API returned status {response.status_code}: {response.text}"
logger.error(f"✗ OpenAI connection failed: {error_message}")
return False, error_message
else:
logger.warning(f"Unknown LLM provider: {provider}, skipping connection check")
return True, ""
except httpx.ConnectError as e:
error_message = f"Cannot connect to Ollama server at {settings.OLLAMA_BASE_URL}. " \
f"Please check if Ollama server is running and accessible."
logger.error(f"✗ Ollama connection failed: {error_message}")
error_message = f"Cannot connect to {provider} server at {base_url}. " \
f"Please check if server is running and accessible."
logger.error(f"{provider} connection failed: {error_message}")
return False, error_message
except httpx.TimeoutException:
error_message = f"Connection to Ollama server at {settings.OLLAMA_BASE_URL} timed out. " \
f"Please check if Ollama server is running and accessible."
logger.error(f"✗ Ollama connection failed: {error_message}")
error_message = f"Connection to {provider} server at {base_url} timed out. " \
f"Please check if server is running and accessible."
logger.error(f"{provider} connection failed: {error_message}")
return False, error_message
except Exception as e:
error_message = f"Unexpected error while checking Ollama connection: {str(e)}"
logger.error(f"Ollama connection check failed: {error_message}")
error_message = f"Unexpected error while checking {provider} connection: {str(e)}"
logger.error(f"{provider} connection check failed: {error_message}")
return False, error_message
def __init__(
@ -129,34 +159,56 @@ class RAGEngine:
request_timeout: float = 120.0,
):
self.vector_store_manager = vector_store_manager
# Configurable LLM / prompt parameters
self._user_prompt_template = prompt_template
self._system_prompt = system_prompt
self._temperature = temperature
self._request_timeout = request_timeout
# Check Ollama connection before initializing
is_connected, error_message = self.check_ollama_connection()
llm_config = settings.get_llm_config()
provider = llm_config["provider"]
base_url = llm_config["base_url"]
model = llm_config["model"]
api_key = llm_config["api_key"]
is_connected, error_message = self.check_llm_connection(provider, base_url, model, api_key)
if not is_connected:
error_msg = (
f"Error: Cannot connect to Ollama server.\n"
f"Error: Cannot connect to {provider} server.\n"
f" {error_message}\n"
f"Connection info: {settings.OLLAMA_BASE_URL}\n"
f"Connection info: {base_url}\n"
f"Please check:\n"
f" 1. Ollama server is running\n"
f" 2. Ollama server is accessible from this host\n"
f" 3. OLLAMA_BASE_URL is correctly configured\n"
f" 4. Firewall rules allow connection to Ollama port"
f" 1. {provider} server is running\n"
f" 2. {provider} server is accessible from this host\n"
f" 3. LLM_BASE_URL is correctly configured\n"
f" 4. Firewall rules allow connection"
)
logger.error(error_msg)
raise RuntimeError(error_msg)
self.llm = Ollama(
model=settings.OLLAMA_MODEL,
base_url=settings.OLLAMA_BASE_URL,
temperature=self._temperature,
request_timeout=self._request_timeout,
)
if provider == "ollama":
self.llm = Ollama(
model=model,
base_url=base_url,
temperature=self._temperature,
request_timeout=self._request_timeout,
)
elif provider == "openai":
self.llm = OpenAI(
model=model,
base_url=base_url,
api_key=api_key,
temperature=self._temperature,
timeout=self._request_timeout,
)
else:
self.llm = Ollama(
model=model,
base_url=base_url,
temperature=self._temperature,
request_timeout=self._request_timeout,
)
self._llm_provider = provider
def extract_text_from_chunk(self, chunk) -> Optional[str]:
if hasattr(chunk, 'delta'):

View File

@ -11,6 +11,7 @@ from chromadb.config import Settings as ChromaSettings
from llama_index.vector_stores.chroma import ChromaVectorStore
from llama_index.core import VectorStoreIndex, StorageContext
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.embeddings.openai import OpenAIEmbedding
from loguru import logger
from config import settings
@ -106,11 +107,31 @@ class VectorStoreManager:
metadata={"hnsw:space": "cosine"}
)
# Initialize embedding model
self.embed_model = OllamaEmbedding(
model_name=settings.OLLAMA_EMBEDDING_MODEL,
base_url=settings.OLLAMA_BASE_URL
)
# Initialize embedding model based on provider
embed_config = settings.get_embedding_config()
provider = embed_config["provider"]
base_url = embed_config["base_url"]
model = embed_config["model"]
api_key = embed_config["api_key"]
if provider == "ollama":
self.embed_model = OllamaEmbedding(
model_name=model,
base_url=base_url
)
elif provider == "openai":
self.embed_model = OpenAIEmbedding(
model=model,
base_url=base_url,
api_key=api_key
)
else:
self.embed_model = OllamaEmbedding(
model_name=model,
base_url=base_url
)
logger.info(f"Initialized {provider} embedding model: {model}")
# Create ChromaVectorStore
self.vector_store = ChromaVectorStore(chroma_collection=self.collection)