导航菜单

  • 1.langchain.intro
  • 2.langchain.chat_models
  • 3.langchain.prompts
  • 4.langchain.example_selectors
  • 5.output_parsers
  • 6.Runnable
  • 7.memory
  • 8.document_loaders
  • 9.text_splitters
  • 10.embeddings
  • 11.tool
  • 12.retrievers
  • 13.optimize
  • 14.项目介绍
  • 15.启动HTTP
  • 16.数据与模型
  • 17.权限管理
  • 18.知识库管理
  • 19.设置
  • 20.文档管理
  • 21.聊天
  • 22.API文档
  • 23.RAG优化
  • 24.索引时优化
  • 25.检索前优化
  • 26.检索后优化
  • 27.系统优化
  • 28.GraphRAG
  • 29.图
  • 30.为什么选择图数据库
  • 31.什么是 Neo4j
  • 32.安装和连接 Neo4j
  • 33.Neo4j核心概念
  • 34.Cypher基础
  • 35.模式匹配
  • 36.数据CRUD操作
  • 37.GraphRAG
  • 38.查询和过滤
  • 39.结果处理和聚合
  • 40.语句组合
  • 41.子查询
  • 42.模式和约束
  • 43.日期时间处理
  • 44.Cypher内置函数
  • 45.Python操作Neo4j
  • 46.neo4j
  • 47.py2neo
  • 48.Streamlit
  • 49.Pandas
  • 50.graphRAG
  • 51.deepdoc
  • 52.deepdoc
  • 53.deepdoc
  • 55.deepdoc
  • 54.deepdoc
  • Pillow
  • 1.本章目标
  • 2.目录结构
  • 3.页面渲染
    • 3.1. settings.py
    • 3.2. settings.html
    • 3.3. init.py
    • 3.4. init.py
  • 4.嵌入模型
    • 4.1. settings_service.py
    • 4.2. models_config.py
    • 4.3. settings.py
    • 4.4. utils.py
    • 4.5. settings.html
  • 5.大模型设置
    • 5.1. settings.html
  • 6.提示词设置
    • 6.1. settings.html
  • 7.检索设置
    • 7.1. settings.html

1.本章目标 #

本章将介绍系统设置模块的主要功能与实现思路。内容涵盖如何通过前端表单配置和保存各类参数(如向量模型、LLM模型、API Key、系统提示词、检索策略等),以及设置页面与后端接口的数据交互机制,帮助读者理解如何自定义和管理平台核心参数,实现智能问答、知识库检索等能力的灵活配置。

2.目录结构 #

// 项目根目录
rag-lite/
    // 应用目录
    ├── app/
        // 蓝图文件夹,包含各个业务模块
        │   ├── blueprints/
        // 蓝图包初始化文件
        │   │   ├── __init__.py
        // 认证相关蓝图
        │   │   ├── auth.py
        // 知识库相关蓝图
        │   │   ├── knowledgebase.py
        // 设置相关蓝图
        │   │   ├── settings.py
        // 工具类相关蓝图
        │   │   └── utils.py
        // ORM模型定义目录
        │   ├── models/
        // models包初始化文件
        │   │   ├── __init__.py
        // 基础模型
        │   │   ├── base.py
        // 聊天消息模型
        │   │   ├── chat_message.py
        // 聊天会话模型
        │   │   ├── chat_session.py
        // 文档模型
        │   │   ├── document.py
        // 知识库模型
        │   │   ├── knowledgebase.py
        // 设置模型
        │   │   ├── settings.py
        // 用户模型
        │   │   └── user.py
        // 业务服务层目录
        │   ├── services/
            // 存储服务相关目录
            │   ├── storage/
                // 存储包初始化文件
                │   ├── __init__.py
                // 存储基础类
                │   ├── base.py
                // 存储工厂
                │   ├── factory.py
                // 本地存储实现
                │   ├── local_storage.py
                // MinIO 对象存储实现
                │   └── minio_storage.py
            // 基础服务
            │   ├── base_service.py
            // 知识库服务
            │   ├── knowledgebase_service.py
            // 设置服务
            │   ├── settings_service.py
            // 存储服务
            │   ├── storage_service.py
            // 用户服务
            │   └── user_service.py
        // 静态文件目录
        │   ├── static/
        // 前端模板目录
        │   ├── templates/
            // 基础模板
            │   ├── base.html
            // 首页模板
            │   ├── home.html
            // 知识库列表页面
            │   ├── kb_list.html
            // 登录页面
            │   ├── login.html
            // 注册页面
            │   ├── register.html
            // 设置页面
            │   └── settings.html
        // 工具函数目录
        │   ├── utils/
            // 认证相关工具
            │   ├── auth.py
            // 数据库工具
            │   ├── db.py
            // 日志相关工具
            │   ├── logger.py
            // 模型配置工具
            │   └── models_config.py
        // 应用初始化文件
        │   ├── __init__.py
        // 配置文件
        │   └── config.py
    // 日志文件目录
    ├── logs/
        // 主日志文件
        │   └── rag_lite.log
    // 存储文件夹
    ├── storages/
        // 封面存储目录
        │   └── covers/
    // 项目主程序入口(Flask应用启动点)
    ├── main.py
    // 项目开发计划
    ├── plan.md
    // Python 项目依赖描述文件(PEP 621)
    └── pyproject.toml

3.页面渲染 #

3.1. settings.py #

app/blueprints/settings.py

"""
设置相关路由(视图 + API)
"""
from flask import Blueprint, render_template
from app.utils.auth import login_required
import logging

logger = logging.getLogger(__name__)

bp = Blueprint('settings', __name__)

@bp.route('/settings')
@login_required
def settings_view():
    """设置页面"""
    return render_template('settings.html')

3.2. settings.html #

app/templates/settings.html

{% extends "base.html" %}

{% block title %}设置 - RAG Lite{% endblock %}

{% block content %}
<div class="row">
    <div class="col-12">
        <nav aria-label="breadcrumb" class="mb-3">
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="/">首页</a></li>
                <li class="breadcrumb-item active">设置</li>
            </ol>
        </nav>

        <h2><i class="bi bi-gear"></i> 系统设置</h2>
        <p class="text-muted">配置模型、提示词和检索参数</p>

        <form id="settingsForm" onsubmit="saveSettings(event)">
            <!-- 标签页导航 -->
            <ul class="nav nav-tabs mb-4" id="settingsTabs" role="tablist">
                <li class="nav-item" role="presentation">
                    <button class="nav-link active" id="embedding-tab" data-bs-toggle="tab" data-bs-target="#embedding" type="button" role="tab">
                        <i class="bi bi-diagram-3"></i> 向量嵌入模型
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="llm-tab" data-bs-toggle="tab" data-bs-target="#llm" type="button" role="tab">
                        <i class="bi bi-robot"></i> 大语言模型
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="prompt-tab" data-bs-toggle="tab" data-bs-target="#prompt" type="button" role="tab">
                        <i class="bi bi-chat-quote"></i> 提示词设置
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="retrieval-tab" data-bs-toggle="tab" data-bs-target="#retrieval" type="button" role="tab">
                        <i class="bi bi-search"></i> 检索设置
                    </button>
                </li>
            </ul>

            <!-- 标签页内容 -->
            <div class="tab-content" id="settingsTabContent">
                <!-- 标签1: 向量嵌入模型 -->
                <div class="tab-pane fade show active" id="embedding" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-diagram-3"></i> 向量嵌入模型(Embedding)</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">提供商 <span class="text-danger">*</span></label>
                                <select class="form-select" id="embeddingProvider" name="embedding_provider" onchange="updateEmbeddingForm()" required>
                                    <option value="huggingface">HuggingFace</option>
                                    <option value="openai">OpenAI</option>
                                    <option value="ollama">Ollama</option>
                                </select>
                                <div class="form-text">选择向量嵌入模型提供商</div>
                            </div>

                            <div class="mb-3" id="embeddingModelNameGroup">
                                <label class="form-label">模型名称</label>
                                <select class="form-select" id="embeddingModelName" name="embedding_model_name">
                                    <option value="">请选择模型</option>
                                    <!-- 动态填充 -->
                                </select>
                                <div class="form-text">选择模型名称或路径</div>
                            </div>

                            <div class="mb-3" id="embeddingApiKeyGroup" style="display: none;">
                                <label class="form-label">API Key</label>
                                <input type="password" class="form-control" id="embeddingApiKey" name="embedding_api_key" placeholder="输入 API Key">
                                <div class="form-text">某些提供商需要 API Key</div>
                            </div>

                            <div class="mb-3" id="embeddingBaseUrlGroup" style="display: none;">
                                <label class="form-label">Base URL</label>
                                <input type="text" class="form-control" id="embeddingBaseUrl" name="embedding_base_url" placeholder="例如: http://localhost:11434">
                                <div class="form-text">API Base URL(Ollama 需要)</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签2: 大语言模型 -->
                <div class="tab-pane fade" id="llm" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-robot"></i> 大语言模型(LLM)</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">提供商 <span class="text-danger">*</span></label>
                                <select class="form-select" id="llmProvider" name="llm_provider" onchange="updateLLMForm()" required>
                                    <option value="deepseek">DeepSeek</option>
                                    <option value="openai">OpenAI</option>
                                    <option value="ollama">Ollama</option>
                                </select>
                                <div class="form-text">选择大语言模型提供商</div>
                            </div>

                            <div class="mb-3" id="llmModelNameGroup">
                                <label class="form-label">模型名称</label>
                                <select class="form-select" id="llmModelName" name="llm_model_name">
                                    <option value="">请选择模型</option>
                                    <!-- 动态填充 -->
                                </select>
                                <div class="form-text">选择模型名称</div>
                            </div>

                            <div class="mb-3" id="llmApiKeyGroup">
                                <label class="form-label">API Key</label>
                                <input type="password" class="form-control" id="llmApiKey" name="llm_api_key" placeholder="输入 API Key">
                                <div class="form-text">某些提供商需要 API Key</div>
                            </div>

                            <div class="mb-3" id="llmBaseUrlGroup">
                                <label class="form-label">Base URL</label>
                                <input type="text" class="form-control" id="llmBaseUrl" name="llm_base_url" placeholder="例如: https://api.deepseek.com">
                                <div class="form-text">API Base URL</div>
                            </div>

                            <div class="mb-3">
                                <label class="form-label">温度 (Temperature)</label>
                                <input type="number" class="form-control" id="llmTemperature" name="llm_temperature" 
                                       value="0.7" step="0.1" min="0" max="2" placeholder="0.7">
                                <div class="form-text">控制输出的随机性,值越大越随机(0-2)</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签3: 提示词设置 -->
                <div class="tab-pane fade" id="prompt" role="tabpanel">
                    <div class="card">
                        <div class="card-body">
                            <!-- 子标签页导航 -->
                            <ul class="nav nav-tabs mb-4" id="promptSubTabs" role="tablist">
                                <li class="nav-item" role="presentation">
                                    <button class="nav-link active" id="chat-prompt-sub-tab" data-bs-toggle="tab" data-bs-target="#chat-prompt-sub" type="button" role="tab">
                                        <i class="bi bi-chat"></i> 普通聊天提示词
                                    </button>
                                </li>
                                <li class="nav-item" role="presentation">
                                    <button class="nav-link" id="rag-prompt-sub-tab" data-bs-toggle="tab" data-bs-target="#rag-prompt-sub" type="button" role="tab">
                                        <i class="bi bi-book"></i> 知识库聊天提示词
                                    </button>
                                </li>
                            </ul>

                            <!-- 子标签页内容 -->
                            <div class="tab-content" id="promptSubTabContent">
                                <!-- 子标签1: 普通聊天提示词 -->
                                <div class="tab-pane fade show active" id="chat-prompt-sub" role="tabpanel">
                                    <div class="mb-4">
                                        <label class="form-label">普通聊天系统提示词</label>
                                        <textarea class="form-control" id="chatSystemPrompt" name="chat_system_prompt" rows="10" 
                                                  placeholder="输入普通聊天提示词..."></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-0">普通聊天提示词用于指导AI助手在普通聊天(未选择知识库)时的回答风格和行为。</p>
                                            <p class="mb-0 mt-2"><strong>注意:</strong>这是系统消息的内容,不能使用变量。</p>
                                        </div>
                                    </div>
                                </div>

                                <!-- 子标签2: 知识库聊天提示词 -->
                                <div class="tab-pane fade" id="rag-prompt-sub" role="tabpanel">
                                    <div class="mb-4">
                                        <label class="form-label">知识库聊天系统提示词</label>
                                        <textarea class="form-control" id="ragSystemPrompt" name="rag_system_prompt" rows="6" 
                                                  placeholder="输入知识库聊天系统提示词..."></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-0">知识库聊天系统提示词用于在会话开始时设置AI助手的角色和行为。</p>
                                            <p class="mb-0 mt-2"><strong>注意:</strong>这是系统消息的内容,不能使用变量(如 {context} 或 {question})。</p>
                                        </div>
                                    </div>

                                    <hr class="my-4">

                                    <div class="mb-3">
                                        <label class="form-label">知识库聊天查询提示词</label>
                                        <textarea class="form-control" id="ragQueryPrompt" name="rag_query_prompt" rows="10" 
                                                  placeholder="例如:文档内容:&#10;{context}&#10;&#10;问题:{question}&#10;&#10;请基于文档内容回答问题。如果文档中没有相关信息,请明确说明。"></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-1">知识库聊天查询提示词用于每次提问时构建提示,指导AI助手如何基于文档内容回答问题。</p>
                                            <p class="mb-0"><strong>必须使用以下变量:</strong></p>
                                            <ul class="mb-0">
                                                <li><code>{context}</code> - 检索到的文档内容(必需)</li>
                                                <li><code>{question}</code> - 用户的问题(必需)</li>
                                            </ul>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签3: 检索设置 -->
                <div class="tab-pane fade" id="retrieval" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-search"></i> 检索设置</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">检索模式 <span class="text-danger">*</span></label>
                                <select class="form-select" id="retrievalMode" name="retrieval_mode" required>
                                    <option value="vector">向量检索</option>
                                    <option value="keyword">全文检索</option>
                                    <option value="hybrid">混合检索</option>
                                </select>
                                <div class="form-text">选择文档检索方式</div>
                            </div>

                            <div class="mb-3" id="vectorThresholdGroup">
                                <label class="form-label">向量检索阈值</label>
                                <input type="number" class="form-control" id="vectorThreshold" name="vector_threshold" 
                                       value="0.2" step="0.1" min="0" max="1" placeholder="0.2">
                                <div class="form-text">向量相似度阈值,低于此值的文档将被过滤(0-1)</div>
                            </div>

                            <div class="mb-3" id="keywordThresholdGroup" style="display: none;">
                                <label class="form-label">全文检索阈值</label>
                                <input type="number" class="form-control" id="keywordThreshold" name="keyword_threshold" 
                                       value="0.5" step="0.1" min="0" max="1" placeholder="0.5">
                                <div class="form-text">关键词匹配阈值(0-1)</div>
                            </div>

                            <div class="mb-3" id="vectorWeightGroup" style="display: none;">
                                <label class="form-label">向量检索权重</label>
                                <input type="number" class="form-control" id="vectorWeight" name="vector_weight" 
                                       value="0.7" step="0.1" min="0" max="1" placeholder="0.7">
                                <div class="form-text">混合检索时向量检索的权重(0-1),关键词检索权重 = 1 - 向量权重</div>
                            </div>

                            <div class="mb-3">
                                <label class="form-label">TopN 结果数量</label>
                                <input type="number" class="form-control" id="topN" name="top_n" 
                                       value="5" min="1" max="50" placeholder="5">
                                <div class="form-text">返回的文档数量(1-50)</div>
                            </div>

                            <div class="alert alert-info">
                                <i class="bi bi-info-circle"></i> <strong>说明:</strong>
                                <ul class="mb-0 mt-2">
                                    <li><strong>向量检索:</strong>基于语义相似度检索,适合理解问题意图</li>
                                    <li><strong>全文检索:</strong>基于关键词匹配检索,适合精确匹配</li>
                                    <li><strong>混合检索:</strong>结合向量和关键词检索,综合两者的优势</li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="d-flex justify-content-end gap-2 mt-4">
                <button type="button" class="btn btn-secondary" onclick="resetSettings()">重置</button>
                <button type="submit" class="btn btn-primary">
                    <i class="bi bi-save"></i> 保存设置
                </button>
            </div>
        </form>
    </div>
