-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 1167331
Showing
17 changed files
with
980 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
root = true | ||
|
||
# all files | ||
[*.go] | ||
indent_style = tab | ||
indent_size = 4 | ||
insert_final_newline = true | ||
|
||
[*.py] | ||
indent_style = space | ||
indent_size = 4 | ||
|
||
[Makefile] | ||
indent_style = tab | ||
|
||
[*.js] | ||
charset = utf-8 | ||
indent_style = space | ||
indent_size = 4 | ||
|
||
[*.json] | ||
indent_style = space | ||
indent_size = 2 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
name: Build and Publish | ||
|
||
on: | ||
# run it on push to the default repository branch | ||
push: | ||
branches: [main] | ||
# run it during pull request | ||
pull_request: | ||
|
||
jobs: | ||
# define job to build and publish docker image | ||
build-and-push-docker-image: | ||
name: Build Docker image and push to repositories | ||
# run only when code is compiling and tests are passing | ||
runs-on: ubuntu-latest | ||
|
||
# steps to perform in job | ||
steps: | ||
- name: Checkout code | ||
uses: actions/checkout@v3 | ||
|
||
# setup Docker buld action | ||
- name: Set up Docker Buildx | ||
id: buildx | ||
uses: docker/setup-buildx-action@v2 | ||
|
||
- name: Login to DockerHub | ||
uses: docker/login-action@v2 | ||
with: | ||
username: ${{ secrets.DOCKERHUB_USERNAME }} | ||
password: ${{ secrets.DOCKERHUB_TOKEN }} | ||
|
||
- name: Build image and push to Docker Hub and GitHub Container Registry | ||
uses: docker/build-push-action@v2 | ||
with: | ||
# 指向带有 Dockerfile 的源代码所在位置的相对路径 | ||
context: ./ | ||
# Note: tags has to be all lower-case | ||
tags: | | ||
talkincode/gptservice:latest-amd64 | ||
# build on feature branches, push only on main branch | ||
push: ${{ github.ref == 'refs/heads/main' }} | ||
|
||
- name: Image digest | ||
run: echo ${{ steps.docker_build.outputs.digest }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
.idea | ||
__pycache__ | ||
.vscode | ||
/release/ | ||
release | ||
Dockerfile.local | ||
__debug_bin | ||
.DS_Store | ||
build | ||
/rundata/ | ||
.env | ||
/venv/ | ||
/playground/chroma_db/ | ||
/playground/local_qdrant/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
# 使用官方的 Python 基础镜像 | ||
FROM python:3.12.0-alpine3.18 | ||
|
||
# 设置工作目录 | ||
WORKDIR /app | ||
|
||
# 将项目文件复制到工作目录 | ||
COPY apps /app/libs | ||
COPY ./main.py /app/main.py | ||
COPY ./webapi.py /app/webapi.py | ||
COPY ./requirements.txt /app/requirements.txt | ||
|
||
# 安装项目依赖 | ||
RUN pip install --no-cache-dir -r requirements.txt | ||
|
||
# 暴露端口 | ||
EXPOSE 8000 | ||
|
||
# 设置启动命令 | ||
CMD ["uvicorn","main:app"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
fastpub: | ||
docker buildx build --build-arg GoArch="amd64" --platform=linux/amd64 -t \ | ||
talkincode/gptservice:latest . | ||
docker push talkincode/gptservice:latest | ||
|
||
arm64: | ||
docker buildx build --build-arg GoArch="arm64" --platform=linux/arm64 -t \ | ||
talkincode/gptservice:latest-arm64 . | ||
docker push talkincode/gptservice:latest-arm64 | ||
|
||
|
||
.PHONY: clean build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# GPTService | ||
|
||
## Introduction of GPTService | ||
|
||
GPTService is an advanced API project designed for custom GPT models. It follows the OpenAPI specification and provides efficient and flexible vector knowledge base retrieval and vector database management capabilities. | ||
|
||
## Features | ||
|
||
- Vector Knowledge Base Retrieval: Efficiently retrieve knowledge related to custom GPT models. | ||
- Vector Database Management: Flexible database management tool for users to manage and update data. | ||
- OpenAPI Compliance: Ensures compatibility with existing systems and tools. | ||
- Easy Integration: Suitable for a wide range of programming environments and frameworks. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
|
||
|
||
__all__ = ['apps', "common", "qdrant_index"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import logging | ||
import os | ||
from fastapi import FastAPI, Depends, HTTPException, status | ||
from fastapi.security import APIKeyHeader | ||
from pydantic import BaseModel, Field | ||
from starlette import status | ||
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint | ||
from starlette.requests import Request | ||
from starlette.responses import Response | ||
from starlette.types import ASGIApp | ||
|
||
from .common import num_tokens_from_string | ||
from .qdrant_index import qdrant | ||
from typing import Any | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
class LimitUploadSize(BaseHTTPMiddleware): | ||
"""限制上传文件大小""" | ||
|
||
def __init__(self, app: ASGIApp, max_upload_size: int) -> None: | ||
super().__init__(app) | ||
self.max_upload_size = max_upload_size | ||
|
||
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: | ||
if request.method == 'POST': | ||
if 'content-length' not in request.headers: | ||
return Response(status_code=status.HTTP_411_LENGTH_REQUIRED) | ||
content_length = int(request.headers['content-length']) | ||
if content_length > self.max_upload_size: | ||
return Response(status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE) | ||
return await call_next(request) | ||
|
||
|
||
app = FastAPI( | ||
title="GPTService API", | ||
description="gptservice api", | ||
version="1.0.0", | ||
) | ||
app.add_middleware(LimitUploadSize, max_upload_size=1024 * 1024 * 10) | ||
|
||
|
||
class TokenData(BaseModel): | ||
api_key: str | ||
|
||
|
||
class IndexItem(BaseModel): | ||
collection: str = Field("default", title="索引名称", description="索引名称") | ||
text: str = Field(title="文本内容", description="要向量化存储的文本内容") | ||
type: str = Field("text", title="文本类型", description="文本类型, text: 文本, webbase: 网页, webpdf: 网页PDF") | ||
url: str = Field("", title="网页地址", description="网页地址, 当type为webbase或webpdf时, 此项必填") | ||
separator: str = Field("\n\n", title="分隔符", description="分隔符, 处理文本时如何切分") | ||
chunk_size: int = Field(2000, title="切分大小", description="切分大小, 处理文本时每段的大小") | ||
chunk_overlap: int = Field(0, title="切分重叠", description="切分重叠, 处理文本时每段的重叠大小") | ||
|
||
|
||
class IndexSearchItem(BaseModel): | ||
collection: str = Field("default", title="索引名称", description="知识库索引存储名称") | ||
query: str = Field(title="查询内容", description="查询文本内容") | ||
|
||
|
||
class TokenItem(BaseModel): | ||
text: str = Field(title="文本内容", description="要向量化存储的文本内容") | ||
encoding: str = Field("cl100k_base", title="编码", description="编码, 默认为cl100k_base") | ||
|
||
|
||
class RestResult(BaseModel): | ||
code: int = Field(0, title="返回码", description="返回码, 0为成功, 其他为失败") | ||
msg: str = Field("ok", title="返回消息", description="返回消息, 成功为ok, 失败为具体错误信息") | ||
result: dict = Field({}, title="返回数据(可选)", description="返回数据(可选), 文本内容或是结构化数据") | ||
|
||
|
||
API_KEY = os.environ.get("API_KEY") | ||
api_key_header = APIKeyHeader(name="Authorization", auto_error=False) | ||
|
||
|
||
def verify_api_key(api_key: str = Depends(api_key_header)): | ||
"""验证API Key""" | ||
if api_key is None or api_key == "" or len(api_key) < 8 or api_key[7:] != API_KEY: | ||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API Key") | ||
return TokenData(api_key=api_key) | ||
|
||
|
||
@app.get("/") | ||
async def root(): | ||
return "ok" | ||
|
||
|
||
@app.post("/token/stat") | ||
async def token_stat(item: TokenItem, td: TokenData = Depends(verify_api_key)): | ||
"""统计文本的token数量""" | ||
try: | ||
return dict(code=0, msg="ok", data=dict( | ||
encoding=item.encoding, | ||
length=num_tokens_from_string(item.text, item.encoding) | ||
)) | ||
except Exception as e: | ||
raise HTTPException(status_code=500, detail=str(e)) | ||
|
||
|
||
@app.post("/knowledge/create") | ||
async def create_index(item: IndexItem, td: TokenData = Depends(verify_api_key)): | ||
"""创建知识库内容索引""" | ||
try: | ||
if item.type == "text": | ||
await qdrant.index_text(item.collection, item.text, item.chunk_size, item.chunk_overlap) | ||
elif item.type == "webbase": | ||
await qdrant.index_text_from_url(item.collection, item.url, item.chunk_size, item.chunk_overlap) | ||
elif item.type == "webpdf": | ||
await qdrant.index_pdf_from_path(item.collection, item.url, item.chunk_size, item.chunk_overlap) | ||
except Exception as e: | ||
log.error(f"create_index error: {e}") | ||
raise HTTPException(status_code=500, detail=str(e)) | ||
return RestResult(code=0, msg="success") | ||
|
||
|
||
@app.post("/knowledge/search", summary="搜索知识库", description="搜索知识库, 获取相关内容") | ||
async def search_index(item: IndexSearchItem, td: TokenData = Depends(verify_api_key)): | ||
"""搜索知识库,返回相关内容""" | ||
try: | ||
result = await qdrant.search(item.collection, item.query) | ||
return RestResult(code=0, msg="ok", result=dict(data=result)) | ||
except Exception as e: | ||
raise HTTPException(status_code=500, detail=str(e)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import tiktoken | ||
|
||
|
||
def num_tokens_from_string(string: str, encoding_name: str) -> int: | ||
"""Returns the number of tokens in a text string.""" | ||
encoding = tiktoken.get_encoding(encoding_name) | ||
num_tokens = len(encoding.encode(string)) | ||
return num_tokens | ||
|
||
|
||
def document_spliter_len(string: str) -> int: | ||
encoding = tiktoken.get_encoding("cl100k_base") | ||
return len(encoding.encode(string)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
import os | ||
|
||
import tiktoken | ||
from langchain.document_loaders import WebBaseLoader | ||
from langchain.embeddings import OpenAIEmbeddings | ||
from langchain.text_splitter import RecursiveCharacterTextSplitter | ||
from langchain.vectorstores import Qdrant | ||
from langchain.document_loaders import PyMuPDFLoader | ||
from .common import document_spliter_len | ||
from qdrant_client import qdrant_client | ||
|
||
|
||
class QdrantIndex(object): | ||
|
||
def __init__(self): | ||
self.qdrant_url = os.environ.get("QDRANT_URL") | ||
self.qdrant_grpc = os.environ.get("QDRANT_GRPC") in ["1", "true", "True", "TRUE"] | ||
|
||
async def search(self, collection, text, topk=3): | ||
client = qdrant_client.QdrantClient( | ||
url=self.qdrant_url, prefer_grpc=self.qdrant_grpc | ||
) | ||
embeddings = OpenAIEmbeddings() | ||
q = Qdrant( | ||
client=client, collection_name=collection, | ||
embeddings=embeddings, | ||
) | ||
result = await q.asimilarity_search_with_score(text, k=topk) | ||
data = [] | ||
if result: | ||
for doc, score in result: | ||
data.append(dict(content=doc.page_content, metadata=doc.metadata, score=score)) | ||
return data | ||
|
||
async def index_text_from_url(self, collection, url, chunk_size=100, chunk_overlap=0): | ||
loader = WebBaseLoader(url) | ||
documents = loader.load() | ||
text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, | ||
chunk_overlap=chunk_overlap, | ||
length_function=document_spliter_len) | ||
docs = text_splitter.split_documents(documents) | ||
embeddings = OpenAIEmbeddings() | ||
await Qdrant.afrom_documents( | ||
docs, embeddings, | ||
url=self.qdrant_url, | ||
prefer_grpc=self.qdrant_grpc, | ||
collection_name=collection, | ||
) | ||
|
||
async def index_pdf_from_path(self, collection, pdffile, chunk_size=1000, chunk_overlap=0): | ||
loader = PyMuPDFLoader(pdffile) | ||
documents = loader.load() | ||
text_splitter = RecursiveCharacterTextSplitter(chunk_size=chunk_size, | ||
chunk_overlap=chunk_overlap, | ||
length_function=document_spliter_len) | ||
docs = text_splitter.split_documents(documents) | ||
embeddings = OpenAIEmbeddings() | ||
await Qdrant.afrom_documents( | ||
docs, embeddings, | ||
url=self.qdrant_url, | ||
prefer_grpc=self.qdrant_grpc, | ||
collection_name=collection, | ||
) | ||
|
||
async def index_text(self, collection, text, chunk_size=1000, chunk_overlap=0): | ||
text_splitter = RecursiveCharacterTextSplitter( | ||
chunk_size=chunk_size, | ||
chunk_overlap=chunk_overlap, | ||
length_function=document_spliter_len | ||
) | ||
docs = text_splitter.create_documents([text]) | ||
embeddings = OpenAIEmbeddings() | ||
await Qdrant.afrom_documents( | ||
docs, embeddings, | ||
url=self.qdrant_url, | ||
prefer_grpc=self.qdrant_grpc, | ||
collection_name=collection, | ||
) | ||
|
||
|
||
qdrant = QdrantIndex() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
version: "3" | ||
services: | ||
gptservice: | ||
container_name: "gptservice" | ||
image: talkincode/gptservice:latest | ||
logging: | ||
driver: "json-file" | ||
options: | ||
max-size: "50m" | ||
environment: | ||
- API_KEY=${API_KEY} | ||
- OPENAI_API_TYPE=${OPENAI_API_TYPE} | ||
- AZURE_OPENAI_API_VERSION=${AZURE_OPENAI_API_VERSION} | ||
- AZURE_OPENAI_API_BASE=${AZURE_OPENAI_API_BASE} | ||
- AZURE_OPENAI_API_KEY=${AZURE_OPENAI_API_KEY} | ||
- OPENAI_API_KEY=${OPENAI_API_KEY} | ||
- QDRANT_URL=${QDRANT_URL} | ||
- DATA_DIR=/data | ||
volumes: | ||
- gptservice-volume:/data | ||
ports: | ||
- "8888:8700" | ||
command: ["uvicorn", "--host","0.0.0.0","main:apps"] | ||
networks: | ||
gptservice_network: | ||
|
||
networks: | ||
gptservice_network: | ||
|
||
volumes: | ||
gptservice-volume: |
Oops, something went wrong.