Skip to content

Commit 2b98e37

Browse files
authored
Merge pull request #2 from tzuhsiang/mcp_agent
Mcp agent
2 parents 8eedda2 + 63ad68f commit 2b98e37

File tree

11 files changed

+465
-93
lines changed

11 files changed

+465
-93
lines changed

README.md

Lines changed: 54 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
# 📚 LibriFlow - 個人圖書管理系統
22

3-
一個基於 **FastAPI****Vue.js 3****PostgreSQL** 的全端圖書管理應用,使用 **Docker Compose** 進行容器化部署
3+
一個基於 **FastAPI****Vue.js 3****PostgreSQL** 的全端圖書管理應用,整合 **AI Agent** **MCP (Model Context Protocol)** 技術,提供智能化的語音/文字互動管理功能
44

55
![LibriFlow UI](imgs/UI.png)
66

77
## 🌟 功能特色
88

99
-**現代化 UI** - 使用 Vue 3 Composition API 和 Tailwind CSS 打造美觀的響應式介面
10+
- 🤖 **AI 智慧助理** - 內建交談式 Agent,可透過自然語言進行書籍管理操作
1011
- 📖 **完整的 CRUD 操作** - 新增、查看、更新和刪除書籍
1112
-**互動式評分系統** - 點擊星星直接為書籍評分,即時回饋
1213
- 📊 **閱讀狀態追蹤** - 視覺化的狀態標籤(未讀、閱讀中、已完成)
@@ -15,14 +16,29 @@
1516
- 🐳 **Docker 部署** - 一鍵啟動所有服務
1617
- 🔄 **RESTful API** - 完整的後端 API,自動生成 Swagger 文件
1718

19+
## 🤖 AI 讀書助理
20+
21+
本專案整合了基於 **Pydantic AI****MCP Server** 的智能助理,使用者可以透過右下角的對話視窗與 Agent 互動。
22+
23+
![LibriFlow Agent UI](imgs/AGENT-UI.png)
24+
25+
### 支援功能
26+
- **新增書籍**:「幫我新增一本《原子習慣》,作者是詹姆斯·克利爾」
27+
- **查詢書籍**:「列出我目前正在閱讀的書」
28+
- **修改狀態**:「把《原子習慣》標記為已讀完」
29+
- **修改評分**:「給《哈利波特》打 5 顆星」
30+
- **自動同步**:Agent 操作完成後,畫面會自動更新顯示最新資料
31+
1832
## 🏗️ 技術架構
1933

2034
### 後端 (Backend)
2135
- **框架**: FastAPI (Python 3.12+)
36+
- **AI Agent**: Pydantic AI
37+
- **工具協定**: Model Context Protocol (MCP) by Anthropic
38+
- **LLM**: Azure OpenAI (GPT-4o)
2239
- **ORM**: SQLAlchemy
2340
- **資料驗證**: Pydantic
2441
- **資料庫**: PostgreSQL 15
25-
- **API 文件**: Swagger UI (自動生成)
2642

2743
### 前端 (Frontend)
2844
- **框架**: Vue.js 3 (Composition API)
@@ -32,15 +48,15 @@
3248

3349
### 部署 (Deployment)
3450
- **容器化**: Docker & Docker Compose
35-
- **網路**: Docker Bridge Network
36-
- **資料持久化**: Docker Volumes
3751

3852
## 📁 專案結構
3953