</div>

{% endblock %}

{% block extra_js %}
<script>
</script>
{% endblock %}

3.3. init.py #

app/init.py

# RAG Lite 应用模块说明
"""
RAG Lite Application
"""

# 导入操作系统相关模块
import os
# 从 Flask 包导入 Flask 应用对象
from flask import Flask
# 导入 Flask 跨域资源共享支持
from flask_cors import CORS
# 导入应用配置类
from app.config import Config
# 导入日志工具,用于获取日志记录器
from app.utils.logger import get_logger
# 导入数据库初始化函数
from app.utils.db import init_db
# 导入蓝图模块
+from app.blueprints import auth,knowledgebase,settings
# 导入获取当前用户信息函数
from app.utils.auth import get_current_user
# 定义创建 Flask 应用的工厂函数
def create_app(config_class=Config):
    # 获取日志记录器,名称为当前模块名
    logger = get_logger(__name__)
    # 尝试初始化数据库
    try:
        # 输出日志,表示即将初始化数据库
        logger.info("初始化数据库...")
        # 执行数据库初始化函数
        init_db()
        # 输出日志,表示数据库初始化成功
        logger.info("数据库初始化成功")
    # 捕获任意异常
    except Exception as e:
        # 输出警告日志,提示数据库初始化失败,并输出异常信息
        logger.warning(f"数据库初始化失败: {e}")
        # 输出警告日志,提示检查数据库是否已存在,并建议手动创建数据表
        logger.warning("请确认数据库已存在,或手动创建数据表")

    # 创建 Flask 应用对象,并指定模板和静态文件目录
    base_dir = os.path.abspath(os.path.dirname(__file__))
    # 创建 Flask 应用对象,并指定模板和静态文件目录
    app = Flask(
        __name__,
        # 指定模板文件目录
        template_folder=os.path.join(base_dir, 'templates'),
        # 指定静态文件目录
        static_folder=os.path.join(base_dir, 'static')
    )
    # 从给定配置类加载配置信息到应用
    app.config.from_object(config_class)

    # 启用跨域请求支持
    CORS(app)

    # 记录应用创建日志信息
    logger.info("Flask 应用已创建")

    # 注册上下文处理器,使 current_user 在所有模板中可用
    @app.context_processor
    def inject_user():
        # 返回当前用户信息字典
        # 使用 get_current_user 获取当前用户信息,并将其添加到上下文字典中
        # 这样在模板中可以直接使用 current_user 变量
        return dict(current_user=get_current_user())

    # 注册蓝图
    app.register_blueprint(auth.bp)
    # 注册知识库蓝图
    app.register_blueprint(knowledgebase.bp)
    # 注册设置蓝图
+   app.register_blueprint(settings.bp)
    # 定义首页路由
    @app.route('/')
    def index():
        return "Hello, World!"

    # 返回已配置的 Flask 应用对象
    return app

3.4. init.py #

app/blueprints/init.py

"""
蓝图模块
"""
+from app.blueprints import auth, settings

+__all__ = ['auth', 'settings']

4.嵌入模型 #

4.1. settings_service.py #

app/services/settings_service.py

"""
设置服务
"""
# 导入 Settings 模型
from app.models.settings import Settings
# 导入配置类
from app.config import Config
# 导入基础服务类
from app.services.base_service import BaseService

# 定义设置服务类,继承基础服务
class SettingsService(BaseService[Settings]):
    """设置服务"""

    # 获取设置的方法(单例模式)
    def get(self) -> dict:
        """
        获取设置(单例模式)

        Returns:
            设置字典,如果不存在则返回默认值
        """
        # 打开数据库会话
        with self.session() as session:
            # 查询主键为 'global' 的设置
            settings = session.query(Settings).filter_by(id='global').first()
            # 如果数据库中存在设置,返回其字典形式
            if settings:
                return settings.to_dict()
            else:
                # 如果不存在,返回默认设置
                return self._get_default_settings()

    # 获取默认设置的方法
    def _get_default_settings(self) -> dict:
        """获取默认设置"""
        # 返回包含所有默认字段值的字典
        return {
            'id': 'global',  # 设置主键
            'embedding_provider': 'huggingface',  # 默认 embedding provider
            'embedding_model_name': 'sentence-transformers/all-MiniLM-L6-v2',  # 默认 embedding 模型
            'embedding_api_key': None,  # 默认无 embedding API key
            'embedding_base_url': None,  # 默认无 embedding base url
            'llm_provider': 'deepseek',  # 默认 LLM provider
            'llm_model_name': Config.DEEPSEEK_CHAT_MODEL,  # 默认 LLM 模型
            'llm_api_key': Config.DEEPSEEK_API_KEY,  # 配置里的默认 LLM API key
            'llm_base_url': Config.DEEPSEEK_BASE_URL,  # 配置里的默认 LLM base url
            'llm_temperature': '0.7',  # 默认温度
            'chat_system_prompt': '你是一个专业的AI助手。请友好、准确地回答用户的问题。',  # 聊天系统默认提示词
            'rag_system_prompt': '你是一个专业的AI助手。请基于文档内容回答问题。',  # RAG系统提示词
            'rag_query_prompt': '文档内容:\n{context}\n\n问题:{question}\n\n请基于文档内容回答问题。如果文档中没有相关信息,请明确说明。',  # RAG查询提示词
            'retrieval_mode': 'vector',  # 默认检索模式
            'vector_threshold': '0.2',  # 向量检索阈值
            'keyword_threshold': '0.5',  # 关键词检索阈值
            'vector_weight': '0.7',  # 检索混合权重
            'top_n': '5',  # 返回结果数量
            'created_at': None,  # 创建时间
            'updated_at': None   # 更新时间
        }            

    # 用于更新设置的方法
    def update(self, data: dict) -> dict:
        """
        更新设置

        Args:
            data: 设置数据

        Returns:
            更新后的设置
        """
        # 启动事务会话
        with self.transaction() as session:
            # 查询主键为 'global' 的设置
            settings = session.query(Settings).filter_by(id='global').first()

            # 如果已存在设置,则逐项更新
            if settings:
                # 遍历提交的所有字段及其对应的值
                for key, value in data.items():
                    # 如果设置中有该属性且值不是None,则进行更新
                    if hasattr(settings, key) and value is not None:
                        setattr(settings, key, value)
            else:
                # 不存在则新建一个 Settings 对象
                settings = Settings(id='global')
                # 设置各属性
                for key, value in data.items():
                    if hasattr(settings, key) and value is not None:
                        setattr(settings, key, value)
                # 添加到会话
                session.add(settings)

            # flush 保证所有更改同步到数据库会话(提交之前,保证主键自增/更新时间等有效)
            session.flush()
            # refresh 保证 settings 对象数据是数据库最新的内容(例如 updated_at 字段)
            session.refresh(settings)
            # 返回已更新的设置字典
            return settings.to_dict()    

# 实例化设置服务
settings_service = SettingsService()            

4.2. models_config.py #

app/utils/models_config.py

# 模型配置
# 定义可用的 Embedding 模型和 LLM 模型列表
"""
模型配置
定义可用的 Embedding 模型和 LLM 模型列表
"""

# 定义向量嵌入模型(Embedding Models)的配置字典
EMBEDDING_MODELS = {
    # HuggingFace 嵌入模型
    'huggingface': {
        # 名称
        'name': 'HuggingFace Embeddings',
        # 描述说明
        'description': '本地 HuggingFace 模型',
        # 可用模型列表
        'models': [
            # 第一个模型:all-MiniLM-L6-v2
            {
                # 模型名称
                'name': 'sentence-transformers/all-MiniLM-L6-v2',
                # 模型路径
                'path': 'sentence-transformers/all-MiniLM-L6-v2',
                # 向量维度
                'dimension': '384',
                # 描述
                'description': '轻量级多语言模型,速度快'
            },
            # 第二个模型:paraphrase-multilingual-MiniLM-L12-v2
            {
                'name': 'sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2',
                'path': 'sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2',
                'dimension': '384',
                'description': '多语言模型,支持中文'
            },
            # 第三个模型:bge-small-zh-v1.5
            {
                'name': 'BAAI/bge-small-zh-v1.5',
                'path': 'BAAI/bge-small-zh-v1.5',
                'dimension': '512',
                'description': '中文优化模型'
            }
        ],
        # 是否需要 API Key
        'requires_api_key': False,
        # 是否需要 Base URL
        'requires_base_url': False
    },
    # OpenAI 嵌入模型
    'openai': {
        'name': 'OpenAI Embeddings',
        'description': 'OpenAI 官方嵌入模型',
        # OpenAI 可用模型
        'models': [
            {
                'name': 'text-embedding-3-small',
                'dimension': '1536',
                'description': '小型模型,速度快'
            },
            {
                'name': 'text-embedding-3-large',
                'dimension': '3072',
                'description': '大型模型,精度高'
            },
            {
                'name': 'text-embedding-ada-002',
                'dimension': '1536',
                'description': '经典模型'
            }
        ],
        'requires_api_key': True,
        'requires_base_url': False
    },
    # 本地 Ollama 嵌入模型
    'ollama': {
        'name': 'Ollama Embeddings',
        'description': '本地 Ollama 模型',
        # Ollama 可用嵌入模型
        'models': [
            {
                'name': 'nomic-embed-text',
                'dimension': '768',
                'description': '通用文本嵌入模型'
            }
        ],
        'requires_api_key': False,
        'requires_base_url': True
    }
}

# 定义 LLM(大模型,推理/对话模型)配置字典
LLM_MODELS = {
    # DeepSeek 模型配置
    'deepseek': {
        'name': 'DeepSeek',
        'description': 'DeepSeek API',
        # DeepSeek 可用模型列表
        'models': [
            # DeepSeek 对话模型
            {
                'name': 'deepseek-chat',
                'description': '对话模型'
            },
            # DeepSeek 代码模型
            {
                'name': 'deepseek-coder',
                'description': '代码模型'
            }
        ],
        'requires_api_key': True,
        'requires_base_url': True
    },
    # OpenAI 大模型配置
    'openai': {
        'name': 'OpenAI',
        'description': 'OpenAI API',
        # OpenAI 可用大模型
        'models': [
            {
                'name': 'gpt-4',
                'description': 'GPT-4 模型'
            },
            {
                'name': 'gpt-4-turbo',
                'description': 'GPT-4 Turbo 模型'
            },
            {
                'name': 'gpt-3.5-turbo',
                'description': 'GPT-3.5 Turbo 模型'
            }
        ],
        'requires_api_key': True,
        'requires_base_url': False
    },
    # 本地 Ollama 大模型配置
    'ollama': {
        'name': 'Ollama',
        'description': '本地 Ollama 模型',
        # Ollama 可用大模型
        'models': [
            {
                'name': 'llama2',
                'description': 'Llama 2 模型'
            },
            {
                'name': 'mistral',
                'description': 'Mistral 模型'
            },
            {
                'name': 'qwen',
                'description': 'Qwen 模型'
            }
        ],
        'requires_api_key': False,
        'requires_base_url': True
    }
}

4.3. settings.py #

app/blueprints/settings.py

# 设置相关路由(视图 + API)
"""
设置相关路由(视图 + API)
"""
# 导入 Flask 蓝图和模板渲染函数
from flask import Blueprint, render_template
# 导入登录校验装饰器
from app.utils.auth import login_required
# 导入标准化响应、错误处理装饰器、请求JSON体校验工具
+from app.blueprints.utils import success_response, handle_api_error, require_json_body
# 导入嵌入模型、LLM模型配置
+from app.utils.models_config import EMBEDDING_MODELS, LLM_MODELS
# 导入设置服务
+from app.services.settings_service import settings_service
# 导入日志模块
import logging

# 获取当前模块的logger对象
logger = logging.getLogger(__name__)

# 创建 settings 蓝图
bp = Blueprint('settings', __name__)

# 注册视图路由:设置页面
@bp.route('/settings')
# 要求登录
@login_required
def settings_view():
    """设置页面"""
    # 渲染 settings.html 模板页面
+   return render_template('settings.html')

# 注册 API 路由:获取可用模型列表
+@bp.route('/api/v1/settings/models', methods=['GET'])
# 错误处理装饰器
+@handle_api_error
+def api_get_available_models():
+   """获取可用的模型列表"""
    # 返回嵌入模型与LLM可用模型数据
+   return success_response({
+       'embedding_models': EMBEDDING_MODELS,
+       'llm_models': LLM_MODELS
+   })   

# 注册 API 路由:更新设置
+@bp.route('/api/v1/settings', methods=['PUT'])
# 错误处理装饰器
+@handle_api_error
+def api_update_settings():
+   """更新设置"""
    # 校验请求体并获取数据
+   data, err = require_json_body()
    # 如果有错误,直接返回错误响应
+   if err:
+       return err

    # 调用设置服务进行更新
+   settings = settings_service.update(data)
    # 返回更新后的设置
+   return success_response(settings, "Settings updated successfully")    

# 注册 API 路由:获取当前设置
+@bp.route('/api/v1/settings', methods=['GET'])
# 错误处理装饰器
+@handle_api_error
+def api_get_settings():
+   """获取设置"""
    # 从设置服务获取当前设置
