Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ DEEPSEEK_BASE_URL=https://api.deepseek.com/v1
DEFAULT_MODEL_NAME=deepseek-chat


# ========== OrcaRouter API配置(可选)==========
# 设置了 ORCAROUTER_API_KEY 后,系统将优先使用 OrcaRouter 作为 AI 引擎
# OrcaRouter:https://www.orcarouter.ai | API:https://api.orcarouter.ai/v1
ORCAROUTER_API_KEY=
ORCAROUTER_BASE_URL=https://api.orcarouter.ai/v1
ORCAROUTER_MODEL=orcarouter/auto


# ========== You.com Research API配置(可选)==========
# You.com API密钥(用于深度研究功能,需要配合 Research API 使用)
# 获取地址:https://you.com/platform/api-keys
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ playwright install chromium
# ===== 必填 =====
DEEPSEEK_API_KEY=your_key_here # DeepSeek API密钥(核心AI引擎)

# ===== AI 引擎(可选,二选一)=====
# 设置了 ORCAROUTER_API_KEY 后,系统优先使用 OrcaRouter 作为 AI 引擎(无需再配 DeepSeek)
# OrcaRouter:https://www.orcarouter.ai | API:https://api.orcarouter.ai/v1
ORCAROUTER_API_KEY=your_orcarouter_key_here
ORCAROUTER_BASE_URL=https://api.orcarouter.ai/v1
ORCAROUTER_MODEL=orcarouter/auto

# ===== 数据源(可选)=====
TUSHARE_TOKEN=your_token_here # Tushare Token(https://tushare.pro)
YDC_API_KEY=your_ydc_api_key_here # You.com API密钥(智策板块Research功能用)
Expand All @@ -74,6 +81,24 @@ MINIQMT_HOST=127.0.0.1
MINIQMT_PORT=58080
```

### 使用 OrcaRouter 作为 AI 引擎(可选)

[OrcaRouter](https://www.orcarouter.ai) 是 OpenAI 兼容的统一模型网关,一个 key 即可路由到 DeepSeek、Qwen、Kimi 等主流模型。在 `.env` 中设置 `ORCAROUTER_API_KEY` 后,系统将**优先使用 OrcaRouter** 作为所有分析模块的 AI 引擎(无需配置 DeepSeek):

```env
# ===== OrcaRouter 配置 =====
ORCAROUTER_API_KEY=your_orcarouter_key_here
ORCAROUTER_BASE_URL=https://api.orcarouter.ai/v1 # 一般无需修改
ORCAROUTER_MODEL=orcarouter/auto # 自动路由最优模型
```

支持的模型(也可在「环境配置」界面查看与修改):
- `orcarouter/auto` — 自动路由(默认)
- `deepseek/deepseek-v4-pro` — DeepSeek V4 Pro
- `qwen/qwen3.6-flash` — Qwen 3.6 Flash

未设置 `ORCAROUTER_API_KEY` 时,系统照常使用上方 DeepSeek 配置,两者互不影响。

### 测试

You.com Research 模块的测试位于 `utils/test_youchannels_research.py`。
Expand Down Expand Up @@ -208,6 +233,7 @@ python3 -m pytest utils/test_youchannels_research.py::TestGetYoudotcomResearchIn
- ✅ **移除所有模型选择下拉框** — 龙虎榜、主力选股、智策板块等页面不再需要手动选模型
- ✅ **环境配置 UI 新增模型输入** — 在「环境配置」中可直接输入模型名称,附常用模型参考
- ✅ **支持任意 OpenAI 兼容模型** — DeepSeek、通义千问、GPT-4o 等一键切换
- ✅ **支持 [OrcaRouter](https://www.orcarouter.ai) 网关** — 设置 `ORCAROUTER_API_KEY` 后一键切换到 OrcaRouter 统一网关,路由 DeepSeek/Qwen/Kimi 等模型

**切换模型只需一步:**
```env
Expand Down
6 changes: 3 additions & 3 deletions ai_agents.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from deepseek_client import DeepSeekClient
from llm_client import get_llm_client
from typing import Dict, Any
import time
import config

class StockAnalysisAgents:
"""股票分析AI智能体集合"""

def __init__(self, model=None):
self.model = model or config.DEFAULT_MODEL_NAME
self.deepseek_client = DeepSeekClient(model=self.model)
self.deepseek_client = get_llm_client(model=model)

def technical_analyst_agent(self, stock_info: Dict, stock_data: Any, indicators: Dict) -> Dict[str, Any]:
"""技术面分析智能体"""
Expand Down
56 changes: 56 additions & 0 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2234,6 +2234,62 @@ def display_config_manager():