4054
```
4155
libray_implement/
42-
├── backend/ # FastAPI 後端
43-
│ ├── main.py # 應用主入口與路由
56+
├── backend/ # FastAPI 後端 & AI Agent
57+
│ ├── main.py # 應用主入口與路由 (含 Chat Endpoint)
58+
│ ├── agent.py # Pydantic AI Agent 定義
59+
│ ├── mcp_server.py # MCP Server 工具實作
4460
│ ├── models.py # SQLAlchemy 資料庫模型
4561
│ ├── schemas.py # Pydantic 資料驗證模型
4662
│ ├── database.py # 資料庫連線配置
@@ -50,18 +66,15 @@ libray_implement/
5066
├── frontend/ # Vue.js 前端
5167
│ ├── src/
5268
│ │ ├── components/ # Vue 組件
53-
│ │ │ ├── BookForm.vue # 新增書籍表單(摺疊式面板)
69+
│ │ │ ├── ChatWindow.vue # AI 對話視窗組件
70+
│ │ │ ├── BookForm.vue # 新增書籍表單
5471
│ │ │ └── BookList.vue # 書籍清單展示
5572
│ │ ├── App.vue # 主頁面
56-
│ │ ├── main.js # 應用入口
57-
│ │ └── style.css # Tailwind CSS 樣式
58-
│ ├── package.json # npm 依賴配置
59-
│ ├── vite.config.js # Vite 配置
73+
│ │ └── ...
6074
│ └── Dockerfile # 前端容器映像檔
6175
├── envs/ # 環境變數配置
62-
│ ├── .env # 環境變數設定檔
76+
│ ├── .env # 環境變數設定檔 (含 Azure OpenAI Key)
6377
│ └── .env.example # 環境變數範例檔
64-
├── imgs/ # 專案截圖
6578
├── docker-compose.yml # 多容器編排定義
6679
└── README.md # 專案說明文件
6780
```
@@ -84,7 +97,13 @@ cd libray_implement
8497
2. **配置環境變數**
8598
```bash
8699
cp envs/.env.example envs/.env
87-
# 根據需要修改 envs/.env 中的配置
100+
```
101+
**重要**:請編輯 `envs/.env`,填入您的 **Azure OpenAI** 相關金鑰,以啟用 AI 功能:
102+
```env
103+
AZURE_OPENAI_API_KEY=your_key
104+
AZURE_OPENAI_ENDPOINT=your_endpoint
105+
AZURE_OPENAI_API_VERSION=2024-02-15-preview
106+
AZURE_DEPLOYMENT_NAME=gpt-4o
88107
```
89108

90109
3. **啟動所有服務**
@@ -94,99 +113,48 @@ docker compose up --build
94113

95114
### 訪問應用
96115

97-
啟動成功後,可以訪問:
98-
99116
- 🌐 **前端界面**: http://localhost:5173
100117
- 🔧 **後端 API**: http://localhost:8000
101118
- 📚 **API 文件**: http://localhost:8000/docs
102119

103-
![API Documentation](imgs/doc.png)
104-
105120
## 📊 資料庫 Schema
106121

107-
**books 表**
108-
109-
| 欄位名 | 類型 | 約束 | 說明 |
110-
|--------|------|------|------|
111-
| id | Integer | Primary Key, Autoincrement | 書籍 ID |
112-
| title | String | Not Null | 書名 |
113-
| author | String | Not Null | 作者 |
114-
| status | String | Default: 'unread' | 閱讀狀態 (unread/reading/finished) |
115-
| rating | Integer | Default: 0, Min: 0, Max: 5 | 評分 |
122+
| 欄位名 | 類型 | 說明 |
123+
|--------|------|------|
124+
| id | Integer | Primary Key |
125+
| title | String | 書名 |
126+
| author | String | 作者 |
127+
| status | String | unread / reading / finished |
128+
| rating | Integer | 0-5 |
116129

117130
## 🔌 API 端點
118131