+   settings = settings_service.get()
    # 返回设置数据
+   return success_response(settings)    

4.4. utils.py #

app/blueprints/utils.py

"""
路由工具函数
"""

# 导入Flask用于返回JSON响应
from flask import jsonify,request
# 导入装饰器工具,用来保持原函数信息
from functools import wraps
# 导入类型提示工具
from typing import Tuple, Optional
# 导入获取当前用户的工具函数
from app.utils.auth import get_current_user
# 导入日志模块
import logging
# 获取logger对象(当前模块名)
logger = logging.getLogger(__name__)

# 定义成功响应函数
def success_response(data=None, message="success"):
    """
    成功响应

    Args:
        data: 响应数据
        message: 响应消息

    Returns:
        JSON 响应
    """
    # 返回标准格式的JSON成功响应
    return jsonify({
        "code": 200,       # 状态码200,表示成功
        "message": message, # 响应消息
        "data": data        # 响应数据
    })

# 定义错误响应函数
def error_response(message: str, code: int = 400):
    """
    错误响应

    Args:
        message: 错误消息
        code: HTTP 状态码

    Returns:
        JSON 响应和状态码
    """
    # 返回标准格式的JSON错误响应,以及相应的HTTP状态码
    return jsonify({
        "code": code,        # 错误码,对应HTTP状态码
        "message": message,  # 错误消息
        "data": None         # 错误时无数据
    }), code

# 定义API错误处理装饰器
def handle_api_error(func):
    """
    API 错误处理装饰器

    使用示例:
        @handle_api_error
        def my_api():
            # API 逻辑
            return success_response(data)
    """
    # 保留原函数信息并定义包装器
    @wraps(func)
    def wrapper(*args, **kwargs):
        try:
            # 正常执行被装饰的API函数
            return func(*args, **kwargs)
        except ValueError as e:
            # 捕获ValueError,日志记录warning信息并返回400错误响应
            logger.warning(f"ValueError in {func.__name__}: {e}")
            return error_response(str(e), 400)
        except Exception as e:
            # 捕获其他所有异常,日志记录error信息并返回500错误响应
            logger.error(f"Error in {func.__name__}: {e}", exc_info=True)
            return error_response(str(e), 500)
    # 返回包装后的函数
    return wrapper

# 定义获取分页参数的函数,允许指定最大每页数量
def get_pagination_params(max_page_size: int = 1000) -> Tuple[int, int]:
    """
    获取分页参数

    Args:
        max_page_size: 最大每页数量

    Returns:
        (page, page_size) 元组
    """
    # 获取请求中的 'page' 参数,默认为1,并将其转换为整数
    page = int(request.args.get('page', 1))
    # 获取请求中的 'page_size' 参数,默认为10,并将其转换为整数
    page_size = int(request.args.get('page_size', 10))

    # 保证 page 至少为1
    page = max(1, page)
    # 保证 page_size 至少为1且不超过 max_page_size
    page_size = max(1, min(page_size, max_page_size))

    # 返回分页的(page, page_size)元组
    return page, page_size

# 定义获取当前用户或返回错误的函数
def get_current_user_or_error():
    """
    获取当前用户,如果未登录则返回错误响应

    Returns:
        如果成功返回 (user_dict, None),如果失败返回 (None, error_response)
    """
    # 调用 get_current_user() 获取当前用户对象
    current_user = get_current_user()
    # 如果没有获取到用户,则返回 (None, 错误响应)
    if not current_user:
        return None, error_response("Unauthorized", 401)
    # 如果获取到用户,则返回 (用户对象, None)
    return current_user, None

# 定义检查资源所有权的函数,判断当前用户是否为资源所有者
def check_ownership(entity_user_id: str, current_user_id: str, 
                   entity_name: str = "资源") -> Tuple[bool, Optional[Tuple]]:
    # 检查资源所属用户ID是否与当前用户ID相同
    if entity_user_id != current_user_id:
        # 如果不同,返回False,并返回403未授权的错误响应
        return False, error_response(f"Unauthorized to access this {entity_name}", 403)
    # 如果相同,则有权限,返回True和None
    return True, None


# 定义函数:检查请求体是否为 JSON
+def require_json_body():
+   """
+   检查请求是否有 JSON 体

+   Returns:
+       如果存在返回 (data, None),如果不存在返回 (None, error_response)
+   """
    # 从请求中获取 JSON 数据
+   data = request.get_json()
    # 如果没有获取到数据,则返回错误响应
+   if not data:
+       return None, error_response("Request body is required", 400)
    # 如果获取到了数据,则返回数据和None表示没有错误
+   return data, None

4.5. settings.html #

app/templates/settings.html

{% extends "base.html" %}

{% block title %}设置 - RAG Lite{% endblock %}

{% block content %}
<div class="row">
    <div class="col-12">
        <nav aria-label="breadcrumb" class="mb-3">
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="/">首页</a></li>
                <li class="breadcrumb-item active">设置</li>
            </ol>
        </nav>

        <h2><i class="bi bi-gear"></i> 系统设置</h2>
        <p class="text-muted">配置模型、提示词和检索参数</p>

        <form id="settingsForm" onsubmit="saveSettings(event)">
            <!-- 标签页导航 -->
            <ul class="nav nav-tabs mb-4" id="settingsTabs" role="tablist">
                <li class="nav-item" role="presentation">
                    <button class="nav-link active" id="embedding-tab" data-bs-toggle="tab" data-bs-target="#embedding" type="button" role="tab">
                        <i class="bi bi-diagram-3"></i> 向量嵌入模型
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="llm-tab" data-bs-toggle="tab" data-bs-target="#llm" type="button" role="tab">
                        <i class="bi bi-robot"></i> 大语言模型
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="prompt-tab" data-bs-toggle="tab" data-bs-target="#prompt" type="button" role="tab">
                        <i class="bi bi-chat-quote"></i> 提示词设置
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="retrieval-tab" data-bs-toggle="tab" data-bs-target="#retrieval" type="button" role="tab">
                        <i class="bi bi-search"></i> 检索设置
                    </button>
                </li>
            </ul>

            <!-- 标签页内容 -->
            <div class="tab-content" id="settingsTabContent">
                <!-- 标签1: 向量嵌入模型 -->
                <div class="tab-pane fade show active" id="embedding" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-diagram-3"></i> 向量嵌入模型(Embedding)</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">提供商 <span class="text-danger">*</span></label>
                                <select class="form-select" id="embeddingProvider" name="embedding_provider" onchange="updateEmbeddingForm()" required>
                                    <option value="huggingface">HuggingFace</option>
                                    <option value="openai">OpenAI</option>
                                    <option value="ollama">Ollama</option>
                                </select>
                                <div class="form-text">选择向量嵌入模型提供商</div>
                            </div>

                            <div class="mb-3" id="embeddingModelNameGroup">
                                <label class="form-label">模型名称</label>
                                <select class="form-select" id="embeddingModelName" name="embedding_model_name">
                                    <option value="">请选择模型</option>
                                    <!-- 动态填充 -->
                                </select>
                                <div class="form-text">选择模型名称或路径</div>
                            </div>

                            <div class="mb-3" id="embeddingApiKeyGroup" style="display: none;">
                                <label class="form-label">API Key</label>
                                <input type="password" class="form-control" id="embeddingApiKey" name="embedding_api_key" placeholder="输入 API Key">
                                <div class="form-text">某些提供商需要 API Key</div>
                            </div>

                            <div class="mb-3" id="embeddingBaseUrlGroup" style="display: none;">
                                <label class="form-label">Base URL</label>
                                <input type="text" class="form-control" id="embeddingBaseUrl" name="embedding_base_url" placeholder="例如: http://localhost:11434">
                                <div class="form-text">API Base URL(Ollama 需要)</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签2: 大语言模型 -->
                <div class="tab-pane fade" id="llm" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-robot"></i> 大语言模型(LLM)</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">提供商 <span class="text-danger">*</span></label>
                                <select class="form-select" id="llmProvider" name="llm_provider" onchange="updateLLMForm()" required>
                                    <option value="deepseek">DeepSeek</option>
                                    <option value="openai">OpenAI</option>
                                    <option value="ollama">Ollama</option>
                                </select>
                                <div class="form-text">选择大语言模型提供商</div>
                            </div>

                            <div class="mb-3" id="llmModelNameGroup">
                                <label class="form-label">模型名称</label>
                                <select class="form-select" id="llmModelName" name="llm_model_name">
                                    <option value="">请选择模型</option>
                                    <!-- 动态填充 -->
                                </select>
                                <div class="form-text">选择模型名称</div>
                            </div>

                            <div class="mb-3" id="llmApiKeyGroup">
                                <label class="form-label">API Key</label>
                                <input type="password" class="form-control" id="llmApiKey" name="llm_api_key" placeholder="输入 API Key">
                                <div class="form-text">某些提供商需要 API Key</div>
                            </div>

                            <div class="mb-3" id="llmBaseUrlGroup">
                                <label class="form-label">Base URL</label>
                                <input type="text" class="form-control" id="llmBaseUrl" name="llm_base_url" placeholder="例如: https://api.deepseek.com">
                                <div class="form-text">API Base URL</div>
                            </div>

                            <div class="mb-3">
                                <label class="form-label">温度 (Temperature)</label>
                                <input type="number" class="form-control" id="llmTemperature" name="llm_temperature" 
                                       value="0.7" step="0.1" min="0" max="2" placeholder="0.7">
                                <div class="form-text">控制输出的随机性,值越大越随机(0-2)</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签3: 提示词设置 -->
                <div class="tab-pane fade" id="prompt" role="tabpanel">
                    <div class="card">
                        <div class="card-body">
                            <!-- 子标签页导航 -->
                            <ul class="nav nav-tabs mb-4" id="promptSubTabs" role="tablist">
                                <li class="nav-item" role="presentation">
                                    <button class="nav-link active" id="chat-prompt-sub-tab" data-bs-toggle="tab" data-bs-target="#chat-prompt-sub" type="button" role="tab">
                                        <i class="bi bi-chat"></i> 普通聊天提示词
                                    </button>
                                </li>
                                <li class="nav-item" role="presentation">
                                    <button class="nav-link" id="rag-prompt-sub-tab" data-bs-toggle="tab" data-bs-target="#rag-prompt-sub" type="button" role="tab">
                                        <i class="bi bi-book"></i> 知识库聊天提示词
                                    </button>
                                </li>
                            </ul>

                            <!-- 子标签页内容 -->
                            <div class="tab-content" id="promptSubTabContent">
                                <!-- 子标签1: 普通聊天提示词 -->
                                <div class="tab-pane fade show active" id="chat-prompt-sub" role="tabpanel">
                                    <div class="mb-4">
                                        <label class="form-label">普通聊天系统提示词</label>
                                        <textarea class="form-control" id="chatSystemPrompt" name="chat_system_prompt" rows="10" 
                                                  placeholder="输入普通聊天提示词..."></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-0">普通聊天提示词用于指导AI助手在普通聊天(未选择知识库)时的回答风格和行为。</p>
                                            <p class="mb-0 mt-2"><strong>注意:</strong>这是系统消息的内容,不能使用变量。</p>
                                        </div>
                                    </div>
                                </div>

                                <!-- 子标签2: 知识库聊天提示词 -->
                                <div class="tab-pane fade" id="rag-prompt-sub" role="tabpanel">
                                    <div class="mb-4">
                                        <label class="form-label">知识库聊天系统提示词</label>
                                        <textarea class="form-control" id="ragSystemPrompt" name="rag_system_prompt" rows="6" 
                                                  placeholder="输入知识库聊天系统提示词..."></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-0">知识库聊天系统提示词用于在会话开始时设置AI助手的角色和行为。</p>
                                            <p class="mb-0 mt-2"><strong>注意:</strong>这是系统消息的内容,不能使用变量(如 {context} 或 {question})。</p>
                                        </div>
                                    </div>

                                    <hr class="my-4">

                                    <div class="mb-3">
                                        <label class="form-label">知识库聊天查询提示词</label>
                                        <textarea class="form-control" id="ragQueryPrompt" name="rag_query_prompt" rows="10" 
                                                  placeholder="例如:文档内容:&#10;{context}&#10;&#10;问题:{question}&#10;&#10;请基于文档内容回答问题。如果文档中没有相关信息,请明确说明。"></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-1">知识库聊天查询提示词用于每次提问时构建提示,指导AI助手如何基于文档内容回答问题。</p>
                                            <p class="mb-0"><strong>必须使用以下变量:</strong></p>
                                            <ul class="mb-0">
                                                <li><code>{context}</code> - 检索到的文档内容(必需)</li>
                                                <li><code>{question}</code> - 用户的问题(必需)</li>
                                            </ul>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签3: 检索设置 -->
                <div class="tab-pane fade" id="retrieval" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-search"></i> 检索设置</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">检索模式 <span class="text-danger">*</span></label>
                                <select class="form-select" id="retrievalMode" name="retrieval_mode" required>
                                    <option value="vector">向量检索</option>
                                    <option value="keyword">全文检索</option>
                                    <option value="hybrid">混合检索</option>
                                </select>
                                <div class="form-text">选择文档检索方式</div>
                            </div>

                            <div class="mb-3" id="vectorThresholdGroup">
                                <label class="form-label">向量检索阈值</label>
                                <input type="number" class="form-control" id="vectorThreshold" name="vector_threshold" 
                                       value="0.2" step="0.1" min="0" max="1" placeholder="0.2">
                                <div class="form-text">向量相似度阈值,低于此值的文档将被过滤(0-1)</div>
                            </div>

                            <div class="mb-3" id="keywordThresholdGroup" style="display: none;">
                                <label class="form-label">全文检索阈值</label>
                                <input type="number" class="form-control" id="keywordThreshold" name="keyword_threshold" 
                                       value="0.5" step="0.1" min="0" max="1" placeholder="0.5">
                                <div class="form-text">关键词匹配阈值(0-1)</div>
                            </div>

                            <div class="mb-3" id="vectorWeightGroup" style="display: none;">
                                <label class="form-label">向量检索权重</label>
                                <input type="number" class="form-control" id="vectorWeight" name="vector_weight" 
                                       value="0.7" step="0.1" min="0" max="1" placeholder="0.7">
                                <div class="form-text">混合检索时向量检索的权重(0-1),关键词检索权重 = 1 - 向量权重</div>
                            </div>

                            <div class="mb-3">
                                <label class="form-label">TopN 结果数量</label>
                                <input type="number" class="form-control" id="topN" name="top_n" 
                                       value="5" min="1" max="50" placeholder="5">
                                <div class="form-text">返回的文档数量(1-50)</div>
                            </div>

                            <div class="alert alert-info">
                                <i class="bi bi-info-circle"></i> <strong>说明:</strong>
                                <ul class="mb-0 mt-2">
                                    <li><strong>向量检索:</strong>基于语义相似度检索,适合理解问题意图</li>
                                    <li><strong>全文检索:</strong>基于关键词匹配检索,适合精确匹配</li>
                                    <li><strong>混合检索:</strong>结合向量和关键词检索,综合两者的优势</li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="d-flex justify-content-end gap-2 mt-4">
                <button type="button" class="btn btn-secondary" onclick="resetSettings()">重置</button>
                <button type="submit" class="btn btn-primary">
                    <i class="bi bi-save"></i> 保存设置
                </button>
            </div>
        </form>
    </div>