st.info("💡 如何获取DeepSeek API密钥?\n\n1. 访问 https://platform.deepseek.com\n2. 注册/登录账号\n3. 进入API密钥管理页面\n4. 创建新的API密钥\n5. 复制密钥并粘贴到上方输入框")

st.markdown("---")
st.markdown("### OrcaRouter API配置(可选)")
st.markdown("[OrcaRouter](https://www.orcarouter.ai) 是 OpenAI 兼容的统一模型网关,设置 `ORCAROUTER_API_KEY` 后将优先使用 OrcaRouter 作为 AI 引擎(未设置时仍使用上方 DeepSeek)。")
st.markdown("OrcaRouter: https://api.orcarouter.ai/v1")

orca_api_key_info = config_info["ORCAROUTER_API_KEY"]
current_orca_api_key = st.session_state.temp_config.get("ORCAROUTER_API_KEY", "")

new_orca_api_key = st.text_input(
f"🔑 {orca_api_key_info['description']}",
value=current_orca_api_key,
type="password",
help="从 https://www.orcarouter.ai 获取API密钥",
key="input_orcarouter_api_key"
)
st.session_state.temp_config["ORCAROUTER_API_KEY"] = new_orca_api_key

if new_orca_api_key:
st.success("✅ OrcaRouter 已启用,系统将使用 OrcaRouter 引擎")

st.markdown("---")

orca_base_url_info = config_info["ORCAROUTER_BASE_URL"]
current_orca_base_url = st.session_state.temp_config.get("ORCAROUTER_BASE_URL", "https://api.orcarouter.ai/v1")

new_orca_base_url = st.text_input(
f"🌐 {orca_base_url_info['description']}",
value=current_orca_base_url,
help="一般无需修改,保持默认即可",
key="input_orcarouter_base_url"
)
st.session_state.temp_config["ORCAROUTER_BASE_URL"] = new_orca_base_url

st.markdown("---")

orca_model_info = config_info["ORCAROUTER_MODEL"]
current_orca_model = st.session_state.temp_config.get("ORCAROUTER_MODEL", "orcarouter/auto")

new_orca_model = st.text_input(
f"🤖 {orca_model_info['description']}",
value=current_orca_model,
help="OrcaRouter 自动路由模型,修改后重启生效",
key="input_orcarouter_model"
)
st.session_state.temp_config["ORCAROUTER_MODEL"] = new_orca_model

if new_orca_model:
st.success(f"✅ OrcaRouter 模型: **{new_orca_model}**")

st.markdown("""
**OrcaRouter 常用模型:**
- `orcarouter/auto` — 自动路由(默认)
- `deepseek/deepseek-v4-pro` — DeepSeek V4 Pro
- `qwen/qwen3.6-flash` — Qwen 3.6 Flash
""")

with tab2:
st.markdown("### Tushare数据接口(可选)")
st.markdown("Tushare提供更丰富的A股财务数据,配置后可以获取更详细的财务分析。")
Expand Down
15 changes: 12 additions & 3 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@
DEEPSEEK_API_KEY = os.getenv("DEEPSEEK_API_KEY", "")
DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1")

# You.com API配置
YDC_API_KEY = os.getenv("YDC_API_KEY", "")
# OrcaRouter API配置(可选,设置了 ORCAROUTER_API_KEY 后作为 AI 引擎使用)
ORCAROUTER_API_KEY = os.getenv("ORCAROUTER_API_KEY", "")
ORCAROUTER_BASE_URL = os.getenv("ORCAROUTER_BASE_URL", "https://api.orcarouter.ai/v1")
ORCAROUTER_MODEL = os.getenv("ORCAROUTER_MODEL", "orcarouter/auto")

# 默认AI模型名称(支持任何OpenAI兼容的模型)
DEFAULT_MODEL_NAME = os.getenv("DEFAULT_MODEL_NAME", "deepseek-chat")
# 设置了 ORCAROUTER_API_KEY 时,默认模型自动落到 OrcaRouter 的配置模型
DEFAULT_MODEL_NAME = os.getenv(
"DEFAULT_MODEL_NAME",
ORCAROUTER_MODEL if ORCAROUTER_API_KEY else "deepseek-chat"
)

# You.com API配置
YDC_API_KEY = os.getenv("YDC_API_KEY", "")

