Qwen-Ranker Pro全栈开发:从后端API到前端展示的完整实现

1. 引言

在当今信息爆炸的时代,如何从海量数据中快速准确地找到最相关的内容,成为了许多应用面临的核心挑战。语义搜索技术通过理解查询的深层含义,而不仅仅是关键词匹配,为用户提供了更精准的搜索结果。Qwen-Ranker Pro作为先进的语义重排序模型,能够对初步检索结果进行智能精排,显著提升搜索质量。

本文将带你完整实现一个基于Qwen-Ranker Pro的全栈应用,从前端界面到后端API,从数据库集成到模型部署,手把手教你构建一个功能完备的语义搜索系统。无论你是前端开发者想了解后端集成,还是后端工程师希望扩展全栈技能,这篇文章都能为你提供实用的指导和代码示例。

2. 技术架构设计

2.1 整体架构概述

我们的全栈应用采用典型的三层架构:前端展示层、后端服务层和数据存储层。前端使用Vue.js构建响应式用户界面,后端采用FastAPI提供RESTful API,数据库使用PostgreSQL存储文档和向量数据,Qwen-Ranker Pro模型负责语义重排序。

这种架构的优势在于各层职责清晰,便于维护和扩展。前端专注于用户体验,后端处理业务逻辑和模型推理,数据库负责数据持久化,模型提供AI能力。

2.2 核心技术选型

前端技术栈:Vue 3 + TypeScript + Element Plus + Axios。Vue 3提供了优秀的响应式系统和组合式API,TypeScript增强了代码类型安全,Element Plus提供了丰富的UI组件,Axios处理HTTP请求。

后端技术栈:FastAPI + Uvicorn + SQLAlchemy + Psycopg2。FastAPI以其高性能和自动API文档生成而著称,Uvicorn作为ASGI服务器,SQLAlchemy提供ORM支持,Psycopg2是PostgreSQL数据库驱动。

数据库:PostgreSQL + pgvector扩展。PostgreSQL是功能强大的关系型数据库,pgvector扩展支持向量相似度搜索,非常适合语义搜索场景。

AI模型:Qwen-Ranker Pro。该模型专门用于语义重排序,能够理解查询和文档之间的语义相关性,提供精准的排序分数。

3. 后端API开发

3.1 环境配置与依赖安装

首先创建项目目录并初始化Python虚拟环境:

mkdir qwen-ranker-pro-app
cd qwen-ranker-pro-app
python -m venv venv
source venv/bin/activate  # Linux/Mac
# 或 venv\Scripts\activate  # Windows

pip install fastapi uvicorn sqlalchemy psycopg2-binary python-multipart
pip install transformers torch

创建主要的依赖文件requirements.txt:

fastapi==0.104.1
uvicorn==0.24.0
sqlalchemy==2.0.23
psycopg2-binary==2.9.9
python-multipart==0.0.6
transformers==4.35.2
torch==2.1.0
python-dotenv==1.0.0

3.2 数据库模型设计

创建models.py文件定义数据模型:

from sqlalchemy import Column, Integer, String, Text, Float, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
from dotenv import load_dotenv

load_dotenv()

Base = declarative_base()

class Document(Base):
    __tablename__ = "documents"
    
    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(500), nullable=False)
    content = Column(Text, nullable=False)
    embedding = Column(Text)  # 存储向量化后的文本表示
    metadata = Column(Text)   # 存储额外元信息

# 数据库连接配置
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://user:password@localhost/qwen_ranker_db")
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# 创建表
def create_tables():
    Base.metadata.create_all(bind=engine)

3.3 FastAPI应用搭建

创建main.py文件设置FastAPI应用:

from fastapi import FastAPI, Depends, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
from typing import List
import models
import schemas
import services

app = FastAPI(
    title="Qwen-Ranker Pro API",
    description="语义搜索重排序API服务",
    version="1.0.0"
)

# 配置CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 数据库依赖
def get_db():
    db = models.SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.on_event("startup")
async def startup_event():
    models.create_tables()

@app.get("/")
async def root():
    return {"message": "Qwen-Ranker Pro API服务运行中"}

@app.post("/documents/", response_model=schemas.DocumentResponse)
async def create_document(document: schemas.DocumentCreate, db: Session = Depends(get_db)):
    return services.create_document(db=db, document=document)