</div>

{% endblock %}

{% block extra_js %}
<script>
+// 监听页面加载完成后,执行异步函数
+document.addEventListener('DOMContentLoaded', async function() {
+   try {
+       // 发送请求获取可用模型列表
+       const modelsResponse = await fetch('/api/v1/settings/models');
+       // 解析返回的JSON数据
+       const modelsResult = await modelsResponse.json();
+       // 如果请求成功(响应码为200)
+       if (modelsResult.code === 200) {
+           // 取得可用模型数据
+           const availableModels = modelsResult.data;
+           // 更新嵌入模型表单
+           updateEmbeddingForm(availableModels);
+           // 发送请求获取当前设置
+           const settingsResponse = await fetch('/api/v1/settings');
+           // 解析返回的JSON数据
+           const settingsResult = await settingsResponse.json();
+           // 如果请求成功(响应码为200)
+           if (settingsResult.code === 200) {
+               // 加载设置到页面
+               loadSettings(settingsResult.data);
+           }
+       }
+   } catch (error) {
+       // 捕获异常并在控制台打印错误信息
+       console.error('加载设置失败:', error);
+       // 弹窗提示加载失败
+       alert('加载设置失败: ' + error.message);
+   }
+});

+// 加载设置并填充到表单
+function loadSettings(settings) {
+   // 设置嵌入向量提供商,默认为huggingface
+   document.getElementById('embeddingProvider').value = settings.embedding_provider || 'huggingface';
+   // 如果有嵌入模型名称,设置模型名称
+   if (settings.embedding_model_name) {
+       document.getElementById('embeddingModelName').value = settings.embedding_model_name;
+   }
+   // 设置API Key,如果没有则为空字符串
+   document.getElementById('embeddingApiKey').value = settings.embedding_api_key || '';
+   // 设置Base URL,如果没有则为空字符串
+   document.getElementById('embeddingBaseUrl').value = settings.embedding_base_url || '';
+}

+// 更新嵌入模型表单(根据可用模型动态渲染选项)
+function updateEmbeddingForm(availableModels) {
+   // 获取当前选中的提供商
+   const provider = document.getElementById('embeddingProvider').value;
+   // 获取模型选择框
+   const modelSelect = document.getElementById('embeddingModelName');
+   // 获取API Key输入框所在元素
+   const apiKeyGroup = document.getElementById('embeddingApiKeyGroup');
+   // 获取Base URL输入框所在元素
+   const baseUrlGroup = document.getElementById('embeddingBaseUrlGroup');
+   // 记住当前选中的模型值(以便刷新后尽量保留)
+   const currentValue = modelSelect.value;
+   // 清空模型下拉框内容,并加一个提示选项
+   modelSelect.innerHTML = '<option value="">请选择模型</option>';
+   // 如果存在可用模型数据,并且当前提供商有对应条目
+   if (availableModels && availableModels.embedding_models[provider]) {
+       // 获取该提供商的详细信息
+       const providerInfo = availableModels.embedding_models[provider];
+       // 遍历所有模型,添加为下拉选项
+       providerInfo.models.forEach(model => {
+           // 创建一个option元素
+           const option = document.createElement('option');
+           // 选项的值为path属性,否则为name
+           const optionValue = model.path || model.name;
+           option.value = optionValue;
+           // 构造显示文本,包括名称、维度和描述
+           const displayText = model.name + (model.dimension ? ` (维度: ${model.dimension})` : '') + (model.description ? ' - ' + model.description : '');
+           option.textContent = displayText;
+           // 添加到模型下拉框
+           modelSelect.appendChild(option);
+       });
+       // 恢复刷新前选中的值(如果还有)
+       if (currentValue) {
+           // 检查当前值是否还在选项中
+           const optionExists = Array.from(modelSelect.options).some(opt => opt.value === currentValue);
+           if (optionExists) {
+               // 如果存在,直接选中
+               modelSelect.value = currentValue;
+           } else {
+               // 如果不存在,尝试“模糊匹配”
+               const matchingOption = Array.from(modelSelect.options).find(opt => {
+                   const optValue = opt.value;
+                   // 判断当前值和选项值是否完全相等或相互包含
+                   return optValue === currentValue || optValue.includes(currentValue) || currentValue.includes(optValue);
+               });
+               // 如果找到匹配项,则选中该项
+               if (matchingOption) {
+                   modelSelect.value = matchingOption.value;
+               }
+           }
+       }
+       // 根据提供商是否需要API Key,显示或隐藏输入框
+       if (providerInfo.requires_api_key) {
+           apiKeyGroup.style.display = 'block';
+           document.getElementById('embeddingApiKey').required = true;
+       } else {
+           apiKeyGroup.style.display = 'none';
+           document.getElementById('embeddingApiKey').required = false;
+       }
+       // 根据提供商是否需要Base URL,显示或隐藏输入框
+       if (providerInfo.requires_base_url) {
+           baseUrlGroup.style.display = 'block';
+       } else {
+           baseUrlGroup.style.display = 'none';
+       }
+   }
+}

+// 保存设置(表单提交处理函数,异步提交到后端)
+async function saveSettings(event) {
+   // 阻止表单的默认提交行为
+   event.preventDefault();
+   // 获取表单dom对象
+   const form = event.target;
+   // 构造FormData对象用于取值
+   const formData = new FormData(form);
+   // 构造要提交的数据对象
+   const data = {
+       embedding_provider: formData.get('embedding_provider'),
+       embedding_model_name: formData.get('embedding_model_name') || null,
+       embedding_api_key: formData.get('embedding_api_key') || null,
+       embedding_base_url: formData.get('embedding_base_url') || null
+   };
+   try {
+       // 发送PUT请求到设置API,提交JSON数据
+       const response = await fetch('/api/v1/settings', {
+           method: 'PUT',
+           headers: {'Content-Type': 'application/json'},
+           body: JSON.stringify(data)
+       });
+       // 解析返回结果
+       const result = await response.json();
+       // 如果提交成功
+       if (response.ok) {
+           alert('设置保存成功!');
+           // 刷新页面
+           location.reload();
+       } else {
+           // 否则弹窗提示失败原因
+           alert('保存失败: ' + result.message);
+       }
+   } catch (error) {
+       // 捕获异常并弹窗提示
+       alert('保存失败: ' + error.message);
+   }
+}
</script>
{% endblock %}

5.大模型设置 #

5.1. settings.html #

app/templates/settings.html

{% extends "base.html" %}

{% block title %}设置 - RAG Lite{% endblock %}

{% block content %}
<div class="row">
    <div class="col-12">
        <nav aria-label="breadcrumb" class="mb-3">
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="/">首页</a></li>
                <li class="breadcrumb-item active">设置</li>
            </ol>
        </nav>

        <h2><i class="bi bi-gear"></i> 系统设置</h2>
        <p class="text-muted">配置模型、提示词和检索参数</p>

        <form id="settingsForm" onsubmit="saveSettings(event)">
            <!-- 标签页导航 -->
            <ul class="nav nav-tabs mb-4" id="settingsTabs" role="tablist">
                <li class="nav-item" role="presentation">
                    <button class="nav-link active" id="embedding-tab" data-bs-toggle="tab" data-bs-target="#embedding" type="button" role="tab">
                        <i class="bi bi-diagram-3"></i> 向量嵌入模型
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="llm-tab" data-bs-toggle="tab" data-bs-target="#llm" type="button" role="tab">
                        <i class="bi bi-robot"></i> 大语言模型
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="prompt-tab" data-bs-toggle="tab" data-bs-target="#prompt" type="button" role="tab">
                        <i class="bi bi-chat-quote"></i> 提示词设置
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="retrieval-tab" data-bs-toggle="tab" data-bs-target="#retrieval" type="button" role="tab">
                        <i class="bi bi-search"></i> 检索设置
                    </button>
                </li>
            </ul>

            <!-- 标签页内容 -->
            <div class="tab-content" id="settingsTabContent">
                <!-- 标签1: 向量嵌入模型 -->
                <div class="tab-pane fade show active" id="embedding" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-diagram-3"></i> 向量嵌入模型(Embedding)</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">提供商 <span class="text-danger">*</span></label>
                                <select class="form-select" id="embeddingProvider" name="embedding_provider" onchange="updateEmbeddingForm()" required>
                                    <option value="huggingface">HuggingFace</option>
                                    <option value="openai">OpenAI</option>
                                    <option value="ollama">Ollama</option>
                                </select>
                                <div class="form-text">选择向量嵌入模型提供商</div>
                            </div>

                            <div class="mb-3" id="embeddingModelNameGroup">
                                <label class="form-label">模型名称</label>
                                <select class="form-select" id="embeddingModelName" name="embedding_model_name">
                                    <option value="">请选择模型</option>
                                    <!-- 动态填充 -->
                                </select>
                                <div class="form-text">选择模型名称或路径</div>
                            </div>

                            <div class="mb-3" id="embeddingApiKeyGroup" style="display: none;">
                                <label class="form-label">API Key</label>
                                <input type="password" class="form-control" id="embeddingApiKey" name="embedding_api_key" placeholder="输入 API Key">
                                <div class="form-text">某些提供商需要 API Key</div>
                            </div>

                            <div class="mb-3" id="embeddingBaseUrlGroup" style="display: none;">
                                <label class="form-label">Base URL</label>
                                <input type="text" class="form-control" id="embeddingBaseUrl" name="embedding_base_url" placeholder="例如: http://localhost:11434">
                                <div class="form-text">API Base URL(Ollama 需要)</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签2: 大语言模型 -->
                <div class="tab-pane fade" id="llm" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-robot"></i> 大语言模型(LLM)</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">提供商 <span class="text-danger">*</span></label>
                                <select class="form-select" id="llmProvider" name="llm_provider" onchange="updateLLMForm()" required>
                                    <option value="deepseek">DeepSeek</option>
                                    <option value="openai">OpenAI</option>
                                    <option value="ollama">Ollama</option>
                                </select>
                                <div class="form-text">选择大语言模型提供商</div>
                            </div>

                            <div class="mb-3" id="llmModelNameGroup">
                                <label class="form-label">模型名称</label>
                                <select class="form-select" id="llmModelName" name="llm_model_name">
                                    <option value="">请选择模型</option>
                                    <!-- 动态填充 -->
                                </select>
                                <div class="form-text">选择模型名称</div>
                            </div>

                            <div class="mb-3" id="llmApiKeyGroup">
                                <label class="form-label">API Key</label>
                                <input type="password" class="form-control" id="llmApiKey" name="llm_api_key" placeholder="输入 API Key">
                                <div class="form-text">某些提供商需要 API Key</div>
                            </div>

                            <div class="mb-3" id="llmBaseUrlGroup">
                                <label class="form-label">Base URL</label>
                                <input type="text" class="form-control" id="llmBaseUrl" name="llm_base_url" placeholder="例如: https://api.deepseek.com">
                                <div class="form-text">API Base URL</div>
                            </div>

                            <div class="mb-3">
                                <label class="form-label">温度 (Temperature)</label>
                                <input type="number" class="form-control" id="llmTemperature" name="llm_temperature" 
                                       value="0.7" step="0.1" min="0" max="2" placeholder="0.7">
                                <div class="form-text">控制输出的随机性,值越大越随机(0-2)</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签3: 提示词设置 -->
                <div class="tab-pane fade" id="prompt" role="tabpanel">
                    <div class="card">
                        <div class="card-body">
                            <!-- 子标签页导航 -->
                            <ul class="nav nav-tabs mb-4" id="promptSubTabs" role="tablist">
                                <li class="nav-item" role="presentation">
                                    <button class="nav-link active" id="chat-prompt-sub-tab" data-bs-toggle="tab" data-bs-target="#chat-prompt-sub" type="button" role="tab">
                                        <i class="bi bi-chat"></i> 普通聊天提示词
                                    </button>
                                </li>
                                <li class="nav-item" role="presentation">
                                    <button class="nav-link" id="rag-prompt-sub-tab" data-bs-toggle="tab" data-bs-target="#rag-prompt-sub" type="button" role="tab">
                                        <i class="bi bi-book"></i> 知识库聊天提示词
                                    </button>
                                </li>
                            </ul>

                            <!-- 子标签页内容 -->
                            <div class="tab-content" id="promptSubTabContent">
                                <!-- 子标签1: 普通聊天提示词 -->
                                <div class="tab-pane fade show active" id="chat-prompt-sub" role="tabpanel">
                                    <div class="mb-4">
                                        <label class="form-label">普通聊天系统提示词</label>
                                        <textarea class="form-control" id="chatSystemPrompt" name="chat_system_prompt" rows="10" 
                                                  placeholder="输入普通聊天提示词..."></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-0">普通聊天提示词用于指导AI助手在普通聊天(未选择知识库)时的回答风格和行为。</p>
                                            <p class="mb-0 mt-2"><strong>注意:</strong>这是系统消息的内容,不能使用变量。</p>
                                        </div>
                                    </div>
                                </div>

                                <!-- 子标签2: 知识库聊天提示词 -->
                                <div class="tab-pane fade" id="rag-prompt-sub" role="tabpanel">
                                    <div class="mb-4">
                                        <label class="form-label">知识库聊天系统提示词</label>
                                        <textarea class="form-control" id="ragSystemPrompt" name="rag_system_prompt" rows="6" 
                                                  placeholder="输入知识库聊天系统提示词..."></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-0">知识库聊天系统提示词用于在会话开始时设置AI助手的角色和行为。</p>
                                            <p class="mb-0 mt-2"><strong>注意:</strong>这是系统消息的内容,不能使用变量(如 {context} 或 {question})。</p>
                                        </div>
                                    </div>

                                    <hr class="my-4">

                                    <div class="mb-3">
                                        <label class="form-label">知识库聊天查询提示词</label>
                                        <textarea class="form-control" id="ragQueryPrompt" name="rag_query_prompt" rows="10" 
                                                  placeholder="例如:文档内容:&#10;{context}&#10;&#10;问题:{question}&#10;&#10;请基于文档内容回答问题。如果文档中没有相关信息,请明确说明。"></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-1">知识库聊天查询提示词用于每次提问时构建提示,指导AI助手如何基于文档内容回答问题。</p>
                                            <p class="mb-0"><strong>必须使用以下变量:</strong></p>
                                            <ul class="mb-0">
                                                <li><code>{context}</code> - 检索到的文档内容(必需)</li>
                                                <li><code>{question}</code> - 用户的问题(必需)</li>
                                            </ul>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签3: 检索设置 -->
                <div class="tab-pane fade" id="retrieval" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-search"></i> 检索设置</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">检索模式 <span class="text-danger">*</span></label>
                                <select class="form-select" id="retrievalMode" name="retrieval_mode" required>
                                    <option value="vector">向量检索</option>
                                    <option value="keyword">全文检索</option>
                                    <option value="hybrid">混合检索</option>
                                </select>
                                <div class="form-text">选择文档检索方式</div>
                            </div>

                            <div class="mb-3" id="vectorThresholdGroup">
                                <label class="form-label">向量检索阈值</label>
                                <input type="number" class="form-control" id="vectorThreshold" name="vector_threshold" 
                                       value="0.2" step="0.1" min="0" max="1" placeholder="0.2">
                                <div class="form-text">向量相似度阈值,低于此值的文档将被过滤(0-1)</div>
                            </div>

                            <div class="mb-3" id="keywordThresholdGroup" style="display: none;">
                                <label class="form-label">全文检索阈值</label>
                                <input type="number" class="form-control" id="keywordThreshold" name="keyword_threshold" 
                                       value="0.5" step="0.1" min="0" max="1" placeholder="0.5">
                                <div class="form-text">关键词匹配阈值(0-1)</div>
                            </div>

                            <div class="mb-3" id="vectorWeightGroup" style="display: none;">
                                <label class="form-label">向量检索权重</label>
                                <input type="number" class="form-control" id="vectorWeight" name="vector_weight" 
                                       value="0.7" step="0.1" min="0" max="1" placeholder="0.7">
                                <div class="form-text">混合检索时向量检索的权重(0-1),关键词检索权重 = 1 - 向量权重</div>
                            </div>

                            <div class="mb-3">
                                <label class="form-label">TopN 结果数量</label>
                                <input type="number" class="form-control" id="topN" name="top_n" 
                                       value="5" min="1" max="50" placeholder="5">
                                <div class="form-text">返回的文档数量(1-50)</div>
                            </div>

                            <div class="alert alert-info">
                                <i class="bi bi-info-circle"></i> <strong>说明:</strong>
                                <ul class="mb-0 mt-2">
                                    <li><strong>向量检索:</strong>基于语义相似度检索,适合理解问题意图</li>
                                    <li><strong>全文检索:</strong>基于关键词匹配检索,适合精确匹配</li>
                                    <li><strong>混合检索:</strong>结合向量和关键词检索,综合两者的优势</li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="d-flex justify-content-end gap-2 mt-4">
                <button type="button" class="btn btn-secondary" onclick="resetSettings()">重置</button>
                <button type="submit" class="btn btn-primary">
                    <i class="bi bi-save"></i> 保存设置
                </button>
            </div>
        </form>
    </div>
