2026年7月2日 周四晚上19:30,报名腾讯会议了解“如何构建自进化的动态知识库(Brain)”(限30人)
免费POC, 零成本试错
FDE知识库

FDE知识库

学习大模型的前沿技术与行业落地应用


收藏

前沿重器[45] RAG开源项目Qanything源码阅读1-概述+服务

发布日期:2024-05-11 21:44:59 浏览次数: 4305
作者:CS的陋室

微信搜一搜,关注“CS的陋室”


本期是概述,主要就是概述整个项目,我这里直接给出本期的目录吧:

  • 为什么希望讲解这个项目。
  • 项目内代码结构概述。
  • 服务架构。
  • 算法以外的细节。
    • 算法服务sanic。
    • 大模型部署。
    • 小模型部署。
    • 中间件。

为什么要讲解这个项目

相比于论文中的对RAG的探索,实践更讲求实用性和全链路的完整性,类似文档处理、精排等逻辑在现实中的使用,一个很快的学习方式就是看开源项目,随着RAG项目的逐渐成熟,完整的开源项目也逐渐变多,包括langchain在内,类似Qanything、RAGflow、fastRAG都是一些被人们所提及比较好的项目了,本次选择了Qanything单纯是看了一段时间了(早在),所以来补坑。

Qanything是一个比较综合完整的开源RAG项目,其完整程度在我的视角是在这几个方面:

  • RAG全流程都具备,从文件上传、处理到在线推理、排序等,关键模块都是有的,而且中文注释,注释和文档也比较完善,所以很适合学习。
  • 包括完整的前后端体系(源码里有看到npm、webpack之类的关键词,相信这块做的也并不含糊)。
  • 部署支持多环境和多种功能,从他多个文档里可以看到,支持linux、windows、Mac等多个环境,而且也有很丰富的教程。

因此,我的评价还是挺高的,特此给大家分享一下。也希望大家在我讲解的帮助下,能快速了解这个项目。

项目内代码结构概述

因为整个项目要适配多平台,而且RAG本身是一个完整的系统,所以有大量算法外围的工作,如好几个中间件如milvus,es,mysql,同时有docker、windows等环境的配置教程,还是比较完善的,先来看一眼根目录中的内容。

|-- Dockerfile  # docker构造文件
|-- FAQ.md  # 常见问题回答
|-- FAQ_zh.md # 常见问题回答
|-- LICENSE  # 证书
|-- README.md # README文档
|-- README_zh.md# README文档
|-- assets  # 一些模型之类的文件会放在这里
|-- close.sh # 关闭脚本
|-- docker-compose-linux.yaml # docker构造所需要的yaml文件,linux专用
|-- docker-compose-windows.yaml # docker构造所需要的yaml文件,windows专用
|-- docs  # 各种使用文档
|-- etc   # 其他文件,此处有一个prompt
|-- qanything_kernel # qanything的核心带代码,基本是python,包括服务、算法等算法
|-- requirements.txt # python依赖
|-- run.sh  # 启动脚本
|-- scripts  # 其他脚本
|-- third_party # 三方库
`-- volumes  # 数据库容器的外部映射地址

这里可以看到:

  • 根目录下的内容主要集中在服务层面的内容,如docker、服务的启停(run/close)、各种文档(README、docs)等。
  • 各种三方的资源似乎都有,可以借此学习一下各种相关组件的知识。
  • 算法的核心代码应该是在qanything_kernel里面。

然后来看qanything_kernel中的文件是什么样的。

|-- __init__.py # 空的
|-- configs  # 这里是模型和服务的配置,还有prompt之类的
|-- connector # 中间件的连接工具
|-- core  # milvus、es、mysql的都在里面,大家其实都能参考着用的
|-- dependent_server # 独立服务,这里主要是放大模型服务、ocr服务(图片文字抽取)、重排模型服务的
|-- qanything_server # qanything的核心服务,也是对外的服务,另外还有一些js文件和图片素材,应该是给前端用的。
`-- utils    # 零散的代码工具。这里可以说是宝藏了,里面挺多工具函数自己平时都能用的,类似safe_get等,另外很多文件处理的工具,类似文件加载、切片等,都在里面。

这一层就基本到服务内部了,挺多核心的东西都在这里的。

服务结构

服务结构单独开一章节,详细聊聊里面的内容,通过这个也可以看出完整的RAG系统可能包含什么部分,这个从服务启动的脚本里面能逐步找到的。

