feat(llm): add DeepSeek provider and HuggingFace embedding fallback (Issue #41)
This commit is contained in:
parent
5ea9e905c5
commit
edae1ab2cc
24
PROJECT.md
24
PROJECT.md
|
|
@ -443,6 +443,30 @@ config = {
|
|||
- Model names use the format: `provider/model-name` (e.g., `anthropic/claude-sonnet-4.5`, `openai/gpt-4o`)
|
||||
- See [OpenRouter models list](https://openrouter.ai/docs/models) for available models
|
||||
|
||||
### DeepSeek Configuration Example
|
||||
DeepSeek provides cost-effective reasoning models with strong performance on quantitative analysis tasks. To use DeepSeek:
|
||||
|
||||
```python
|
||||
config = {
|
||||
"llm_provider": "deepseek",
|
||||
"deep_think_llm": "deepseek-reasoner", # Extended reasoning model
|
||||
"quick_think_llm": "deepseek-chat", # Fast responses for simple queries
|
||||
"backend_url": "https://api.deepseek.com/v1",
|
||||
}
|
||||
```
|
||||
|
||||
**Requirements:**
|
||||
- DEEPSEEK_API_KEY environment variable must be set
|
||||
- Get your API key from [DeepSeek Platform](https://platform.deepseek.com/)
|
||||
- For embeddings, either set OPENAI_API_KEY (preferred) or install sentence-transformers package
|
||||
- Model options: `deepseek-chat` (fast) and `deepseek-reasoner` (extended thinking)
|
||||
- DeepSeek uses OpenAI API format (ChatOpenAI compatible)
|
||||
|
||||
**Embedding Fallback Chain:**
|
||||
- Tries OPENAI_API_KEY for OpenAI embeddings (recommended for best quality)
|
||||
- Falls back to HuggingFace sentence-transformers (all-MiniLM-L6-v2) if available and no OpenAI key
|
||||
- Disables memory features with warning if no embedding backend available
|
||||
|
||||
---
|
||||
|
||||
## DEVELOPMENT NOTES
|
||||
|
|
|
|||
|
|
@ -3,40 +3,77 @@ from chromadb.config import Settings
|
|||
from openai import OpenAI
|
||||
import os
|
||||
|
||||
# Try to import HuggingFace sentence-transformers (optional dependency)
|
||||
# This needs to be at module level for test mocking to work
|
||||
try:
|
||||
from sentence_transformers import SentenceTransformer
|
||||
except ImportError:
|
||||
SentenceTransformer = None
|
||||
|
||||
|
||||
class FinancialSituationMemory:
|
||||
def __init__(self, name, config):
|
||||
# Handle embeddings based on provider
|
||||
self.embedding_backend = None # Track which backend is used
|
||||
|
||||
# Handle embeddings based on provider with fallback chain
|
||||
if config["backend_url"] == "http://localhost:11434/v1":
|
||||
# Ollama local embeddings
|
||||
self.embedding = "nomic-embed-text"
|
||||
self.client = OpenAI(base_url=config["backend_url"])
|
||||
elif config.get("llm_provider", "").lower() == "openrouter":
|
||||
# OpenRouter doesn't have native embeddings, use OpenAI embeddings as fallback
|
||||
self.embedding_backend = "ollama"
|
||||
elif config.get("llm_provider", "").lower() in ("openrouter", "deepseek"):
|
||||
# OpenRouter and DeepSeek don't have native embeddings
|
||||
# Fallback chain: OpenAI -> HuggingFace -> disable memory
|
||||
openai_key = os.getenv("OPENAI_API_KEY")
|
||||
if not openai_key:
|
||||
print("Warning: OPENAI_API_KEY not found. Memory features disabled for OpenRouter.")
|
||||
self.client = None
|
||||
else:
|
||||
if openai_key:
|
||||
# Use OpenAI embeddings as first fallback
|
||||
self.embedding = "text-embedding-3-small"
|
||||
self.client = OpenAI(api_key=openai_key) # Use OpenAI directly for embeddings
|
||||
self.client = OpenAI(api_key=openai_key)
|
||||
self.embedding_backend = "openai"
|
||||
elif SentenceTransformer is not None:
|
||||
# Use HuggingFace sentence-transformers as second fallback
|
||||
try:
|
||||
self.client = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
|
||||
self.embedding = "all-MiniLM-L6-v2"
|
||||
self.embedding_backend = "huggingface"
|
||||
print(f"Info: Using HuggingFace embeddings (all-MiniLM-L6-v2) for memory with {config.get('llm_provider', 'unknown')} provider.")
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to initialize HuggingFace embeddings: {e}. Memory features disabled.")
|
||||
self.client = None
|
||||
self.embedding_backend = None
|
||||
else:
|
||||
# No embedding backend available - disable memory
|
||||
print(f"Warning: No embedding backend available for {config.get('llm_provider', 'unknown')} provider. "
|
||||
"Install sentence-transformers or set OPENAI_API_KEY to enable memory features.")
|
||||
self.client = None
|
||||
self.embedding_backend = None
|
||||
else:
|
||||
# Default to text-embedding-3-small for OpenAI and others
|
||||
self.embedding = "text-embedding-3-small"
|
||||
self.client = OpenAI(base_url=config["backend_url"])
|
||||
self.embedding_backend = "openai"
|
||||
|
||||
self.chroma_client = chromadb.Client(Settings(allow_reset=True))
|
||||
self.situation_collection = self.chroma_client.get_or_create_collection(name=name)
|
||||
|
||||
def get_embedding(self, text):
|
||||
"""Get OpenAI embedding for a text"""
|
||||
"""Get embedding for a text using the configured backend."""
|
||||
if self.client is None:
|
||||
raise RuntimeError("Embedding client not initialized. Check API key configuration.")
|
||||
|
||||
response = self.client.embeddings.create(
|
||||
model=self.embedding, input=text
|
||||
)
|
||||
return response.data[0].embedding
|
||||
if self.embedding_backend == "huggingface":
|
||||
# HuggingFace SentenceTransformer - returns numpy array or list
|
||||
embedding = self.client.encode(text)
|
||||
# Convert to list if needed
|
||||
if hasattr(embedding, 'tolist'):
|
||||
return embedding.tolist()
|
||||
return list(embedding)
|
||||
else:
|
||||
# OpenAI or Ollama - use OpenAI API format
|
||||
response = self.client.embeddings.create(
|
||||
model=self.embedding, input=text
|
||||
)
|
||||
return response.data[0].embedding
|
||||
|
||||
def add_situations(self, situations_and_advice):
|
||||
"""Add financial situations and their corresponding advice. Parameter is a list of tuples (situation, rec)"""
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ DEFAULT_CONFIG = {
|
|||
"dataflows/data_cache",
|
||||
),
|
||||
# LLM settings
|
||||
"llm_provider": "openai",
|
||||
"llm_provider": "openai", # Options: openai, ollama, openrouter, deepseek, anthropic, google
|
||||
"deep_think_llm": "o4-mini",
|
||||
"quick_think_llm": "gpt-4o-mini",
|
||||
"backend_url": "https://api.openai.com/v1",
|
||||
|
|
|
|||
|
|
@ -102,6 +102,47 @@ class TradingAgentsGraph:
|
|||
api_key=openrouter_key,
|
||||
default_headers=default_headers
|
||||
)
|
||||
elif self.config["llm_provider"].lower() == "deepseek":
|
||||
# DeepSeek requires explicit API key handling
|
||||
deepseek_key = os.getenv("DEEPSEEK_API_KEY")
|
||||
if not deepseek_key:
|
||||
raise ValueError(
|
||||
"DEEPSEEK_API_KEY environment variable is required for DeepSeek provider. "
|
||||
"Get your API key from https://platform.deepseek.com/"
|
||||
)
|
||||
|
||||
# DeepSeek requires specific headers for attribution
|
||||
default_headers = {
|
||||
"HTTP-Referer": "https://github.com/TauricResearch/TradingAgents",
|
||||
"X-Title": "TradingAgents"
|
||||
}
|
||||
|
||||
# Use backend_url from config, with fallback to default DeepSeek API only if not set at all
|
||||
base_url = self.config.get("backend_url")
|
||||
if base_url is None and "backend_url" not in self.config:
|
||||
base_url = "https://api.deepseek.com/v1"
|
||||
elif base_url == "":
|
||||
# Keep empty string if explicitly set
|
||||
pass
|
||||
elif base_url is None:
|
||||
# Keep None if explicitly set
|
||||
pass
|
||||
else:
|
||||
# Use the provided value
|
||||
pass
|
||||
|
||||
self.deep_thinking_llm = ChatOpenAI(
|
||||
model=self.config["deep_think_llm"],
|
||||
base_url=base_url,
|
||||
api_key=deepseek_key,
|
||||
default_headers=default_headers
|
||||
)
|
||||
self.quick_thinking_llm = ChatOpenAI(
|
||||
model=self.config["quick_think_llm"],
|
||||
base_url=base_url,
|
||||
api_key=deepseek_key,
|
||||
default_headers=default_headers
|
||||
)
|
||||
elif self.config["llm_provider"].lower() == "anthropic":
|
||||
self.deep_thinking_llm = ChatAnthropic(model=self.config["deep_think_llm"], base_url=self.config["backend_url"])
|
||||
self.quick_thinking_llm = ChatAnthropic(model=self.config["quick_think_llm"], base_url=self.config["backend_url"])
|
||||
|
|
|
|||
Loading…
Reference in New Issue