</div>

{% endblock %}

{% block extra_js %}
<script>
// 监听页面加载完成后,执行异步函数
document.addEventListener('DOMContentLoaded', async function() {
    try {
        // 发送请求获取可用模型列表
        const modelsResponse = await fetch('/api/v1/settings/models');
        // 解析返回的JSON数据
        const modelsResult = await modelsResponse.json();
        // 如果请求成功(响应码为200)
        if (modelsResult.code === 200) {
            // 取得可用模型数据
            const availableModels = modelsResult.data;
            // 更新嵌入模型表单
            updateEmbeddingForm(availableModels);
+           // 更新大语言模型表单
+           updateLLMForm(availableModels)
            // 发送请求获取当前设置
            const settingsResponse = await fetch('/api/v1/settings');
            // 解析返回的JSON数据
            const settingsResult = await settingsResponse.json();
            // 如果请求成功(响应码为200)
            if (settingsResult.code === 200) {
                // 加载设置到页面
                loadSettings(settingsResult.data);
            }
        }
    } catch (error) {
        // 捕获异常并在控制台打印错误信息
        console.error('加载设置失败:', error);
        // 弹窗提示加载失败
        alert('加载设置失败: ' + error.message);
    }
});

// 加载设置并填充到表单
function loadSettings(settings) {
    // 设置嵌入向量提供商,默认为huggingface
    document.getElementById('embeddingProvider').value = settings.embedding_provider || 'huggingface';
+   // 设置大语言模型提供商,默认为deepseek
+   document.getElementById('llmProvider').value = settings.llm_provider || 'deepseek';
    // 如果有嵌入模型名称,设置模型名称
    if (settings.embedding_model_name) {
        document.getElementById('embeddingModelName').value = settings.embedding_model_name;
    }
+   if (settings.llm_model_name) {
+       document.getElementById('llmModelName').value = settings.llm_model_name;
+   }
    // 设置API Key,如果没有则为空字符串
    document.getElementById('embeddingApiKey').value = settings.embedding_api_key || '';
    // 设置Base URL,如果没有则为空字符串
    document.getElementById('embeddingBaseUrl').value = settings.embedding_base_url || '';
+   // 加载其他 LLM 设置
+   document.getElementById('llmApiKey').value = settings.llm_api_key || '';
+   // 设置Base URL,如果没有则为空字符串
+   document.getElementById('llmBaseUrl').value = settings.llm_base_url || '';
+   // 设置温度,如果没有则为0.7
+   document.getElementById('llmTemperature').value = settings.llm_temperature || '0.7';
}

// 更新嵌入模型表单(根据可用模型动态渲染选项)
function updateEmbeddingForm(availableModels) {
    // 获取当前选中的提供商
    const provider = document.getElementById('embeddingProvider').value;
    // 获取模型选择框
    const modelSelect = document.getElementById('embeddingModelName');
    // 获取API Key输入框所在元素
    const apiKeyGroup = document.getElementById('embeddingApiKeyGroup');
    // 获取Base URL输入框所在元素
    const baseUrlGroup = document.getElementById('embeddingBaseUrlGroup');
    // 记住当前选中的模型值(以便刷新后尽量保留)
    const currentValue = modelSelect.value;
    // 清空模型下拉框内容,并加一个提示选项
    modelSelect.innerHTML = '<option value="">请选择模型</option>';
    // 如果存在可用模型数据,并且当前提供商有对应条目
    if (availableModels && availableModels.embedding_models[provider]) {
        // 获取该提供商的详细信息
        const providerInfo = availableModels.embedding_models[provider];
        // 遍历所有模型,添加为下拉选项
        providerInfo.models.forEach(model => {
            // 创建一个option元素
            const option = document.createElement('option');
            // 选项的值为path属性,否则为name
            const optionValue = model.path || model.name;
            option.value = optionValue;
            // 构造显示文本,包括名称、维度和描述
            const displayText = model.name + (model.dimension ? ` (维度: ${model.dimension})` : '') + (model.description ? ' - ' + model.description : '');
            option.textContent = displayText;
            // 添加到模型下拉框
            modelSelect.appendChild(option);
        });
        // 恢复刷新前选中的值(如果还有)
        if (currentValue) {
            // 检查当前值是否还在选项中
            const optionExists = Array.from(modelSelect.options).some(opt => opt.value === currentValue);
            if (optionExists) {
                // 如果存在,直接选中
                modelSelect.value = currentValue;
            } else {
                // 如果不存在,尝试“模糊匹配”
                const matchingOption = Array.from(modelSelect.options).find(opt => {
                    const optValue = opt.value;
                    // 判断当前值和选项值是否完全相等或相互包含
                    return optValue === currentValue || optValue.includes(currentValue) || currentValue.includes(optValue);
                });
                // 如果找到匹配项,则选中该项
                if (matchingOption) {
                    modelSelect.value = matchingOption.value;
                }
            }
        }
        // 根据提供商是否需要API Key,显示或隐藏输入框
        if (providerInfo.requires_api_key) {
            apiKeyGroup.style.display = 'block';
            document.getElementById('embeddingApiKey').required = true;
        } else {
            apiKeyGroup.style.display = 'none';
            document.getElementById('embeddingApiKey').required = false;
        }
        // 根据提供商是否需要Base URL,显示或隐藏输入框
        if (providerInfo.requires_base_url) {
            baseUrlGroup.style.display = 'block';
        } else {
            baseUrlGroup.style.display = 'none';
        }
    }
}

// 保存设置(表单提交处理函数,异步提交到后端)
async function saveSettings(event) {
    // 阻止表单的默认提交行为
    event.preventDefault();
    // 获取表单dom对象
    const form = event.target;
    // 构造FormData对象用于取值
    const formData = new FormData(form);
    // 构造要提交的数据对象
    const data = {
+       embedding_provider: formData.get('embedding_provider'),// 嵌入模型提供商
+       embedding_model_name: formData.get('embedding_model_name') || null,// 嵌入模型名称
+       embedding_api_key: formData.get('embedding_api_key') || null,// 嵌入API Key
+       embedding_base_url: formData.get('embedding_base_url') || null,// 嵌入Base URL
+       llm_provider: formData.get('llm_provider'),// 大语言模型提供商
+       llm_model_name: formData.get('llm_model_name') || null,// 大语言模型名称
+       llm_api_key: formData.get('llm_api_key') || null,// 大语言API Key
+       llm_base_url: formData.get('llm_base_url') || null,// 大语言Base URL
+       llm_temperature: formData.get('llm_temperature') || null,// 大语言温度
    };
    try {
        // 发送PUT请求到设置API,提交JSON数据
        const response = await fetch('/api/v1/settings', {
            method: 'PUT',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
        });
        // 解析返回结果
        const result = await response.json();
        // 如果提交成功
        if (response.ok) {
            alert('设置保存成功!');
            // 刷新页面
            location.reload();
        } else {
            // 否则弹窗提示失败原因
            alert('保存失败: ' + result.message);
        }
    } catch (error) {
        // 捕获异常并弹窗提示
        alert('保存失败: ' + error.message);
    }
}
+// 定义函数用于更新大语言模型(LLM)表单选项
+function updateLLMForm(availableModels) {
+   // 获取大语言模型提供商的当前选中值
+   const provider = document.getElementById('llmProvider').value;
+   // 获取模型选择下拉框元素
+   const modelSelect = document.getElementById('llmModelName');
+   // 获取 API Key 输入框分组
+   const apiKeyGroup = document.getElementById('llmApiKeyGroup');
+   // 获取 Base URL 输入框分组
+   const baseUrlGroup = document.getElementById('llmBaseUrlGroup');

+   // 在清空模型选项前,保存一下当前选中的选项值
+   const currentValue = modelSelect.value;

+   // 清空模型选择下拉框的所有选项,并添加默认提示选项
+   modelSelect.innerHTML = '<option value="">请选择模型</option>';

+   // 判断可用模型对象及当前 provider 是否存在
+   if (availableModels && availableModels.llm_models[provider]) {
+       // 获取对应 provider 的模型信息
+       const providerInfo = availableModels.llm_models[provider];

+       // 遍历该 provider 的所有模型,依次添加到下拉框
+       providerInfo.models.forEach(model => {
+           // 创建 option 节点
+           const option = document.createElement('option');
+           // 设置 option 的值为模型名
+           option.value = model.name;
+           // 设置 option 的显示文本(包含模型描述)
+           option.textContent = `${model.name}${model.description ? ' - ' + model.description : ''}`;
+           // 添加 option 到下拉框
+           modelSelect.appendChild(option);
+       });

+       // 如果之前选中过某个值,则恢复它(仅限该值仍存在于新选项中时)
+       if (currentValue) {
+           // 检测之前选中的值是否还存在于下拉选项里
+           const optionExists = Array.from(modelSelect.options).some(opt => opt.value === currentValue);
+           if (optionExists) {
+               // 恢复之前选中的值
+               modelSelect.value = currentValue;
+           }
+       }

+       // 判断该 provider 是否需要 API Key,显示或隐藏 API Key 分组
+       if (providerInfo.requires_api_key) {
+           apiKeyGroup.style.display = 'block';
+           // 设置输入框为必填
+           document.getElementById('llmApiKey').required = true;
+       } else {
+           // 隐藏分组并取消必填
+           apiKeyGroup.style.display = 'none';
+           document.getElementById('llmApiKey').required = false;
+       }

+       // 判断该 provider 是否需要 Base URL,显示或隐藏 Base URL 分组
+       if (providerInfo.requires_base_url) {
+           baseUrlGroup.style.display = 'block';
+       } else {
+           baseUrlGroup.style.display = 'none';
+       }
+   }
+}
</script>
{% endblock %}

6.提示词设置 #

6.1. settings.html #

app/templates/settings.html

{% extends "base.html" %}

{% block title %}设置 - RAG Lite{% endblock %}