首先是服务启动,找到run_for_local_option.sh这个东西,因为本身是python服务,所以启动的时候大都避不开命令python,排除掉一些在线请求的服务,只考虑local模式,并过滤一些大概可以找到这几个命令:

compute_capability=$(python3 scripts/get_cuda_capability.py $gpu_id1)
nohup python3 -u llm_server_entrypoint.py --host="0.0.0.0" --port=36001 --model-path="tokenizer_assets" --model-url="0.0.0.0:10001" > /workspace/qanything_local/logs/debug_logs/llm_server_entrypoint.log 2>&1 &
nohup python3 -u qanything_kernel/dependent_server/rerank_for_local_serve/rerank_server.py > /workspace/qanything_local/logs/debug_logs/rerank_server.log 2>&1 &
CUDA_VISIBLE_DEVICES=$gpu_id2 nohup python3 -u qanything_kernel/dependent_server/ocr_serve/ocr_server.py > /workspace/qanything_local/logs/debug_logs/ocr_server.log 2>&1 &
nohup python3 -u qanything_kernel/qanything_server/sanic_api.py --mode "local" > /workspace/qanything_local/logs/debug_logs/sanic_api.log 2>&1 &

这里每行的动作,是干了这些事:

  • scripts/get_cuda_capability.py:检测cuda可用性
  • llm_server_entrypoint.py:启动大模型服务(模型中转,即调接口的),如果要本地部署,还需要用fastchat启动大模型服务。
  • ependent_server/rerank_for_local_serve/rerank_server.py:重排序服务
  • dependent_server/ocr_serve/ocr_server.py:ocr服务
  • qanything_server/sanic_api.py:核心sanic服务

这里看起来,服务的拆分还是比较简单的,但从接口层面,可能就会比较复杂,我们进一步看qanything_kernel\qanything_server\sanic_api.py,直接看这段代码:

app.add_route(document, "/api/docs", methods=['GET'])
app.add_route(new_knowledge_base, "/api/local_doc_qa/new_knowledge_base", methods=['POST'])  # tags=["新建知识库"]
app.add_route(upload_weblink, "/api/local_doc_qa/upload_weblink", methods=['POST'])  # tags=["上传网页链接"]
app.add_route(upload_files, "/api/local_doc_qa/upload_files", methods=['POST'])  # tags=["上传文件"] 
app.add_route(local_doc_chat, "/api/local_doc_qa/local_doc_chat", methods=['POST'])  # tags=["问答接口"] 
app.add_route(list_kbs, "/api/local_doc_qa/list_knowledge_base", methods=['POST'])  # tags=["知识库列表"] 
app.add_route(list_docs, "/api/local_doc_qa/list_files", methods=['POST'])  # tags=["文件列表"]
app.add_route(get_total_status, "/api/local_doc_qa/get_total_status", methods=['POST'])  # tags=["获取所有知识库状态"]
app.add_route(clean_files_by_status, "/api/local_doc_qa/clean_files_by_status", methods=['POST'])  # tags=["清理数据库"]
app.add_route(delete_docs, "/api/local_doc_qa/delete_files", methods=['POST'])  # tags=["删除文件"] 
app.add_route(delete_knowledge_base, "/api/local_doc_qa/delete_knowledge_base", methods=['POST'])  # tags=["删除知识库"] 
app.add_route(rename_knowledge_base, "/api/local_doc_qa/rename_knowledge_base", methods=['POST'])  # tags=["重命名知识库"] 

可以看到,这里的服务接口远比实际想的还要多,这里绝大部分是文档相关的接口,包括文档多个类型的加载、查询、删除,至于这里类似documentnew_knowledge_base的定义,都在隔壁的qanything_kernel\qanything_server\handler.py里面(说实话我个人并不喜欢from XXX import *的模式,写起来简单,但是不利于阅读和后续的开发,包括后面可能重名、文件重复之类的,非常不好找,不过见仁见智因地制宜各有取舍吧)。

__all__ = ["new_knowledge_base""upload_files""list_kbs""list_docs""delete_knowledge_base""delete_docs",
           "rename_knowledge_base""get_total_status""clean_files_by_status""upload_weblink""local_doc_chat",
           "document"]

这就是所有的接口。

