This commit is contained in:
MarkLo 2025-11-23 07:31:00 +08:00
parent 91ad37412b
commit 02b83a6ddd
8 changed files with 502 additions and 160 deletions

237
README.md
View File

@ -21,16 +21,70 @@
### 🎯 核心特色
- 🤖 **多代理協作架構** - 專業化的 AI 代理團隊協同工作
- 🌐 **多模型靈活支援** - 支援 OpenAI、Anthropic、Grok、DeepSeek、Qwen 等多家 LLM 提供商
- 🔧 **自訂端點配置** - 完整支援自訂 API 端點,可連接任何 OpenAI 兼容的服務
- 📊 **全方位市場分析** - 整合技術面、基本面、情緒面、新聞面分析
- 🔄 **結構化決策流程** - 透過看漲/看跌辯論機制減少偏見
- 🧠 **長期記憶系統** - 使用 ChromaDB 向量數據庫儲存歷史決策
- 🎨 **現代化 Web 介面** - 基於 Next.js 16 的響應式 UI
- 🔌 **RESTful API** - 完整的後端 API 支援
- 🐳 **一鍵部署** - 支援 Docker Compose 和 Railway 部署
- 🐳 **一鍵部署** - 支援 Docker Compose 部署
- 🔑 **BYOK (Bring Your Own Key)** - 使用者自帶 API 金鑰,保障隱私與成本控制
---
## 🤖 LLM 模型支援
TradingAgents 支援業界領先的多家 LLM 提供商,並為每個模型配置**獨立的 API Key 和 Base URL**,實現最大靈活性。
### 📋 支援的 LLM 提供商矩陣
| 提供商 | 支援模型 | Base URL | 是否支援自訂端點 |
|--------|---------|----------|----------------|
| **OpenAI** | GPT-5.1, GPT-5 Mini/Nano, GPT-4.1 Mini/Nano, o4-mini | `https://api.openai.com/v1` | ✅ 是 |
| **Anthropic** | Claude Haiku 4.5, Claude Sonnet 4.5/4.0, Claude 3.5 Haiku, Claude 3 Haiku | `https://api.anthropic.com/` | ✅ 是 |
| **Grok (xAI)** | Grok-4.1 Fast, Grok-4 Fast, Grok-4, Grok-3, Grok-3 Mini | `https://api.x.ai/v1` | ✅ 是 |
| **DeepSeek** | DeepSeek Reasoner, DeepSeek Chat | `https://api.deepseek.com` | ✅ 是 |
| **Qwen (Alibaba)** | Qwen3-Max, Qwen-Plus | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` | ✅ 是 |
| **自訂端點** | 任何 OpenAI 兼容的 API | 使用者自訂 | ✅ 完全支援 |
### 🔧 三層獨立配置
系統支援**三個獨立的 LLM 配置點**,每個都可使用不同的提供商和 API Key
#### 1⃣ 快速思維模型 (Quick Thinking)
用於快速分析和即時回應(市場分析師、情緒分析師等)
#### 2⃣ 深層思維模型 (Deep Thinking)
用於複雜推理和深度分析(研究團隊辯論、風險管理等)
#### 3⃣ 嵌入模型 (Embedding)
用於向量記憶體系統ChromaDB 嵌入生成)
**配置示例:**
```yaml
快速思維: OpenAI GPT-5 Mini @ api.openai.com
深層思維: Anthropic Claude Sonnet 4.5 @ api.anthropic.com
嵌入模型: 自訂端點 @ your-custom-endpoint.com
```
### 🌍 自訂端點支援
**完整支援自訂 API 端點**,任何實現 OpenAI Chat Completions API 規範的服務都可以使用:
✅ **支援場景:**
- 私有化部署的 LLM 服務
- 第三方 OpenAI 兼容代理
- 本地運行的 LLM如 Ollama、LocalAI
- 企業內部 AI Gateway
**配置方式:**
1. 在前端表單的 Base URL 輸入框直接輸入自訂 URL
2. 填入對應的 API Key
3. 系統自動使用您的端點進行推理
---
## 🏗️ 系統架構
TradingAgents 採用前後端分離架構,後端使用 FastAPI 提供 RESTful API前端使用 Next.js 打造現代化的使用者介面。
@ -48,14 +102,14 @@ TradingAgents/
│ │ ├── routes.py # API 端點定義
│ │ └── dependencies.py # 依賴注入
│ ├── core/ # 核心配置
│ │ ├── config.py # 環境變數與設定
│ │ ├──config.py # 環境變數與設定
│ │ └── cors.py # CORS 中間件配置
│ ├── models/ # 資料模型
│ │ └── schemas.py # Pydantic 資料結構
│ └── services/ # 業務邏輯層
│ ├── trading_service.py # TradingAgents 核心整合
│ └── price_service.py # 股價資料處理服務
│ └── task_manager.py # 異步任務管理
├── frontend/ # Next.js 前端應用
│ ├── app/ # Next.js App Router
│ │ ├── layout.tsx # 根佈局組件
@ -72,10 +126,7 @@ TradingAgents/
│ │ ├── layout/ # 佈局組件
│ │ │ ├── Header.tsx # 頂部導航欄
│ │ │ └── Footer.tsx # 頁腳
│ │ ├── shared/ # 共用組件
│ │ └── ui/ # shadcn/ui 基礎組件
│ ├── context/ # React Context API
│ │ └── AnalysisContext.tsx # 分析狀態管理
│ ├── hooks/ # 自定義 React Hooks
│ │ ├── useAnalysis.ts # 分析請求管理
│ │ └── useConfig.ts # 配置資料獲取
@ -83,7 +134,7 @@ TradingAgents/
│ ├── api.ts # API 客戶端封裝
│ ├── types.ts # TypeScript 型別定義
│ └── utils.ts # 通用輔助函式
└── tradingagents/ # 核心 Python 套件
├── agents/ # AI 代理定義
├── dataflows/ # 資料流處理
@ -103,6 +154,7 @@ TradingAgents/
| **yfinance** | 股票市場資料獲取 | ≥0.2.63 |
| **Uvicorn** | ASGI 伺服器 | ≥0.24.0 |
| **python-dotenv** | 環境變數管理 | 1.0.0 |
| **Redis** | 任務隊列與緩存 | Latest |
#### 其他整合
- **stockstats**: 技術指標計算
@ -141,9 +193,19 @@ TradingAgents/
#### 必要的 API 金鑰
- **OpenAI API Key** (必需) - 用於驅動 AI 代理
根據您選擇的 LLM 提供商,準備相應的 API 金鑰:
- **OpenAI API Key** - GPT 系列模型
- 申請網址: https://platform.openai.com/api-keys
- **Alpha Vantage API Key** (可選) - 用於更詳細的股票資料
- **Anthropic API Key** - Claude 系列模型
- 申請網址: https://console.anthropic.com
- **Grok API Key** - Grok 系列模型
- 申請網址: https://console.x.ai
- **DeepSeek API Key** - DeepSeek 系列模型
- 申請網址: https://platform.deepseek.com
- **Qwen API Key** - Qwen 系列模型
- 申請網址: https://www.alibabacloud.com
- **Alpha Vantage API Key** (必需) - 股票基本面資料
- 申請網址: https://www.alphavantage.co/support/#api-key
> 💡 **提示**: 本系統採用 BYOK (Bring Your Own Key) 模式,您可以在前端介面直接輸入 API 金鑰,無需設定環境變數(適合快速測試)。
@ -185,7 +247,7 @@ pip install -e .
pip install -r backend/requirements.txt
```
##### 2.3 配置環境變數
##### 2.3 配置環境變數(可選)
複製範例環境變數檔案並編輯:
@ -196,19 +258,25 @@ cp .env.example .env
編輯 `.env` 檔案,填入您的 API 金鑰:
```bash
# ============ API 金鑰配置 ============
# OpenAI API (必需)
# ============ LLM API 金鑰配置 ============
# OpenAI (可選 - 可在前端直接輸入)
OPENAI_API_KEY=sk-your-openai-api-key-here
OPENAI_BASE_URL=https://api.openai.com/v1
# Alpha Vantage API (可選)
# Anthropic Claude (可選)
ANTHROPIC_API_KEY=sk-ant-your-claude-key
# Grok / xAI (可選)
XAI_API_KEY=your-grok-key
# DeepSeek (可選)
DEEPSEEK_API_KEY=your-deepseek-key
# Qwen / Alibaba Cloud (可選)
DASHSCOPE_API_KEY=your-qwen-key
# Alpha Vantage (強烈建議 - 用於基本面數據)
ALPHA_VANTAGE_API_KEY=your-alpha-vantage-key
# 其他 LLM 提供商 (可選)
ANTHROPIC_API_KEY=your-claude-api-key
GOOGLE_API_KEY=your-gemini-api-key
GOOGLE_API_KEY=your-gemini-api-key
# ============ 後端服務配置 ============
BACKEND_HOST=0.0.0.0
BACKEND_PORT=8000
@ -220,6 +288,8 @@ CORS_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
TRADINGAGENTS_RESULTS_DIR=./results
```
> 📝 **注意**: 環境變數中的 API Key 為可選配置。您可以在前端表單中直接輸入,系統會優先使用前端輸入的 Key。
##### 2.4 啟動後端服務
```bash
@ -276,17 +346,27 @@ npm --prefix frontend run dev
最簡單的部署方式,一鍵啟動前後端服務:
**前置要求:**
- Docker Engine 20.10+
- Docker Compose V2
**部署步驟:**
```bash
# 啟動所有服務(首次執行會自動構建映像)
# 1. 確保 .env 文件已配置(至少包含 Alpha Vantage API Key
cp .env.example .env
# 編輯 .env填入必要的 API 金鑰
# 2. 啟動所有服務(首次執行會自動構建映像)
docker compose up -d --build
# 查看服務運行狀態
# 3. 查看服務運行狀態
docker compose ps
# 查看即時日誌
# 4. 查看即時日誌
docker compose logs -f
# 查看特定服務日誌
# 5. 查看特定服務日誌
docker compose logs -f backend
docker compose logs -f frontend
@ -301,6 +381,7 @@ docker compose down -v
- 後端服務運行於: `http://localhost:8000`
- 前端服務運行於: `http://localhost:3000`
- 分析結果會持久化儲存在 `./results` 目錄
- 環境變數從 `.env` 文件自動載入
---
@ -318,6 +399,8 @@ docker compose down -v
3. **配置分析參數**
#### 📊 基本設定
- **選擇分析師團隊**: 勾選您需要的分析師類型
- ✅ 市場分析師 (Market Analyst) - 技術分析與價格走勢
- ✅ 情緒分析師 (Sentiment Analyst) - 社交媒體情緒評估
@ -335,31 +418,44 @@ docker compose down -v
- 🟡 **中等 (Medium)**: 平衡速度與深度
- 🔴 **深層 (Deep)**: 全面深入分析,耗時較長
- **選擇 LLM 模型**:
系統提供兩種類型的模型配置:
**快速思維模型** (用於快速分析和即時回應):
- `gpt-5.1-2025-11-13` - GPT-5.1 (最新)
- `gpt-5-mini-2025-08-07` - GPT-5 Mini (預設)
- `gpt-5-nano-2025-08-07` - GPT-5 Nano
- `gpt-4.1-mini` - GPT-4.1 Mini
- `gpt-4.1-nano` - GPT-4.1 Nano
- `o4-mini-2025-04-16` - o4-mini
**深層思維模型** (用於複雜推理和深度分析):
- `gpt-5.1-2025-11-13` - GPT-5.1 (最新)
- `gpt-5-mini-2025-08-07` - GPT-5 Mini (預設)
- `gpt-5-nano-2025-08-07` - GPT-5 Nano
- `gpt-4.1-mini` - GPT-4.1 Mini
- `gpt-4.1-nano` - GPT-4.1 Nano
- `o4-mini-2025-04-16` - o4-mini
> 💡 **提示**: 快速思維模型用於初步分析和資料收集,深層思維模型用於複雜決策和策略制定。您可以根據需求選擇不同的模型組合。
#### 🤖 LLM 模型配置
- **輸入 API 金鑰**:
- 在表單中直接輸入您的 OpenAI API Key
- 或使用環境變數預設值(如已配置)
系統提供**三個獨立的 LLM 配置選項**,每個都可使用不同的提供商:
**1. 快速思維模型配置**
- **模型選擇**: 從下拉選單選擇模型OpenAI, Anthropic, Grok, DeepSeek, Qwen
- **Base URL**: 直接輸入自訂端點 URL例如`https://api.your-custom-endpoint.com/v1`
- **API Key**: 輸入對應的 API 金鑰
**2. 深層思維模型配置**
- **模型選擇**: 可選擇與快速思維不同的模型
- **Base URL**: 支援不同的端點
- **API Key**: 支援不同的金鑰
**3. 嵌入模型配置**
- **Base URL**: 下拉選擇 OpenAI 或自訂端點
- **API Key**: 若留空則使用環境變數 `OPENAI_API_KEY`
**配置示例:**
```
快速思維模型: gpt-5-mini-2025-08-07
快速思維 Base URL: https://api.openai.com/v1
快速思維 API Key: sk-your-openai-key
深層思維模型: claude-sonnet-4-5-20250929
深層思維 Base URL: https://api.anthropic.com/
深層思維 API Key: sk-ant-your-claude-key
嵌入模型 Base URL: 自訂 → https://api.your-embedding-service.com/v1
嵌入模型 API Key: your-embedding-key
```
> 💡 **靈活性**: 您可以混合使用不同提供商的模型,例如用 OpenAI 做快速分析,用 Claude 做深度推理,用自訂端點做嵌入生成。
#### 🔑 API 金鑰配置
- **Alpha Vantage API Key** (必填): 用於獲取股票基本面數據
- 如未在環境變數中配置 LLM API Key需在此填入
4. **執行分析**
- 檢查所有參數無誤後,點擊「執行分析」按鈕
@ -399,7 +495,7 @@ docker compose down -v
curl http://localhost:8000/api/health
```
#### 執行股票分析
#### 執行股票分析(使用自訂端點)
```bash
curl -X POST http://localhost:8000/api/analyze \
@ -407,10 +503,17 @@ curl -X POST http://localhost:8000/api/analyze \
-d '{
"ticker": "NVDA",
"analysis_date": "2024-01-15",
"research_depth": "medium",
"model": "gpt-5-mini-2025-08-07",
"selected_analysts": ["market", "sentiment", "news", "fundamental"],
"api_key": "sk-your-openai-key"
"research_depth": 2,
"deep_think_llm": "claude-sonnet-4-5-20250929",
"quick_think_llm": "gpt-5-mini-2025-08-07",
"analysts": ["market", "sentiment", "news", "fundamental"],
"quick_think_base_url": "https://api.openai.com/v1",
"deep_think_base_url": "https://api.anthropic.com/",
"embedding_base_url": "https://api.openai.com/v1",
"quick_think_api_key": "sk-your-openai-key",
"deep_think_api_key": "sk-ant-your-claude-key",
"embedding_api_key": "sk-your-embedding-key",
"alpha_vantage_api_key": "your-alpha-vantage-key"
}'
```
@ -492,31 +595,25 @@ TradingAgents 模擬真實交易公司的組織架構,每個代理都有其專
### 智能特性
#### 1. 動態研究深度調整
- **Shallow**: 每個代理進行 1 輪分析,適合快速決策
- **Medium**: 每個代理進行 2-3 輪分析,平衡深度與速度
- **Deep**: 每個代理進行 5+ 輪分析,全面深入研究
- **Shallow (1)**: 每個代理進行 1 輪分析,適合快速決策
- **Medium (2)**: 每個代理進行 2 輪分析,平衡深度與速度
- **Deep (3+)**: 每個代理進行 3+ 輪分析,全面深入研究
#### 2. 多模型支持
- 支援 OpenAI (GPT-4, GPT-4o, o1 系列)
- 支援 Anthropic Claude
- 支援 Google Gemini
- 支援 Google Gemini
#### 3. 長期記憶系統
#### 2. 長期記憶系統
- 使用 ChromaDB 向量資料庫儲存歷史決策
- 代理可以參考過去類似情況的決策
- 持續學習與改進分析品質
#### 4. 結構化輸出
#### 3. 結構化輸出
- 所有報告均採用 Markdown 格式
- 清晰的章節結構
- 支援表格、列表、程式碼區塊等豐富格式
#### 5. 實時資料整合
#### 4. 實時資料整合
- yfinance: 即時股價與歷史資料
- Reddit API: 社群情緒分析
- RSS Feeds: 財經新聞抓取
- Alpha Vantage: 詳細財務資料(可選
- Alpha Vantage: 詳細財務資料(必需
---
@ -530,7 +627,7 @@ TradingAgents 模擬真實交易公司的組織架構,每個代理都有其專
### 分析配置頁面
直觀的表單介面,輕鬆配置所有分析參數
直觀的表單介面,輕鬆配置所有分析參數(包含多模型和自訂端點配置)
![分析配置頁面](web_screenshot/2.png)
@ -589,3 +686,9 @@ TradingAgents 模擬真實交易公司的組織架構,每個代理都有其專
- [shadcn/ui](https://github.com/shadcn/ui) - 精美的 React 組件庫
- [ChromaDB](https://github.com/chroma-core/chroma) - AI 原生向量資料庫
- [yfinance](https://github.com/ranaroussi/yfinance) - Yahoo Finance 資料下載工具
---
## 📄 License
本專案採用 Apache 2.0 許可證 - 查看 [LICENSE](LICENSE) 文件了解詳情。

View File

@ -93,6 +93,12 @@ async def run_analysis(
quick_think_llm=request.quick_think_llm,
openai_api_key=request.openai_api_key or "", # Pass empty string if None, service handles it
openai_base_url=request.openai_base_url,
quick_think_base_url=request.quick_think_base_url,
deep_think_base_url=request.deep_think_base_url,
quick_think_api_key=request.quick_think_api_key or "",
deep_think_api_key=request.deep_think_api_key or "",
embedding_base_url=request.embedding_base_url,
embedding_api_key=request.embedding_api_key or "",
alpha_vantage_api_key=request.alpha_vantage_api_key,
))

View File

@ -22,11 +22,27 @@ class AnalysisRequest(BaseModel):
openai_api_key: Optional[str] = Field(None, description="OpenAI API Key (optional if set on server)", min_length=0)
openai_base_url: Optional[str] = Field(
default="https://api.openai.com/v1",
description="OpenAI API Base URL (optional)"
description="OpenAI API Base URL (optional, deprecated)"
)
alpha_vantage_api_key: Optional[str] = Field(
None,
description="Alpha Vantage API Key (optional, for enhanced data)"
quick_think_base_url: Optional[str] = Field(
default="https://api.openai.com/v1",
description="Base URL for Quick Thinking Model"
)
deep_think_base_url: Optional[str] = Field(
default="https://api.openai.com/v1",
description="Base URL for Deep Thinking Model"
)
quick_think_api_key: Optional[str] = Field(None, description="API Key for Quick Thinking Model", min_length=0)
deep_think_api_key: Optional[str] = Field(None, description="API Key for Deep Thinking Model", min_length=0)
embedding_base_url: Optional[str] = Field(
default="https://api.openai.com/v1",
description="Base URL for Embedding Model"
)
embedding_api_key: Optional[str] = Field(None, description="API Key for Embedding Model", min_length=0)
alpha_vantage_api_key: str = Field(
...,
description="Alpha Vantage API Key (required for fundamental data)",
min_length=1
)

View File

@ -42,8 +42,14 @@ class TradingService:
self,
ticker: str,
analysis_date: str,
openai_api_key: str,
openai_api_key: Optional[str] = None,
openai_base_url: str = "https://api.openai.com/v1",
quick_think_base_url: str = "https://api.openai.com/v1",
deep_think_base_url: str = "https://api.openai.com/v1",
quick_think_api_key: Optional[str] = None,
deep_think_api_key: Optional[str] = None,
embedding_base_url: str = "https://api.openai.com/v1",
embedding_api_key: Optional[str] = None,
alpha_vantage_api_key: Optional[str] = None,
analysts: Optional[List[str]] = None,
research_depth: int = 1,
@ -57,7 +63,9 @@ class TradingService:
ticker: Stock ticker symbol
analysis_date: Date in YYYY-MM-DD format
openai_api_key: OpenAI API Key (required)
openai_base_url: OpenAI API Base URL (optional)
openai_base_url: OpenAI API Base URL (optional, deprecated)
quick_think_base_url: Base URL for Quick Thinking Model
deep_think_base_url: Base URL for Deep Thinking Model
alpha_vantage_api_key: Alpha Vantage API Key (optional)
analysts: List of analyst types to include
research_depth: Research depth (1-5)
@ -78,13 +86,7 @@ class TradingService:
original_alpha_key = os.environ.get("ALPHA_VANTAGE_API_KEY")
try:
# Set API keys for this request
if openai_api_key:
os.environ["OPENAI_API_KEY"] = openai_api_key
elif not os.environ.get("OPENAI_API_KEY"):
# If no key provided and no env var, this will fail later, but let's log it
logger.warning("No OpenAI API key provided in request or environment")
# Set Alpha Vantage API key if provided
if alpha_vantage_api_key:
os.environ["ALPHA_VANTAGE_API_KEY"] = alpha_vantage_api_key
@ -92,9 +94,33 @@ class TradingService:
logger.info(f"Initializing TradingAgents for {ticker} on {analysis_date}")
config = self.create_config(research_depth, deep_think_llm, quick_think_llm)
# Normalize base URLs (ensure lowercase paths, common issue with custom endpoints)
def normalize_base_url(url: str) -> str:
"""Normalize base URL to ensure proper formatting"""
if url:
# Replace common case variations
url = url.replace("/V1", "/v1")
url = url.replace("/V2", "/v2")
return url
# Override with user-provided settings
config["llm_provider"] = "openai"
config["backend_url"] = openai_base_url
# Use specific base URLs if provided, otherwise fallback to openai_base_url
config["quick_think_base_url"] = normalize_base_url(
quick_think_base_url if quick_think_base_url != "https://api.openai.com/v1" else openai_base_url
)
config["deep_think_base_url"] = normalize_base_url(
deep_think_base_url if deep_think_base_url != "https://api.openai.com/v1" else openai_base_url
)
# Set backend_url as a fallback
config["backend_url"] = normalize_base_url(openai_base_url)
# Resolve API keys: Use specific key if provided, else fallback to openai_api_key (legacy/shared)
# Note: For non-OpenAI providers, the user MUST provide the specific key if it differs from the shared one.
config["quick_think_api_key"] = quick_think_api_key if quick_think_api_key else openai_api_key
config["deep_think_api_key"] = deep_think_api_key if deep_think_api_key else openai_api_key
config["embedding_base_url"] = normalize_base_url(embedding_base_url)
config["embedding_api_key"] = embedding_api_key if embedding_api_key else openai_api_key
# Initialize TradingAgents graph
graph = TradingAgentsGraph(analysts, config=config, debug=True)
@ -183,10 +209,6 @@ class TradingService:
"claude-sonnet-4-0",
"claude-3-5-haiku-20241022",
"claude-3-haiku-20240307",
# Google
"gemini-2.0-flash-lite",
"gemini-2.0-flash",
"gemini-2.5-flash-lite",
# Grok
"grok-4-1-fast-reasoning",
"grok-4-1-fast-non-reasoning",

View File

@ -197,9 +197,11 @@ def select_shallow_thinking_agent(provider) -> str:
("Claude Haiku 3", "claude-3-haiku-20240307")
],
"google": [
("Gemini 2.0 Flash-Lite", "gemini-2.0-flash-lite"),
("Gemini 2.5 Pro", "gemini-2.5-pro"),
("Gemini 2.5 Flash", "gemini-2.5-flash"),
("Gemini 2.5 Flash Lite", "gemini-2.5-flash-lite"),
("Gemini 2.0 Flash", "gemini-2.0-flash"),
("Gemini 2.5 Flash Lite", "gemini-2.5-flash-lite")
("Gemini 2.0 Flash-Lite", "gemini-2.0-flash-lite")
],
"Grok":[
("Grok 4.1 Fast Reasoning","grok-4-1-fast-reasoning"),
@ -215,7 +217,7 @@ def select_shallow_thinking_agent(provider) -> str:
("DeepSeek Chat","deepseek-chat")
],
"Qwen":[
("Qwen 3.5 Max", "qwen3-max"),
("Qwen 3 Max", "qwen3-max"),
("Qwen Plus", "qwen-plus")
]
}
@ -279,9 +281,11 @@ def select_deep_thinking_agent(provider) -> str:
("Claude Haiku 3", "claude-3-haiku-20240307")
],
"google": [
("Gemini 2.0 Flash-Lite", "gemini-2.0-flash-lite"),
("Gemini 2.5 Pro", "gemini-2.5-pro"),
("Gemini 2.5 Flash", "gemini-2.5-flash"),
("Gemini 2.5 Flash Lite", "gemini-2.5-flash-lite"),
("Gemini 2.0 Flash", "gemini-2.0-flash"),
("Gemini 2.5 Flash Lite", "gemini-2.5-flash-lite")
("Gemini 2.0 Flash-Lite", "gemini-2.0-flash-lite")
],
"Grok":[
("Grok 4.1 Fast Reasoning","grok-4-1-fast-reasoning"),
@ -297,7 +301,7 @@ def select_deep_thinking_agent(provider) -> str:
("DeepSeek Chat","deepseek-chat")
],
"Qwen":[
("Qwen 3.5 Max", "qwen3-max"),
("Qwen 3 Max", "qwen3-max"),
("Qwen Plus", "qwen-plus")
]
}

View File

@ -40,9 +40,13 @@ const formSchema = z.object({
deep_thinking_agent: z.string().min(1, "請選擇深層思維模型"),
// API Configuration
openai_api_key: z.string().optional().or(z.literal("")),
openai_base_url: z.string().url("請輸入有效的 URL").optional().or(z.literal("")),
alpha_vantage_api_key: z.string().optional().or(z.literal("")),
quick_think_base_url: z.string().url("請輸入有效的 URL").optional().or(z.literal("")),
deep_think_base_url: z.string().url("請輸入有效的 URL").optional().or(z.literal("")),
quick_think_api_key: z.string().optional().or(z.literal("")),
deep_think_api_key: z.string().optional().or(z.literal("")),
embedding_base_url: z.string().url("請輸入有效的 URL").optional().or(z.literal("")),
embedding_api_key: z.string().optional().or(z.literal("")),
alpha_vantage_api_key: z.string().min(1, "請輸入 Alpha Vantage API Key"),
});
interface AnalysisFormProps {
@ -67,8 +71,12 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
research_depth: 3, // 預設中等層級
shallow_thinking_agent: "gpt-5-mini-2025-08-07",
deep_thinking_agent: "gpt-5-mini-2025-08-07",
openai_api_key: "",
openai_base_url: "https://api.openai.com/v1",
quick_think_base_url: "https://api.openai.com/v1",
deep_think_base_url: "https://api.openai.com/v1",
quick_think_api_key: "",
deep_think_api_key: "",
embedding_base_url: "https://api.openai.com/v1",
embedding_api_key: "",
alpha_vantage_api_key: "",
},
});
@ -229,7 +237,10 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="選擇模型" />
@ -251,10 +262,6 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
<SelectItem value="claude-3-5-haiku-20241022">Anthropic: Claude 3.5 Haiku</SelectItem>
<SelectItem value="claude-3-haiku-20240307">Anthropic: Claude 3 Haiku</SelectItem>
{/* Google */}
<SelectItem value="gemini-2.0-flash-lite">Google: Gemini 2.0 Flash-Lite</SelectItem>
<SelectItem value="gemini-2.0-flash">Google: Gemini 2.0 Flash</SelectItem>
<SelectItem value="gemini-2.5-flash-lite">Google: Gemini 2.5 Flash Lite</SelectItem>
{/* Grok */}
<SelectItem value="grok-4-1-fast-reasoning">Grok: 4.1 Fast Reasoning</SelectItem>
@ -270,7 +277,7 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
<SelectItem value="deepseek-chat">DeepSeek: Chat</SelectItem>
{/* Qwen */}
<SelectItem value="qwen3-max">Qwen: 3.5 Max</SelectItem>
<SelectItem value="qwen3-max">Qwen: 3 Max</SelectItem>
<SelectItem value="qwen-plus">Qwen: Plus</SelectItem>
</SelectContent>
</Select>
@ -288,7 +295,10 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
render={({ field }) => (
<FormItem>
<FormLabel></FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<Select
onValueChange={field.onChange}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="選擇模型" />
@ -310,10 +320,6 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
<SelectItem value="claude-3-5-haiku-20241022">Anthropic: Claude 3.5 Haiku</SelectItem>
<SelectItem value="claude-3-haiku-20240307">Anthropic: Claude 3 Haiku</SelectItem>
{/* Google */}
<SelectItem value="gemini-2.0-flash-lite">Google: Gemini 2.0 Flash-Lite</SelectItem>
<SelectItem value="gemini-2.0-flash">Google: Gemini 2.0 Flash</SelectItem>
<SelectItem value="gemini-2.5-flash-lite">Google: Gemini 2.5 Flash Lite</SelectItem>
{/* Grok */}
<SelectItem value="grok-4-1-fast-reasoning">Grok: 4.1 Fast Reasoning</SelectItem>
@ -329,7 +335,7 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
<SelectItem value="deepseek-chat">DeepSeek: Chat</SelectItem>
{/* Qwen */}
<SelectItem value="qwen3-max">Qwen: 3.5 Max</SelectItem>
<SelectItem value="qwen3-max">Qwen: 3 Max</SelectItem>
<SelectItem value="qwen-plus">Qwen: Plus</SelectItem>
</SelectContent>
</Select>
@ -343,33 +349,17 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
</div>
</div>
{/* API Configuration Section */}
<div className="space-y-4 border-t pt-6 mt-6">
<h3 className="text-lg font-semibold">API </h3>
<FormField
control={form.control}
name="openai_api_key"
render={({ field }) => (
<FormItem>
<FormLabel>OpenAI API Key</FormLabel>
<FormControl>
<Input type="password" placeholder="sk-..." {...field} />
</FormControl>
<FormDescription>
OpenAI API Key使
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{/* API Configuration Section */}
<div className="space-y-4 border-t pt-6 mt-6">
<h3 className="text-lg font-semibold">API </h3>
<FormField
control={form.control}
name="openai_base_url"
name="quick_think_base_url"
render={({ field }) => (
<FormItem>
<FormLabel>API Base URL</FormLabel>
<FormLabel> Base URL</FormLabel>
<Select
onValueChange={(value) => {
if (value !== "custom") {
@ -377,7 +367,6 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
} else {
field.onChange(""); // Clear value for custom input
}
// Store selection state if needed, or just rely on field value
}}
defaultValue={
[
@ -408,7 +397,6 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
</SelectContent>
</Select>
{/* Show input only when custom is selected or value is not in the list */}
{(![
"https://api.openai.com/v1",
"https://api.anthropic.com/",
@ -429,7 +417,179 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
)}
<FormDescription>
LLM API
API
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="quick_think_api_key"
render={({ field }) => (
<FormItem>
<FormLabel> API Key</FormLabel>
<FormControl>
<Input type="password" placeholder="sk-..." {...field} />
</FormControl>
<FormDescription>
API Key使/
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="deep_think_base_url"
render={({ field }) => (
<FormItem>
<FormLabel> Base URL</FormLabel>
<Select
onValueChange={(value) => {
if (value !== "custom") {
field.onChange(value);
} else {
field.onChange(""); // Clear value for custom input
}
}}
defaultValue={
[
"https://api.openai.com/v1",
"https://api.anthropic.com/",
"https://generativelanguage.googleapis.com/v1",
"https://api.x.ai/v1",
"https://api.deepseek.com",
"https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
].includes(field.value || "")
? field.value
: "custom"
}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="選擇 API 端點" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="https://api.openai.com/v1">OpenAI ()</SelectItem>
<SelectItem value="https://api.anthropic.com/">Anthropic</SelectItem>
<SelectItem value="https://generativelanguage.googleapis.com/v1">Google Gemini</SelectItem>
<SelectItem value="https://api.x.ai/v1">Grok (xAI)</SelectItem>
<SelectItem value="https://api.deepseek.com">DeepSeek</SelectItem>
<SelectItem value="https://dashscope-intl.aliyuncs.com/compatible-mode/v1">Qwen (Alibaba)</SelectItem>
<SelectItem value="custom"></SelectItem>
</SelectContent>
</Select>
{(![
"https://api.openai.com/v1",
"https://api.anthropic.com/",
"https://generativelanguage.googleapis.com/v1",
"https://api.x.ai/v1",
"https://api.deepseek.com",
"https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
].includes(field.value || "") || field.value === "") && (
<div className="mt-2">
<FormControl>
<Input
placeholder="請輸入自訂 Base URL"
value={field.value || ""}
onChange={field.onChange}
/>
</FormControl>
</div>
)}
<FormDescription>
API
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="deep_think_api_key"
render={({ field }) => (
<FormItem>
<FormLabel> API Key</FormLabel>
<FormControl>
<Input type="password" placeholder="sk-..." {...field} />
</FormControl>
<FormDescription>
API Key使/
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="embedding_base_url"
render={({ field }) => (
<FormItem>
<FormLabel> Base URL</FormLabel>
<Select
onValueChange={(value) => {
if (value !== "custom") {
field.onChange(value);
} else {
field.onChange(""); // Clear value for custom input
}
}}
defaultValue={
field.value === "https://api.openai.com/v1" || !field.value
? "https://api.openai.com/v1"
: "custom"
}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="選擇嵌入模型端點" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="https://api.openai.com/v1">OpenAI ()</SelectItem>
<SelectItem value="custom"></SelectItem>
</SelectContent>
</Select>
{field.value !== "https://api.openai.com/v1" && (
<div className="mt-2">
<FormControl>
<Input
placeholder="請輸入自訂 Base URL"
value={field.value || ""}
onChange={field.onChange}
/>
</FormControl>
</div>
)}
<FormDescription>
API
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="embedding_api_key"
render={({ field }) => (
<FormItem>
<FormLabel> API Key</FormLabel>
<FormControl>
<Input type="password" placeholder="sk-..." {...field} />
</FormControl>
<FormDescription>
API Key使 OPENAI_API_KEY
</FormDescription>
<FormMessage />
</FormItem>
@ -441,12 +601,12 @@ export function AnalysisForm({ onSubmit, loading = false }: AnalysisFormProps) {
name="alpha_vantage_api_key"
render={({ field }) => (
<FormItem>
<FormLabel>Alpha Vantage API Key</FormLabel>
<FormLabel>Alpha Vantage API Key *</FormLabel>
<FormControl>
<Input type="password" placeholder="選填,用於更詳細的數據" {...field} />
<Input type="password" placeholder="輸入 Alpha Vantage API Key必填" {...field} />
</FormControl>
<FormDescription>
</FormDescription>
<FormMessage />
</FormItem>

View File

@ -7,9 +7,24 @@ from openai import OpenAI
class FinancialSituationMemory:
def __init__(self, name, config):
self.embedding = "text-embedding-3-small"
# Get the OpenAI API key from environment variable
openai_api_key = os.getenv("OPENAI_API_KEY")
self.client = OpenAI(base_url=config["backend_url"], api_key=openai_api_key)
# Get embedding configuration from config, with fallbacks
embedding_base_url = config.get("embedding_base_url", "https://api.openai.com/v1")
embedding_api_key = config.get("embedding_api_key")
# Fallback chain for API key
if not embedding_api_key:
# Try environment variable first
embedding_api_key = os.getenv("OPENAI_API_KEY")
if not embedding_api_key:
# Fall back to LLM keys as last resort
embedding_api_key = config.get("quick_think_api_key") or config.get("deep_think_api_key")
if embedding_api_key:
import logging
logging.warning("Using LLM API key for embeddings. Consider setting embedding_api_key or OPENAI_API_KEY.")
# Use configured endpoint for embeddings
self.client = OpenAI(base_url=embedding_base_url, api_key=embedding_api_key)
self.chroma_client = chromadb.Client(Settings(allow_reset=True))
self.situation_collection = self.chroma_client.get_or_create_collection(name=name)

View File

@ -81,29 +81,45 @@ class TradingAgentsGraph:
exist_ok=True,
)
# 初始化 LLM
# 初始化 LLM
provider = self.config["llm_provider"].lower()
if provider in ["openai"]:
# Get the OpenAI API key from environment variable
openai_api_key = os.getenv("OPENAI_API_KEY")
self.deep_thinking_llm = ChatOpenAI(
model=self.config["deep_think_llm"],
base_url=self.config["backend_url"],
openai_api_key=openai_api_key
)
self.quick_thinking_llm = ChatOpenAI(
model=self.config["quick_think_llm"],
base_url=self.config["backend_url"],
openai_api_key=openai_api_key
)
elif provider == "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"])
elif provider == "google":
self.deep_thinking_llm = ChatGoogleGenerativeAI(model=self.config["deep_think_llm"])
self.quick_thinking_llm = ChatGoogleGenerativeAI(model=self.config["quick_think_llm"])
else:
raise ValueError(f"不支援的 LLM 供應商: {self.config['llm_provider']}")
# Get base URLs from config, defaulting to backend_url for backward compatibility
deep_base_url = self.config.get("deep_think_base_url", self.config.get("backend_url"))
quick_base_url = self.config.get("quick_think_base_url", self.config.get("backend_url"))
# Get API keys from config
deep_api_key = self.config.get("deep_think_api_key", os.getenv("OPENAI_API_KEY"))
quick_api_key = self.config.get("quick_think_api_key", os.getenv("OPENAI_API_KEY"))
# Helper to initialize LLM based on URL/Provider
def _create_llm(model: str, base_url: str, api_key: str):
# Determine provider based on Base URL
if "anthropic.com" in base_url:
return ChatAnthropic(model=model, base_url=base_url, api_key=api_key)
else:
# Default to ChatOpenAI for OpenAI, Grok, DeepSeek, Qwen, and other OpenAI-compatible APIs
return ChatOpenAI(
model=model,
base_url=base_url,
openai_api_key=api_key
)
# Initialize LLMs independently
print(f"DEBUG: Initializing Deep Thinking LLM: Model={self.config['deep_think_llm']}, BaseURL={deep_base_url}, Key={deep_api_key[:10]}...")
self.deep_thinking_llm = _create_llm(
self.config["deep_think_llm"],
deep_base_url,
deep_api_key
)
print(f"DEBUG: Initializing Quick Thinking LLM: Model={self.config['quick_think_llm']}, BaseURL={quick_base_url}, Key={quick_api_key[:10]}...")
self.quick_thinking_llm = _create_llm(
self.config["quick_think_llm"],
quick_base_url,
quick_api_key
)
# 初始化記憶體
self.bull_memory = FinancialSituationMemory("bull_memory", self.config)