{% block content %}
<div class="row">
    <div class="col-12">
        <nav aria-label="breadcrumb" class="mb-3">
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="/">首页</a></li>
                <li class="breadcrumb-item active">设置</li>
            </ol>
        </nav>

        <h2><i class="bi bi-gear"></i> 系统设置</h2>
        <p class="text-muted">配置模型、提示词和检索参数</p>

        <form id="settingsForm" onsubmit="saveSettings(event)">
            <!-- 标签页导航 -->
            <ul class="nav nav-tabs mb-4" id="settingsTabs" role="tablist">
                <li class="nav-item" role="presentation">
                    <button class="nav-link active" id="embedding-tab" data-bs-toggle="tab" data-bs-target="#embedding" type="button" role="tab">
                        <i class="bi bi-diagram-3"></i> 向量嵌入模型
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="llm-tab" data-bs-toggle="tab" data-bs-target="#llm" type="button" role="tab">
                        <i class="bi bi-robot"></i> 大语言模型
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="prompt-tab" data-bs-toggle="tab" data-bs-target="#prompt" type="button" role="tab">
                        <i class="bi bi-chat-quote"></i> 提示词设置
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="retrieval-tab" data-bs-toggle="tab" data-bs-target="#retrieval" type="button" role="tab">
                        <i class="bi bi-search"></i> 检索设置
                    </button>
                </li>
            </ul>

            <!-- 标签页内容 -->
            <div class="tab-content" id="settingsTabContent">
                <!-- 标签1: 向量嵌入模型 -->
                <div class="tab-pane fade show active" id="embedding" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-diagram-3"></i> 向量嵌入模型(Embedding)</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">提供商 <span class="text-danger">*</span></label>
                                <select class="form-select" id="embeddingProvider" name="embedding_provider" onchange="updateEmbeddingForm()" required>
                                    <option value="huggingface">HuggingFace</option>
                                    <option value="openai">OpenAI</option>
                                    <option value="ollama">Ollama</option>
                                </select>
                                <div class="form-text">选择向量嵌入模型提供商</div>
                            </div>

                            <div class="mb-3" id="embeddingModelNameGroup">
                                <label class="form-label">模型名称</label>
                                <select class="form-select" id="embeddingModelName" name="embedding_model_name">
                                    <option value="">请选择模型</option>
                                    <!-- 动态填充 -->
                                </select>
                                <div class="form-text">选择模型名称或路径</div>
                            </div>

                            <div class="mb-3" id="embeddingApiKeyGroup" style="display: none;">
                                <label class="form-label">API Key</label>
                                <input type="password" class="form-control" id="embeddingApiKey" name="embedding_api_key" placeholder="输入 API Key">
                                <div class="form-text">某些提供商需要 API Key</div>
                            </div>

                            <div class="mb-3" id="embeddingBaseUrlGroup" style="display: none;">
                                <label class="form-label">Base URL</label>
                                <input type="text" class="form-control" id="embeddingBaseUrl" name="embedding_base_url" placeholder="例如: http://localhost:11434">
                                <div class="form-text">API Base URL(Ollama 需要)</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签2: 大语言模型 -->
                <div class="tab-pane fade" id="llm" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-robot"></i> 大语言模型(LLM)</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">提供商 <span class="text-danger">*</span></label>
                                <select class="form-select" id="llmProvider" name="llm_provider" onchange="updateLLMForm()" required>
                                    <option value="deepseek">DeepSeek</option>
                                    <option value="openai">OpenAI</option>
                                    <option value="ollama">Ollama</option>
                                </select>
                                <div class="form-text">选择大语言模型提供商</div>
                            </div>

                            <div class="mb-3" id="llmModelNameGroup">
                                <label class="form-label">模型名称</label>
                                <select class="form-select" id="llmModelName" name="llm_model_name">
                                    <option value="">请选择模型</option>
                                    <!-- 动态填充 -->
                                </select>
                                <div class="form-text">选择模型名称</div>
                            </div>

                            <div class="mb-3" id="llmApiKeyGroup">
                                <label class="form-label">API Key</label>
                                <input type="password" class="form-control" id="llmApiKey" name="llm_api_key" placeholder="输入 API Key">
                                <div class="form-text">某些提供商需要 API Key</div>
                            </div>

                            <div class="mb-3" id="llmBaseUrlGroup">
                                <label class="form-label">Base URL</label>
                                <input type="text" class="form-control" id="llmBaseUrl" name="llm_base_url" placeholder="例如: https://api.deepseek.com">
                                <div class="form-text">API Base URL</div>
                            </div>

                            <div class="mb-3">
                                <label class="form-label">温度 (Temperature)</label>
                                <input type="number" class="form-control" id="llmTemperature" name="llm_temperature" 
                                       value="0.7" step="0.1" min="0" max="2" placeholder="0.7">
                                <div class="form-text">控制输出的随机性,值越大越随机(0-2)</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签3: 提示词设置 -->
                <div class="tab-pane fade" id="prompt" role="tabpanel">
                    <div class="card">
                        <div class="card-body">
                            <!-- 子标签页导航 -->
                            <ul class="nav nav-tabs mb-4" id="promptSubTabs" role="tablist">
                                <li class="nav-item" role="presentation">
                                    <button class="nav-link active" id="chat-prompt-sub-tab" data-bs-toggle="tab" data-bs-target="#chat-prompt-sub" type="button" role="tab">
                                        <i class="bi bi-chat"></i> 普通聊天提示词
                                    </button>
                                </li>
                                <li class="nav-item" role="presentation">
                                    <button class="nav-link" id="rag-prompt-sub-tab" data-bs-toggle="tab" data-bs-target="#rag-prompt-sub" type="button" role="tab">
                                        <i class="bi bi-book"></i> 知识库聊天提示词
                                    </button>
                                </li>
                            </ul>

                            <!-- 子标签页内容 -->
                            <div class="tab-content" id="promptSubTabContent">
                                <!-- 子标签1: 普通聊天提示词 -->
                                <div class="tab-pane fade show active" id="chat-prompt-sub" role="tabpanel">
                                    <div class="mb-4">
                                        <label class="form-label">普通聊天系统提示词</label>
                                        <textarea class="form-control" id="chatSystemPrompt" name="chat_system_prompt" rows="10" 
                                                  placeholder="输入普通聊天提示词..."></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-0">普通聊天提示词用于指导AI助手在普通聊天(未选择知识库)时的回答风格和行为。</p>
                                            <p class="mb-0 mt-2"><strong>注意:</strong>这是系统消息的内容,不能使用变量。</p>
                                        </div>
                                    </div>
                                </div>

                                <!-- 子标签2: 知识库聊天提示词 -->
                                <div class="tab-pane fade" id="rag-prompt-sub" role="tabpanel">
                                    <div class="mb-4">
                                        <label class="form-label">知识库聊天系统提示词</label>
                                        <textarea class="form-control" id="ragSystemPrompt" name="rag_system_prompt" rows="6" 
                                                  placeholder="输入知识库聊天系统提示词..."></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-0">知识库聊天系统提示词用于在会话开始时设置AI助手的角色和行为。</p>
                                            <p class="mb-0 mt-2"><strong>注意:</strong>这是系统消息的内容,不能使用变量(如 {context} 或 {question})。</p>
                                        </div>
                                    </div>

                                    <hr class="my-4">

                                    <div class="mb-3">
                                        <label class="form-label">知识库聊天查询提示词</label>
                                        <textarea class="form-control" id="ragQueryPrompt" name="rag_query_prompt" rows="10" 
                                                  placeholder="例如:文档内容:&#10;{context}&#10;&#10;问题:{question}&#10;&#10;请基于文档内容回答问题。如果文档中没有相关信息,请明确说明。"></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-1">知识库聊天查询提示词用于每次提问时构建提示,指导AI助手如何基于文档内容回答问题。</p>
                                            <p class="mb-0"><strong>必须使用以下变量:</strong></p>
                                            <ul class="mb-0">
                                                <li><code>{context}</code> - 检索到的文档内容(必需)</li>
                                                <li><code>{question}</code> - 用户的问题(必需)</li>
                                            </ul>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签3: 检索设置 -->
                <div class="tab-pane fade" id="retrieval" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-search"></i> 检索设置</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">检索模式 <span class="text-danger">*</span></label>
                                <select class="form-select" id="retrievalMode" name="retrieval_mode" required>
                                    <option value="vector">向量检索</option>
                                    <option value="keyword">全文检索</option>
                                    <option value="hybrid">混合检索</option>
                                </select>
                                <div class="form-text">选择文档检索方式</div>
                            </div>

                            <div class="mb-3" id="vectorThresholdGroup">
                                <label class="form-label">向量检索阈值</label>
                                <input type="number" class="form-control" id="vectorThreshold" name="vector_threshold" 
                                       value="0.2" step="0.1" min="0" max="1" placeholder="0.2">
                                <div class="form-text">向量相似度阈值,低于此值的文档将被过滤(0-1)</div>
                            </div>

                            <div class="mb-3" id="keywordThresholdGroup" style="display: none;">
                                <label class="form-label">全文检索阈值</label>
                                <input type="number" class="form-control" id="keywordThreshold" name="keyword_threshold" 
                                       value="0.5" step="0.1" min="0" max="1" placeholder="0.5">
                                <div class="form-text">关键词匹配阈值(0-1)</div>
                            </div>

                            <div class="mb-3" id="vectorWeightGroup" style="display: none;">
                                <label class="form-label">向量检索权重</label>
                                <input type="number" class="form-control" id="vectorWeight" name="vector_weight" 
                                       value="0.7" step="0.1" min="0" max="1" placeholder="0.7">
                                <div class="form-text">混合检索时向量检索的权重(0-1),关键词检索权重 = 1 - 向量权重</div>
                            </div>

                            <div class="mb-3">
                                <label class="form-label">TopN 结果数量</label>
                                <input type="number" class="form-control" id="topN" name="top_n" 
                                       value="5" min="1" max="50" placeholder="5">
                                <div class="form-text">返回的文档数量(1-50)</div>
                            </div>

                            <div class="alert alert-info">
                                <i class="bi bi-info-circle"></i> <strong>说明:</strong>
                                <ul class="mb-0 mt-2">
                                    <li><strong>向量检索:</strong>基于语义相似度检索,适合理解问题意图</li>
                                    <li><strong>全文检索:</strong>基于关键词匹配检索,适合精确匹配</li>
                                    <li><strong>混合检索:</strong>结合向量和关键词检索,综合两者的优势</li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="d-flex justify-content-end gap-2 mt-4">
                <button type="button" class="btn btn-secondary" onclick="resetSettings()">重置</button>
                <button type="submit" class="btn btn-primary">
                    <i class="bi bi-save"></i> 保存设置
                </button>
            </div>
        </form>
    </div>
</div>

{% endblock %}

{% block extra_js %}
<script>
// 监听页面加载完成后,执行异步函数
document.addEventListener('DOMContentLoaded', async function() {
    try {
        // 发送请求获取可用模型列表
        const modelsResponse = await fetch('/api/v1/settings/models');
        // 解析返回的JSON数据
        const modelsResult = await modelsResponse.json();
        // 如果请求成功(响应码为200)
        if (modelsResult.code === 200) {
            // 取得可用模型数据
            const availableModels = modelsResult.data;
            // 更新嵌入模型表单
            updateEmbeddingForm(availableModels);
            // 更新大语言模型表单
            updateLLMForm(availableModels)
            // 发送请求获取当前设置
            const settingsResponse = await fetch('/api/v1/settings');
            // 解析返回的JSON数据
            const settingsResult = await settingsResponse.json();
            // 如果请求成功(响应码为200)
            if (settingsResult.code === 200) {
                // 加载设置到页面
                loadSettings(settingsResult.data);
            }
        }
    } catch (error) {
        // 捕获异常并在控制台打印错误信息
        console.error('加载设置失败:', error);
        // 弹窗提示加载失败
        alert('加载设置失败: ' + error.message);
    }
});

// 加载设置并填充到表单
function loadSettings(settings) {
    // 设置嵌入向量提供商,默认为huggingface
    document.getElementById('embeddingProvider').value = settings.embedding_provider || 'huggingface';
    // 设置大语言模型提供商,默认为deepseek
    document.getElementById('llmProvider').value = settings.llm_provider || 'deepseek';
    // 如果有嵌入模型名称,设置模型名称
    if (settings.embedding_model_name) {
        document.getElementById('embeddingModelName').value = settings.embedding_model_name;
    }
    if (settings.llm_model_name) {
        document.getElementById('llmModelName').value = settings.llm_model_name;
    }
    // 设置API Key,如果没有则为空字符串
    document.getElementById('embeddingApiKey').value = settings.embedding_api_key || '';
    // 设置Base URL,如果没有则为空字符串
    document.getElementById('embeddingBaseUrl').value = settings.embedding_base_url || '';
    // 加载其他 LLM 设置
    document.getElementById('llmApiKey').value = settings.llm_api_key || '';
    // 设置Base URL,如果没有则为空字符串
    document.getElementById('llmBaseUrl').value = settings.llm_base_url || '';
    // 设置温度,如果没有则为0.7
    document.getElementById('llmTemperature').value = settings.llm_temperature || '0.7';

+   // 加载系统提示词
+   document.getElementById('chatSystemPrompt').value = settings.chat_system_prompt || '';
+   // 加载知识库聊天系统提示词
+   document.getElementById('ragSystemPrompt').value = settings.rag_system_prompt || '';
+   // 加载知识库聊天查询提示词
+   document.getElementById('ragQueryPrompt').value = settings.rag_query_prompt || '';
}

// 更新嵌入模型表单(根据可用模型动态渲染选项)
function updateEmbeddingForm(availableModels) {
    // 获取当前选中的提供商
    const provider = document.getElementById('embeddingProvider').value;
    // 获取模型选择框
    const modelSelect = document.getElementById('embeddingModelName');
    // 获取API Key输入框所在元素
    const apiKeyGroup = document.getElementById('embeddingApiKeyGroup');
    // 获取Base URL输入框所在元素
    const baseUrlGroup = document.getElementById('embeddingBaseUrlGroup');
    // 记住当前选中的模型值(以便刷新后尽量保留)
    const currentValue = modelSelect.value;
    // 清空模型下拉框内容,并加一个提示选项
    modelSelect.innerHTML = '<option value="">请选择模型</option>';
    // 如果存在可用模型数据,并且当前提供商有对应条目
    if (availableModels && availableModels.embedding_models[provider]) {
        // 获取该提供商的详细信息
        const providerInfo = availableModels.embedding_models[provider];
        // 遍历所有模型,添加为下拉选项
        providerInfo.models.forEach(model => {
            // 创建一个option元素
            const option = document.createElement('option');
            // 选项的值为path属性,否则为name
            const optionValue = model.path || model.name;
            option.value = optionValue;
            // 构造显示文本,包括名称、维度和描述
            const displayText = model.name + (model.dimension ? ` (维度: ${model.dimension})` : '') + (model.description ? ' - ' + model.description : '');
            option.textContent = displayText;
            // 添加到模型下拉框
            modelSelect.appendChild(option);
        });
        // 恢复刷新前选中的值(如果还有)
        if (currentValue) {
            // 检查当前值是否还在选项中
            const optionExists = Array.from(modelSelect.options).some(opt => opt.value === currentValue);
            if (optionExists) {
                // 如果存在,直接选中
                modelSelect.value = currentValue;
            } else {
                // 如果不存在,尝试“模糊匹配”
                const matchingOption = Array.from(modelSelect.options).find(opt => {
                    const optValue = opt.value;
                    // 判断当前值和选项值是否完全相等或相互包含
                    return optValue === currentValue || optValue.includes(currentValue) || currentValue.includes(optValue);
                });
                // 如果找到匹配项,则选中该项
                if (matchingOption) {
                    modelSelect.value = matchingOption.value;
                }
            }
        }
        // 根据提供商是否需要API Key,显示或隐藏输入框
        if (providerInfo.requires_api_key) {
            apiKeyGroup.style.display = 'block';
            document.getElementById('embeddingApiKey').required = true;
        } else {
            apiKeyGroup.style.display = 'none';
            document.getElementById('embeddingApiKey').required = false;
        }
        // 根据提供商是否需要Base URL,显示或隐藏输入框
        if (providerInfo.requires_base_url) {
            baseUrlGroup.style.display = 'block';
        } else {
            baseUrlGroup.style.display = 'none';
        }
    }
}