# 其他配置
TUSHARE_TOKEN = os.getenv("TUSHARE_TOKEN", "")
Expand Down
31 changes: 29 additions & 2 deletions config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,24 @@ def __init__(self, env_file: str = ".env"):
"required": False,
"type": "text"
},
"ORCAROUTER_API_KEY": {
"value": "",
"description": "OrcaRouter API密钥(可选,设置后优先使用OrcaRouter引擎)",
"required": False,
"type": "password"
},
"ORCAROUTER_BASE_URL": {
"value": "https://api.orcarouter.ai/v1",
"description": "OrcaRouter API地址",
"required": False,
"type": "text"
},
"ORCAROUTER_MODEL": {
"value": "orcarouter/auto",
"description": "OrcaRouter模型名称",
"required": False,
"type": "text"
},
"TUSHARE_TOKEN": {
"value": "",
"description": "Tushare数据接口Token(可选)",
Expand Down Expand Up @@ -202,6 +220,9 @@ def write_env(self, config: Dict[str, str]) -> bool:
lines.append(f'DEEPSEEK_API_KEY="{full_config.get("DEEPSEEK_API_KEY", "")}"')
lines.append(f'DEEPSEEK_BASE_URL="{full_config.get("DEEPSEEK_BASE_URL", "https://api.deepseek.com/v1")}"')
lines.append(f'DEFAULT_MODEL_NAME="{full_config.get("DEFAULT_MODEL_NAME", "deepseek-chat")}"')
lines.append(f'ORCAROUTER_API_KEY="{full_config.get("ORCAROUTER_API_KEY", "")}"')
lines.append(f'ORCAROUTER_BASE_URL="{full_config.get("ORCAROUTER_BASE_URL", "https://api.orcarouter.ai/v1")}"')
lines.append(f'ORCAROUTER_MODEL="{full_config.get("ORCAROUTER_MODEL", "orcarouter/auto")}"')
lines.append("")

# 数据接口配置
Expand Down Expand Up @@ -240,6 +261,7 @@ def write_env(self, config: Dict[str, str]) -> bool:
# 保留其他非标准自定义键
written_keys = {
"DEEPSEEK_API_KEY", "DEEPSEEK_BASE_URL", "DEFAULT_MODEL_NAME",
"ORCAROUTER_API_KEY", "ORCAROUTER_BASE_URL", "ORCAROUTER_MODEL",
"TUSHARE_TOKEN", "TDX_BASE_URL", "YDC_API_KEY", "YDC_RESEARCH_EFFORT",
"MINIQMT_ENABLED", "MINIQMT_ACCOUNT_ID", "MINIQMT_HOST", "MINIQMT_PORT",
"EMAIL_ENABLED", "SMTP_SERVER", "SMTP_PORT", "EMAIL_FROM", "EMAIL_PASSWORD", "EMAIL_TO",
Expand Down Expand Up @@ -280,17 +302,22 @@ def get_config_info(self) -> Dict[str, Dict[str, Any]]:

def validate_config(self, config: Dict[str, str]) -> tuple[bool, str]:
"""验证配置"""
# 设置了 OrcaRouter 密钥时,DeepSeek 密钥不再是必填项(二选一)
has_orcarouter = bool(config.get("ORCAROUTER_API_KEY"))

# 检查必填项
for key, info in self.default_config.items():
if key == "DEEPSEEK_API_KEY" and has_orcarouter:
continue
if info["required"] and not config.get(key):
return False, f"必填项 {info['description']} 不能为空"

# 验证API Key格式(简单检查长度)
if config.get("DEEPSEEK_API_KEY"):
api_key = config.get("DEEPSEEK_API_KEY", "")
if len(api_key) < 20:
return False, "DeepSeek API Key格式不正确(长度太短)"

return True, "配置验证通过"

def reload_config(self):
Expand Down
6 changes: 6 additions & 0 deletions env_example.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
DEEPSEEK_API_KEY=your_deepseek_api_key_here
DEEPSEEK_BASE_URL=https://api.deepseek.com/v1

# OrcaRouter API配置(可选,设置了 ORCAROUTER_API_KEY 后优先使用 OrcaRouter 引擎)
# OrcaRouter:https://www.orcarouter.ai | API:https://api.orcarouter.ai/v1
ORCAROUTER_API_KEY=
ORCAROUTER_BASE_URL=https://api.orcarouter.ai/v1
ORCAROUTER_MODEL=orcarouter/auto

# Tushare数据源配置(备用数据源)
TUSHARE_TOKEN=your_tushare_token_here

Expand Down
21 changes: 21 additions & 0 deletions llm_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""
LLM客户端工厂

根据 .env 配置自动选择 AI 引擎:
- 设置了 ORCAROUTER_API_KEY → 使用 OrcaRouter(命名 OpenAI 兼容网关)
- 否则回落到 DeepSeek(原有默认引擎)
"""
import config
from deepseek_client import DeepSeekClient


def get_llm_client(model=None):
"""返回当前配置对应的 LLM 客户端。

Args:
model: 可选的模型名称覆盖,默认从对应 provider 的配置读取。
"""
if config.ORCAROUTER_API_KEY:
from orcarouter_client import OrcaRouterClient
return OrcaRouterClient(model=model)
return DeepSeekClient(model=model)
6 changes: 3 additions & 3 deletions longhubang_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
专注于龙虎榜数据的多维度分析
"""

from deepseek_client import DeepSeekClient
from llm_client import get_llm_client
from typing import Dict, Any, List
import time
import config


class LonghubangAgents:
"""龙虎榜AI分析师集合"""

def __init__(self, model=None):
self.model = model or config.DEFAULT_MODEL_NAME
self.deepseek_client = DeepSeekClient(model=self.model)
self.deepseek_client = get_llm_client(model=model)
print(f"[智瞰龙虎] AI分析师系统初始化 (模型: {self.model})")

def youzi_behavior_analyst(self, longhubang_data: str, summary: Dict) -> Dict[str, Any]:
Expand Down
4 changes: 2 additions & 2 deletions macro_analysis_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@
from typing import Any, Dict, List

import config
from deepseek_client import DeepSeekClient
from llm_client import get_llm_client


class MacroAnalysisAgents:
"""宏观分析多智能体"""

def __init__(self, model: str | None = None) -> None:
self.model = model or config.DEFAULT_MODEL_NAME
self.client = DeepSeekClient(model=self.model)
self.client = get_llm_client(model=model)

def macro_analyst_agent(self, context_text: str) -> Dict[str, Any]:
prompt = f"""
Expand Down
4 changes: 2 additions & 2 deletions macro_cycle_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
包含四位专业分析师:康波周期分析师、美林时钟分析师、中国政策分析师、首席宏观策略师
"""

from deepseek_client import DeepSeekClient
from llm_client import get_llm_client
from typing import Dict, Any
import time
import config
Expand All @@ -14,7 +14,7 @@ class MacroCycleAgents:

def __init__(self, model=None):
self.model = model or config.DEFAULT_MODEL_NAME
self.deepseek_client = DeepSeekClient(model=self.model)
self.deepseek_client = get_llm_client(model=model)
print(f"[宏观周期] AI智能体系统初始化 (模型: {self.model})")

def kondratieff_wave_agent(self, macro_data_text: str) -> Dict[str, Any]:
Expand Down
1 change: 0 additions & 1 deletion main_force_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from main_force_selector import main_force_selector
from stock_data import StockDataFetcher
from ai_agents import StockAnalysisAgents
from deepseek_client import DeepSeekClient
import time
import json
import config
Expand Down
1 change: 1 addition & 0 deletions model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"moonshotai/Kimi-K2-Instruct-0905": "Kimi (硅基流动)",
"Ring-1T": "蚂蚁百灵 (硅基流动)",
"step3": "阶跃星辰(硅基流动)",
"orcarouter/auto": "OrcaRouter 自动路由",
}

# 获取 .env 中配置的默认模型名称
Expand Down
10 changes: 5 additions & 5 deletions news_flow_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ def __init__(self, model: str = None):
self._init_client()

def _init_client(self):
"""初始化DeepSeek客户端"""
"""初始化AI客户端(OrcaRouter 或 DeepSeek)"""
try:
from deepseek_client import DeepSeekClient
self.deepseek_client = DeepSeekClient(model=self.model)
logger.info(f"✅ DeepSeek客户端初始化成功,模型: {self.model}")
from llm_client import get_llm_client
self.deepseek_client = get_llm_client(model=self.model)
logger.info(f"✅ AI客户端初始化成功,模型: {self.model}")
except Exception as e:
logger.error(f"❌ DeepSeek客户端初始化失败: {e}")
logger.error(f"❌ AI客户端初始化失败: {e}")
self.deepseek_client = None

def is_available(self) -> bool:
Expand Down
23 changes: 23 additions & 0 deletions orcarouter_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import openai
from deepseek_client import DeepSeekClient
import config


class OrcaRouterClient(DeepSeekClient):
"""OrcaRouter API客户端

基于 DeepSeekClient,复用其全部技术面/基本面/资金面分析 prompt 与
chat/completions 调用逻辑,仅替换为 OrcaRouter 的 OpenAI 兼容网关配置。
"""

# 系统的 DeepSeek 默认占位模型名:视为"未显式指定模型"
_deepseek_default_placeholder = "deepseek-chat"

def __init__(self, model=None):
if not model or model == self._deepseek_default_placeholder:
model = config.ORCAROUTER_MODEL
self.model = model
self.client = openai.OpenAI(
api_key=config.ORCAROUTER_API_KEY,
base_url=config.ORCAROUTER_BASE_URL
)
Loading