132+
包含標準 RESTful API 與 Chat API:
133+
119134
| 方法 | 路徑 | 說明 |
120135
|------|------|------|
121-
| GET | `/books` | 取得所有書籍清單 |
122-
| GET | `/books/{id}` | 取得單一書籍 |
136+
| POST | `/chat` | 與 AI Agent 對話 |
137+
| GET | `/books` | 取得書籍清單 |
123138
| POST | `/books` | 新增書籍 |
124-
| PUT | `/books/{id}` | 更新書籍資訊 |
125-
| DELETE | `/books/{id}` | 刪除書籍 |
126-
| GET | `/docs` | Swagger UI 文件 |
127-
128-
## 🎨 UI 功能
129-
130-
### 新增書籍 (優化)
131-
- **摺疊式面板設計**,保持介面整潔
132-
- 點擊展開輸入表單,新增或取消後自動收合
133-
- 完整欄位支援:書名、作者、狀態、評分
134-
135-
### 書籍清單 (優化)
136-
- **卡片式展示**:美觀的陰影與圓角設計
137-
- **互動式評分**:滑鼠懸停預覽星星,點擊即時更新
138-
- **視覺化狀態標籤**
139-
- 📕 未讀 (灰色背景)
140-
- 📖 閱讀中 (藍色背景)
141-
- ✅ 已完成 (綠色背景)
142-
- **空狀態引導**:當無書籍時顯示友善的提示圖示
143-
- **即時響應**:操作後無需重整頁面
144-
- **一鍵刪除**:支援確認對話框防止誤刪
145-
146-
### 統計儀表板
147-
- 總書籍數
148-
- 閱讀中書籍數
149-
- 已完成書籍數
150-
151-
## 🛠️ 開發
152-
153-
### 停止服務
154-
```bash
155-
docker compose down
156-
```
139+
| ... | ... | ... |
157140

158-
### 查看日誌
159-
```bash
160-
docker compose logs -f
161-
```
141+
## 🛠️ 開發與除錯
162142

163-
### 僅重建特定服務
143+
- 查看後端日誌 (包含 Agent 思考過程):
164144
```bash
165-
docker compose up --build backend
166-
docker compose up --build frontend
167-
```
168-
169-
## 🌐 Proxy 設定
170-
171-
如果您的網路環境需要使用 proxy,已在 `envs/.env` 中配置:
172-
173-
```env
174-
HTTP_PROXY=http://{proxy_ip:port}
175-
HTTPS_PROXY=https://{proxy_ip:port}
176-
NO_PROXY=localhost,127.0.0.1,db,backend,frontend
145+
docker compose logs -f backend
177146
```
178147

179-
## 📝 授權
148+
## 授權
180149

181150
MIT License
182151

183-
## 👨‍💻 作者
152+
## 作者
184153

185154
tzuhsiang
186155

187-
## 🙏 致謝
156+
## 致謝
188157

189-
- FastAPI - 現代化 Python Web 框架
190-
- Vue.js - 漸進式 JavaScript 框架
191-
- Tailwind CSS - 實用優先的 CSS 框架
192-
- PostgreSQL - 強大的開源關聯式資料庫
158+
- Pydantic AI
159+
- Model Context Protocol (MCP)
160+
- FastAPI, Vue.js, PostgreSQL

PROMPT_SPEC.md renamed to SPEC.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,7 @@ rating Integer Min: 1, Max: 5, Default: 0
7676

7777

7878

79+
80+
81+
82+
File renamed without changes.

SPEC_3.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
在既有專案做以下修改
2+
3+
1. 建立一個新的 mcp server,參考後端,提供以下功能:
4+
- 新增書籍
5+
- 刪除書籍
6+
- 修改評價
7+
- 修改閱讀狀態
8+
- 列出書籍
9+
2. 建立一個新的 Agent,註冊 mcp server 的工具
10+
3. 在UI介面新增一個對話視窗,用Agent來與使用者互動
11+
- 視窗尺寸需放大
12+
- 支援重置對話功能
13+
- Agent 操作完成後需自動更新頁面列表
14+
4. 使用envs/.env的設定檔,設定azure openai api key
15+
5. 使用 docker compose 來管理所有開發套件
16+