// 保存设置(表单提交处理函数,异步提交到后端)
async function saveSettings(event) {
    // 阻止表单的默认提交行为
    event.preventDefault();
    // 获取表单dom对象
    const form = event.target;
    // 构造FormData对象用于取值
    const formData = new FormData(form);
    // 构造要提交的数据对象
    const data = {
        embedding_provider: formData.get('embedding_provider'),// 嵌入模型提供商
        embedding_model_name: formData.get('embedding_model_name') || null,// 嵌入模型名称
        embedding_api_key: formData.get('embedding_api_key') || null,// 嵌入API Key
        embedding_base_url: formData.get('embedding_base_url') || null,// 嵌入Base URL
        llm_provider: formData.get('llm_provider'),// 大语言模型提供商
        llm_model_name: formData.get('llm_model_name') || null,// 大语言模型名称
        llm_api_key: formData.get('llm_api_key') || null,// 大语言API Key
        llm_base_url: formData.get('llm_base_url') || null,// 大语言Base URL
        llm_temperature: formData.get('llm_temperature') || null,// 大语言温度
+       chat_system_prompt: formData.get('chat_system_prompt') || null,// 普通聊天系统提示词
+       rag_system_prompt: formData.get('rag_system_prompt') || null,// 知识库聊天系统提示词
+       rag_query_prompt: formData.get('rag_query_prompt') || null,// 知识库聊天查询提示词
    };
    try {
        // 发送PUT请求到设置API,提交JSON数据
        const response = await fetch('/api/v1/settings', {
            method: 'PUT',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
        });
        // 解析返回结果
        const result = await response.json();
        // 如果提交成功
        if (response.ok) {
            alert('设置保存成功!');
            // 刷新页面
            location.reload();
        } else {
            // 否则弹窗提示失败原因
            alert('保存失败: ' + result.message);
        }
    } catch (error) {
        // 捕获异常并弹窗提示
        alert('保存失败: ' + error.message);
    }
}
// 定义函数用于更新大语言模型(LLM)表单选项
function updateLLMForm(availableModels) {
    // 获取大语言模型提供商的当前选中值
    const provider = document.getElementById('llmProvider').value;
    // 获取模型选择下拉框元素
    const modelSelect = document.getElementById('llmModelName');
    // 获取 API Key 输入框分组
    const apiKeyGroup = document.getElementById('llmApiKeyGroup');
    // 获取 Base URL 输入框分组
    const baseUrlGroup = document.getElementById('llmBaseUrlGroup');

    // 在清空模型选项前,保存一下当前选中的选项值
    const currentValue = modelSelect.value;

    // 清空模型选择下拉框的所有选项,并添加默认提示选项
    modelSelect.innerHTML = '<option value="">请选择模型</option>';

    // 判断可用模型对象及当前 provider 是否存在
    if (availableModels && availableModels.llm_models[provider]) {
        // 获取对应 provider 的模型信息
        const providerInfo = availableModels.llm_models[provider];

        // 遍历该 provider 的所有模型,依次添加到下拉框
        providerInfo.models.forEach(model => {
            // 创建 option 节点
            const option = document.createElement('option');
            // 设置 option 的值为模型名
            option.value = model.name;
            // 设置 option 的显示文本(包含模型描述)
            option.textContent = `${model.name}${model.description ? ' - ' + model.description : ''}`;
            // 添加 option 到下拉框
            modelSelect.appendChild(option);
        });

        // 如果之前选中过某个值,则恢复它(仅限该值仍存在于新选项中时)
        if (currentValue) {
            // 检测之前选中的值是否还存在于下拉选项里
            const optionExists = Array.from(modelSelect.options).some(opt => opt.value === currentValue);
            if (optionExists) {
                // 恢复之前选中的值
                modelSelect.value = currentValue;
            }
        }

        // 判断该 provider 是否需要 API Key,显示或隐藏 API Key 分组
        if (providerInfo.requires_api_key) {
            apiKeyGroup.style.display = 'block';
            // 设置输入框为必填
            document.getElementById('llmApiKey').required = true;
        } else {
            // 隐藏分组并取消必填
            apiKeyGroup.style.display = 'none';
            document.getElementById('llmApiKey').required = false;
        }

        // 判断该 provider 是否需要 Base URL,显示或隐藏 Base URL 分组
        if (providerInfo.requires_base_url) {
            baseUrlGroup.style.display = 'block';
        } else {
            baseUrlGroup.style.display = 'none';
        }
    }
}
+function resetSettings() {
+   if (confirm('确定要重置为默认设置吗?')) {
+       loadSettings({
+           embedding_provider: 'huggingface',
+           embedding_model_name: 'sentence-transformers/all-MiniLM-L6-v2',
+           embedding_api_key: '',
+           embedding_base_url: '',
+           llm_provider: 'deepseek',
+           llm_model_name: '',
+           llm_api_key: '',
+           llm_base_url: '',
+           llm_temperature: '0.7',
+           chat_system_prompt: '你是一个专业的AI助手。请友好、准确地回答用户的问题。',
+           rag_system_prompt: '你是一个专业的AI助手。请基于文档内容回答问题。',
+           rag_query_prompt: '文档内容:\n{context}\n\n问题:{question}\n\n请基于文档内容回答问题。如果文档中没有相关信息,请明确说明。',
+           retrieval_mode: 'vector',
+           vector_threshold: '0.2',
+           keyword_threshold: '0.5',
+           vector_weight: '0.7',
+           top_n: '5'
+       });
+   }
+}
</script>
{% endblock %}

7.检索设置 #

7.1. settings.html #

app/templates/settings.html

{% extends "base.html" %}

{% block title %}设置 - RAG Lite{% endblock %}

{% block content %}
<div class="row">
    <div class="col-12">
        <nav aria-label="breadcrumb" class="mb-3">
            <ol class="breadcrumb">
                <li class="breadcrumb-item"><a href="/">首页</a></li>
                <li class="breadcrumb-item active">设置</li>
            </ol>
        </nav>

        <h2><i class="bi bi-gear"></i> 系统设置</h2>
        <p class="text-muted">配置模型、提示词和检索参数</p>

        <form id="settingsForm" onsubmit="saveSettings(event)">
            <!-- 标签页导航 -->
            <ul class="nav nav-tabs mb-4" id="settingsTabs" role="tablist">
                <li class="nav-item" role="presentation">
                    <button class="nav-link active" id="embedding-tab" data-bs-toggle="tab" data-bs-target="#embedding" type="button" role="tab">
                        <i class="bi bi-diagram-3"></i> 向量嵌入模型
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="llm-tab" data-bs-toggle="tab" data-bs-target="#llm" type="button" role="tab">
                        <i class="bi bi-robot"></i> 大语言模型
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="prompt-tab" data-bs-toggle="tab" data-bs-target="#prompt" type="button" role="tab">
                        <i class="bi bi-chat-quote"></i> 提示词设置
                    </button>
                </li>
                <li class="nav-item" role="presentation">
                    <button class="nav-link" id="retrieval-tab" data-bs-toggle="tab" data-bs-target="#retrieval" type="button" role="tab">
                        <i class="bi bi-search"></i> 检索设置
                    </button>
                </li>
            </ul>

            <!-- 标签页内容 -->
            <div class="tab-content" id="settingsTabContent">
                <!-- 标签1: 向量嵌入模型 -->
                <div class="tab-pane fade show active" id="embedding" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-diagram-3"></i> 向量嵌入模型(Embedding)</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">提供商 <span class="text-danger">*</span></label>
                                <select class="form-select" id="embeddingProvider" name="embedding_provider" onchange="updateEmbeddingForm()" required>
                                    <option value="huggingface">HuggingFace</option>
                                    <option value="openai">OpenAI</option>
                                    <option value="ollama">Ollama</option>
                                </select>
                                <div class="form-text">选择向量嵌入模型提供商</div>
                            </div>

                            <div class="mb-3" id="embeddingModelNameGroup">
                                <label class="form-label">模型名称</label>
                                <select class="form-select" id="embeddingModelName" name="embedding_model_name">
                                    <option value="">请选择模型</option>
                                    <!-- 动态填充 -->
                                </select>
                                <div class="form-text">选择模型名称或路径</div>
                            </div>

                            <div class="mb-3" id="embeddingApiKeyGroup" style="display: none;">
                                <label class="form-label">API Key</label>
                                <input type="password" class="form-control" id="embeddingApiKey" name="embedding_api_key" placeholder="输入 API Key">
                                <div class="form-text">某些提供商需要 API Key</div>
                            </div>

                            <div class="mb-3" id="embeddingBaseUrlGroup" style="display: none;">
                                <label class="form-label">Base URL</label>
                                <input type="text" class="form-control" id="embeddingBaseUrl" name="embedding_base_url" placeholder="例如: http://localhost:11434">
                                <div class="form-text">API Base URL(Ollama 需要)</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签2: 大语言模型 -->
                <div class="tab-pane fade" id="llm" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-robot"></i> 大语言模型(LLM)</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">提供商 <span class="text-danger">*</span></label>
                                <select class="form-select" id="llmProvider" name="llm_provider" onchange="updateLLMForm()" required>
                                    <option value="deepseek">DeepSeek</option>
                                    <option value="openai">OpenAI</option>
                                    <option value="ollama">Ollama</option>
                                </select>
                                <div class="form-text">选择大语言模型提供商</div>
                            </div>

                            <div class="mb-3" id="llmModelNameGroup">
                                <label class="form-label">模型名称</label>
                                <select class="form-select" id="llmModelName" name="llm_model_name">
                                    <option value="">请选择模型</option>
                                    <!-- 动态填充 -->
                                </select>
                                <div class="form-text">选择模型名称</div>
                            </div>

                            <div class="mb-3" id="llmApiKeyGroup">
                                <label class="form-label">API Key</label>
                                <input type="password" class="form-control" id="llmApiKey" name="llm_api_key" placeholder="输入 API Key">
                                <div class="form-text">某些提供商需要 API Key</div>
                            </div>

                            <div class="mb-3" id="llmBaseUrlGroup">
                                <label class="form-label">Base URL</label>
                                <input type="text" class="form-control" id="llmBaseUrl" name="llm_base_url" placeholder="例如: https://api.deepseek.com">
                                <div class="form-text">API Base URL</div>
                            </div>

                            <div class="mb-3">
                                <label class="form-label">温度 (Temperature)</label>
                                <input type="number" class="form-control" id="llmTemperature" name="llm_temperature" 
                                       value="0.7" step="0.1" min="0" max="2" placeholder="0.7">
                                <div class="form-text">控制输出的随机性,值越大越随机(0-2)</div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签3: 提示词设置 -->
                <div class="tab-pane fade" id="prompt" role="tabpanel">
                    <div class="card">
                        <div class="card-body">
                            <!-- 子标签页导航 -->
                            <ul class="nav nav-tabs mb-4" id="promptSubTabs" role="tablist">
                                <li class="nav-item" role="presentation">
                                    <button class="nav-link active" id="chat-prompt-sub-tab" data-bs-toggle="tab" data-bs-target="#chat-prompt-sub" type="button" role="tab">
                                        <i class="bi bi-chat"></i> 普通聊天提示词
                                    </button>
                                </li>
                                <li class="nav-item" role="presentation">
                                    <button class="nav-link" id="rag-prompt-sub-tab" data-bs-toggle="tab" data-bs-target="#rag-prompt-sub" type="button" role="tab">
                                        <i class="bi bi-book"></i> 知识库聊天提示词
                                    </button>
                                </li>
                            </ul>

                            <!-- 子标签页内容 -->
                            <div class="tab-content" id="promptSubTabContent">
                                <!-- 子标签1: 普通聊天提示词 -->
                                <div class="tab-pane fade show active" id="chat-prompt-sub" role="tabpanel">
                                    <div class="mb-4">
                                        <label class="form-label">普通聊天系统提示词</label>
                                        <textarea class="form-control" id="chatSystemPrompt" name="chat_system_prompt" rows="10" 
                                                  placeholder="输入普通聊天提示词..."></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-0">普通聊天提示词用于指导AI助手在普通聊天(未选择知识库)时的回答风格和行为。</p>
                                            <p class="mb-0 mt-2"><strong>注意:</strong>这是系统消息的内容,不能使用变量。</p>
                                        </div>
                                    </div>
                                </div>

                                <!-- 子标签2: 知识库聊天提示词 -->
                                <div class="tab-pane fade" id="rag-prompt-sub" role="tabpanel">
                                    <div class="mb-4">
                                        <label class="form-label">知识库聊天系统提示词</label>
                                        <textarea class="form-control" id="ragSystemPrompt" name="rag_system_prompt" rows="6" 
                                                  placeholder="输入知识库聊天系统提示词..."></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-0">知识库聊天系统提示词用于在会话开始时设置AI助手的角色和行为。</p>
                                            <p class="mb-0 mt-2"><strong>注意:</strong>这是系统消息的内容,不能使用变量(如 {context} 或 {question})。</p>
                                        </div>
                                    </div>

                                    <hr class="my-4">

                                    <div class="mb-3">
                                        <label class="form-label">知识库聊天查询提示词</label>
                                        <textarea class="form-control" id="ragQueryPrompt" name="rag_query_prompt" rows="10" 
                                                  placeholder="例如:文档内容:&#10;{context}&#10;&#10;问题:{question}&#10;&#10;请基于文档内容回答问题。如果文档中没有相关信息,请明确说明。"></textarea>
                                        <div class="form-text mt-2">
                                            <p class="mb-1">知识库聊天查询提示词用于每次提问时构建提示,指导AI助手如何基于文档内容回答问题。</p>
                                            <p class="mb-0"><strong>必须使用以下变量:</strong></p>
                                            <ul class="mb-0">
                                                <li><code>{context}</code> - 检索到的文档内容(必需)</li>
                                                <li><code>{question}</code> - 用户的问题(必需)</li>
                                            </ul>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- 标签3: 检索设置 -->
                <div class="tab-pane fade" id="retrieval" role="tabpanel">
                    <div class="card">
                        <div class="card-header">
                            <h5 class="mb-0"><i class="bi bi-search"></i> 检索设置</h5>
                        </div>
                        <div class="card-body">
                            <div class="mb-3">
                                <label class="form-label">检索模式 <span class="text-danger">*</span></label>
                                <select class="form-select" id="retrievalMode" name="retrieval_mode" required>
                                    <option value="vector">向量检索</option>
                                    <option value="keyword">全文检索</option>
                                    <option value="hybrid">混合检索</option>
                                </select>
                                <div class="form-text">选择文档检索方式</div>
                            </div>

                            <div class="mb-3" id="vectorThresholdGroup">
                                <label class="form-label">向量检索阈值</label>
                                <input type="number" class="form-control" id="vectorThreshold" name="vector_threshold" 
                                       value="0.2" step="0.1" min="0" max="1" placeholder="0.2">
                                <div class="form-text">向量相似度阈值,低于此值的文档将被过滤(0-1)</div>
                            </div>

                            <div class="mb-3" id="keywordThresholdGroup" style="display: none;">
                                <label class="form-label">全文检索阈值</label>
                                <input type="number" class="form-control" id="keywordThreshold" name="keyword_threshold" 
                                       value="0.5" step="0.1" min="0" max="1" placeholder="0.5">
                                <div class="form-text">关键词匹配阈值(0-1)</div>
                            </div>

                            <div class="mb-3" id="vectorWeightGroup" style="display: none;">
                                <label class="form-label">向量检索权重</label>
                                <input type="number" class="form-control" id="vectorWeight" name="vector_weight" 
                                       value="0.7" step="0.1" min="0" max="1" placeholder="0.7">
                                <div class="form-text">混合检索时向量检索的权重(0-1),关键词检索权重 = 1 - 向量权重</div>
                            </div>

                            <div class="mb-3">
                                <label class="form-label">TopN 结果数量</label>
                                <input type="number" class="form-control" id="topN" name="top_n" 
                                       value="5" min="1" max="50" placeholder="5">
                                <div class="form-text">返回的文档数量(1-50)</div>
                            </div>

                            <div class="alert alert-info">
                                <i class="bi bi-info-circle"></i> <strong>说明:</strong>
                                <ul class="mb-0 mt-2">
                                    <li><strong>向量检索:</strong>基于语义相似度检索,适合理解问题意图</li>
                                    <li><strong>全文检索:</strong>基于关键词匹配检索,适合精确匹配</li>
                                    <li><strong>混合检索:</strong>结合向量和关键词检索,综合两者的优势</li>
                                </ul>
                            </div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="d-flex justify-content-end gap-2 mt-4">
                <button type="button" class="btn btn-secondary" onclick="resetSettings()">重置</button>
                <button type="submit" class="btn btn-primary">
                    <i class="bi bi-save"></i> 保存设置
                </button>
            </div>
        </form>
    </div>