而这里比较重要的,当然就是/api/local_doc_qa/local_doc_chat这个问答接口了,在线推理主要就是请求这个来实现的。当然了,这里有关上传文件、链接之类的功能,也非常重要,在后续的篇章里,我会谈及,重点分别在“文件处理”、“在线推理”这两步上,敬请期待。

算法之外的细节

本期还是重点聚焦在算法以外的事吧,通过对整体项目多个细节的学习,能让我们的技术栈更加完整吧。

算法服务sanic

sanic似乎不是一个新东西了,qanything用的版本,根据requirements使用的是sanic==23.6.0

  • 官方文档:https://sanic.readthedocs.io/en/stable/

通过这玩意,就能把一个模块包装成服务,供别人随时请求使用了,就这个功能而言,和fastapi(这个的推荐度也比较高)、tornado之类的还是比较类似的。

这里以一个最简单的接口来解释一下。首先是需要定义一个接口,在qanything项目内就是一个handler,写在qanything_kernel\qanything_server\handler.py里面。这是其中一个最简单的,展示基本文档的接口。

from sanic import request
from sanic.response import text as sanic_text
async def document(req: request):
    description = """
# QAnything 介绍
...
"""

    return sanic_text(description)

有了这个接口后,在服务的启动中,增加这个接口即可。

from sanic import Sanic
app = Sanic("QAnything")
app.add_route(document, "/api/docs", methods=['GET'])

app.run(host='0.0.0.0', port=8777, workers=10, access_log=False)

这样就完成了一个接口的编写,还是比较简单的。

具体详细的使用方法,包括POST请求、流式等,具体可以详细看看项目,这些都是有的。

大模型部署

大模型服务这里使用了两种方案,一种是直接用sanic做中转站的方案,一种是使用fastchat来进行。

先把整块部署代码的启动脚本搬出来,比较长,可以直接翻到最后面看解析。

if [ "$runtime_backend" = "default" ]; then
    echo "Executing default FastTransformer runtime_backend"
    # start llm server
    # 判断一下,如果gpu_id1和gpu_id2相同,则只启动一个triton_server
    if [ $gpu_id1 -eq $gpu_id2 ]; then
        echo "The triton server will start on $gpu_id1 GPU"
        CUDA_VISIBLE_DEVICES=$gpu_id1 nohup /opt/tritonserver/bin/tritonserver --model-store=/model_repos/QAEnsemble --http-port=10000 --grpc-port=10001 --metrics-port=10002 --log-verbose=1 >  /workspace/qanything_local/logs/debug_logs/llm_embed_rerank_tritonserver.log 2>&1 &
        update_or_append_to_env "RERANK_PORT" "10001"
        update_or_append_to_env "EMBED_PORT" "10001"
    else
        echo "The triton server will start on $gpu_id1 and $gpu_id2 GPUs"

        CUDA_VISIBLE_DEVICES=$gpu_id1 nohup /opt/tritonserver/bin/tritonserver --model-store=/model_repos/QAEnsemble_base --http-port=10000 --grpc-port=10001 --metrics-port=10002 --log-verbose=1 > /workspace/qanything_local/logs/debug_logs/llm_tritonserver.log 2>&1 &
        CUDA_VISIBLE_DEVICES=$gpu_id2 nohup /opt/tritonserver/bin/tritonserver --model-store=/model_repos/QAEnsemble_embed_rerank --http-port=9000 --grpc-port=9001 --metrics-port=9002 --log-verbose=1 > /workspace/qanything_local/logs/debug_logs/embed_rerank_tritonserver.log 2>&1 &
        update_or_append_to_env "RERANK_PORT" "9001"
        update_or_append_to_env "EMBED_PORT" "9001"
    fi

    cd /workspace/qanything_local/qanything_kernel/dependent_server/llm_for_local_serve || exit
    nohup python3 -u llm_server_entrypoint.py --host="0.0.0.0" --port=36001 --model-path="tokenizer_assets" --model-url="0.0.0.0:10001" > /workspace/qanything_local/logs/debug_logs/llm_server_entrypoint.log 2>&1 &
    echo "The llm transfer service is ready! (1/8)"
    echo "大模型中转服务已就绪! (1/8)"
