Skill: boteeenchan-ship-it/alphapai-research (version 1.0.0)

--- name: alphapai-research description: Alpha派金融投研平台API技能，用于调用Alpha派（AlphaPai/PaiPai）的投资研究接口。覆盖五大核心能力：投研知识问答、投研数据检索、投资研究Agent（公司一页纸/业绩点评/调研大纲/主题选股/投资逻辑/可比公司/观点Challenge/行业一页纸/个股选基/主题选基/画图）、股票公告列表查询、投研图表搜索。当用户提到AlphaPai、Alpha派、PaiPai、投研问答、召回数据、公司一页纸、行业一页纸、业绩点评、调研大纲、投资逻辑、可比公司、选基、搜图表等关键词时，务必使用本skill。 metadata: version: 1.0 --- # AlphaPai Research Skill 基于Alpha派（AlphaPai）Open API 的投研能力技能。所有 API 调用均通过 `scripts/alphapai_client.py` 的 CLI 完成，无需手动编写请求代码。 ## 输出原则 Alpha派各接口的返回内容均由专业投研模型生成，质量高、格式完整，**调用完成后应将原始输出直接呈现给用户，不做二次加工**： - **禁止总结或压缩**：不得将原文缩写为摘要，不得用自己的语言改写 - **禁止截断**：无论内容多长，必须完整输出，不得以"以上为主要内容"等方式省略 - **保留原始格式**：Markdown 标题层级、加粗、列表、表格、引用块等结构一律原样保留 - **引用来源随正文输出**：若接口返回了参考来源（文档标题、日期、评分等），紧随正文完整呈现 > 若用户明确要求"总结"或"提炼"，则在完整输出原文之后，再额外附上总结，而非替代原文。 ## 数据背景 Alpha派是讯兔科技开发的金融投研 AI 应用，具有丰富的投研场景数据。不同数据源对应不同质量与特征，对专业用户的判断至关重要： - **路演纪要**：A股上市公司业绩会、券商路演、专家交流等一线会议内容 - **券商点评**：分析师每日给机构投资者发送的点评（时效性高，质量参差不齐） - **微信公众号**：投研相关公众号及上市公司官方公众号内容 - **券商研报**：国内外券商分析师撰写的研究报告 - **公司公告**：上市公司在交易所发布的官方公告（最权威口径） - **图表与数据**：公告/研报图片、表格，及EDB宏观/行业/个股时序数据 ## 首次使用：配置 API Key ```bash python scripts/alphapai_client.py config --set-key YOUR_API_KEY # 非默认服务地址时追加：--set-url https://your-host ``` 查看当前配置： ```bash python scripts/alphapai_client.py config --show ``` > 如用户未提供 api_key，向其说明：可在 Alpha 派💻电脑端获取，或联系客户经理。不要在对话中回显完整 api_key。 ## 接口一：投研知识问答 向 Alpha派的投研助手 PaiPai 提问，获取答案。按照**输出原则**完整呈现回答正文与引用来源，不做二次总结。 ```bash python scripts/alphapai_client.py qa --question "问题内容" [选项] ``` | 选项 | 说明 | | ------------------------- | ----------------------------------------------------------------------------------------------- | | `--question` / `-q` | 问题内容（必填） | | `--mode Flash\|Think` | 问答模式（默认 `Flash`）：`Flash`=简单搜索问答，一问一搜一答；`Think`=Wide Search，一问多搜一答 | | `--context MSG [MSG ...]` | 多轮对话历史，按顺序传入 | | `--web-search` | 开启联网搜索 | | `--deep-reasoning` | 开启深度推理 | | `--start YYYY-MM-DD` | 数据筛选开始日期 | | `--end YYYY-MM-DD` | 数据筛选结束日期 | | `--json` | 输出原始JSON（供程序解析） | **示例：** ```bash # 基础问答（Flash 模式，默认） python scripts/alphapai_client.py qa --question "贵州茅台2024年经营情况如何？" # Think 模式（Wide Search，更广泛检索） python scripts/alphapai_client.py qa --question "贵州茅台2024年经营情况如何？" --mode Think # 多轮对话 python scripts/alphapai_client.py qa \ --question "对行业的影响呢？" \ --context "云南缺电对哪些公司会造成影响？" "影响主要集中在高耗能行业" # 联网搜索 + 时间范围 python scripts/alphapai_client.py qa \ --question "近期新能源车销量趋势" \ --web-search --start 2025-01-01 --end 2025-03-01 ``` ## 接口二：投研知识检索（基于RAG技术） 获取会被送入大模型的原始底层原始数据，适合自行加工或构建自定义 RAG 流程。 ```bash python scripts/alphapai_client.py recall --query "查询问题" [选项] ``` | 选项 | 说明 | | -------------------- | ------------------------------------------------------ | | `--query` / `-q` | 查询问题（必填） | | `--type TYPES` | 数据类型，逗号分隔（不传则全类型） | | `--no-cutoff` | 返回截断前完整内容（默认截断，与送入大模型的数据一致） | | `--start YYYY-MM-DD` | 数据筛选开始日期 | | `--end YYYY-MM-DD` | 数据筛选结束日期 | | `--json` | 输出原始JSON（供程序解析） | **示例：** ```bash # 检索和召回点评和Q&A类型数据 python scripts/alphapai_client.py recall \ --query "贵州茅台2024年市值" \ --type comment,qa \ --start 2025-01-01 --end 2025-03-20 # 检索和召回全类型完整内容 python scripts/alphapai_client.py recall --query "宁德时代电池技术" --no-cutoff ``` ### recallType 枚举值 选择 `--type` 参数时，根据用户意图选择合适的数据类型： | 类型值 | 含义 | 特点 | | ----------------------- | -------------------- | ---------------------------------------- | | `comment` | 券商点评 | 时效性最强，分析师每日发送，质量参差不齐 | | `roadShow` | 路演会议纪要 | 业绩会、券商路演、专家交流，一线投研内容 | | `roadShow_ir` | 上市公司官方路演纪要 | 官方披露，量少 | | `roadShow_us` | 美股 earnings 纪要 | 美股上市公司业绩会 | | `roadShow_od` | 私域会议纪要 | 内部渠道 | | `report` | 国内券商研报 | 深度分析，时效性较低 | | `foreign_report` | 海外券商研报 | 摩根、花旗等头部机构 | | `wechat_public_article` | 微信公众号 | 行业资讯及上市公司官方公众号 | | `ann` | 上市公司官方公告 | 最权威口径，交易所披露 | | `vps` | 基金定期报告 | 基金持仓、季报等 | | `table` | 公告/研报数据表格 | 结构化表格数据 | | `image` | 研报图片 | 产业链图示、指标对比图表 | | `qa` | 调研/路演中提取的Q&A | 结构化问答对 | | `edb` | EDB时序数据库 | 宏观、行业、个股时间序列数据 | ## 接口三：股票 Agent `POST /alpha/open-api/v1/paipai/stock/agent` — SSE 流式，响应结构同接口一。业绩点评场景需先通过接口四获取公告ID。 > **输出要求**：严格遵照**输出原则**。Agent 生成内容通常较长（千字以上），必须完整输出全文，不得以任何形式截断、摘要或改写。Markdown 格式（标题、表格、加粗、列表）和参考来源原样保留。 --- ### mode 1 — 个股业绩点评 根据公司公告、业绩会路演、研报，对公司最新财务报告进行点评。 **必填：** `--stock CODE:NAME` `--report-type TYPE` `--report-id ID` `--report-title TITLE` `--report-period PERIOD`（公告信息先用 `report` 命令查询） **可选：** `--concern TEXT`（用户关注方向） ```bash # Step 1：查询公告列表，获取 report-id 等信息 python scripts/alphapai_client.py report --code 603380.SH # Step 2：调用业绩点评 python scripts/alphapai_client.py agent --mode 1 \ --question "易德龙2025年一季报业绩点评" --stock 603380.SH:易德龙 \ --report-type 季报 --report-id HANNC002658114224 \ --report-title "易德龙:2025年第一季度报告" --report-period "2025年一季报" ``` ### mode 2 — 公司一页纸 基于最新公告、路演、研报、点评，对公司进行初步完整分析。 **必填：** `--stock CODE:NAME` **可选：** `--language 中文|英文`（美股公司可选）`--template-text TEXT`（自定义公司一页纸模板，默认为空） ```bash python scripts/alphapai_client.py agent --mode 2 \ --question "亿纬锂能（300014.SZ）的公司一页纸" --stock 300014.SZ:亿纬锂能 ``` ### mode 3 — 个股调研大纲 快速生成调研一家公司时应该问的问题清单。 **必填：** `--stock CODE:NAME` **可选：** `--template-text TEXT`（调研大纲中希望关注的内容要点，默认为空） ```bash python scripts/alphapai_client.py agent --mode 3 \ --question "巨星科技（002444.SZ）的调研问题大纲" --stock 002444.SZ:巨星科技 ``` ### mode 5 — 主题选股 根据事件、主题、关键词筛选相关股票。（`template` 值自动设为 `1`，无需手动传） **必填：** `--template-text TEXT`（选股主题，与 `--question` 保持一致） ```bash python scripts/alphapai_client.py agent --mode 5 \ --question "和白酒相关的公司" --template-text "和白酒相关的公司" ``` ### mode 7 — 投资逻辑 梳理一家公司近期事件，提炼投资逻辑。 **必填：** `--stock CODE:NAME` **可选：** `--template-text TEXT`（关注的分析要点、维度、指标，默认为空）`--only-answer`（仅返回最终答案，不返回中间过程） ```bash python scripts/alphapai_client.py agent --mode 7 \ --question "亿纬锂能（300014.SZ）的公司投资逻辑" --stock 300014.SZ:亿纬锂能 ``` ### mode 8 — 可比公司 从业务经营、产业链位置上，检索最具对标性的其他公司。（`template` 值自动设为 `1`，无需手动传） **必填：** `--stock CODE:NAME` **可选：** `--concern TEXT`（对比过程中关注的话题，如产品参数、技术路线、估值、盈利等） ```bash python scripts/alphapai_client.py agent --mode 8 \ --question "比亚迪（002594.SZ）的可比公司" --stock 002594.SZ:比亚迪 ``` ### mode 9 — 观点 Challenge 利用Alpha派信息，对用户观点进行逻辑挑战，启发多角度思考。（`template` 值自动设为 `1`，无需手动传） **必填：** `--template-text TEXT`（待 Challenge 的观点，与 `--question` 中观点描述保持一致） **可选：** `--concern TEXT`（关注焦点，进一步限定 Challenge 范围）`--start / --end YYYY-MM-DD`（检索信息时间范围） ```bash python scripts/alphapai_client.py agent --mode 9 \ --question "Challenge该观点：小米汽车未来的增长" \ --template-text "小米汽车未来的增长" \ --concern "YU7" --start 2024-10-25 --end 2025-10-24 ``` ### mode 11 — 行业一页纸 基于最新公告、路演、研报、点评，对一个行业进行初步完整分析（细粒度行业通常更佳）。 **必填：** `--industry NAME`（行业名称） **可选：** `--template-text TEXT`（自定义行业一页纸模板，默认为空） ```bash python scripts/alphapai_client.py agent --mode 11 \ --question "白酒的行业一页纸" --industry 白酒 ``` ### mode 12 — 个股选基 根据个股及公募基金定期报告，查找持仓个股权重最高的基金。 **必填：** `--stock-list CODE:NAME [...]` `--report-date DATE` `--fund-type TYPE` **可选：** `--if-annual 0|1`（是否年报，默认0） ```bash python scripts/alphapai_client.py agent --mode 12 \ --question "环旭电子、中际旭创、卓胜微的持仓基金" \ --report-date 2025-09-30 --fund-type 全部 \ --stock-list 601231.SH:环旭电子 300308.SZ:中际旭创 300782.SZ:卓胜微 ``` ### mode 13 — 主题选基 根据事件、主题、关键词筛选相关基金。 **必填：** `--report-date DATE` `--fund-type 全部|主动|指数|ETF` **可选：** `--if-annual 0|1` ```bash python scripts/alphapai_client.py agent --mode 13 \ --question "2025三季报持有房地产的主动基金有哪些？" \ --report-date 2025-09-30 --fund-type 主动 ``` ### mode 15 — 画图 分析用户 query 并以图片方式展示调研结果，一图胜千言。 **必填：** `--picture-color 主色HEX 辅色HEX` `--picture-style PPT风格|科普风格` > `--picture-color` 固定传**两个** HEX 值（主色、辅色），不含 `#` 前缀，如 `2A66F6 A5A8AF`，不可多传或少传。 **可选：** `--source 0|1`（0=仅图片，1=图文，默认0） ```bash python scripts/alphapai_client.py agent --mode 15 \ --question "黄金" --picture-color 2A66F6 A5A8AF --picture-style 科普风格 ``` ## 接口四：获取股票公告列表 查询某只股票在交易所公开的公告列表，用于获取业绩点评所需的公告ID。 ```bash python scripts/alphapai_client.py report --code <STOCK_CODE> [--json] ``` **示例：** ```bash python scripts/alphapai_client.py report --code 603380.SH ``` ## 接口五：搜图表 从研报和公告中搜索相关图片和表格。 ```bash python scripts/alphapai_client.py image --query "搜索内容" [选项] ``` | 选项 | 说明 | | ---------------------------- | ----------------------------------------------------------------------------------- | | `--query` / `-q` | 搜索内容（必填） | | `--files-range CODE [...]` | 来源类型代码（可多个，默认不限制）：`3`=内资研报 `8`=外资研报 `6`=公告 `9`=三方研报 | | `--topk N` | 返回数量(1-100，默认50) | | `--recall-mode MODE` | 召回模式: both(默认)/vector_only/es_only | | `--llm-rank` | 使用LLM重排序 | | `--start / --end YYYY-MM-DD` | 发布日期范围 | | `--json` | 输出原始JSON | **示例：** ```bash python scripts/alphapai_client.py image --query "新能源车销量趋势" --topk 10 --start 2025-01-01 ``` ## 典型工作流 ### 工作流1：投研问答 1. 确认配置存在（`config --show`） 2. 执行 `qa` 命令，回答和引用来源直接输出 3. 格式化呈现给用户 ### 工作流2：获取原始数据自定义分析 1. 确认配置存在 2. 执行 `recall` 命令，根据需求选择 `--type` 和时间范围 3. 使用 `--json` 输出原始数据供后续脚本处理 ### 工作流3：个股业绩点评（Agent） 1. 确认配置存在 2. 执行 `report --code <CODE>` 查询公告列表，获取 `stockReportId`、`stockReportTitle`、`stockReportPeriod`、`reportType` 3. 执行 `agent --mode 1` 并传入上述公告信息，得到业绩点评 ### 工作流4：其他 Agent 场景 1. 确认配置存在 2. 根据场景选择 `--mode`，按接口三参数表传入必填项 3. 格式化呈现回答和引用来源 ### 工作流5：图表搜索 1. 确认配置存在 2. 执行 `image --query "..."` 搜索相关图表 3. 从返回的 `imageUrl` 获取图片链接展示给用户 ## 升级 Skill 当用户说"帮我升级 alphapai-research skill"或类似意图时，执行以下流程： 1. **获取远程版本信息**：通过 WebFetch 工具拉取安装指引文档：`https://open-api.rabyte.cn/alpha/open-api/v1/file/api-docs/install.md` 2. **解析远程版本**：读取返回文档 frontmatter 中的 `version` 字段 3. **读取本地版本**：读取当前 `SKILL.md` frontmatter 中的 `metadata.version` 字段 4. **版本对比与决策**： - 远程版本 == 本地版本 → 告知用户"alphapai-research 已是最新版本（vX.X.X），无需升级。"流程结束 - 远程版本 > 本地版本 → 按照远程 `install.md` 中的 **升级安装** 流程执行 - 远程版本 < 本地版本 → 告知用户"本地版本高于远程版本，可能使用了预发布版，跳过升级。"流程结束 > 升级安装的具体步骤（下载、解压、备份、替换、验证）完全遵照远程 `install.md` 文档中的指引执行，无需硬编码在此处。 ## 参考文件 | 文件 | 内容 | 何时读取 | | ----------------------------- | ------------------------------------------------ | ---------------------------------------------- | | `references/api_reference.md` | 完整 API 字段定义、响应结构、枚举值（含5个接口） | 调试异常响应、需要完整字段说明、参数含义不明时 | | `scripts/alphapai_client.py` | CLI 源码 + 可导入的 `AlphaPaiClient` 类 | 需要模块化/程序化调用，或排查 CLI 底层行为时 |