@app.get("/documents/", response_model=List[schemas.DocumentResponse])
async def get_documents(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
    return services.get_documents(db=db, skip=skip, limit=limit)

@app.post("/search/", response_model=List[schemas.SearchResult])
async def semantic_search(query: schemas.SearchQuery, db: Session = Depends(get_db)):
    return services.semantic_search(db=db, query=query.query)

3.4 Qwen-Ranker Pro集成

创建services.py实现核心业务逻辑:

from sqlalchemy.orm import Session
import models
import schemas
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import torch
import numpy as np
from typing import List

# 加载Qwen-Ranker Pro模型
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen-Ranker-Pro")
model = AutoModelForSequenceClassification.from_pretrained("Qwen/Qwen-Ranker-Pro")

def create_document(db: Session, document: schemas.DocumentCreate):
    db_document = models.Document(
        title=document.title,
        content=document.content,
        metadata=document.metadata
    )
    db.add(db_document)
    db.commit()
    db.refresh(db_document)
    return db_document

def get_documents(db: Session, skip: int = 0, limit: int = 100):
    return db.query(models.Document).offset(skip).limit(limit).all()

def semantic_search(db: Session, query: str):
    # 首先进行初步检索(这里简化处理,实际应使用向量数据库)
    documents = db.query(models.Document).limit(50).all()
    
    # 使用Qwen-Ranker Pro进行重排序
    ranked_results = rerank_documents(query, documents)
    
    return ranked_results

def rerank_documents(query: str, documents: List[models.Document]):
    # 准备模型输入
    pairs = [[query, doc.content] for doc in documents]
    
    # 批量处理
    inputs = tokenizer(
        pairs, 
        padding=True, 
        truncation=True, 
        max_length=512, 
        return_tensors="pt"
    )
    
    # 模型推理
    with torch.no_grad():
        scores = model(**inputs).logits
    
    # 处理结果
    results = []
    for i, doc in enumerate(documents):
        results.append({
            "id": doc.id,
            "title": doc.title,
            "content": doc.content[:200] + "..." if len(doc.content) > 200 else doc.content,
            "score": float(scores[i]),
            "metadata": doc.metadata
        })
    
    # 按分数降序排序
    results.sort(key=lambda x: x["score"], reverse=True)
    
    return results[:10]  # 返回前10个结果

3.5 数据序列化模型

创建schemas.py定义Pydantic模型:

from pydantic import BaseModel
from typing import Optional

class DocumentBase(BaseModel):
    title: str
    content: str
    metadata: Optional[str] = None

class DocumentCreate(DocumentBase):
    pass

class DocumentResponse(DocumentBase):
    id: int
    
    class Config:
        from_attributes = True

class SearchQuery(BaseModel):
    query: str

class SearchResult(BaseModel):
    id: int
    title: str
    content: str
    score: float
    metadata: Optional[str] = None

4. 前端界面开发

4.1 Vue项目初始化

使用Vite创建Vue项目:

npm create vite@latest frontend -- --template vue-ts
cd frontend
npm install
npm install element-plus axios

配置main.ts:

import { createApp } from 'vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(ElementPlus)
app.use(router)
app.mount('#app')

4.2 搜索界面组件

创建SearchComponent.vue:

<template>
  <div class="search-container">
    <el-input
      v-model="searchQuery"
      placeholder="请输入搜索内容..."
      size="large"
      @keyup.enter="handleSearch"
    >
      <template #append>
        <el-button :icon="Search" @click="handleSearch">搜索</el-button>
      </template>
    </el-input>

    <div v-if="loading" class="loading">
      <el-icon class="is-loading"><Loading /></el-icon>
      搜索中...
    </div>

    <div v-if="results.length > 0" class="results-container">
      <div class="results-count">找到 {{ results.length }} 条结果</div>
      
      <el-card 
        v-for="(result, index) in results" 
        :key="result.id"
        class="result-card"
        :class="{ 'first-result': index === 0 }"
      >
        <template #header>
          <div class="card-header">
            <span class="result-title">{{ result.title }}</span>
            <el-tag 
              type="success" 
              size="small"
              class="score-tag"
            >
              相关度: {{ result.score.toFixed(4) }}
            </el-tag>
          </div>
        </template>
        
        <div class="result-content">
          {{ result.content }}
        </div>
        
        <div v-if="result.metadata" class="result-metadata">
          <el-text type="info" size="small">
            {{ result.metadata }}
          </el-text>
        </div>
      </el-card>
    </div>

    <div v-if="searched && results.length === 0" class="no-results">
      <el-empty description="没有找到相关结果" />
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
import { Search, Loading } from '@element-plus/icons-vue'
import axios from 'axios'

interface SearchResult {
  id: number
  title: string
  content: string
  score: number
  metadata?: string
}

const searchQuery = ref('')
const results = ref<SearchResult[]>([])
const loading = ref(false)
const searched = ref(false)

const API_BASE = import.meta.env.VITE_API_BASE || 'http://localhost:8000'

const handleSearch = async () => {
  if (!searchQuery.value.trim()) return

  loading.value = true
  searched.value = true
  
  try {
    const response = await axios.post(`${API_BASE}/search/`, {
      query: searchQuery.value
    })
    results.value = response.data
  } catch (error) {
    console.error('搜索失败:', error)
    ElMessage.error('搜索失败,请稍后重试')
  } finally {
    loading.value = false
  }
}
</script>

<style scoped>
.search-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.loading {
  text-align: center;
  padding: 40px;
  color: #666;
}

.results-count {
  margin: 20px 0;
  font-size: 14px;
  color: #666;
}

.result-card {
  margin-bottom: 16px;
  transition: all 0.3s ease;
}

.result-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.first-result {
  border: 2px solid #409eff;
}

.card-header {
  display: flex;
  justify-content: between;
  align-items: center;
}

.result-title {
  font-weight: bold;
  font-size: 16px;
}

.score-tag {
  margin-left: auto;
}

.result-content {
  line-height: 1.6;
  color: #333;
}

.result-metadata {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid #eee;
}

.no-results {
  text-align: center;
  padding: 40px 0;
}
</style>

4.3 主应用组件

更新App.vue:

<template>
  <div id="app">
    <el-container>
      <el-header class="app-header">
        <h1>Qwen-Ranker Pro 语义搜索系统</h1>
        <el-text type="info">基于先进AI模型的智能语义重排序</el-text>
      </el-header>
      
      <el-main>
        <router-view />
      </el-main>
      
      <el-footer class="app-footer">
        <el-text type="info" size="small">
          © 2024 语义搜索系统 | 基于 Qwen-Ranker Pro 构建
        </el-text>
      </el-footer>
    </el-container>
  </div>
</template>

<script setup lang="ts">
// 主应用组件逻辑
</script>

<style>
#app {
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

.app-header {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  text-align: center;
  padding: 20px;
}

.app-header h1 {
  margin: 0 0 8px 0;
  font-size: 2em;
}

.app-footer {
  text-align: center;
  padding: 20px;
  background-color: #f5f7fa;
}
</style>

5. 系统部署与优化

5.1 环境配置与部署

创建Dockerfile用于容器化部署:

# 后端Dockerfile
FROM python:3.9-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]