else
    echo "The triton server for embedding and reranker will start on $gpu_id2 GPUs"
    CUDA_VISIBLE_DEVICES=$gpu_id2 nohup /opt/tritonserver/bin/tritonserver --model-store=/model_repos/QAEnsemble_embed_rerank --http-port=9000 --grpc-port=9001 --metrics-port=9002 --log-verbose=1 > /workspace/qanything_local/logs/debug_logs/embed_rerank_tritonserver.log 2>&1 &
    update_or_append_to_env "RERANK_PORT" "9001"
    update_or_append_to_env "EMBED_PORT" "9001"

    LLM_API_SERVE_CONV_TEMPLATE="$conv_template"
    LLM_API_SERVE_MODEL="$model_name"

    check_folder_existence "$LLM_API_SERVE_MODEL"

    update_or_append_to_env "LLM_API_SERVE_PORT" "7802"
    update_or_append_to_env "LLM_API_SERVE_MODEL" "$LLM_API_SERVE_MODEL"
    update_or_append_to_env "LLM_API_SERVE_CONV_TEMPLATE" "$LLM_API_SERVE_CONV_TEMPLATE"

    mkdir -p /workspace/qanything_local/logs/debug_logs/fastchat_logs && cd /workspace/qanything_local/logs/debug_logs/fastchat_logs
    nohup python3 -m fastchat.serve.controller --host 0.0.0.0 --port 7800 > /workspace/qanything_local/logs/debug_logs/fastchat_logs/fschat_controller_7800.log 2>&1 &
    nohup python3 -m fastchat.serve.openai_api_server --host 0.0.0.0 --port 7802 --controller-address http://0.0.0.0:7800 > /workspace/qanything_local/logs/debug_logs/fastchat_logs/fschat_openai_api_server_7802.log 2>&1 &

    gpus=$tensor_parallel
    if [ $tensor_parallel -eq 2 ]; then
        gpus="$gpu_id1,$gpu_id2"
    else
        gpus="$gpu_id1"
    fi

    case $runtime_backend in
    "hf")
        echo "Executing hf runtime_backend"
        
        CUDA_VISIBLE_DEVICES=$gpus nohup python3 -m fastchat.serve.model_worker --host 0.0.0.0 --port 7801 \
            --controller-address http://0.0.0.0:7800 --worker-address http://0.0.0.0:7801 \
            --model-path /model_repos/CustomLLM/$LLM_API_SERVE_MODEL --load-8bit \
            --gpus $gpus --num-gpus $tensor_parallel --dtype bfloat16 --conv-template $LLM_API_SERVE_CONV_TEMPLATE > /workspace/qanything_local/logs/debug_logs/fastchat_logs/fschat_model_worker_7801.log 2>&1 &

        ;;
    "vllm")
        echo "Executing vllm runtime_backend"

        CUDA_VISIBLE_DEVICES=$gpus nohup python3 -m fastchat.serve.vllm_worker --host 0.0.0.0 --port 7801 \
            --controller-address http://0.0.0.0:7800 --worker-address http://0.0.0.0:7801 \
            --model-path /model_repos/CustomLLM/$LLM_API_SERVE_MODEL --trust-remote-code --block-size 32 --tensor-parallel-size $tensor_parallel \
            --max-model-len 4096 --gpu-memory-utilization $gpu_memory_utilization --dtype bfloat16 --conv-template $LLM_API_SERVE_CONV_TEMPLATE > /workspace/qanything_local/logs/debug_logs/fastchat_logs/fschat_model_worker_7801.log 2>&1 &
        
        ;;
    "sglang")
        echo "Executing sglang runtime_backend"
        ;;
    *)
        echo "Invalid runtime_backend option"; exit 1
        ;;
    esac
fi

先忽略这里有关rank之类模型部署的内容。

首先是sanic方案,qanything_kernel\dependent_server\llm_for_local_serve\llm_server_entrypoint.py里面,这只是一个中转服务,内部只是在请求QwenTritonModel,调用的逻辑在qanything_kernel\dependent_server\llm_for_local_serve\modeling_qwen.py,不过在代码内没有找到具体模型部署的代码,从请求代码来看,像是有一个grpc接口。

