docs: add avatar chat implementation plan
This commit is contained in:
319
docs/superpowers/plans/2026-07-23-avatar-chat-knowledge-plan.md
Normal file
319
docs/superpowers/plans/2026-07-23-avatar-chat-knowledge-plan.md
Normal file
@@ -0,0 +1,319 @@
|
||||
# 数字分身聊天与知识库增强实施计划
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 实现“标准问答对 > 文件知识库 > OpenAI 兼容 Qwen”的数字分身聊天链路,并补齐 MD/TXT 文档、人格编辑配置和知识库双表界面。
|
||||
|
||||
**Architecture:** 后端新增聊天编排接口,统一负责用户/分身权限、标准问答匹配、向量召回和 Qwen 调用;前端只传递消息历史并展示答案来源。现有 SQLite/SQLAlchemy 数据模型继续复用,聊天历史暂不持久化,Qwen 只通过后端环境变量访问。
|
||||
|
||||
**Tech Stack:** FastAPI、SQLAlchemy、Python `unittest`/`unittest.mock`、Vue 3 Composition API、Vue Router、Pinia、Axios、Vite。
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- 回复优先级固定为标准问答对 > 文件知识库 > Qwen。
|
||||
- Qwen 配置只读取后端 `CHAT_API_URL`、`CHAT_API_KEY`、`CHAT_MODEL`,前端不得接触密钥。
|
||||
- 所有聊天、知识库、问答操作必须校验 Bearer app token 对应的分身归属。
|
||||
- 文档支持 `.md`、`.txt`、`.pdf`、`.doc`、`.docx`、`.xlsx`,上传文件名只展示,落盘使用随机名。
|
||||
- 不新增聊天历史表,不改会会登录和用户体系。
|
||||
- 任何生产代码变更前先写并运行对应失败测试;每项任务完成后运行该任务的完整测试。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 建立后端测试夹具和文本抽取回归测试
|
||||
|
||||
**Files:**
|
||||
- Create: `digital-avatar-app/backend/tests/__init__.py`
|
||||
- Create: `digital-avatar-app/backend/tests/test_embeddings.py`
|
||||
- Create: `digital-avatar-app/backend/tests/test_chat_orchestration.py`
|
||||
|
||||
**Interfaces:**
|
||||
- Produces temporary SQLite database fixtures and deterministic fake embeddings/model clients for later backend tasks.
|
||||
- Tests import `embeddings.extract_text`, `routers.knowledge._match_standard_qa`, and `routers.chat._build_prompt`/`routers.chat._resolve_reply` once those functions exist.
|
||||
|
||||
- [ ] **Step 1: Add a failing MD/TXT extraction test**
|
||||
|
||||
Create two temporary files with UTF-8 Chinese text and assert `extract_text(path, '.md')` and `extract_text(path, '.txt')` return the source text. Also assert an unsupported extension raises `ValueError` rather than silently returning an empty string.
|
||||
|
||||
- [ ] **Step 2: Run the focused test and verify the expected failure**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/yqq/Works/AiProject/huihuiSquare/digital-avatar-app/backend
|
||||
python3 -m unittest tests.test_embeddings -v
|
||||
```
|
||||
|
||||
Expected: the MD/TXT cases fail because `extract_text` currently only handles the existing binary formats.
|
||||
|
||||
- [ ] **Step 3: Add failing orchestration tests**
|
||||
|
||||
Cover these exact behaviors with an in-memory `Avatar`/`QAPair` setup and mocked embedding/model calls:
|
||||
|
||||
```python
|
||||
def test_enabled_qa_wins_without_calling_model():
|
||||
result = _resolve_reply(db, avatar, " 公司地址? ", [], model_client=fake_model)
|
||||
self.assertEqual(result["source"], "qa")
|
||||
self.assertEqual(result["answer"], "标准地址")
|
||||
fake_model.assert_not_called()
|
||||
|
||||
def test_knowledge_context_is_sent_to_qwen_after_qa_miss():
|
||||
result = _resolve_reply(db, avatar, "退款规则", [], search_fn=lambda *_: [knowledge_hit], model_client=fake_model)
|
||||
self.assertEqual(result["source"], "knowledge")
|
||||
self.assertIn("知识库内容", fake_model.call_args.kwargs["messages"][-1]["content"])
|
||||
|
||||
def test_chat_rejects_avatar_owned_by_another_user():
|
||||
with self.assertRaises(HTTPException) as caught:
|
||||
_require_owned_avatar(db, avatar.id, other_user_token)
|
||||
self.assertEqual(caught.exception.status_code, 403)
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run the focused test and verify it fails for missing behavior**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
python3 -m unittest tests.test_chat_orchestration -v
|
||||
```
|
||||
|
||||
Expected: import/behavior failures identify the missing helper and endpoint orchestration, not test syntax errors.
|
||||
|
||||
- [ ] **Step 5: Commit the test scaffolding**
|
||||
|
||||
```bash
|
||||
git add digital-avatar-app/backend/tests
|
||||
git commit -m "test: cover avatar chat priority and text extraction"
|
||||
```
|
||||
|
||||
### Task 2: Add MD/TXT extraction and secure knowledge operations
|
||||
|
||||
**Files:**
|
||||
- Modify: `digital-avatar-app/backend/embeddings.py`
|
||||
- Modify: `digital-avatar-app/backend/routers/knowledge.py`
|
||||
- Modify: `digital-avatar-app/backend/routers/avatars.py`
|
||||
- Test: `digital-avatar-app/backend/tests/test_embeddings.py`
|
||||
|
||||
**Interfaces:**
|
||||
- `embeddings.extract_text(path, ext)` accepts `.md` and `.txt` and decodes text with UTF-8 replacement handling.
|
||||
- `knowledge._require_owned_avatar(db, avatar_id, authorization)` returns the owned `Avatar` or raises 401/403.
|
||||
- Existing docs/QA/search handlers use the ownership helper before reading or mutating data.
|
||||
|
||||
- [ ] **Step 1: Implement the smallest extraction change**
|
||||
|
||||
Add `.md` and `.txt` to the text branch of `extract_text`; use `open(path, 'r', encoding='utf-8', errors='replace')`. Update the allowed extension set and client-facing error text to list MD/TXT.
|
||||
|
||||
- [ ] **Step 2: Run the extraction test to green**
|
||||
|
||||
Run `python3 -m unittest tests.test_embeddings -v`; all extraction tests must pass.
|
||||
|
||||
- [ ] **Step 3: Add ownership resolution without changing public response shapes**
|
||||
|
||||
Reuse the existing token lookup pattern from `avatars.py`. For no token return `HTTPException(status_code=401, detail='未登录')`; for a valid token whose `huihui_user_id` does not equal `avatar.owner_id`, return 403. Keep the current seed/empty-owner behavior only for explicitly unowned setup records, not for authenticated user data.
|
||||
|
||||
- [ ] **Step 4: Apply the helper to documents, search, and QA routes**
|
||||
|
||||
Every route under `/avatar/{avatar_id}/knowledge/...` must call the helper before querying. Upload must also enforce a 10 MB request file limit based on bytes read and return `文件不能超过 10MB` before vectorization.
|
||||
|
||||
- [ ] **Step 5: Run backend compile and focused tests**
|
||||
|
||||
```bash
|
||||
cd /Users/yqq/Works/AiProject/huihuiSquare/digital-avatar-app/backend
|
||||
python3 -m unittest tests.test_embeddings tests.test_chat_orchestration -v
|
||||
python3 -m py_compile main.py database.py models.py embeddings.py routers/*.py
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Commit the backend document/security changes**
|
||||
|
||||
```bash
|
||||
git add digital-avatar-app/backend/embeddings.py digital-avatar-app/backend/routers/knowledge.py digital-avatar-app/backend/routers/avatars.py digital-avatar-app/backend/tests
|
||||
git commit -m "feat: support text knowledge files and protect avatar knowledge"
|
||||
```
|
||||
|
||||
### Task 3: Implement QA-first Qwen chat orchestration
|
||||
|
||||
**Files:**
|
||||
- Create: `digital-avatar-app/backend/routers/chat.py`
|
||||
- Modify: `digital-avatar-app/backend/main.py`
|
||||
- Modify: `digital-avatar-app/backend/requirements.txt`
|
||||
- Modify: `digital-avatar-app/backend/tests/test_chat_orchestration.py`
|
||||
|
||||
**Interfaces:**
|
||||
- `POST /api/avatar/{avatar_id}/chat` accepts `{message: string, history?: [{role, content}]}`.
|
||||
- Success response is `{answer, source, references}` where `source` is `qa`, `knowledge`, or `qwen`.
|
||||
- `routers.chat._match_standard_qa(question, qa_pairs)` returns the winning enabled QA or `None`.
|
||||
- `routers.chat._build_prompt(avatar, history, question, knowledge_hits)` returns system/user messages with no secrets.
|
||||
|
||||
- [ ] **Step 1: Make the QA-first tests fail for the current backend**
|
||||
|
||||
Run the orchestration tests from Task 1 and confirm no chat router/helper currently exists.
|
||||
|
||||
- [ ] **Step 2: Implement deterministic QA matching**
|
||||
|
||||
Normalize Unicode whitespace, case, and Chinese/ASCII punctuation. Check exact normalized questions first; only then use `difflib.SequenceMatcher` against enabled questions and accept a similarity of at least `0.86`. Disabled QAs never match.
|
||||
|
||||
- [ ] **Step 3: Implement knowledge context assembly**
|
||||
|
||||
Call the existing vector search logic with top five hits. Include only non-empty snippets and filenames in the prompt. Preserve hit metadata as `references` and use `source='knowledge'` whenever at least one document hit was supplied to Qwen.
|
||||
|
||||
- [ ] **Step 4: Implement the OpenAI-compatible Qwen client**
|
||||
|
||||
Read:
|
||||
|
||||
```python
|
||||
CHAT_API_URL = os.getenv("CHAT_API_URL", "https://dashscope.aliyuncs.com/compatible-mode/v1")
|
||||
CHAT_API_KEY = os.getenv("CHAT_API_KEY", "")
|
||||
CHAT_MODEL = os.getenv("CHAT_MODEL", "qwen-plus")
|
||||
```
|
||||
|
||||
POST to `CHAT_API_URL.rstrip('/') + '/chat/completions'` with `Authorization: Bearer ...`, timeout 30 seconds, the configured model, bounded recent history, and the generated system prompt. Handle non-2xx responses, malformed JSON, and empty choices with user-safe errors.
|
||||
|
||||
- [ ] **Step 5: Map Avatar.config into the prompt**
|
||||
|
||||
Use defaults matching `AvatarCreate.vue`: professional, creativity 50, rigor 50, humor 30, medium, empty system prompt. Convert creativity to `temperature = 0.2 + creativity / 100 * 0.6`; describe the other fields in Chinese instructions. Clamp history to the last 10 messages and each content field to 4,000 characters.
|
||||
|
||||
- [ ] **Step 6: Register the router and run all backend tests**
|
||||
|
||||
Include `routers.chat.router` in `main.py`, then run:
|
||||
|
||||
```bash
|
||||
python3 -m unittest discover -s tests -v
|
||||
python3 -m py_compile main.py database.py models.py embeddings.py routers/*.py
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Commit the chat endpoint**
|
||||
|
||||
```bash
|
||||
git add digital-avatar-app/backend/routers/chat.py digital-avatar-app/backend/main.py digital-avatar-app/backend/requirements.txt digital-avatar-app/backend/tests
|
||||
git commit -m "feat: add QA-first avatar chat with Qwen fallback"
|
||||
```
|
||||
|
||||
### Task 4: Add frontend API, avatar editing controls, and chat route
|
||||
|
||||
**Files:**
|
||||
- Modify: `digital-avatar-app/src/api/index.ts`
|
||||
- Modify: `digital-avatar-app/src/utils/avatar-page-data.js`
|
||||
- Modify: `digital-avatar-app/src/views/AvatarEdit.vue`
|
||||
- Modify: `digital-avatar-app/src/views/AvatarManage.vue`
|
||||
- Create: `digital-avatar-app/src/views/AvatarChat.vue`
|
||||
- Modify: `digital-avatar-app/src/router/index.ts`
|
||||
- Test: `digital-avatar-app/scripts/avatar-page-data.test.mjs`
|
||||
|
||||
**Interfaces:**
|
||||
- `sendAvatarChat(avatarId, payload)` calls `POST /avatar/${avatarId}/chat`.
|
||||
- `normalizeAvatarEditForm` returns all six config values with defaults.
|
||||
- `buildAvatarUpdatePayload` includes the nested `config` object.
|
||||
- `/avatar/chat/:id` renders a usable conversation without requiring a second login flow.
|
||||
|
||||
- [ ] **Step 1: Extend the frontend helper test first**
|
||||
|
||||
Add assertions for missing config defaults, payload preservation, chat request shape, and source labels `qa`, `knowledge`, `qwen`; run the test and verify the new assertions fail.
|
||||
|
||||
- [ ] **Step 2: Add chat API types and helper functions**
|
||||
|
||||
Add `ChatMessage`, `ChatResponse`, and `sendAvatarChat`. Keep Axios response unwrapping consistent with the existing interceptor; do not read an extra `.data` layer in the view.
|
||||
|
||||
- [ ] **Step 3: Extend AvatarEdit controls and payload**
|
||||
|
||||
Add the same style chips, range sliders, response length choices, and system prompt textarea used by `AvatarCreate.vue`. Initialize through `normalizeAvatarEditForm`; save through `buildAvatarUpdatePayload` so existing name/description/photo/status fields remain intact.
|
||||
|
||||
- [ ] **Step 4: Add the chat route and page**
|
||||
|
||||
Create a mobile-first page with a top back button, avatar identity header, scrollable message list, source/reference labels, textarea input, send button, empty-state starter prompts, and error retry. On send, append the user message, send the last 10 messages, append the assistant response, and keep the input focused only when the request succeeds.
|
||||
|
||||
- [ ] **Step 5: Wire management entry points**
|
||||
|
||||
Make the primary digital-avatar card/action open `/avatar/chat/${avatar.id}`; keep edit, knowledge, and delete actions separate so clicking chat does not trigger an unrelated action.
|
||||
|
||||
- [ ] **Step 6: Run frontend helper test and production build**
|
||||
|
||||
```bash
|
||||
cd /Users/yqq/Works/AiProject/huihuiSquare/digital-avatar-app
|
||||
node scripts/avatar-page-data.test.mjs
|
||||
./node_modules/.bin/vite build
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Commit the frontend chat/edit changes**
|
||||
|
||||
```bash
|
||||
git add digital-avatar-app/src/api/index.ts digital-avatar-app/src/utils/avatar-page-data.js digital-avatar-app/src/views/AvatarEdit.vue digital-avatar-app/src/views/AvatarManage.vue digital-avatar-app/src/views/AvatarChat.vue digital-avatar-app/src/router/index.ts digital-avatar-app/scripts/avatar-page-data.test.mjs
|
||||
git commit -m "feat: add avatar chat and editable personality settings"
|
||||
```
|
||||
|
||||
### Task 5: Rebuild knowledge management as scrollable tabbed tables
|
||||
|
||||
**Files:**
|
||||
- Modify: `digital-avatar-app/src/views/KnowledgeManage.vue`
|
||||
- Modify: `digital-avatar-app/src/views/QaPairEdit.vue`
|
||||
- Modify: `digital-avatar-app/src/api/index.ts`
|
||||
|
||||
**Interfaces:**
|
||||
- Documents and QAs remain loaded through the existing APIs and are independently rendered.
|
||||
- The page exposes `activeTab` values `docs` and `qa` and keeps data loaded when switching tabs.
|
||||
- Upload accept string and displayed help text include `.md` and `.txt`.
|
||||
|
||||
- [ ] **Step 1: Add a failing UI contract smoke test**
|
||||
|
||||
Add a small source-level Node assertion script or extend the existing helper test to assert the component contains the two tab labels, `activeTab`, `.md`, and `.txt`; run it before changing the component and confirm failure.
|
||||
|
||||
- [ ] **Step 2: Implement tab state and document panel**
|
||||
|
||||
Keep the upload zone and search controls in the document tab. Replace document cards with a semantic table: filename/type, size, vectorization status/chunks, created time, and delete action. Use a `.table-scroll` wrapper with a bounded `max-height` and `overflow-y: auto`.
|
||||
|
||||
- [ ] **Step 3: Implement the QA table panel**
|
||||
|
||||
Render question, answer summary, enabled switch/status, updated time, edit and delete actions. Keep the add button routing to `QaPairEdit.vue`. Do not fetch from the API again on every tab switch.
|
||||
|
||||
- [ ] **Step 4: Fix page scrolling and responsive layout**
|
||||
|
||||
Set the page to `min-height: 100dvh`, `overflow-x: hidden`, and normal vertical flow. Make the tab/table wrappers responsive so desktop uses side-by-side table columns where space permits and mobile stacks the active table with horizontal table overflow only when required.
|
||||
|
||||
- [ ] **Step 5: Run frontend build and inspect the generated CSS/JS markers**
|
||||
|
||||
Run `./node_modules/.bin/vite build` and assert the generated bundle contains the new tab labels and MD/TXT accept values.
|
||||
|
||||
- [ ] **Step 6: Commit the knowledge UI changes**
|
||||
|
||||
```bash
|
||||
git add digital-avatar-app/src/views/KnowledgeManage.vue digital-avatar-app/src/views/QaPairEdit.vue digital-avatar-app/src/api/index.ts digital-avatar-app/scripts
|
||||
git commit -m "feat: add text uploads and tabbed knowledge tables"
|
||||
```
|
||||
|
||||
### Task 6: Backend/API integration verification and test deployment
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/yqq/Works/AiProject/huihuiSquare/digital-avatar-app/.env` only if local test defaults need documenting; never commit secrets.
|
||||
- Modify: `/Users/yqq/Works/AiProject/huihuiSquare/digital-avatar-app/backend/.env` only through the test server deployment environment, never commit secrets.
|
||||
- Test: `digital-avatar-app/backend/tests/test_chat_orchestration.py`
|
||||
- Test: `digital-avatar-app/scripts/avatar-page-data.test.mjs`
|
||||
|
||||
**Interfaces:**
|
||||
- Test server receives `CHAT_API_URL`, `CHAT_API_KEY`, and `CHAT_MODEL` in `/root/avatar-test/.env` without logging the key.
|
||||
- Frontend `/api` reaches the newly deployed backend router.
|
||||
|
||||
- [ ] **Step 1: Verify model configuration availability without printing secrets**
|
||||
|
||||
On `geo-server`, inspect only whether `CHAT_API_KEY` is set and print `CHAT_API_URL`/`CHAT_MODEL`. If the key is absent, stop deployment and report that the user must provide/configure it; do not fall back to a fake answer.
|
||||
|
||||
- [ ] **Step 2: Rebuild the backend image and restart only avatar-test services**
|
||||
|
||||
From `/root/avatar-test`, rebuild `avatar-test-backend`, recreate that container, and confirm `/api/health` plus an unauthenticated chat request returns 401/未登录. Preserve the existing SQLite file by taking a timestamped copy before recreation and restoring it only if the compose flow replaces the container without a volume.
|
||||
|
||||
- [ ] **Step 3: Build and deploy the frontend dist safely**
|
||||
|
||||
Run `vite build`, upload `dist` to a timestamped staging directory, back up `/root/avatar-test/run-dist`, then copy only `assets/.` and `index.html` into `avatar-test-frontend` and restart it. Do not use a broad `docker cp run-dist/. html/` replacement.
|
||||
|
||||
- [ ] **Step 4: Run live QA-first acceptance checks**
|
||||
|
||||
Using a valid test app token, create one short MD or TXT document and one enabled QA pair. Send the exact QA question and verify response `source=qa`; send a non-QA question and verify the response is from `knowledge` or `qwen` with no secret leakage. Delete the temporary QA/document records after capturing results.
|
||||
|
||||
- [ ] **Step 5: Verify frontend and backend health**
|
||||
|
||||
```bash
|
||||
curl -fsS http://192.168.1.188:8099/api/health
|
||||
curl -fsS http://192.168.1.188:8011/api/health
|
||||
```
|
||||
|
||||
Inspect the deployed `index.html` asset names and confirm the chat route, MD/TXT accept values, and tab labels are in the new bundle. Record the deployed container status and backup path.
|
||||
|
||||
- [ ] **Step 6: Final verification and handoff**
|
||||
|
||||
Run the backend unit suite, Python compile check, frontend smoke test, Vite build, `git diff --check`, and `git status --short`. Report any remaining type-check limitation separately from build status; do not claim Qwen live success unless the configured test key produced a real response.
|
||||
Reference in New Issue
Block a user