backend/agent.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import os
2+
import asyncio
3+
from dataclasses import dataclass
4+
from mcp import StdioServerParameters, ClientSession
5+
from mcp.client.stdio import stdio_client
6+
from pydantic_ai import Agent, RunContext
7+
import nest_asyncio
8+
9+
# Apply nest_asyncio to allow nested event loops if needed
10+
nest_asyncio.apply()
11+
12+
@dataclass
13+
class McpDeps:
14+
session: ClientSession
15+
16+
# Initialize Agent
17+
# Ensure env vars for Azure OpenAI are set (AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_API_VERSION)
18+
# The model name is passed in the constructor.
19+
agent = Agent(
20+
f'azure:{os.getenv("AZURE_DEPLOYMENT_NAME", "gpt-4o")}',
21+
deps_type=McpDeps,
22+
system_prompt="""
23+
你是一個圖書管理助理。
24+
你可以協助使用者新增、查詢、修改、刪除書籍。
25+
26+
支援的功能:
27+
- 新增書籍 (add_book)
28+
- 刪除書籍 (delete_book)
29+
- 修改評價 (update_book_rating)
30+
- 修改閱讀狀態 (update_book_status)
31+
- 列出書籍 (list_books)
32+
33+
書籍狀態 (status) 只能是以下之一:unread (未讀), reading (閱讀中), finished (已讀)。
34+
35+
請使用提供的工具執行操作。請用繁體中文回答。
36+
"""
37+
)
38+
39+
@agent.tool
40+
async def list_books(ctx: RunContext[McpDeps], limit: int = 20, offset: int = 0) -> str:
41+
"""列出書籍清單"""
42+
result = await ctx.deps.session.call_tool("list_books", arguments={"limit": limit, "offset": offset})
43+
return result.content[0].text
44+
45+
@agent.tool
46+
async def add_book(ctx: RunContext[McpDeps], title: str, author: str, status: str = "unread", rating: int = 0) -> str:
47+
"""新增書籍 (status: unread, reading, finished)"""
48+
result = await ctx.deps.session.call_tool("add_book", arguments={
49+
"title": title, "author": author, "status": status, "rating": rating
50+
})
51+
return result.content[0].text
52+
53+
@agent.tool
54+
async def delete_book(ctx: RunContext[McpDeps], book_id: int) -> str:
55+
"""刪除書籍"""
56+
result = await ctx.deps.session.call_tool("delete_book", arguments={"book_id": book_id})
57+
return result.content[0].text
58+
59+
@agent.tool
60+
async def update_book_rating(ctx: RunContext[McpDeps], book_id: int, rating: int) -> str:
61+
"""修改書本評價 (0-5)"""
62+
result = await ctx.deps.session.call_tool("update_book_rating", arguments={"book_id": book_id, "rating": rating})
63+
return result.content[0].text
64+
65+
@agent.tool
66+
async def update_book_status(ctx: RunContext[McpDeps], book_id: int, status: str) -> str:
67+
"""修改閱讀狀態 (unread, reading, finished)"""
68+
result = await ctx.deps.session.call_tool("update_book_status", arguments={"book_id": book_id, "status": status})
69+
return result.content[0].text
70+
71+
async def process_message(user_message: str):
72+
server_script = os.path.join(os.path.dirname(__file__), "mcp_server.py")
73+
74+
server_params = StdioServerParameters(
75+
command="python",
76+
args=[server_script],
77+
env=os.environ.copy()
78+
)
79+
80+
async with stdio_client(server_params) as (read, write):
81+
async with ClientSession(read, write) as session:
82+
await session.initialize()
83+
84+
deps = McpDeps(session=session)
85+
result = await agent.run(user_message, deps=deps)
86+
return result.output

backend/main.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,26 @@ def delete_book(book_id: int, db: Session = Depends(get_db)):
7575
return None
7676

7777

78+
class ChatRequest(schemas.BaseModel): # schemas usually has Pydantic models, or standard import
79+
message: str
80+
81+
class ChatResponse(schemas.BaseModel):
82+
reply: str
83+
84+
import agent
85+
86+
@app.post("/chat", response_model=ChatResponse)
87+
async def chat_endpoint(request: ChatRequest):
88+
"""跟 Agent 對話"""
89+
# Simply call the agent function.
90+
# Note: agent.process_message spawns a subprocess each time.
91+
# For a high traffic app this is bad, but for this task/demo it's fine.
92+
try:
93+
reply = await agent.process_message(request.message)
94+
return ChatResponse(reply=reply)
95+
except Exception as e:
96+
raise HTTPException(status_code=500, detail=str(e))
97+
98+
7899
if __name__ == "__main__":
79100
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)

0 commit comments

Comments
 (0)