然后是fastchat方案,在else下,代码比较详尽的是runtime_backendhfvllm的模式。具体的部署,都是围绕着fastchat进行的(https://github.com/lm-sys/FastChat),这个工具目前看还比较方便的,直接命令式就能做了。

小模型部署

这里涉及的小模型还不少。

首先是向量模型、排序模型,基本的模式都是triton启服务,然后再启一个服务做中转,至少向量模型和排序模型都是,类似下面这样。

CUDA_VISIBLE_DEVICES=$gpu_id2 nohup /opt/tritonserver/bin/tritonserver --model-store=/model_repos/QAEnsemble_embed_rerank --http-port=9000 --grpc-port=9001 --metrics-port=9002 --log-verbose=1 > /workspace/qanything_local/logs/debug_logs/embed_rerank_tritonserver.log 2>&1 &
nohup python3 -u qanything_kernel/dependent_server/rerank_for_local_serve/rerank_server.py > /workspace/qanything_local/logs/debug_logs/rerank_server.log 2>&1 &

rerank_server后面会请求grpc服务,具体的请求则写在了qanything_kernel\dependent_server\rerank_for_local_serve\rerank_server_backend.py里面。

比较特殊的是OCR模型,内部用的是PaddleOCR,这个直接包了层sanic服务来实现的。

CUDA_VISIBLE_DEVICES=$gpu_id2 nohup python3 -u qanything_kernel/dependent_server/ocr_serve/ocr_server.py > /workspace/qanything_local/logs/debug_logs/ocr_server.log 2>&1 &

中间件

首先是向量库milvus(https://milvus.io/),也支持python(pymilvus==2.3.4)。在qanything的代码中,给出了一个比较完整的客户端使用组件(qanything_kernel\connector\database\milvus\milvus_client.py),这个工具写的很完整,健壮性也很高,仔细研读和使用的可靠性都很高。

至于ES,也就是elasticsearch(项目中用的版本应该是8.11.4),应该是做搜索引擎的鼻祖了,这已经非常成熟,同样支持python客户端的(pip install elasticsearch),qanything_kernel\connector\database\milvus\es_client.py是一个不错的工具,此处ES主要用于字面的检索,在milvus_client.py中有提及字面和向量混合搜索的功能,默认是只支持向量检索,如果要混合检索,就会请求一次ES。

def __search_emb_sync(self, embs, expr='', top_k=None, client_timeout=None, queries=None):
    if not top_k:
        top_k = self.top_k
    milvus_records = self.sess.search(data=embs, partition_names=self.kb_ids, anns_field="embedding",
                                        param=self.search_params, limit=top_k,
                                        output_fields=self.output_fields, expr=expr, timeout=client_timeout)
    milvus_records_proc = self.parse_batch_result(milvus_records)
    # debug_logger.info(milvus_records)

    # 混合检索
    if self.hybrid_search:
        es_records = self.client.search(queries) # 补一个client的定义:self.client = ElasticsearchClient(index_name=self.index_name)
        es_records_proc = self.parse_es_batch_result(es_records, milvus_records)
        milvus_records_proc.extend(es_records_proc)

    return milvus_records_proc

值得提醒的是,ES在比较新的版本其实也支持构造向量索引了(也就是支持向量召回了),此处还使用milvus应该有别的原因吧。

最后就是mysql了,mysql在这里主要是起到了记录文档、知识管理、用户管理等作用,保持数据的一致性,核心代码在qanything_kernel\connector\database\mysql\mysql_client.py,这里的mysql没有做一个通用组件,而是直接定制化了一个类KnowledgeBaseManager,直接做文档、知识等信息的总结。

小结

qanything的内容太多,一篇文章不足以讲述,本期第一篇先给大家介绍这个项目,同时对里面的服务架构进行详细讲解,另外把非得算法,但是值得学习的内容,如算法服务、关键的中间件等,也进行了一定的解析,希望对大家有所帮助。


53AI,企业落地大模型首选服务商

产品:场景落地咨询+大模型应用平台+行业解决方案

承诺:免费POC验证,效果达标后再合作。零风险落地应用大模型,已交付160+中大型企业

联系我们

售前咨询
186 6662 7370
预约演示
185 8882 0121

微信扫码

添加专属顾问

回到顶部

加载中...

扫码咨询

扫码登录
登录即表示您同意《53AI网站服务协议》
服务协议

欢迎您使用【53AI 官方网站】(以下简称“本网站”或“我们”)。本《会员服务协议》(以下简称“本协议”)是您(以下简称“会员”或“用户”)与【深圳市博思协创网络科技有限公司】之间关于注册、登录及使用本网站会员服务所订立的法律协议。

在您注册或登录前,请务必审慎阅读、充分理解各条款内容,特别是免除或限制责任的条款、知识产权条款、争议解决条款等。此类条款将以加粗形式提示您注意。 当您通过微信公众号授权、手机验证码验证或其他方式成功登录本网站时,即视为您已完全理解并同意接受本协议的全部内容。

一、 定义

本网站:指由【深圳市博思协创网络科技有限公司】运营的,域名为【53ai.com】的网站及相关移动端页面。

会员服务:指本网站向注册会员提供的知识库文章查阅、内容检索及其他相关增值服务。

知识库内容:指本网站发布的包括但不限于文字、图表、数据、研究报告、行业分析等数字化内容资源。

二、 账号注册与登录

登录方式:本网站支持以下登录方式,您可根据实际情况选择:

微信公众号授权登录:您同意将您的微信OpenID信息授权给本网站,用于创建或关联会员账号。

手机验证码登录:您需提供真实有效的手机号码,并通过短信验证码完成身份验证与登录/注册。

账号安全:您的账号仅限您本人使用,禁止赠与、借用、租用、转让或售卖。因您保管不善导致的账号被盗、密码泄露等损失,由您自行承担。

实名认证:根据相关法律法规要求,我们可能要求您在特定功能下完成实名认证。如您拒绝提供,可能无法使用部分或全部服务。

未成年人保护:若您未满18周岁,请在法定监护人的陪同下阅读本协议,并在征得监护人同意后使用本服务。

三、 服务内容与规范

知识库查阅权限:会员登录后,有权按照其会员等级对应的权限范围,在线浏览、检索本网站知识库中的相关文章及内容。

服务变更:我们有权根据业务发展需要,调整、变更或终止部分服务内容,并将以网站公告、公众号消息等方式提前通知。

禁止行为:您在使用服务时不得实施以下行为:

利用技术手段批量爬取、下载、转存知识库内容;

将知识库内容用于商业目的或未经授权地向第三方传播;

干扰本网站正常运行或侵犯其他用户合法权益;

发布违法违规信息或从事违反公序良俗的活动。

四、 知识产权声明

权利归属:本网站知识库中的排版设计、软件代码等内容的知识产权均归【公司全称】或原权利人所有,受《中华人民共和国著作权法》等法律保护。

有限许可:本网站授予会员一项非独占、不可转让、不可转授权的普通许可,仅限于个人学习、研究之目的在线查阅知识库内容。

侵权追责:未经书面许可,任何单位或个人不得以任何形式复制、转载、摘编、镜像、汇编或以其他方式使用上述内容。一经发现,我们保留追究其法律责任的权利。

五、 个人信息保护

我们重视对您个人信息的保护。关于我们如何收集、使用、存储和保护您的个人信息,请单独阅读 《隐私政策》。

您通过微信公众号授权或手机号验证所提供的信息,我们将严格按照《个人信息保护法》的规定处理,仅用于身份识别、服务提供及安全验证等必要用途。

您可以随时通过网站设置或联系客服行使查阅、更正、删除个人信息及撤回授权同意的权利。

六、 免责声明

内容准确性:知识库内容仅供参考,不构成专业建议。我们不对其完整性、准确性、时效性作任何明示或暗示的保证,您应自行判断并承担使用风险。

不可抗力:因自然灾害、政策法规变化、网络故障、第三方平台接口异常(如微信接口维护、运营商短信通道故障)等不可抗力导致的服务中断或延迟,我们不承担违约责任。

第三方链接:本网站可能包含指向第三方网站的链接,该等网站的内容和服务不受我们控制,请您自行甄别风险。

七、 违约责任

如您违反本协议约定,我们有权视情节采取警告、限制功能、暂停服务、注销账号等措施,并保留要求赔偿损失的权利。

如因您的违约行为导致我们遭受行政处罚、第三方索赔或商誉损失,您应承担全部赔偿责任(包括但不限于罚款、赔偿金、律师费、公证费等)。

八、 法律适用与争议解决

本协议的订立、执行和解释均适用中华人民共和国大陆地区法律。

因本协议产生的或与本协议有关的任何争议,双方应友好协商解决;协商不成的,任何一方均可向【公司所在地】有管辖权的人民法院提起诉讼。

九、 其他

本协议构成双方就本服务达成的完整协议,取代此前任何口头或书面约定。

本协议任一条款被认定为无效或不可执行的,不影响其他条款的效力。

我们对本协议享有最终解释权,并在法律允许的范围内保留随时修改的权利。修改后的协议一经公布即生效,继续使用服务即视为同意修订内容。


已查阅