path: config.json
content: api_key: niili8cng7x3azxbfxaxgp2m
base_url: https://open-api.rabyte.cn

path: references/api_reference.md
content: # AlphaPai Open API 完整参考文档

本文档包含所有接口的详细字段定义和请求/响应示例。

## 目录

- [AlphaPai Open API 完整参考文档](#alphapai-open-api-完整参考文档)
  - [目录](#目录)
  - [1. 投研知识问答](#1-投研知识问答)
    - [请求参数](#请求参数)
    - [响应参数](#响应参数)
    - [回答类型码(type)](#回答类型码type)
    - [引用数据结构 (references)](#引用数据结构-references)
    - [引用类型枚举](#引用类型枚举)
    - [请求示例 - 非stream模式](#请求示例---非stream模式)
    - [请求示例 - stream模式](#请求示例---stream模式)
  - [2. 投研知识检索（基于RAG技术）](#2-投研知识检索基于rag技术)
    - [请求参数](#请求参数-1)
    - [recallType 可选值](#recalltype-可选值)
    - [响应参数](#响应参数-1)
    - [请求示例](#请求示例)
    - [响应示例](#响应示例)
  - [3. 股票 Agent 接口](#3-股票-agent-接口)
    - [agentMode=1 — 个股业绩点评](#agentmode1--个股业绩点评)
    - [agentMode=2 — 公司一页纸](#agentmode2--公司一页纸)
    - [agentMode=3 — 个股调研大纲](#agentmode3--个股调研大纲)
    - [agentMode=5 — 主题选股](#agentmode5--主题选股)
    - [agentMode=7 — 投资逻辑](#agentmode7--投资逻辑)
    - [agentMode=8 — 可比公司](#agentmode8--可比公司)
    - [agentMode=9 — 观点 Challenge](#agentmode9--观点-challenge)
    - [agentMode=11 — 行业一页纸](#agentmode11--行业一页纸)
    - [agentMode=12 — 个股选基](#agentmode12--个股选基)
    - [agentMode=13 — 主题选基](#agentmode13--主题选基)
    - [agentMode=15 — 画图](#agentmode15--画图)
  - [4. 获取股票公告列表](#4-获取股票公告列表)
    - [请求参数](#请求参数-2)
    - [响应参数 (data 列表每条)](#响应参数-data-列表每条)
  - [5. 搜图表](#5-搜图表)
    - [请求参数](#请求参数-3)
    - [响应参数 (data 列表每条)](#响应参数-data-列表每条-1)

---

## 1. 投研知识问答

**URL**: `POST /alpha/open-api/v1/paipai/qa-text`

> 根据用户输入问题，采用RAG（Retrivel-Augment-Generation，检索增强生成)技术从Alpha派（一个覆盖A股、港股、美股的专业的投资研究SaaS产品）平台获取高质量的投研参考资料，然后进行总结推理回答。

### 请求参数

| 字段                   | 必填 | 类型         | 说明                                  | 示例                                                                                                                  |
| ---------------------- | ---- | ------------ | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| question               | 是   | String       | 问题内容                              | "为什么美光比海力士在hbm的市占率低？"                                                                                 |
| mode                   | 是   | String       | 模式                                  | 字符串枚举，Alpha派问答思考模式，`Flash`——简单搜索问答，一问一搜一答，`Think`——Wide Search，一问多搜一答，默认为Flash |
| context                | 否   | List[String] | 多轮对话上下文                        | ["云南缺电对哪些公司会造成影响？","对行业的影响呢？"]                                                                 |
| questionId             | 否   | String       | 唯一标识，并发时区分问题              | "OZqY\_-E55-M1OwxZTL4U81713770934462"                                                                                 |
| isAutoRoute            | 否   | Boolean      | 自动路由(默认true)，false强制文本回答 | true                                                                                                                  |
| isStream               | 否   | Boolean      | 流式返回(默认false)                   | false                                                                                                                 |
| isWebSearch            | 否   | Boolean      | 联网搜索(默认false)                   | false                                                                                                                 |
| isDeepReasoning        | 否   | Boolean      | 深度推理(默认false)                   | false                                                                                                                 |
| requestSelectStartTime | 否   | String       | 筛选开始时间                          | "2025-01-01"                                                                                                          |
| requestSelectEndTime   | 否   | String       | 筛选结束时间                          | "2025-03-01"                                                                                                          |

### 响应参数

| 字段        | 类型       | 说明                           |
| ----------- | ---------- | ------------------------------ |
| answer      | String     | 回答内容，stream模式逐字返回   |
| answerOrder | Integer    | stream回答序号                 |
| references  | List[Dict] | 引用数据列表（见下方引用结构） |
| type        | Integer    | 回答类型(见下方类型码)         |
| isEnd       | Boolean    | 本次回答是否完结               |
| routeName   | String     | 处理逻辑路由名称               |
| questionId  | String     | 问题唯一标识                   |

### 回答类型码(type)

| 值  | 含义             |
| --- | ---------------- |
| 101 | 问题分析阶段     |
| 102 | 获取资料阶段     |
| 103 | 获取资料完成     |
| 104 | 正在生成回答     |
| 108 | 思维链           |
| 200 | 成功             |
| 201 | 结果为空         |
| 202 | GPT              |
| 203 | 用户当日超过限制 |
| 204 | 系统当前超过限制 |
| 206 | 敏感词限制       |
| 207 | PRE_QA           |
| 500 | 失败             |

### 引用数据结构 (references)

每条引用包含以下字段：

| 字段          | 说明         |
| ------------- | ------------ |
| sentence      | 引用句子     |
| rank          | 排序号       |
| chunk         | 上下文文本   |
| id            | 引用资源id   |
| type          | 引用资源类型 |
| title         | 标题         |
| publishDate   | 发布日期     |
| isSelfPrivate | 是否私有纪要 |
| url           | 引用资源链接 |
| teamName      | 团队名称     |
| instShortName | 机构名称     |
| filePath      | 引用文件路径 |

### 引用类型枚举

| type值                | 含义                 |
| --------------------- | -------------------- |
| comment               | 机构点评             |
| vps                   | 基金定期报告         |
| report                | 内资研报             |
| foreign_report        | 外资研报             |
| wechat_public_article | 公众号               |
| ann                   | 公司公告             |
| roadShow              | 路演纪要             |
| roadShow_ir           | 上市公司披露投关纪要 |
| roadShow_us           | 美股纪要             |
| table                 | 公告/研报表格        |
| image                 | 研报图片             |
| edb                   | EDB数据库            |

### 请求示例 - 非stream模式

```json
{
    "question": "请梳理下国内各车厂智能驾驶的研发进度",
    "questionId": [REDACTED_SECRET],
    "isStream": false,
    "isAutoRoute": false
}
```

### 请求示例 - stream模式

```json
{
    "question": "讯兔科技首席科学家是谁？",
    "questionId": [REDACTED_SECRET],
    "isStream": true,
    "isAutoRoute": false
}
```

---

## 2. 投研知识检索（基于RAG技术）

> 根据用户输入问题，采用RAG（Retrivel-Augment-Generation，检索增强生成)技术从Alpha派（一个覆盖A股、港股、美股的专业的投资研究SaaS产品）平台检索召回高质量的投研参考资料

**URL**: `POST /alpha/open-api/v1/paipai/recall-data`

### 请求参数

| 字段       | 必填 | 类型         | 说明                                                                            | 示例                        |
| ---------- | ---- | ------------ | ------------------------------------------------------------------------------- | --------------------------- |
| query      | 是   | String       | 查询问题                                                                        | "贵州茅台2024年市值多少?"   |
| recallType | 是   | List[String] | 召回数据类型筛选                                                                | ["comment", "qa", "report"] |
| isCutOff   | 否   | Boolean      | 是否截断数据(默认true)。true=与送入大模型一致；false=返回排序后截断前的完整内容 | false                       |
| startTime  | 否   | String       | 开始日期，格式yyyy-MM-dd                                                        | "2025-02-18"                |
| endTime    | 否   | String       | 结束日期，格式yyyy-MM-dd                                                        | "2025-03-20"                |

### recallType 可选值

List[str]，文档类型列表，JSON列表字符串，示例：`[]`表示不限制类型，`["report"]`表示限定`report`类型。支持的报告类型及含义如下：

- "comment"——点评，覆盖了国内券商分析师，每日给专业机构投资者发送的点评信息（时效性高，但可能存在部分吹水、夸大、信息质量不扎实的问题）
- "vps"——基金定期报告
- "report"——国内券商研报，覆盖了国内券商分析师撰写的研究报告
- "foreign_report"——海外券商研报 ，覆盖了国外头部券商分析师（比如摩根、花旗等机构分析师）撰写的研究报告
- "wechat_public_article"——微信公众号，覆盖了国内微信生态下的一些对投研有帮助的公众号内容，包括各个行业主题相关的公众号资讯，以及上市公司官方微信公众号
- "ann"——上市公司官方公告，覆盖了上市公司在交易所发布的最权威最官方口径的公告内容
- "roadShow"——路演会议纪要，包括上市公司的业绩交流会、券商路演、专家交流等各种国内主流路演平台的会议纪要
- "roadShow_ir"——上市公司官方披露的公开路演纪要，量少，且优于披露要求稀缺性弱于`roadShow`类型
- "roadShow_us"——美股上市公司earnings_scripts纪要，
- "roadShow_od"——私域会议纪要，"table"——公告/研报中的数据表格，
- "image"——研报中的图片，通常包括一些产业链图示、指标分析对比的图表等，
- "qa"——基于调研纪要、上市公司公告，提取的调研与路演中的Q&A
- "edb"——宏观经济、行业、个股主题的时间序列数据库

### 响应参数

返回 `data` 为 List，每条数据结构：

| 字段        | 类型         | 说明                             |
| ----------- | ------------ | -------------------------------- |
| id          | String       | 数据ID                           |
| type        | String       | 数据类型（comment/qa/report等）  |
| contextInfo | String       | 上下文信息（含发布时间、标题等） |
| chunks      | List[String] | 文本块内容                       |
| sentenceIds | List[String] | 句子ID列表                       |
| contextText | String       | 问题文本（仅qa类型有值）         |
| answer      | String       | 回答文本（仅qa类型有值）         |

### 请求示例

```json
{
    "query": "贵州茅台2024年市值多少?",
    "isCutOff": false,
    "recallType": ["comment", "qa", "report"],
    "startTime": "2025-02-18",
    "endTime": "2025-03-20"
}
```

### 响应示例

```json
{
    "code": 200000,
    "message": "success",
    "data": [
        {
            "id": "HCMT00000000839881",
            "type": "comment",
            "contextInfo": "(发布时间为:2025-03-18 08:53:40)标题：河南&江苏白酒综合大商交流250317，点评内容：",
            "chunks": ["波汾价格从470-480升至500..."],
            "sentenceIds": ["HCMT00000000839881_11"],
            "contextText": "",
            "answer": ""
        },
        {
            "id": "TRANSQA000011039428",
            "type": "qa",
            "contextInfo": "(发布时间为:2025-02-18)股票：中国神华...",
            "chunks": [],
            "sentenceIds": [],
            "contextText": "2024年年度的分红展望是什么?",
            "answer": "2023年按A股口径分红比例为75.2%..."
        }
    ]
}
```

---

## 3. 股票 Agent 接口

**URL**: `POST /alpha/open-api/v1/paipai/stock/agent`

专业 Agentic-Workflow，支持多种投研场景。SSE 流式返回，响应结构与文本问答接口一致（type 状态码、references 结构相同）。

所有模式均需传 `question`（String，必填），⚠️不同Mode下，question的含义不同，详情参考下文

---

### agentMode=1 — 个股业绩点评

| 字段              | 必填 | 类型    | 说明                                                                                      |
| ----------------- | :--: | ------- | ----------------------------------------------------------------------------------------- |
| question          |  是  | String  | 模板字符串，`{stock.name}{stockReportPeriod}业绩点评`，比如：`易德龙2025年一季报业绩点评` |
| stock             |  是  | Dict    | `{"code": "603380.SH", "name": "易德龙"}`                                                 |
| reportType        |  是  | String  | 报告类型，如 `季报` `年报`                                                                |
| stockReportId     |  是  | String  | 公告ID（先从[4. 获取股票公告列表](#4-获取股票公告列表)查询获取）                          |
| stockReportTitle  |  是  | String  | 公告标题，参考从[4. 获取股票公告列表](#4-获取股票公告列表)查询获取的结果                  |
| stockReportPeriod |  是  | String  | 报告期，如 `2025年一季报`                                                                 |
| template          |  是  | Integer | `0`（固定传alpha派模板）`1` 时需要填写`templateText`字段                                  |
| templateText      |  否  | String  | 用户自己的业绩点评模板                                                                    |
| templateConcern   |  否  | String  | 用户点评中，希望关注的话题、要点                                                          |

如果用户需要根据自己的点评模板填写，需要修改`templateText`，如果额外一些关注话题，修改`templateConcern`

示例参数：

```json
{
    "agentMode": 1,
    "question": "易德龙2025年一季报业绩点评",
    "stock": {
        "code": "603380.SH",
        "name": "易德龙"
    },
    "template": 0,
    "templateText": "",
    "reportType": "季报",
    "stockReportId": "HANNC002658114224",
    "stockReportTitle": "易德龙(603380.SH):苏州易德龙科技股份有限公司2025年第一季度报告",
    "stockReportPeriod": "2025年一季报",
    "templateConcern": ""
}
```

---

### agentMode=2 — 公司一页纸

| 字段         | 必填 | 类型    | 说明                                                                                              |
| ------------ | :--: | ------- | ------------------------------------------------------------------------------------------------- |
| question     |  是  | String  | 模板字符串，`{stock.name}（{stock.code}）的公司一页纸`，比如：`亿纬锂能（300014.SZ）的公司一页纸` |
| stock        |  是  | Dict    | `{"code": "300014.SZ", "name": "亿纬锂能"}`                                                       |
| template     |  是  | Integer | `0`（固定传alpha派模板）`1` 时需要填写`templateText`字段                                          |
| templateText |  否  | String  | 用户自己的公司一页纸分析模板                                                                      |
| language     |  否  | String  | 语言，美股可选 `中文` / `英文`                                                                    |

示例参数：

```json
{
    "agentMode": 2,
    "question": "亿纬锂能（300014.SZ）的公司一页纸",
    "stock": {
        "code": "300014.SZ",
        "name": "亿纬锂能"
    },
    "template": 0,
    "templateText": ""
}
```

---

### agentMode=3 — 个股调研大纲

| 字段         | 必填 | 类型    | 说明                                                                                                  |
| ------------ | :--: | ------- | ----------------------------------------------------------------------------------------------------- |
| question     |  是  | String  | 模板字符串，`{stock.name}（{stock.code}）的调研问题大纲`，比如：`巨星科技（002444.SZ）的调研问题大纲` |
| stock        |  是  | Dict    | 股票信息，`{"code": "002444.SZ", "name": "巨星科技"}`                                                 |
| template     |  是  | Integer | `0`（固定参数）                                                                                       |
| templateText |  否  | String  | 用户在调研大纲总结中关注的内容，默认传空字符串                                                        |

示例参数：

```json
{
    "agentMode": 3,
    "question": "巨星科技（002444.SZ）的调研问题大纲",
    "stock": {
        "code": "002444.SZ",
        "name": "巨星科技"
    },
    "template": 0,
    "templateText": ""
}
```

---

### agentMode=5 — 主题选股

| 字段         | 必填 | 类型    | 说明                                                             |
| ------------ | :--: | ------- | ---------------------------------------------------------------- |
| question     |  是  | String  | 选股主题描述，与`templateText`保持一致，比如：`和白酒相关的公司` |
| template     |  是  | Integer | `1`（固定参数）                                                  |
| templateText |  是  | String  | 选股主题描述，与`question`保持一致                               |

示例参数：

```json
{
    "agentMode": 5,
    "question": "和白酒相关的公司",
    "template": 1,
    "templateText": "和白酒相关的公司"
}
```

---

### agentMode=7 — 投资逻辑

| 字段         | 必填 | 类型    | 说明                                                                                                  |
| ------------ | :--: | ------- | ----------------------------------------------------------------------------------------------------- |
| question     |  是  | String  | 模板字符串，`{stock.name}（{stock.code}）的公司投资逻辑`，比如：`亿纬锂能（300014.SZ）的公司投资逻辑` |
| stock        |  是  | Dict    | 股票信息，`{"code": "300014.SZ", "name": "亿纬锂能"}`                                                 |
| template     |  是  | Integer | `0`（固定参数）                                                                                       |
| templateText |  否  | String  | 用户在投资逻辑中关注的分析要点、维度、指标等，默认传空字符串                                          |
| onlyAnswer   |  否  | Boolean | 是否只返回最终答案，不返回中间过程（默认 `false`）                                                    |

示例参数：

```json
{
    "agentMode": 7,
    "question": "亿纬锂能（300014.SZ）的公司投资逻辑",
    "onlyAnswer": false,
    "stock": {
        "code": "300014.SZ",
        "name": "亿纬锂能"
    },
    "template": 0,
    "templateText": ""
}
```

---

### agentMode=8 — 可比公司

| 字段            | 必填 | 类型    | 说明                                                                                        |
| --------------- | :--: | ------- | ------------------------------------------------------------------------------------------- |
| question        |  是  | String  | 模板字符串，`{stock.name}（{stock.code}）的可比公司`，比如：`比亚迪（002594.SZ）的可比公司` |
| stock           |  是  | Dict    | 股票信息，`{"code": "002594.SZ", "name": "比亚迪"}`                                         |
| template        |  是  | Integer | `1`（固定参数）                                                                             |
| templateText    |  否  | String  | 暂未启用，默认传空字符串                                                                    |
| templateConcern |  否  | String  | 对比过程中，用户关心的话题，比如产品参数、技术路线，估值、盈利等                            |

示例参数：

```json
{
    "agentMode": 8,
    "question": "比亚迪（002594.SZ）的可比公司",
    "stock": {
        "code": "002594.SZ",
        "name": "比亚迪"
    },
    "template": 1,
    "templateText": "",
    "templateConcern": "关注技术路线，盈利和出海能力"
}
```

---

### agentMode=9 — 观点 Challenge

| 字段                   | 必填 | 类型    | 说明                                                                                       |
| ---------------------- | :--: | ------- | ------------------------------------------------------------------------------------------ |
| question               |  是  | String  | 模板字符串，`Challenge该观点：{templateText}`，比如：`Challenge该观点：小米汽车未来的增长` |
| template               |  是  | Integer | `1`（固定参数）                                                                            |
| templateText           |  是  | String  | 待 Challenge 的观点内容，与`question`中观点描述保持一致                                    |
| templateConcern        |  否  | String  | 关注焦点，可进一步限定 Challenge 范围，如 `YU7`                                            |
| requestSelectStartTime |  否  | String  | 检索信息起始时间，格式 `yyyy-MM-dd`                                                        |
| requestSelectEndTime   |  否  | String  | 检索信息截止时间，格式 `yyyy-MM-dd`                                                        |

示例参数：

```json
{
    "agentMode": 9,
    "question": "Challenge该观点：小米汽车未来的增长",
    "template": 1,
    "templateText": "小米汽车未来的增长",
    "templateConcern": "YU7",
    "requestSelectStartTime": "2024-10-25",
    "requestSelectEndTime": "2025-10-24"
}
```

---

### agentMode=11 — 行业一页纸

| 字段          | 必填 | 类型    | 说明                                                                |
| ------------- | :--: | ------- | ------------------------------------------------------------------- |
| question      |  是  | String  | 模板字符串，`{inputIndustry}的行业一页纸`，比如：`白酒的行业一页纸` |
| inputIndustry |  是  | String  | 行业名称，如 `白酒`、`光伏`                                         |
| template      |  是  | Integer | `0`（固定传alpha派模板）；`1` 时需要填写`templateText`字段          |
| templateText  |  否  | String  | 用户自己的行业一页纸模板，`template=1` 时填写，默认传空字符串       |

示例参数：

```json
{
    "agentMode": 11,
    "question": "白酒的行业一页纸",
    "inputIndustry": "白酒",
    "template": 0,
    "templateText": ""
}
```

---

### agentMode=12 — 个股选基

| 字段         | 必填 | 类型       | 说明                                                                                                                                                                                                                |
| ------------ | :--: | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| question     |  是  | String     | 自然语言描述查询条件，格式参考：`{报告期}持有{股票名列表}的基金有哪些？`，比如：`2025三季报持有环旭电子、中际旭创、卓胜微的基金有哪些？`                                                                            |
| stockList    |  是  | List[Dict] | 股票列表，每项 `{"code": "...", "name": "..."}`                                                                                                                                                                     |
| reportDate   |  是  | String     | 报告期对应日期，格式 `yyyy-MM-dd`；需要参考`ifAnnual`参数，每年四档：`03-31`一季报、`06-30` 二级报（ifAnnual=0）/中报（ifAnnual=1）、`09-30`三季报、`12-31`四季报（ifAnnual=0）/年报（ifAnnual=1），如 `2025-09-30` |
| fundType     |  是  | String     | 基金类型筛选：`全部` / `主动` / `指数` / `ETF`                                                                                                                                                                      |
| template     |  是  | Integer    | `0`（固定参数）                                                                                                                                                                                                     |
| templateText |  否  | String     | 暂未启用，默认传空字符串                                                                                                                                                                                            |
| ifAnnual     |  否  | Integer    | 是否年报：`0`=否（默认），`1`=是                                                                                                                                                                                    |

示例参数：

```json
{
    "agentMode": 12,
    "question": "2025三季报持有环旭电子、中际旭创、卓胜微的基金有哪些？",
    "fundType": "全部",
    "reportDate": "2025-09-30",
    "ifAnnual": 0,
    "template": 0,
    "templateText": "",
    "stockList": [
        {
            "code": "601231.SH",
            "name": "环旭电子"
        },
        {
            "code": "300308.SZ",
            "name": "中际旭创"
        },
        {
            "code": "300782.SZ",
            "name": "卓胜微"
        }
    ]
}
```

---

### agentMode=13 — 主题选基

| 字段         | 必填 | 类型    | 说明                                                                                                                                                                                                                                  |
| ------------ | :--: | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| question     |  是  | String  | 自然语言描述查询条件，格式参考：`{报告期}持有{主题}的{基金类型}基金有哪些？`，比如：`2025三季报持有房地产的主动基金有哪些？`                                                                                                          |
| reportDate   |  是  | String  | 报告期对应日期，格式 `yyyy-MM-dd`；需要参考`ifAnnual`参数，每年四档：`03-31`一季报、`06-30` 二级报（ifAnnual=0）/中报（ifAnnual=1）、`09-30`三季报、`12-31`四季报（ifAnnual=0）/年报（ifAnnual=1），如 `2025-09-30`，表示`2025三季报` |
| fundType     |  是  | String  | 基金类型筛选：`全部` / `主动` / `指数` / `ETF`                                                                                                                                                                                        |
| template     |  是  | Integer | `0`（固定参数）                                                                                                                                                                                                                       |
| templateText |  否  | String  | 暂未启用，默认传空字符串                                                                                                                                                                                                              |
| ifAnnual     |  否  | Integer | 是否年报：`0`=否（默认），`1`=是                                                                                                                                                                                                      |

示例参数：

```json
{
    "agentMode": 13,
    "question": "2025三季报持有房地产的主动基金有哪些？",
    "fundType": "主动",
    "reportDate": "2025-09-30",
    "ifAnnual": 0,
    "template": 0,
    "templateText": ""
}
```

---

### agentMode=15 — 画图

| 字段         | 必填 | 类型         | 说明                                                                            |
| ------------ | :--: | ------------ | ------------------------------------------------------------------------------- |
| question     |  是  | String       | 画图主题，即用户实际输入的内容，比如：`黄金`、`新能源汽车`                      |
| pictureColor |  是  | List[String] | 颜色HEX值，二元组，分别表示主色和辅色，不含 `#` 前缀，如 `["2A66F6", "A5A8AF"]` |
| pictureStyle |  是  | String       | 画图风格：`PPT风格` / `科普风格`                                                |
| source       |  否  | Integer      | 产出形式：`0`=仅图片（默认），`1`=图文                                          |

示例参数：

```json
{
    "agentMode": 15,
    "question": "黄金",
    "pictureColor": ["2A66F6", "A5A8AF"],
    "pictureStyle": "科普风格",
    "source": 0
}
```

---

## 4. 获取股票公告列表

**URL**: `POST /alpha/open-api/v1/paipai/stock/report`

根据股票代码获取上市公司在交易所公开的公告列表，常用于查询业绩点评所需的 `stockReportId`。

### 请求参数

| 字段 | 必填 | 类型   | 说明     | 示例值        |
| ---- | ---- | ------ | -------- | ------------- |
| code | 是   | String | 股票编码 | `"603380.SH"` |

### 响应参数 (data 列表每条)

| 字段             | 类型   | 说明     |
| ---------------- | ------ | -------- |
| stockCode        | String | 股票编码 |
| stockName        | String | 股票名称 |
| reportType       | String | 报告类型 |
| reportPeriod     | String | 报告期   |
| stockReportId    | String | 公告ID   |
| stockReportTitle | String | 报告标题 |

---

## 5. 搜图表

**URL**: `POST /alpha/open-api/v1/paipai/search-image`

根据语义从研报和公告中搜索相关图片和表格。

### 请求参数

| 字段       | 必填 | 类型         | 说明                                                                 | 示例值         |
| ---------- | ---- | ------------ | -------------------------------------------------------------------- | -------------- |
| queryText  | 是   | String       | 检索查询的文本内容                                                   | "黄金价格趋势" |
| filesRange | 否   | List[String] | 来源类型代码列表，可选值：3-内资研报，8-外资研报，6-公告，9-三方研报 | `["3","8"]`    |
| topk       | 否   | Integer      | 返回结果数量，范围1-100，默认50                                      | `80`           |
| recallMode | 否   | String       | 召回模式: both(默认)/vector_only/es_only                             | `"both"`       |
| useLlmRank | 否   | Boolean      | 是否使用LLM重排序，默认false                                         | `false`        |
| startDate  | 否   | String       | 开始日期，格式 yyyy-MM-dd                                            | `"2025-01-01"` |
| endDate    | 否   | String       | 结束日期，格式 yyyy-MM-dd                                            | `"2025-10-01"` |

### 响应参数 (data 列表每条)

| 字段            | 类型         | 说明                                    |
| --------------- | ------------ | --------------------------------------- |
| articleId       | String       | 文章ID                                  |
| articleTitle    | String       | 文章标题                                |
| articleType     | String       | 文章类型                                |
| publishDate     | String       | 发布日期 yyyy-MM-dd HH:mm:ss            |
| pageIndex       | Integer      | 页码索引                                |
| bbox            | String       | 图片在页面上的位置坐标 [x1, y1, x2, y2] |
| captionList     | List[String] | 图片标题列表                            |
| footnoteList    | List[String] | 脚注列表                                |
| imageUrl        | String       | 图片URL                                 |
| source          | String       | 图片来源                                |
| llmScore        | Float        | LLM评分                                 |
| llmReason       | String       | LLM评分理由                             |
| institutionList | List[Dict]   | 机构信息列表 (code, name, logo)         |
| authorList      | List[String] | 作者列表                                |

---


path: scripts/alphapai_client.py
content: #!/usr/bin/env python3
"""
AlphaPai Research CLI
=====================
Alpha派投研 API 命令行工具。

用法：
  python alphapai_client.py config --set-key YOUR_KEY
  python alphapai_client.py qa --question "贵州茅台2024年经营情况？"
  python alphapai_client.py recall --query "茅台市值" --type comment,qa --start 2025-01-01

模块用法（供其他脚本导入）：
  from alphapai_client import AlphaPaiClient, load_config
  client = AlphaPaiClient(load_config())
  result = client.qa_text("问题")
"""

import argparse
import json
import os
import sys
from typing import Any, Dict, List, Optional

import requests

# ============================================================
# 配置管理
# ============================================================

DEFAULT_CONFIG_PATH = os.path.join(
    os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config.json"
)


def load_config(config_path: str = DEFAULT_CONFIG_PATH) -> Optional[Dict]:
    """加载API配置，返回 {"api_key": "...", "base_url": "..."} 或 None"""
    if not os.path.exists(config_path):
        return None
    with open(config_path, "r", encoding="utf-8") as f:
        config = json.load(f)
    if not config.get("api_key"):
        return None
    config.setdefault("base_url", "https://open-api.rabyte.cn")
    return config


def save_config(
    api_key: str,
    base_url: str = "https://open-api.rabyte.cn",
    config_path: str = DEFAULT_CONFIG_PATH,
) -> Dict:
    """保存API配置到 config.json"""
    config = {"api_key": api_key, "base_url": base_url}
    os.makedirs(os.path.dirname(config_path), exist_ok=True)
    with open(config_path, "w", encoding="utf-8") as f:
        json.dump(config, f, indent=2, ensure_ascii=False)
    return config


# ============================================================
# API Client（可单独导入使用）
# ============================================================


class AlphaPaiClient:
    """AlphaPai Open API 客户端"""

    def __init__(self, config: Dict):
        self.base_url = config["base_url"].rstrip("/")
        self.headers = {
            "app-agent": config["api_key"],
            "Content-Type": "application/json",
        }

    def _post(self, endpoint: str, payload: Dict, stream: bool = False) -> Any:
        """通用POST请求。stream=True时返回原始Response，False时返回解析后的JSON。"""
        url = f"{self.base_url}{endpoint}"
        response = requests.post(
            url,
            headers=self.headers,
            data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
            stream=stream,
        )
        response.raise_for_status()
        return response if stream else response.json()

    @staticmethod
    def parse_sse(response) -> Dict:
        """解析SSE流式响应，返回 {"answer": str, "references": list}"""
        buffer, answer, references = "", "", []
        for chunk in response.iter_content(chunk_size=4096):
            if not chunk:
                continue
            buffer += chunk.decode("utf-8", errors="replace")
            while "\n\n" in buffer:
                event, buffer = buffer.split("\n\n", 1)
                event = event.strip()
                if not event.startswith("data: "):
                    continue
                line = event[6:].strip()
                if not line:
                    continue
                try:
                    data = json.loads(line)
                    if isinstance(data, dict):
                        d = data.get("data", data)
                        if d.get("answer"):
                            answer += d["answer"]
                        if d.get("references"):
                            references.extend(d["references"])
                except json.JSONDecodeError:
                    continue
        return {"answer": answer, "references": references}

    def qa_text(
        self,
        question: str,
        context: Optional[List[str]] = None,
        mode: str = "Flash",
        is_stream: bool = False,
        is_auto_route: bool = False,
        is_web_search: bool = False,
        is_deep_reasoning: bool = False,
        start_time: Optional[str] = None,
        end_time: Optional[str] = None,
        question_id: Optional[str] = None,
    ) -> Dict:
        """投研知识问答接口。is_stream=True时内部解析SSE并返回聚合结果。"""
        payload = {
            "question": question,
            "mode": mode,
            "isStream": is_stream,
            "isAutoRoute": is_auto_route,
            "userInfo": {
                "userCode": "001010",
                "userName": "rabyte",
                "roleName": "基金经理",
                "roleCode": 5,
            },
        }
        if context:
            payload["context"] = context
        if is_web_search:
            payload["isWebSearch"] = True
        if is_deep_reasoning:
            payload["isDeepReasoning"] = True
        if start_time:
            payload["requestSelectStartTime"] = start_time
        if end_time:
            payload["requestSelectEndTime"] = end_time
        if question_id:
            payload["questionId"] = question_id

        if is_stream:
            return self.parse_sse(
                self._post([REDACTED_SECRET], payload, stream=True)
            )
        return self._post("/alpha/open-api/v1/paipai/qa-text", payload)

    def recall_data(
        self,
        query: str,
        is_cut_off: bool = True,
        recall_type: Optional[List[str]] = None,
        start_time: Optional[str] = None,
        end_time: Optional[str] = None,
    ) -> Dict:
        """获取问题召回数据"""
        payload = {"query": query, "isCutOff": is_cut_off}
        if recall_type:
            payload["recallType"] = recall_type
        else:
            payload["recallType"] = []
        if start_time:
            payload["startTime"] = start_time
        if end_time:
            payload["endTime"] = end_time
        return self._post("/alpha/open-api/v1/paipai/recall-data", payload)

    def stock_agent(
        self,
        question: str,
        agent_mode: int,
        stock: Optional[Dict] = None,
        template: int = 0,
        template_text: str = "",
        report_type: Optional[str] = None,
        stock_report_id: Optional[str] = None,
        stock_report_title: Optional[str] = None,
        stock_report_period: Optional[str] = None,
        template_concern: Optional[str] = None,
        request_select_start_time: Optional[str] = None,
        request_select_end_time: Optional[str] = None,
        input_industry: Optional[str] = None,
        report_date: Optional[str] = None,
        fund_type: Optional[str] = None,
        if_annual: Optional[int] = None,
        stock_list: Optional[List[Dict]] = None,
        picture_color: Optional[List[str]] = None,
        picture_style: Optional[str] = None,
        source: Optional[int] = None,
        language: Optional[str] = None,
        only_answer: Optional[bool] = None,
    ) -> Dict:
        """Alpha派投研Agent接口（公司一页纸、业绩点评、调研大纲等），SSE 流式请求。"""
        payload: Dict = {
            "question": question,
            "agentMode": agent_mode,
            "template": template,
            "templateText": template_text,
        }
        if stock:
            payload["stock"] = stock
        if report_type:
            payload["reportType"] = report_type
        if stock_report_id:
            payload["stockReportId"] = stock_report_id
        if stock_report_title:
            payload["stockReportTitle"] = stock_report_title
        if stock_report_period:
            payload["stockReportPeriod"] = stock_report_period
        if template_concern:
            payload["templateConcern"] = template_concern
        if request_select_start_time:
            payload["requestSelectStartTime"] = request_select_start_time
        if request_select_end_time:
            payload["requestSelectEndTime"] = request_select_end_time
        if input_industry:
            payload["inputIndustry"] = input_industry
        if report_date:
            payload["reportDate"] = report_date
        if fund_type:
            payload["fundType"] = fund_type
        if if_annual is not None:
            payload["ifAnnual"] = if_annual
        if stock_list:
            payload["stockList"] = stock_list
        if picture_color:
            payload["pictureColor"] = picture_color
        if picture_style:
            payload["pictureStyle"] = picture_style
        if source is not None:
            payload["source"] = source
        if language:
            payload["language"] = language
        if only_answer is not None:
            payload["onlyAnswer"] = only_answer
        return self.parse_sse(
            self._post([REDACTED_SECRET], payload, stream=True)
        )

    def stock_report(self, code: str) -> Dict:
        """获取某个股票的公告列表（上市公司在交易所公开的公告）"""
        return self._post("/alpha/open-api/v1/paipai/stock/report", {"code": code})

    def search_image(
        self,
        query_text: str,
        files_range: Optional[List[str]] = None,
        topk: int = 50,
        recall_mode: str = "both",
        use_llm_rank: bool = False,
        start_date: Optional[str] = None,
        end_date: Optional[str] = None,
    ) -> Dict:
        """搜图表：从研报/公告中搜索相关图片和表格"""
        payload: Dict = {
            "queryText": query_text,
            "topk": topk,
            "recallMode": recall_mode,
            "useLlmRank": use_llm_rank,
        }
        if files_range:
            payload["filesRange"] = files_range
        if start_date:
            payload["startDate"] = start_date
        if end_date:
            payload["endDate"] = end_date
        return self._post("/alpha/open-api/v1/paipai/search-image", payload)


# ============================================================
# 输出格式化
# ============================================================

_TYPE_LABEL = {
    "comment": "机构点评",
    "vps": "基金定期报告",
    "report": "内资研报",
    "foreign_report": "外资研报",
    "wechat_public_article": "公众号",
    "ann": "公司公告",
    "roadShow": "路演纪要",
    "roadShow_ir": "上市公司披露投关纪要",
    "roadShow_us": "美股纪要",
    "table": "公告/研报表格",
    "image": "研报图片",
    "edb": "EDB数据库",
}


def _type_label(type_val: str) -> str:
    return _TYPE_LABEL.get(type_val, type_val)


def _require_success(result: Dict):
    """检查响应码，非成功则打印错误并退出"""
    if result.get("code") != 200000:
        print(
            f"[错误] code={result.get('code')} message={result.get('message')}",
            file=sys.stderr,
        )
        sys.exit(1)


def format_qa(result: Dict) -> str:
    # parse_sse returns {"answer": ..., "references": [...]} directly (no code wrapper)
    if "answer" in result:
        answer = result.get("answer", "（无回答）")
        references = result.get("references", [])
    else:
        _require_success(result)
        data = result.get("data", {})
        answer = data.get("answer", "（无回答）")
        references = data.get("references", [])

    lines = ["[回答]", answer]
    if references:
        lines.append(f"\n[引用来源 {len(references)} 条]")
        for i, ref in enumerate(references, 1):
            date = f" ({ref.get('publishDate', '')})" if ref.get("publishDate") else ""
            lines.append(
                f"  {i}. [{_type_label(ref.get('type', ''))}] {ref.get('title', '')}{date}"
            )
    return "\n".join(lines)


def format_recall(result: Dict) -> str:
    _require_success(result)
    items = result.get("data", [])
    lines = [f"[召回数据 {len(items)} 条]\n"]
    for i, item in enumerate(items, 1):
        item_type = item.get("type", "")
        context_info = item.get("contextInfo", "")
        lines.append(f"#{i} [{_type_label(item_type)}]")
        lines.append(f"  {context_info}")
        if item_type == "qa":
            if item.get("contextText"):
                lines.append(f"  Q: {item['contextText']}")
            if item.get("answer"):
                text = item["answer"]
                lines.append(f"  A: {text[:300]}{'...' if len(text) > 300 else ''}")
        elif item.get("chunks"):
            text = item["chunks"][0]
            lines.append(f"  {text[:300]}{'...' if len(text) > 300 else ''}")
        lines.append("")
    return "\n".join(lines)


def format_agent(result: Dict) -> str:
    if "answer" in result:
        answer = result.get("answer", "（无回答）")
        references = result.get("references", [])
    else:
        _require_success(result)
        data = result.get("data", {})
        answer = data.get("answer", "（无回答）")
        references = data.get("references", [])
    lines = ["[Agent 回答]", answer]
    if references:
        lines.append(f"\n[引用来源 {len(references)} 条]")
        for i, ref in enumerate(references, 1):
            date = f" ({ref.get('publishDate', '')})" if ref.get("publishDate") else ""
            lines.append(
                f"  {i}. [{_type_label(ref.get('type', ''))}] {ref.get('title', '')}{date}"
            )
    return "\n".join(lines)


def format_report(result: Dict) -> str:
    _require_success(result)
    items = result.get("data", [])
    if not items:
        return "[公告列表] 未找到相关公告"
    lines = [f"[公告列表 {len(items)} 条]\n"]
    for i, item in enumerate(items, 1):
        lines.append(
            f"#{i} [{item.get('reportType', '')}] {item.get('stockReportTitle', '')}"
        )
        lines.append(
            f"   期间: {item.get('reportPeriod', '')}  ID: {item.get('stockReportId', '')}"
        )
        lines.append("")
    return "\n".join(lines)


def format_image(result: Dict) -> str:
    _require_success(result)
    items = result.get("data", [])
    if not items:
        return "[图表搜索] 未找到相关图表"
    lines = [f"[图表搜索 {len(items)} 条]\n"]
    for i, item in enumerate(items, 1):
        lines.append(f"#{i} {item.get('articleTitle', '')}")
        caption = ", ".join(item.get("captionList") or [])
        if caption:
            lines.append(f"   说明: {caption}")
        lines.append(
            f"   来源: {item.get('source', '')}  日期: {item.get('publishDate', '')}"
        )
        if item.get("imageUrl"):
            lines.append(f"   图片: {item['imageUrl']}")
        lines.append("")
    return "\n".join(lines)


# ============================================================
# CLI 子命令
# ============================================================


def cmd_config(args):
    config_path = DEFAULT_CONFIG_PATH
    if args.set_key or args.set_url:
        config = load_config(config_path) or {}
        if args.set_key:
            config["api_key"] = args.set_key
        if args.set_url:
            config["base_url"] = args.set_url
        config.setdefault("base_url", "https://open-api.rabyte.cn")
        save_config(config["api_key"], config["base_url"], config_path)
        key = config["api_key"]
        masked = key[:4] + "****" + key[-4:] if len(key) > 8 else "****"
        print(f"配置已保存: [REDACTED_SECRET]  base_url={config['base_url']}")
        print(f"路径: {config_path}")
    elif args.show:
        config = load_config(config_path)
        if not config:
            print(
                "未找到配置，请先运行: python alphapai_client.py config --set-key YOUR_KEY"
            )
            sys.exit(1)
        key = config["api_key"]
        masked = key[:4] + "****" + key[-4:] if len(key) > 8 else "****"
        print(f"[REDACTED_SECRET]")
        print(f"base_url: {config['base_url']}")
        print(f"路径    : {config_path}")
    else:
        print(
            "用法: python alphapai_client.py config --show | --set-key KEY [--set-url URL]"
        )


def cmd_qa(args):
    config = load_config()
    if not config:
        print(
            "[错误] 未找到配置，请先运行: python alphapai_client.py config --set-key YOUR_KEY",
            file=sys.stderr,
        )
        sys.exit(1)

    client = AlphaPaiClient(config)
    result = client.qa_text(
        question=args.question,
        context=args.context or None,
        mode=args.mode,
        is_stream=True,
        is_web_search=args.web_search,
        is_deep_reasoning=args.deep_reasoning,
        start_time=args.start,
        end_time=args.end,
    )

    if args.json:
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        print(format_qa(result))


def cmd_recall(args):
    config = load_config()
    if not config:
        print(
            "[错误] 未找到配置，请先运行: python alphapai_client.py config --set-key YOUR_KEY",
            file=sys.stderr,
        )
        sys.exit(1)

    client = AlphaPaiClient(config)
    recall_type = [t.strip() for t in args.type.split(",")] if args.type else []

    result = client.recall_data(
        query=args.query,
        is_cut_off=not args.no_cutoff,
        recall_type=recall_type,
        start_time=args.start,
        end_time=args.end,
    )

    if args.json:
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        print(format_recall(result))


def _parse_stock(s: str) -> Dict:
    """解析 CODE:NAME 格式的股票参数"""
    parts = s.split(":", 1)
    return {"code": parts[0], "name": parts[1] if len(parts) == 2 else ""}


def cmd_agent(args):
    config = load_config()
    if not config:
        print(
            "[错误] 未找到配置，请先运行: python alphapai_client.py config --set-key YOUR_KEY",
            file=sys.stderr,
        )
        sys.exit(1)

    client = AlphaPaiClient(config)
    stock = _parse_stock(args.stock) if args.stock else None
    stock_list = [_parse_stock(s) for s in args.stock_list] if args.stock_list else None

    # modes 5/8/9 have template fixed at 1 per API spec
    template = 1 if args.mode in (5, 8, 9) else args.template

    result = client.stock_agent(
        question=args.question,
        agent_mode=args.mode,
        stock=stock,
        template=template,
        template_text=args.template_text or "",
        report_type=args.report_type,
        stock_report_id=args.report_id,
        stock_report_title=args.report_title,
        stock_report_period=args.report_period,
        template_concern=args.concern,
        request_select_start_time=args.start,
        request_select_end_time=args.end,
        input_industry=args.industry,
        report_date=args.report_date,
        fund_type=args.fund_type,
        if_annual=args.if_annual,
        stock_list=stock_list,
        picture_color=args.picture_color,
        picture_style=args.picture_style,
        source=args.source,
        language=args.language,
        only_answer=True if args.only_answer else None,
    )

    if args.json:
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        print(format_agent(result))


def cmd_report(args):
    config = load_config()
    if not config:
        print(
            "[错误] 未找到配置，请先运行: python alphapai_client.py config --set-key YOUR_KEY",
            file=sys.stderr,
        )
        sys.exit(1)

    client = AlphaPaiClient(config)
    result = client.stock_report(code=args.code)

    if args.json:
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        print(format_report(result))


def cmd_image(args):
    config = load_config()
    if not config:
        print(
            "[错误] 未找到配置，请先运行: python alphapai_client.py config --set-key YOUR_KEY",
            file=sys.stderr,
        )
        sys.exit(1)

    client = AlphaPaiClient(config)
    result = client.search_image(
        query_text=args.query,
        files_range=args.files_range,
        topk=args.topk,
        recall_mode=args.recall_mode,
        use_llm_rank=args.llm_rank,
        start_date=args.start,
        end_date=args.end,
    )

    if args.json:
        print(json.dumps(result, ensure_ascii=False, indent=2))
    else:
        print(format_image(result))


# ============================================================
# CLI 入口
# ============================================================


def main():
    parser = argparse.ArgumentParser(
        prog="alphapai_client.py",
        description="AlphaPai Research CLI — Alpha派投研API命令行工具",
    )
    sub = parser.add_subparsers(dest="command", required=True)

    # --- config ---
    p_cfg = sub.add_parser("config", help="查看或设置API配置")
    p_cfg.add_argument("--show", action="store_true", help="显示当前配置")
    p_cfg.add_argument("--set-key", metavar="KEY", help="设置api_key")
    p_cfg.add_argument(
        "--set-url",
        metavar="URL",
        help="设置base_url（默认 https://open-api.rabyte.cn）",
    )

    # --- qa ---
    p_qa = sub.add_parser("qa", help="投研知识问答")
    p_qa.add_argument("--question", "-q", required=True, help="问题内容")
    p_qa.add_argument(
        "--mode",
        default="Flash",
        choices=["Flash", "Think"],
        help="问答模式: Flash=简单搜索问答，一问一搜一答（默认）; Think=Wide Search，一问多搜一答",
    )
    p_qa.add_argument(
        "--context",
        "-c",
        nargs="+",
        metavar="MSG",
        help="多轮对话上下文，按顺序传入历史消息列表",
    )
    p_qa.add_argument("--web-search", action="store_true", help="开启联网搜索")
    p_qa.add_argument("--deep-reasoning", action="store_true", help="开启深度推理")
    p_qa.add_argument("--start", metavar="YYYY-MM-DD", help="数据筛选开始日期")
    p_qa.add_argument("--end", metavar="YYYY-MM-DD", help="数据筛选结束日期")
    p_qa.add_argument("--json", action="store_true", help="输出原始JSON（供程序解析）")

    # --- recall ---
    p_rc = sub.add_parser("recall", help="获取问题召回的底层数据")
    p_rc.add_argument("--query", "-q", required=True, help="查询问题")
    p_rc.add_argument(
        "--type",
        "-t",
        metavar="TYPES",
        help="数据类型，逗号分隔，如 comment,qa,report（不传则全类型）",
    )
    p_rc.add_argument(
        "--no-cutoff",
        action="store_true",
        help="返回截断前完整内容（默认截断，与送入大模型的数据一致）",
    )
    p_rc.add_argument("--start", metavar="YYYY-MM-DD", help="数据筛选开始日期")
    p_rc.add_argument("--end", metavar="YYYY-MM-DD", help="数据筛选结束日期")
    p_rc.add_argument("--json", action="store_true", help="输出原始JSON（供程序解析）")

    # --- agent ---
    p_ag = sub.add_parser(
        "agent", help="股票 Agent（公司一页纸、业绩点评、调研大纲等）"
    )
    p_ag.add_argument(
        "--mode",
        "-m",
        type=int,
        required=True,
        help="Agent模式: 1=业绩点评 2=公司一页纸 3=调研大纲 5=主题选股 "
        "7=投资逻辑 8=可比公司 9=观点Challenge 11=行业一页纸 "
        "12=个股选基 13=主题选基 15=画图",
    )
    p_ag.add_argument("--question", "-q", required=True, help="问题内容")
    p_ag.add_argument(
        "--stock",
        metavar="CODE:NAME",
        help="股票信息，格式 CODE:NAME，如 300014.SZ:亿纬锂能",
    )
    p_ag.add_argument(
        "--template",
        type=int,
        default=0,
        help="模版类型: 0=alpha派模板(默认) 1=用户模板",
    )
    p_ag.add_argument(
        "--template-text", metavar="TEXT", help="用户模版正文（template=1时需传）"
    )
    p_ag.add_argument(
        "--report-type", metavar="TYPE", help="报告类型（业绩点评必填），如: 季报"
    )
    p_ag.add_argument("--report-id", metavar="ID", help="公告ID（业绩点评必填）")
    p_ag.add_argument(
        "--report-title", metavar="TITLE", help="报告标题（业绩点评必填）"
    )
    p_ag.add_argument(
        "--report-period",
        metavar="PERIOD",
        help="报告期（业绩点评必填），如: 2025年一季报",
    )
    p_ag.add_argument(
        "--concern", metavar="TEXT", help="用户关注内容（业绩点评/观点Challenge选填）"
    )
    p_ag.add_argument(
        "--industry", metavar="NAME", help="行业信息（行业一页纸必填），如: 白酒"
    )
    p_ag.add_argument(
        "--report-date",
        metavar="DATE",
        help="报告期（个股选基/主题选基必填），如: 2025-09-30",
    )
    p_ag.add_argument(
        "--fund-type",
        metavar="TYPE",
        help="基金类型（个股选基/主题选基必填）: 全部|主动|指数|ETF",
    )
    p_ag.add_argument(
        "--if-annual", type=int, choices=[0, 1], help="是否年报: 0=否 1=是"
    )
    p_ag.add_argument(
        "--stock-list",
        nargs="+",
        metavar="CODE:NAME",
        help="股票列表（个股选基必填），如: 601231.SH:环旭电子 300308.SZ:中际旭创",
    )
    p_ag.add_argument(
        "--picture-color",
        nargs="+",
        metavar="HEX",
        help="图片颜色HEX值（画图必填），如: 2A66F6 A5A8AF",
    )
    p_ag.add_argument(
        "--picture-style",
        metavar="STYLE",
        help="图片风格（画图必填）: PPT风格|科普风格",
    )
    p_ag.add_argument(
        "--source",
        type=int,
        choices=[0, 1],
        help="画图样式: 0=仅图片(默认) 1=图文",
    )
    p_ag.add_argument(
        "--language", metavar="LANG", help="语言（美股公司一页纸可选）: 中文|英文"
    )
    p_ag.add_argument(
        "--only-answer",
        action="store_true",
        help="仅返回最终答案（mode 7 投资逻辑可选）",
    )
    p_ag.add_argument("--start", metavar="YYYY-MM-DD", help="数据开始日期")
    p_ag.add_argument("--end", metavar="YYYY-MM-DD", help="数据结束日期")
    p_ag.add_argument("--json", action="store_true", help="输出原始JSON（供程序解析）")

    # --- report ---
    p_rp = sub.add_parser("report", help="获取股票公告列表（供业绩点评查询公告ID）")
    p_rp.add_argument("--code", "-c", required=True, help="股票编码，如 603380.SH")
    p_rp.add_argument("--json", action="store_true", help="输出原始JSON（供程序解析）")

    # --- image ---
    p_img = sub.add_parser("image", help="搜图表（从研报/公告中搜索图片和表格）")
    p_img.add_argument("--query", "-q", required=True, help="搜索内容")
    p_img.add_argument(
        "--files-range",
        nargs="+",
        metavar="CODE",
        help="来源类型代码列表，可选值: 3=内资研报 8=外资研报 6=公告 9=三方研报",
    )
    p_img.add_argument("--topk", type=int, default=50, help="返回数量(1-100，默认50)")
    p_img.add_argument(
        "--recall-mode",
        default="both",
        choices=["both", "vector_only", "es_only"],
        help="召回模式（默认both）",
    )
    p_img.add_argument("--llm-rank", action="store_true", help="使用LLM重排序")
    p_img.add_argument("--start", metavar="YYYY-MM-DD", help="开始日期")
    p_img.add_argument("--end", metavar="YYYY-MM-DD", help="结束日期")
    p_img.add_argument("--json", action="store_true", help="输出原始JSON（供程序解析）")

    args = parser.parse_args()
    {
        "config": cmd_config,
        "qa": cmd_qa,
        "recall": cmd_recall,
        "agent": cmd_agent,
        "report": cmd_report,
        "image": cmd_image,
    }[args.command](args)


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)