</div>

{% endblock %}

{% block extra_js %}
<script>
// 监听页面加载完成后,执行异步函数
document.addEventListener('DOMContentLoaded', async function() {
    try {
        // 发送请求获取可用模型列表
        const modelsResponse = await fetch('/api/v1/settings/models');
        // 解析返回的JSON数据
        const modelsResult = await modelsResponse.json();
        // 如果请求成功(响应码为200)
        if (modelsResult.code === 200) {
            // 取得可用模型数据
            const availableModels = modelsResult.data;
            // 更新嵌入模型表单
            updateEmbeddingForm(availableModels);
            // 更新大语言模型表单
            updateLLMForm(availableModels)
+           // 更新检索设置表单
+           updateRetrievalForm();
            // 发送请求获取当前设置
            const settingsResponse = await fetch('/api/v1/settings');
            // 解析返回的JSON数据
            const settingsResult = await settingsResponse.json();
            // 如果请求成功(响应码为200)
            if (settingsResult.code === 200) {
                // 加载设置到页面
                loadSettings(settingsResult.data);
            }
        }
    } catch (error) {
        // 捕获异常并在控制台打印错误信息
        console.error('加载设置失败:', error);
        // 弹窗提示加载失败
        alert('加载设置失败: ' + error.message);
    }
});
+function updateRetrievalForm() {
+   const mode = document.getElementById('retrievalMode').value;
+   const vectorThresholdGroup = document.getElementById('vectorThresholdGroup');
+   const keywordThresholdGroup = document.getElementById('keywordThresholdGroup');
+   const vectorWeightGroup = document.getElementById('vectorWeightGroup');

+   if (mode === 'vector') {
+       vectorThresholdGroup.style.display = 'block';
+       keywordThresholdGroup.style.display = 'none';
+       vectorWeightGroup.style.display = 'none';
+   } else if (mode === 'keyword') {
+       vectorThresholdGroup.style.display = 'none';
+       keywordThresholdGroup.style.display = 'block';
+       vectorWeightGroup.style.display = 'none';
+   } else if (mode === 'hybrid') {
+       vectorThresholdGroup.style.display = 'block';
+       keywordThresholdGroup.style.display = 'block';
+       vectorWeightGroup.style.display = 'block';
+   }
+}
// 加载设置并填充到表单
function loadSettings(settings) {
    // 设置嵌入向量提供商,默认为huggingface
    document.getElementById('embeddingProvider').value = settings.embedding_provider || 'huggingface';
    // 设置大语言模型提供商,默认为deepseek
    document.getElementById('llmProvider').value = settings.llm_provider || 'deepseek';
    // 如果有嵌入模型名称,设置模型名称
    if (settings.embedding_model_name) {
        document.getElementById('embeddingModelName').value = settings.embedding_model_name;
    }
    if (settings.llm_model_name) {
        document.getElementById('llmModelName').value = settings.llm_model_name;
    }
    // 设置API Key,如果没有则为空字符串
    document.getElementById('embeddingApiKey').value = settings.embedding_api_key || '';
    // 设置Base URL,如果没有则为空字符串
    document.getElementById('embeddingBaseUrl').value = settings.embedding_base_url || '';
    // 加载其他 LLM 设置
    document.getElementById('llmApiKey').value = settings.llm_api_key || '';
    // 设置Base URL,如果没有则为空字符串
    document.getElementById('llmBaseUrl').value = settings.llm_base_url || '';
    // 设置温度,如果没有则为0.7
    document.getElementById('llmTemperature').value = settings.llm_temperature || '0.7';

    // 加载系统提示词
    document.getElementById('chatSystemPrompt').value = settings.chat_system_prompt || '';
    // 加载知识库聊天系统提示词
    document.getElementById('ragSystemPrompt').value = settings.rag_system_prompt || '';
    // 加载知识库聊天查询提示词
    document.getElementById('ragQueryPrompt').value = settings.rag_query_prompt || '';

+   // 设置检索模式,默认为'vector'
+   document.getElementById('retrievalMode').value = settings.retrieval_mode || 'vector';
+   // 设置向量检索阈值,默认为'0.2'
+   document.getElementById('vectorThreshold').value = settings.vector_threshold || '0.2';
+   // 设置关键词检索阈值,默认为'0.5'
+   document.getElementById('keywordThreshold').value = settings.keyword_threshold || '0.5';
+   // 设置向量权重,默认为'0.7'
+   document.getElementById('vectorWeight').value = settings.vector_weight || '0.7';
+   // 设置topN,默认为'5'
+   document.getElementById('topN').value = settings.top_n || '5';
}

// 更新嵌入模型表单(根据可用模型动态渲染选项)
function updateEmbeddingForm(availableModels) {
    // 获取当前选中的提供商
    const provider = document.getElementById('embeddingProvider').value;
    // 获取模型选择框
    const modelSelect = document.getElementById('embeddingModelName');
    // 获取API Key输入框所在元素
    const apiKeyGroup = document.getElementById('embeddingApiKeyGroup');
    // 获取Base URL输入框所在元素
    const baseUrlGroup = document.getElementById('embeddingBaseUrlGroup');
    // 记住当前选中的模型值(以便刷新后尽量保留)
    const currentValue = modelSelect.value;
    // 清空模型下拉框内容,并加一个提示选项
    modelSelect.innerHTML = '<option value="">请选择模型</option>';
    // 如果存在可用模型数据,并且当前提供商有对应条目
    if (availableModels && availableModels.embedding_models[provider]) {
        // 获取该提供商的详细信息
        const providerInfo = availableModels.embedding_models[provider];
        // 遍历所有模型,添加为下拉选项
        providerInfo.models.forEach(model => {
            // 创建一个option元素
            const option = document.createElement('option');
            // 选项的值为path属性,否则为name
            const optionValue = model.path || model.name;
            option.value = optionValue;
            // 构造显示文本,包括名称、维度和描述
            const displayText = model.name + (model.dimension ? ` (维度: ${model.dimension})` : '') + (model.description ? ' - ' + model.description : '');
            option.textContent = displayText;
            // 添加到模型下拉框
            modelSelect.appendChild(option);
        });
        // 恢复刷新前选中的值(如果还有)
        if (currentValue) {
            // 检查当前值是否还在选项中
            const optionExists = Array.from(modelSelect.options).some(opt => opt.value === currentValue);
            if (optionExists) {
                // 如果存在,直接选中
                modelSelect.value = currentValue;
            } else {
                // 如果不存在,尝试“模糊匹配”
                const matchingOption = Array.from(modelSelect.options).find(opt => {
                    const optValue = opt.value;
                    // 判断当前值和选项值是否完全相等或相互包含
                    return optValue === currentValue || optValue.includes(currentValue) || currentValue.includes(optValue);
                });
                // 如果找到匹配项,则选中该项
                if (matchingOption) {
                    modelSelect.value = matchingOption.value;
                }
            }
        }
        // 根据提供商是否需要API Key,显示或隐藏输入框
        if (providerInfo.requires_api_key) {
            apiKeyGroup.style.display = 'block';
            document.getElementById('embeddingApiKey').required = true;
        } else {
            apiKeyGroup.style.display = 'none';
            document.getElementById('embeddingApiKey').required = false;
        }
        // 根据提供商是否需要Base URL,显示或隐藏输入框
        if (providerInfo.requires_base_url) {
            baseUrlGroup.style.display = 'block';
        } else {
            baseUrlGroup.style.display = 'none';
        }
    }
}

// 保存设置(表单提交处理函数,异步提交到后端)
async function saveSettings(event) {
    // 阻止表单的默认提交行为
    event.preventDefault();
    // 获取表单dom对象
    const form = event.target;
    // 构造FormData对象用于取值
    const formData = new FormData(form);
    // 构造要提交的数据对象
    const data = {
        embedding_provider: formData.get('embedding_provider'),// 嵌入模型提供商
        embedding_model_name: formData.get('embedding_model_name') || null,// 嵌入模型名称
        embedding_api_key: formData.get('embedding_api_key') || null,// 嵌入API Key
        embedding_base_url: formData.get('embedding_base_url') || null,// 嵌入Base URL
        llm_provider: formData.get('llm_provider'),// 大语言模型提供商
        llm_model_name: formData.get('llm_model_name') || null,// 大语言模型名称
        llm_api_key: formData.get('llm_api_key') || null,// 大语言API Key
        llm_base_url: formData.get('llm_base_url') || null,// 大语言Base URL
        llm_temperature: formData.get('llm_temperature') || null,// 大语言温度
        chat_system_prompt: formData.get('chat_system_prompt') || null,// 普通聊天系统提示词
        rag_system_prompt: formData.get('rag_system_prompt') || null,// 知识库聊天系统提示词
        rag_query_prompt: formData.get('rag_query_prompt') || null,// 知识库聊天查询提示词
+       retrieval_mode: formData.get('retrieval_mode'),// 检索模式
+       vector_threshold: formData.get('vector_threshold') || null,// 向量检索阈值
+       keyword_threshold: formData.get('keyword_threshold') || null,// 全文检索阈值
+       vector_weight: formData.get('vector_weight') || null,// 向量检索权重
+       top_n: formData.get('top_n') || null// TopN 结果数量
    };
    try {
        // 发送PUT请求到设置API,提交JSON数据
        const response = await fetch('/api/v1/settings', {
            method: 'PUT',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify(data)
        });
        // 解析返回结果
        const result = await response.json();
        // 如果提交成功
        if (response.ok) {
            alert('设置保存成功!');
            // 刷新页面
            location.reload();
        } else {
            // 否则弹窗提示失败原因
            alert('保存失败: ' + result.message);
        }
    } catch (error) {
        // 捕获异常并弹窗提示
        alert('保存失败: ' + error.message);
    }
}
// 定义函数用于更新大语言模型(LLM)表单选项
function updateLLMForm(availableModels) {
    // 获取大语言模型提供商的当前选中值
    const provider = document.getElementById('llmProvider').value;
    // 获取模型选择下拉框元素
    const modelSelect = document.getElementById('llmModelName');
    // 获取 API Key 输入框分组
    const apiKeyGroup = document.getElementById('llmApiKeyGroup');
    // 获取 Base URL 输入框分组
    const baseUrlGroup = document.getElementById('llmBaseUrlGroup');

    // 在清空模型选项前,保存一下当前选中的选项值
    const currentValue = modelSelect.value;

    // 清空模型选择下拉框的所有选项,并添加默认提示选项
    modelSelect.innerHTML = '<option value="">请选择模型</option>';

    // 判断可用模型对象及当前 provider 是否存在
    if (availableModels && availableModels.llm_models[provider]) {
        // 获取对应 provider 的模型信息
        const providerInfo = availableModels.llm_models[provider];

        // 遍历该 provider 的所有模型,依次添加到下拉框
        providerInfo.models.forEach(model => {
            // 创建 option 节点
            const option = document.createElement('option');
            // 设置 option 的值为模型名
            option.value = model.name;
            // 设置 option 的显示文本(包含模型描述)
            option.textContent = `${model.name}${model.description ? ' - ' + model.description : ''}`;
            // 添加 option 到下拉框
            modelSelect.appendChild(option);
        });

        // 如果之前选中过某个值,则恢复它(仅限该值仍存在于新选项中时)
        if (currentValue) {
            // 检测之前选中的值是否还存在于下拉选项里
            const optionExists = Array.from(modelSelect.options).some(opt => opt.value === currentValue);
            if (optionExists) {
                // 恢复之前选中的值
                modelSelect.value = currentValue;
            }
        }

        // 判断该 provider 是否需要 API Key,显示或隐藏 API Key 分组
        if (providerInfo.requires_api_key) {
            apiKeyGroup.style.display = 'block';
            // 设置输入框为必填
            document.getElementById('llmApiKey').required = true;
        } else {
            // 隐藏分组并取消必填
            apiKeyGroup.style.display = 'none';
            document.getElementById('llmApiKey').required = false;
        }

        // 判断该 provider 是否需要 Base URL,显示或隐藏 Base URL 分组
        if (providerInfo.requires_base_url) {
            baseUrlGroup.style.display = 'block';
        } else {
            baseUrlGroup.style.display = 'none';
        }
    }
}
function resetSettings() {
    if (confirm('确定要重置为默认设置吗?')) {
        loadSettings({
            embedding_provider: 'huggingface',
            embedding_model_name: 'sentence-transformers/all-MiniLM-L6-v2',
            embedding_api_key: '',
            embedding_base_url: '',
            llm_provider: 'deepseek',
            llm_model_name: '',
            llm_api_key: '',
            llm_base_url: '',
            llm_temperature: '0.7',
            chat_system_prompt: '你是一个专业的AI助手。请友好、准确地回答用户的问题。',
            rag_system_prompt: '你是一个专业的AI助手。请基于文档内容回答问题。',
            rag_query_prompt: '文档内容:\n{context}\n\n问题:{question}\n\n请基于文档内容回答问题。如果文档中没有相关信息,请明确说明。',
            retrieval_mode: 'vector',
            vector_threshold: '0.2',
            keyword_threshold: '0.5',
            vector_weight: '0.7',
            top_n: '5'
        });
    }
}
</script>
{% endblock %}
← 上一节 18.知识库管理 下一节 20.文档管理 →

访问验证

请输入访问令牌

Token不正确,请重新输入