创建docker-compose.yml编排服务:

version: '3.8'

services:
  backend:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:password@db:5432/qwen_ranker_db
    depends_on:
      - db
    volumes:
      - .:/app

  frontend:
    build: ./frontend
    ports:
      - "3000:3000"
    depends_on:
      - backend

  db:
    image: postgres:13
    environment:
      - POSTGRES_DB=qwen_ranker_db
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data

volumes:
  postgres_data:

5.2 性能优化建议

数据库优化

# 添加索引优化查询性能
class Document(Base):
    # ... 其他字段
    __table_args__ = (
        Index('ix_document_content', 'content', postgresql_using='gin'),
        Index('ix_document_title', 'title'),
    )

模型推理优化

# 使用模型缓存和批量处理
model = AutoModelForSequenceClassification.from_pretrained(
    "Qwen/Qwen-Ranker-Pro",
    torchscript=True  # 启用TorchScript优化
)

# 启用模型eval模式和多线程
model.eval()
torch.set_num_threads(4)

API响应优化

# 使用异步数据库操作
@app.post("/search/")
async def semantic_search(query: schemas.SearchQuery, db: Session = Depends(get_db)):
    # 使用异步方式执行耗时操作
    results = await run_in_threadpool(services.semantic_search, db, query.query)
    return results

6. 总结

通过本文的完整实现,我们构建了一个基于Qwen-Ranker Pro的全栈语义搜索应用。从后端的FastAPI服务到前端的Vue界面,从数据库设计到模型集成,每个环节都提供了详细的代码示例和实践建议。

这个项目展示了如何将先进的AI模型与传统的Web开发技术相结合,创造出智能化的应用体验。Qwen-Ranker Pro的语义重排序能力显著提升了搜索结果的相关性,而全栈架构确保了系统的可扩展性和维护性。

在实际部署时,建议进一步考虑缓存机制、负载均衡和监控系统,以确保生产环境的稳定性和性能。此外,可以根据具体业务需求调整模型参数和搜索策略,获得更好的效果。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