Skip to content
Merged
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
18 changes: 5 additions & 13 deletions backend/app/api/v1/rewrite.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from sqlalchemy.ext.asyncio import AsyncSession

from app.api.deps import get_db
from app.services.llm.client import LLMClient, get_llm_client
from app.services.llm.client import get_llm_client
from app.services.user_settings_service import UserSettingsService

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -85,12 +85,10 @@ async def _stream_rewrite(request: RewriteRequest, db: AsyncSession):

full_text = ""
try:
async for token in asyncio.wait_for(
_collect_stream(llm, messages),
timeout=REWRITE_TIMEOUT,
):
full_text += token
yield _sse("rewrite_delta", {"delta": token})
async with asyncio.timeout(REWRITE_TIMEOUT):
async for token in llm.chat_stream(messages, temperature=0.3, task_type="rewrite"):
full_text += token
yield _sse("rewrite_delta", {"delta": token})
except TimeoutError:
yield _sse("error", {"code": "timeout", "message": "Rewrite timed out after 30s"})
return
Expand All @@ -105,12 +103,6 @@ async def _stream_rewrite(request: RewriteRequest, db: AsyncSession):
yield _sse("error", {"code": "rewrite_error", "message": str(e)})


async def _collect_stream(llm: LLMClient, messages: list[dict[str, str]]):
"""Wrap the async iterator so asyncio.wait_for can timeout the whole stream."""
async for token in llm.chat_stream(messages, temperature=0.3, task_type="rewrite"):
yield token


@router.post("/rewrite")
async def rewrite_stream(
request: RewriteRequest,
Expand Down
2 changes: 1 addition & 1 deletion backend/app/services/search_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def _affiliation(auth: dict) -> str:
class ArXivProvider(SearchProvider):
"""arXiv API — Atom XML feed."""

BASE = "http://export.arxiv.org/api/query"
BASE = "https://export.arxiv.org/api/query"

@property
def name(self) -> str:
Expand Down
4 changes: 2 additions & 2 deletions backend/app/services/subscription_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ def get_common_feeds() -> list[dict]:
return [
{
"name": "arXiv - Physics Optics",
"url": "http://export.arxiv.org/rss/physics.optics",
"url": "https://export.arxiv.org/rss/physics.optics",
"category": "preprint",
},
{"name": "arXiv - Quantum Physics", "url": "http://export.arxiv.org/rss/quant-ph", "category": "preprint"},
{"name": "arXiv - Quantum Physics", "url": "https://export.arxiv.org/rss/quant-ph", "category": "preprint"},
{"name": "Nature Photonics", "url": "https://www.nature.com/nphoton.rss", "category": "journal"},
{
"name": "Science - Latest",
Expand Down
121 changes: 121 additions & 0 deletions docs/api/chat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Chat API

Chat 模块提供基于 SSE 的流式对话与文本改写接口,支持知识库 RAG 检索、多工具模式及实时流式输出。

**Base path:** `/api/v1/chat`

---

## 1. 流式对话

### POST /api/v1/chat/stream

基于 SSE 的流式对话接口,支持知识库检索、引用标注及多轮对话上下文。

#### 请求体 (ChatStreamRequest)

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `conversation_id` | int | 否 | 对话 ID,续写时传入以保持上下文 |
| `message` | str | 是 | 用户消息内容(至少 1 字符) |
| `knowledge_base_ids` | list[int] | 否 | 知识库(项目)ID 列表,用于 RAG 检索 |
| `model` | str | 否 | 模型标识,空则使用用户设置 |
| `tool_mode` | str | 否 | 工具模式,默认 `"qa"` |

**tool_mode 可选值:**

| 值 | 说明 |
|----|------|
| `qa` | 问答模式:基于上下文回答问题,使用 [1]、[2] 等引用格式 |
| `citation_lookup` | 引用查找:识别并列出与文本最相关的参考文献 |
| `review_outline` | 综述提纲:生成结构化文献综述提纲 |
| `gap_analysis` | 研究缺口分析:识别研究空白与未来方向 |

#### 对话响应格式

SSE 流式响应,`Content-Type: text/event-stream`。

#### 对话 SSE 事件类型

| 事件 | 说明 | data 字段 |
|------|------|-----------|
| `message_start` | 消息开始 | `{ message_id }` |
| `citation` | 引用信息(每个来源一条) | `{ index, paper_id, paper_title, page_number, excerpt, relevance_score, chunk_type, authors, year, doi }` |
| `text_delta` | 文本增量 | `{ delta }` |
| `message_end` | 消息结束 | `{ message_id, conversation_id, finish_reason }` |
| `error` | 错误 | `{ code, message }` |

#### 对话示例

```bash
curl -X POST "http://localhost:8000/api/v1/chat/stream" \
-H "Content-Type: application/json" \
-d '{
"message": "什么是注意力机制?",
"knowledge_base_ids": [1, 2],
"tool_mode": "qa"
}'
```

#### 对话错误码

| code | 说明 |
|------|------|
| `stream_error` | 流式处理异常 |

---

## 2. 文本改写

### POST /api/v1/chat/rewrite

基于 SSE 的流式文本改写接口,支持多种风格与自定义提示。

#### 请求体 (RewriteRequest)

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `excerpt` | str | 是 | 待改写文本,**最多 2000 字符** |
| `style` | str | 是 | 改写风格 |
| `custom_prompt` | str | 否 | 自定义提示,`style=custom` 时必填 |
| `source_language` | str | 否 | 源语言,默认 `"auto"` |

**style 可选值:**

| 值 | 说明 |
|----|------|
| `simplify` | 通俗化:将学术文本改写为易懂语言 |
| `academic` | 学术化:改写为正式学术风格 |
| `translate_en` | 英译:翻译为英文 |
| `translate_zh` | 中译:翻译为中文 |
| `custom` | 自定义:使用 `custom_prompt` 作为系统提示 |

#### 改写响应格式

SSE 流式响应,`Content-Type: text/event-stream`。

#### 改写 SSE 事件类型

| 事件 | 说明 | data 字段 |
|------|------|-----------|
| `rewrite_delta` | 改写文本增量 | `{ delta }` |
| `rewrite_end` | 改写完成 | `{ full_text }` |
| `error` | 错误 | `{ code, message }` |

#### 改写示例

```bash
curl -X POST "http://localhost:8000/api/v1/chat/rewrite" \
-H "Content-Type: application/json" \
-d '{
"excerpt": "The attention mechanism allows the model to focus on different parts of the input.",
"style": "translate_zh"
}'
```

#### 改写错误码

| code | 说明 |
|------|------|
| `timeout` | 改写超时(30 秒) |
| `rewrite_error` | 改写处理异常 |
Loading
Loading