Compare commits
1 Commits
feature/co
...
master
| Author | SHA1 | Date |
|---|---|---|
|
|
1cb82f4216 |
21
.env.example
21
.env.example
|
|
@ -30,9 +30,26 @@ CHROMA_SERVER_PORT=8002
|
||||||
CHROMA_COLLECTION_NAME=rag_collection
|
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_BASE_URL=http://localhost:11434
|
||||||
OLLAMA_MODEL=qwen3:8b
|
OLLAMA_MODEL=qwen3:8b
|
||||||
OLLAMA_EMBEDDING_MODEL=qwen3-embedding:0.6b
|
OLLAMA_EMBEDDING_MODEL=qwen3-embedding:0.6b
|
||||||
|
|
|
||||||
88
config.py
88
config.py
|
|
@ -138,13 +138,24 @@ class Settings(BaseSettings):
|
||||||
CHROMA_DB_PATH: str = "./chroma_db" # Only used for PersistentClient mode
|
CHROMA_DB_PATH: str = "./chroma_db" # Only used for PersistentClient mode
|
||||||
CHROMA_COLLECTION_NAME: str = "rag_collection"
|
CHROMA_COLLECTION_NAME: str = "rag_collection"
|
||||||
|
|
||||||
# Ollama Settings
|
# LLM Settings
|
||||||
# Configure OLLAMA_BASE_URL in .env file based on your deployment
|
# LLM provider: "ollama", "vllm", "openai", "deepseek" etc.
|
||||||
# - Local: http://localhost:11434
|
LLM_PROVIDER: str = "ollama"
|
||||||
# - Remote: http://192.168.1.100:11434
|
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_BASE_URL: str = "http://localhost:11434"
|
||||||
OLLAMA_MODEL: str = "qwen3:1.7b" # LLM model for text generation
|
OLLAMA_MODEL: str = "qwen3:8b"
|
||||||
OLLAMA_EMBEDDING_MODEL: str = "qwen3-embedding:0.6b" # Embedding model for vectorization
|
OLLAMA_EMBEDDING_MODEL: str = "qwen3-embedding:0.6b"
|
||||||
|
|
||||||
# RAG Settings
|
# RAG Settings
|
||||||
EMBEDDING_DIMENSION: int = 768
|
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
|
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]:
|
def get_data_sources(self) -> List[BaseDataSourceConfig]:
|
||||||
"""
|
"""
|
||||||
Get list of data source configurations
|
Get list of data source configurations
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ from llama_index.core.query_engine import RetrieverQueryEngine
|
||||||
from llama_index.core.response_synthesizers import ResponseMode
|
from llama_index.core.response_synthesizers import ResponseMode
|
||||||
from llama_index.core.base.response.schema import StreamingResponse
|
from llama_index.core.base.response.schema import StreamingResponse
|
||||||
from llama_index.llms.ollama import Ollama
|
from llama_index.llms.ollama import Ollama
|
||||||
|
from llama_index.llms.openai import OpenAI
|
||||||
from llama_index.core import PromptTemplate
|
from llama_index.core import PromptTemplate
|
||||||
from typing import AsyncIterator, Optional, Tuple
|
from typing import AsyncIterator, Optional, Tuple
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
@ -82,42 +83,71 @@ class RAGEngine:
|
||||||
"""Main RAG engine for query processing"""
|
"""Main RAG engine for query processing"""
|
||||||
|
|
||||||
@staticmethod
|
@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:
|
Returns:
|
||||||
Tuple of (is_connected: bool, error_message: str)
|
Tuple of (is_connected: bool, error_message: str)
|
||||||
If connected, error_message will be empty string
|
If connected, error_message will be empty string
|
||||||
"""
|
"""
|
||||||
try:
|
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
|
if provider == "ollama":
|
||||||
with httpx.Client(timeout=10.0) as client:
|
logger.info(f"Checking Ollama connection to {base_url}...")
|
||||||
response = client.get(f"{settings.OLLAMA_BASE_URL}/api/tags")
|
with httpx.Client(timeout=10.0) as client:
|
||||||
if response.status_code == 200:
|
response = client.get(f"{base_url}/api/tags")
|
||||||
models = response.json().get("models", [])
|
if response.status_code == 200:
|
||||||
model_names = [m.get("name", "unknown") for m in models]
|
models = response.json().get("models", [])
|
||||||
logger.info(f"✓ Ollama connection successful (found {len(models)} model(s): {', '.join(model_names[:3])}{'...' if len(model_names) > 3 else ''})")
|
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, ""
|
return True, ""
|
||||||
else:
|
with httpx.Client(timeout=10.0, headers=headers) as client:
|
||||||
error_message = f"Ollama API returned status {response.status_code}: {response.text}"
|
response = client.get(f"{base_url}/models")
|
||||||
logger.error(f"✗ Ollama connection failed: {error_message}")
|
if response.status_code == 200:
|
||||||
return False, error_message
|
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:
|
except httpx.ConnectError as e:
|
||||||
error_message = f"Cannot connect to Ollama server at {settings.OLLAMA_BASE_URL}. " \
|
error_message = f"Cannot connect to {provider} server at {base_url}. " \
|
||||||
f"Please check if Ollama server is running and accessible."
|
f"Please check if server is running and accessible."
|
||||||
logger.error(f"✗ Ollama connection failed: {error_message}")
|
logger.error(f"✗ {provider} connection failed: {error_message}")
|
||||||
return False, error_message
|
return False, error_message
|
||||||
except httpx.TimeoutException:
|
except httpx.TimeoutException:
|
||||||
error_message = f"Connection to Ollama server at {settings.OLLAMA_BASE_URL} timed out. " \
|
error_message = f"Connection to {provider} server at {base_url} timed out. " \
|
||||||
f"Please check if Ollama server is running and accessible."
|
f"Please check if server is running and accessible."
|
||||||
logger.error(f"✗ Ollama connection failed: {error_message}")
|
logger.error(f"✗ {provider} connection failed: {error_message}")
|
||||||
return False, error_message
|
return False, error_message
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_message = f"Unexpected error while checking Ollama connection: {str(e)}"
|
error_message = f"Unexpected error while checking {provider} connection: {str(e)}"
|
||||||
logger.error(f"✗ Ollama connection check failed: {error_message}")
|
logger.error(f"✗ {provider} connection check failed: {error_message}")
|
||||||
return False, error_message
|
return False, error_message
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
@ -129,34 +159,56 @@ class RAGEngine:
|
||||||
request_timeout: float = 120.0,
|
request_timeout: float = 120.0,
|
||||||
):
|
):
|
||||||
self.vector_store_manager = vector_store_manager
|
self.vector_store_manager = vector_store_manager
|
||||||
# Configurable LLM / prompt parameters
|
|
||||||
self._user_prompt_template = prompt_template
|
self._user_prompt_template = prompt_template
|
||||||
self._system_prompt = system_prompt
|
self._system_prompt = system_prompt
|
||||||
self._temperature = temperature
|
self._temperature = temperature
|
||||||
self._request_timeout = request_timeout
|
self._request_timeout = request_timeout
|
||||||
|
|
||||||
# Check Ollama connection before initializing
|
llm_config = settings.get_llm_config()
|
||||||
is_connected, error_message = self.check_ollama_connection()
|
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:
|
if not is_connected:
|
||||||
error_msg = (
|
error_msg = (
|
||||||
f"Error: Cannot connect to Ollama server.\n"
|
f"Error: Cannot connect to {provider} server.\n"
|
||||||
f" {error_message}\n"
|
f" {error_message}\n"
|
||||||
f"Connection info: {settings.OLLAMA_BASE_URL}\n"
|
f"Connection info: {base_url}\n"
|
||||||
f"Please check:\n"
|
f"Please check:\n"
|
||||||
f" 1. Ollama server is running\n"
|
f" 1. {provider} server is running\n"
|
||||||
f" 2. Ollama server is accessible from this host\n"
|
f" 2. {provider} server is accessible from this host\n"
|
||||||
f" 3. OLLAMA_BASE_URL is correctly configured\n"
|
f" 3. LLM_BASE_URL is correctly configured\n"
|
||||||
f" 4. Firewall rules allow connection to Ollama port"
|
f" 4. Firewall rules allow connection"
|
||||||
)
|
)
|
||||||
logger.error(error_msg)
|
logger.error(error_msg)
|
||||||
raise RuntimeError(error_msg)
|
raise RuntimeError(error_msg)
|
||||||
|
|
||||||
self.llm = Ollama(
|
if provider == "ollama":
|
||||||
model=settings.OLLAMA_MODEL,
|
self.llm = Ollama(
|
||||||
base_url=settings.OLLAMA_BASE_URL,
|
model=model,
|
||||||
temperature=self._temperature,
|
base_url=base_url,
|
||||||
request_timeout=self._request_timeout,
|
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]:
|
def extract_text_from_chunk(self, chunk) -> Optional[str]:
|
||||||
if hasattr(chunk, 'delta'):
|
if hasattr(chunk, 'delta'):
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ from chromadb.config import Settings as ChromaSettings
|
||||||
from llama_index.vector_stores.chroma import ChromaVectorStore
|
from llama_index.vector_stores.chroma import ChromaVectorStore
|
||||||
from llama_index.core import VectorStoreIndex, StorageContext
|
from llama_index.core import VectorStoreIndex, StorageContext
|
||||||
from llama_index.embeddings.ollama import OllamaEmbedding
|
from llama_index.embeddings.ollama import OllamaEmbedding
|
||||||
|
from llama_index.embeddings.openai import OpenAIEmbedding
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from config import settings
|
from config import settings
|
||||||
|
|
||||||
|
|
@ -106,11 +107,31 @@ class VectorStoreManager:
|
||||||
metadata={"hnsw:space": "cosine"}
|
metadata={"hnsw:space": "cosine"}
|
||||||
)
|
)
|
||||||
|
|
||||||
# Initialize embedding model
|
# Initialize embedding model based on provider
|
||||||
self.embed_model = OllamaEmbedding(
|
embed_config = settings.get_embedding_config()
|
||||||
model_name=settings.OLLAMA_EMBEDDING_MODEL,
|
provider = embed_config["provider"]
|
||||||
base_url=settings.OLLAMA_BASE_URL
|
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
|
# Create ChromaVectorStore
|
||||||
self.vector_store = ChromaVectorStore(chroma_collection=self.collection)
|
self.vector_store = ChromaVectorStore(chroma_collection=self.collection)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue