chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
|
||||
from typing import Literal
|
||||
|
||||
import httpx
|
||||
|
||||
RAW_RESPONSE_HEADER = "X-Stainless-Raw-Response"
|
||||
OVERRIDE_CAST_TO_HEADER = "____stainless_override_cast_to"
|
||||
|
||||
# default timeout is 10 minutes
|
||||
DEFAULT_TIMEOUT = httpx.Timeout(timeout=600.0, connect=5.0)
|
||||
DEFAULT_MAX_RETRIES = 2
|
||||
DEFAULT_CONNECTION_LIMITS = httpx.Limits(max_connections=1000, max_keepalive_connections=100)
|
||||
|
||||
INITIAL_RETRY_DELAY = 0.5
|
||||
MAX_RETRY_DELAY = 8.0
|
||||
|
||||
EMBEDDING_MODEL: str = "bge-large-zh-v1.5"
|
||||
HTTPX_TIMEOUT: float = 10.0
|
||||
API_BASE_URI: str = 'http://127.0.0.1:7861/'
|
||||
|
||||
# 知识库相关
|
||||
"""知识库中单段文本长度(不适用MarkdownHeaderTextSplitter)"""
|
||||
CHUNK_SIZE: int = 250
|
||||
"""知识库中相邻文本重合长度(不适用MarkdownHeaderTextSplitter)"""
|
||||
OVERLAP_SIZE: int = 50
|
||||
"""是否开启中文标题加强,以及标题增强的相关配置"""
|
||||
ZH_TITLE_ENHANCE: bool = False
|
||||
"""知识库匹配向量数量"""
|
||||
VECTOR_SEARCH_TOP_K: int = 3 # TODO: 与 tool 配置项重复
|
||||
"""知识库匹配相关度阈值,取值范围在0-2之间,SCORE越小,相关度越高,取到2相当于不筛选,建议设置在0.5左右"""
|
||||
SCORE_THRESHOLD: float = 0.4
|
||||
"""默认向量库/全文检索引擎类型"""
|
||||
VS_TYPE: Literal["faiss", "milvus", "zilliz", "pg", "es", "relyt", "chromadb"] = "faiss"
|
||||
# llm
|
||||
TEMPERATURE: float = 0.7
|
||||
LLM_MODEL = "chatglm-6b"
|
||||
MAX_TOKENS = 2048
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
|
||||
__title__ = "open_chatcaht"
|
||||
__version__ = "1.35.13"
|
||||
@@ -0,0 +1,87 @@
|
||||
from typing import Optional, List, Literal, Union
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from open_chatcaht._constants import MAX_TOKENS, LLM_MODEL, TEMPERATURE, SCORE_THRESHOLD, VECTOR_SEARCH_TOP_K
|
||||
from open_chatcaht.api_client import ApiClient
|
||||
from open_chatcaht.types.chat.chat_feedback_param import ChatFeedbackParam
|
||||
from open_chatcaht.types.chat.chat_message import ChatMessage
|
||||
from open_chatcaht.types.chat.file_chat_param import FileChatParam
|
||||
from open_chatcaht.types.chat.kb_chat_param import KbChatParam
|
||||
|
||||
API_URI_CHAT_FEEDBACK = "/chat/feedback"
|
||||
API_URI_FILE_CHAT = "/chat/file_chat"
|
||||
API_URI_KB_CHAT = "/chat/kb_chat"
|
||||
|
||||
|
||||
class ChatClient(ApiClient):
|
||||
|
||||
def chat_feedback(self,
|
||||
message_id: str,
|
||||
score: int = 100,
|
||||
reason: str = ""):
|
||||
data = ChatFeedbackParam(
|
||||
message_id=message_id,
|
||||
score=score,
|
||||
reason=reason,
|
||||
).dict()
|
||||
resp = self._post(API_URI_CHAT_FEEDBACK, json=data)
|
||||
return self._get_response_value(resp, as_json=True)
|
||||
|
||||
def kb_chat(self,
|
||||
query: str,
|
||||
mode: Literal["local_kb", "temp_kb", "search_engine"] = "local_kb",
|
||||
kb_name: str = "",
|
||||
top_k: int = VECTOR_SEARCH_TOP_K,
|
||||
score_threshold: float = SCORE_THRESHOLD,
|
||||
history: List[Union[ChatMessage, dict]] = [],
|
||||
stream: bool = True,
|
||||
model: str = LLM_MODEL,
|
||||
temperature: float = TEMPERATURE,
|
||||
max_tokens: Optional[int] = MAX_TOKENS,
|
||||
prompt_name: str = "default",
|
||||
return_direct: bool = False,
|
||||
):
|
||||
kb_chat_param = KbChatParam(
|
||||
query=query,
|
||||
mode=mode,
|
||||
kb_name=kb_name,
|
||||
top_k=top_k,
|
||||
score_threshold=score_threshold,
|
||||
history=history,
|
||||
stream=stream,
|
||||
model=model,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
prompt_name=prompt_name,
|
||||
return_direct=return_direct,
|
||||
).dict()
|
||||
response = self._post(API_URI_KB_CHAT, json=kb_chat_param, stream=True)
|
||||
return self._httpx_stream2generator(response, as_json=True)
|
||||
|
||||
def file_chat(self,
|
||||
query: str,
|
||||
knowledge_id: str,
|
||||
top_k: int = VECTOR_SEARCH_TOP_K,
|
||||
score_threshold: float = SCORE_THRESHOLD,
|
||||
history: List[Union[dict, ChatMessage]] = [],
|
||||
stream: bool = True,
|
||||
model_name: str = LLM_MODEL,
|
||||
temperature: float = 0.01,
|
||||
max_tokens: Optional[int] = MAX_TOKENS,
|
||||
prompt_name: str = "default",
|
||||
):
|
||||
file_chat_param = FileChatParam(
|
||||
query=query,
|
||||
knowledge_id=knowledge_id,
|
||||
top_k=top_k,
|
||||
score_threshold=score_threshold,
|
||||
history=history,
|
||||
stream=stream,
|
||||
model_name=model_name,
|
||||
temperature=temperature,
|
||||
max_tokens=max_tokens,
|
||||
prompt_name=prompt_name,
|
||||
).dict()
|
||||
response = self._post(API_URI_FILE_CHAT, json=file_chat_param, stream=True)
|
||||
return self._httpx_stream2generator(response, as_json=True)
|
||||
@@ -0,0 +1,321 @@
|
||||
from pydantic import Field
|
||||
|
||||
from open_chatcaht._constants import EMBEDDING_MODEL, VS_TYPE, VECTOR_SEARCH_TOP_K, SCORE_THRESHOLD, CHUNK_SIZE, \
|
||||
OVERLAP_SIZE, ZH_TITLE_ENHANCE, LLM_MODEL
|
||||
from open_chatcaht.api_client import ApiClient, post
|
||||
from open_chatcaht.types.knowledge_base.create_knowledge_base_param import CreateKnowledgeBaseParam
|
||||
import json
|
||||
import os
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import *
|
||||
|
||||
from open_chatcaht.types.knowledge_base.delete_knowledge_base_param import DeleteKnowledgeBaseParam
|
||||
from open_chatcaht.types.knowledge_base.doc.delete_kb_docs_param import DeleteKbDocsParam
|
||||
from open_chatcaht.types.knowledge_base.doc.download_kb_doc_param import DownloadKbDocParam
|
||||
from open_chatcaht.types.knowledge_base.doc.search_kb_docs_param import SearchKbDocsParam
|
||||
from open_chatcaht.types.knowledge_base.doc.search_temp_docs_param import SearchTempDocsParam
|
||||
from open_chatcaht.types.knowledge_base.doc.upload_kb_docs_param import UploadKbDocsParam
|
||||
from open_chatcaht.types.knowledge_base.doc.upload_temp_docs_param import UploadTempDocsParam
|
||||
from open_chatcaht.types.knowledge_base.recreate_vector_store_param import RecreateVectorStoreParam
|
||||
from open_chatcaht.types.knowledge_base.summary.recreate_summary_vector_store_param import \
|
||||
RecreateSummaryVectorStoreParam
|
||||
from open_chatcaht.types.knowledge_base.summary.summary_doc_ids_to_vector_store_param import \
|
||||
SummaryDocIdsToVectorStoreParam
|
||||
from open_chatcaht.types.knowledge_base.summary.summary_file_to_vector_store_param import SummaryFileToVectorStoreParam
|
||||
from open_chatcaht.types.knowledge_base.update_kb_info_param import UpdateKbInfoParam
|
||||
from open_chatcaht.types.response.base import BaseResponse
|
||||
from open_chatcaht.utils import convert_file
|
||||
|
||||
API_URI_CREATE_KB = "/knowledge_base/create_knowledge_base"
|
||||
API_URI_DELETE_KB = "/knowledge_base/delete_knowledge_base"
|
||||
API_URI_KB_UPDATE_INFO = "/knowledge_base/update_info"
|
||||
API_URI_LIST_KB = "/knowledge_base/list_knowledge_bases"
|
||||
|
||||
API_URI_URI_LIST_KB_FILE = "/knowledge_base/list_files"
|
||||
API_URI_SEARCH_KB_DOCS = "/knowledge_base/search_docs"
|
||||
|
||||
API_URI_KB_UPLOAD_DOCS = "/knowledge_base/upload_docs"
|
||||
API_URI_KB_DOWNLOAD_DOC = "/knowledge_base/download_doc"
|
||||
API_URI_DELETE_KB_DOCS = "/knowledge_base/delete_docs"
|
||||
API_URI_KB_RECREATE_VECTOR_STORE = "/knowledge_base/recreate_vector_store"
|
||||
API_URI_KB_SEARCH_TEMP_DOCS = "/knowledge_base/search_temp_docs"
|
||||
API_URI_KB_UPLOAD_TEMP_DOCS = "/knowledge_base/upload_temp_docs"
|
||||
|
||||
API_URI_KB_SUMMARY_FILE_TO_VECTOR_STORE = "/knowledge_base/kb_summary_api/summary_file_to_vector_store"
|
||||
API_URI_KB_SUMMARY_DOC_IDS_TO_VECTOR_STORE = "/knowledge_base/kb_summary_api/summary_doc_ids_to_vector_store"
|
||||
API_URI_KB_SUMMARY_RECREATE_VECTOR_STORE = "/knowledge_base/kb_summary_api/recreate_summary_vector_store"
|
||||
|
||||
|
||||
class KbClient(ApiClient):
|
||||
|
||||
@post(url=API_URI_CREATE_KB
|
||||
, body_model=CreateKnowledgeBaseParam)
|
||||
def create_kb(
|
||||
self,
|
||||
knowledge_base_name: str,
|
||||
kb_info: str = "",
|
||||
vector_store_type: str = VS_TYPE,
|
||||
embed_model: str = EMBEDDING_MODEL,
|
||||
) -> BaseResponse:
|
||||
...
|
||||
|
||||
# def create_knowledge_base(
|
||||
# self,
|
||||
# knowledge_base_name: str,
|
||||
# kb_info: str = "",
|
||||
# vector_store_type: str = VS_TYPE,
|
||||
# embed_model: str = EMBEDDING_MODEL,
|
||||
# ):
|
||||
# data = CreateKnowledgeBaseParam(
|
||||
# knowledge_base_name=knowledge_base_name,
|
||||
# kb_info=kb_info,
|
||||
# vector_store_type=vector_store_type,
|
||||
# embed_model=embed_model,
|
||||
# ).dict()
|
||||
# response = self.post(API_URI_CREATE_KB, json=data)
|
||||
# return self._get_response_value(response, as_json=True)
|
||||
|
||||
def delete_kb(
|
||||
self,
|
||||
knowledge_base_name: str,
|
||||
):
|
||||
response = self._post(API_URI_DELETE_KB, json=knowledge_base_name)
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def list_kb(self):
|
||||
response = self._get(API_URI_LIST_KB)
|
||||
return self._get_response_value(response, as_json=True, value_func=lambda r: r.get("data", []))
|
||||
|
||||
def list_kb_docs_file(
|
||||
self,
|
||||
knowledge_base_name: str,
|
||||
):
|
||||
params = DeleteKnowledgeBaseParam(knowledge_base_name=knowledge_base_name).dict()
|
||||
response = self._get(API_URI_URI_LIST_KB_FILE, params=params)
|
||||
return self._get_response_value(response, as_json=True, value_func=lambda r: r.get("data", []))
|
||||
|
||||
def search_kb_docs(
|
||||
self,
|
||||
knowledge_base_name: str,
|
||||
query: str = "",
|
||||
top_k: int = VECTOR_SEARCH_TOP_K,
|
||||
score_threshold: float = SCORE_THRESHOLD,
|
||||
file_name: str = "",
|
||||
metadata: dict = {},
|
||||
) -> List:
|
||||
data = SearchKbDocsParam(
|
||||
query=query,
|
||||
knowledge_base_name=knowledge_base_name,
|
||||
top_k=top_k,
|
||||
score_threshold=score_threshold,
|
||||
file_name=file_name,
|
||||
metadata=metadata,
|
||||
).dict()
|
||||
response = self._post(API_URI_SEARCH_KB_DOCS, json=data)
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def upload_kb_docs(
|
||||
self,
|
||||
files: List[Union[str, Path, bytes]],
|
||||
knowledge_base_name: str,
|
||||
override: bool = False,
|
||||
to_vector_store: bool = True,
|
||||
chunk_size=CHUNK_SIZE,
|
||||
chunk_overlap=OVERLAP_SIZE,
|
||||
zh_title_enhance=ZH_TITLE_ENHANCE,
|
||||
docs: Dict = {},
|
||||
not_refresh_vs_cache: bool = False,
|
||||
):
|
||||
files = [convert_file(file) for file in files]
|
||||
data = UploadKbDocsParam(
|
||||
knowledge_base_name=knowledge_base_name,
|
||||
override=override,
|
||||
to_vector_store=to_vector_store,
|
||||
chunk_size=chunk_size,
|
||||
chunk_overlap=chunk_overlap,
|
||||
zh_title_enhance=zh_title_enhance,
|
||||
docs=json.dumps(docs, ensure_ascii=False),
|
||||
not_refresh_vs_cache=not_refresh_vs_cache,
|
||||
).dict()
|
||||
response = self._post(API_URI_KB_UPLOAD_DOCS, data=data,
|
||||
files=[("files", (filename, file)) for filename, file in files])
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def delete_kb_docs(
|
||||
self,
|
||||
knowledge_base_name: str,
|
||||
file_names: List[str],
|
||||
delete_content: bool = False,
|
||||
not_refresh_vs_cache: bool = False,
|
||||
):
|
||||
data = DeleteKbDocsParam(
|
||||
knowledge_base_name=knowledge_base_name,
|
||||
file_names=file_names,
|
||||
delete_content=delete_content,
|
||||
not_refresh_vs_cache=not_refresh_vs_cache,
|
||||
).dict()
|
||||
response = self._post(API_URI_DELETE_KB_DOCS, json=data)
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def update_kb_info(self, knowledge_base_name, kb_info):
|
||||
data = UpdateKbInfoParam(
|
||||
knowledge_base_name=knowledge_base_name,
|
||||
kb_info=kb_info,
|
||||
).dict()
|
||||
response = self._post(API_URI_KB_UPDATE_INFO, json=data)
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def recreate_vector_store(
|
||||
self,
|
||||
knowledge_base_name: str,
|
||||
allow_empty_kb: bool = True,
|
||||
vs_type: str = VS_TYPE,
|
||||
embed_model: str = EMBEDDING_MODEL,
|
||||
chunk_size=CHUNK_SIZE,
|
||||
chunk_overlap=OVERLAP_SIZE,
|
||||
zh_title_enhance=ZH_TITLE_ENHANCE,
|
||||
):
|
||||
data = RecreateVectorStoreParam(
|
||||
knowledge_base_name=knowledge_base_name,
|
||||
allow_empty_kb=allow_empty_kb,
|
||||
vs_type=vs_type,
|
||||
embed_model=embed_model,
|
||||
chunk_size=chunk_size,
|
||||
chunk_overlap=chunk_overlap,
|
||||
zh_title_enhance=zh_title_enhance,
|
||||
).dict()
|
||||
response = self._post(API_URI_KB_RECREATE_VECTOR_STORE, json=data, stream=True, timeout=None)
|
||||
return self._httpx_stream2generator(response, as_json=True)
|
||||
|
||||
# def recreate_summary_vector_store(self,
|
||||
# knowledge_base_name: str,
|
||||
# allow_empty_kb: bool = True,
|
||||
# vs_type: str = VS_TYPE,
|
||||
# embed_model: str = EMBEDDING_MODEL,
|
||||
# file_description: str = "",
|
||||
# model_name: str = None,
|
||||
# temperature: float = 0.01,
|
||||
# max_tokens: Optional[int] = None):
|
||||
# data = RecreateSummaryVectorStoreParam(
|
||||
# knowledge_base_name=knowledge_base_name,
|
||||
# allow_empty_kb=allow_empty_kb,
|
||||
# vs_type=vs_type,
|
||||
# embed_model=embed_model,
|
||||
# file_description=file_description,
|
||||
# model_name=model_name,
|
||||
# temperature=temperature,
|
||||
# max_tokens=max_tokens).dict()
|
||||
# response = self._post(API_URI_KB_SUMMARY_RECREATE_VECTOR_STORE, json=data)
|
||||
# return self._get_response_value(response, as_json=True)
|
||||
#
|
||||
# def summary_doc_ids_to_vector_store(self,
|
||||
# knowledge_base_name: str,
|
||||
# doc_ids: List = [],
|
||||
# vs_type: str = VS_TYPE,
|
||||
# embed_model: str = EMBEDDING_MODEL,
|
||||
# file_description: str = "",
|
||||
# model_name: str = None,
|
||||
# temperature: float = 0.01,
|
||||
# max_tokens: Optional[int] = None,
|
||||
# ):
|
||||
# data = SummaryDocIdsToVectorStoreParam(
|
||||
# knowledge_base_name=knowledge_base_name,
|
||||
# doc_ids=doc_ids,
|
||||
# vs_type=vs_type,
|
||||
# embed_model=embed_model,
|
||||
# file_description=file_description,
|
||||
# model_name=model_name,
|
||||
# temperature=temperature,
|
||||
# max_tokens=max_tokens,
|
||||
# ).dict()
|
||||
# response = self._post(API_URI_KB_SUMMARY_DOC_IDS_TO_VECTOR_STORE, json=data)
|
||||
# return self._get_response_value(response, as_json=True)
|
||||
#
|
||||
# def summary_file_to_vector_store(self, knowledge_base_name: str,
|
||||
# file_name: str,
|
||||
# allow_empty_kb: bool = True,
|
||||
# vs_type: str = VS_TYPE,
|
||||
# embed_model: str = EMBEDDING_MODEL,
|
||||
# file_description: str = "",
|
||||
# model_name: str = LLM_MODEL,
|
||||
# temperature: float = 0.01,
|
||||
# max_tokens: Optional[int] = 1000):
|
||||
# data = SummaryFileToVectorStoreParam(
|
||||
# knowledge_base_name=knowledge_base_name,
|
||||
# file_name=file_name,
|
||||
# allow_empty_kb=allow_empty_kb,
|
||||
# vs_type=vs_type,
|
||||
# embed_model=embed_model,
|
||||
# file_description=file_description,
|
||||
# model_name=model_name,
|
||||
# temperature=temperature,
|
||||
# max_tokens=max_tokens,
|
||||
# ).dict()
|
||||
# response = self._post(API_URI_KB_SUMMARY_FILE_TO_VECTOR_STORE, json=data,stream=True)
|
||||
# return self._httpx_stream2generator(response, as_json=True)
|
||||
|
||||
def upload_temp_docs(self,
|
||||
files: List[Union[str, Path, bytes]],
|
||||
knowledge_id: str = None,
|
||||
chunk_size: int = CHUNK_SIZE,
|
||||
chunk_overlap: int = OVERLAP_SIZE,
|
||||
zh_title_enhance: bool = ZH_TITLE_ENHANCE,
|
||||
):
|
||||
data = UploadTempDocsParam(
|
||||
prev_id=knowledge_id,
|
||||
chunk_size=chunk_size,
|
||||
chunk_overlap=chunk_overlap,
|
||||
zh_title_enhance=zh_title_enhance
|
||||
).dict()
|
||||
_files = [convert_file(file) for file in files]
|
||||
response = self._post(
|
||||
"/knowledge_base/upload_temp_docs",
|
||||
data=data,
|
||||
files=[("files", (filename, file)) for filename, file in _files],
|
||||
)
|
||||
return self._get_response_value(response, as_json=True)
|
||||
# _files = [convert_file(file) for file in files]
|
||||
# response = self._post(API_URI_KB_UPLOAD_TEMP_DOCS, data=data,
|
||||
# files=[("files", (filename, file)) for filename, file in _files])
|
||||
# return self._get_response_value(response, as_json=True)
|
||||
|
||||
def search_temp_kb_docs(
|
||||
self,
|
||||
knowledge_id: str,
|
||||
query: str,
|
||||
top_k: int = VECTOR_SEARCH_TOP_K,
|
||||
score_threshold: float = SCORE_THRESHOLD,
|
||||
) -> List:
|
||||
data = SearchTempDocsParam(
|
||||
knowledge_id=knowledge_id,
|
||||
query=query,
|
||||
top_k=top_k,
|
||||
score_threshold=score_threshold,
|
||||
).dict()
|
||||
response = self._post(API_URI_KB_SEARCH_TEMP_DOCS, json=data)
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def download_kb_doc_file(self, knowledge_base_name: str, file_name: str, file_path: Optional[str] = None):
|
||||
params = DownloadKbDocParam(
|
||||
knowledge_base_name=knowledge_base_name,
|
||||
file_name=file_name,
|
||||
preview=False
|
||||
).dict()
|
||||
response = self._get(API_URI_KB_DOWNLOAD_DOC, params=params)
|
||||
file_content = self._get_response_value(response, as_json=False, value_func=lambda r: r.content)
|
||||
if file_path is None:
|
||||
file_path = file_name
|
||||
with open(file_path, 'wb') as file:
|
||||
file.write(file_content)
|
||||
return file_path
|
||||
|
||||
def kb_doc_file_content(self, knowledge_base_name: str, file_name: str):
|
||||
params = DownloadKbDocParam(
|
||||
knowledge_base_name=knowledge_base_name,
|
||||
file_name=file_name,
|
||||
preview=True
|
||||
).dict()
|
||||
response = self._get(API_URI_KB_DOWNLOAD_DOC, params=params)
|
||||
file_content = self._get_response_value(response, as_json=False, value_func=lambda r: r.content)
|
||||
return file_content.decode('utf-8')
|
||||
@@ -0,0 +1,23 @@
|
||||
from open_chatcaht.api_client import ApiClient
|
||||
|
||||
API_URI_GET_SERVER_CONFIGS = "/server/configs"
|
||||
API_URI_GET_PROMPT_TEMPLATE = "/server/get_prompt_template"
|
||||
|
||||
|
||||
class ServerClient(ApiClient):
|
||||
# 服务器信息
|
||||
def get_server_configs(self) -> dict:
|
||||
response = self._post(API_URI_GET_SERVER_CONFIGS)
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def get_prompt_template(
|
||||
self,
|
||||
_type: str = "knowledge_base_chat",
|
||||
name: str = "default",
|
||||
) -> str:
|
||||
data = {
|
||||
"type": _type, # 模板类型
|
||||
"name": name # 模板名称
|
||||
}
|
||||
response = self._post(API_URI_GET_PROMPT_TEMPLATE, json=data)
|
||||
return self._get_response_value(response, value_func=lambda r: r.text)
|
||||
@@ -0,0 +1,116 @@
|
||||
from typing import Dict, List
|
||||
|
||||
from open_chatcaht.api_client import ApiClient, get
|
||||
from open_chatcaht.types.standard_openai.audio_speech_input import OpenAIAudioSpeechInput
|
||||
from open_chatcaht.types.standard_openai.audio_transcriptions_input import OpenAIAudioTranscriptionsInput
|
||||
from open_chatcaht.types.standard_openai.audio_translations_input import OpenAIAudioTranslationsInput
|
||||
from open_chatcaht.types.standard_openai.chat_input import OpenAIChatInput
|
||||
from open_chatcaht.types.standard_openai.embeddings_Input import OpenAIEmbeddingsInput
|
||||
from open_chatcaht.types.standard_openai.image_edits_input import OpenAIImageEditsInput
|
||||
from open_chatcaht.types.standard_openai.image_generations_input import OpenAIImageGenerationsInput
|
||||
from open_chatcaht.types.standard_openai.image_variations_input import OpenAIImageVariationsInput
|
||||
|
||||
API_UTI_STANDARD_OPENAI_LIST_MODELS = "/v1/models"
|
||||
API_UTI_STANDARD_OPENAI_CHAT_COMPLETIONS = "/v1/chat/completions"
|
||||
API_UTI_STANDARD_OPENAI_COMPLETIONS = "/v1/chat/completions"
|
||||
API_UTI_STANDARD_OPENAI_EMBEDDINGS = "/v1/embeddings"
|
||||
|
||||
API_UTI_STANDARD_OPENAI_IMAGE_GENERATIONS = "/v1//images/generations"
|
||||
API_UTI_STANDARD_OPENAI_IMAGE_VARIATIONS = "/v1//images/variations"
|
||||
API_UTI_STANDARD_OPENAI_IMAGE_EDIT = "/v1//images/edit"
|
||||
|
||||
API_UTI_STANDARD_OPENAI_AUDIO_TRANSLATIONS = "/v1//audio/translations"
|
||||
API_UTI_STANDARD_OPENAI_AUDIO_TRANSCRIPTIONS = "/v1//audio/transcriptions"
|
||||
API_UTI_STANDARD_OPENAI_AUDIO_SPEECH = "/v1/audio/speech"
|
||||
|
||||
API_UTI_STANDARD_OPENAI_FILES = "/v1/files"
|
||||
API_UTI_STANDARD_OPENAI_LIST_FILES = "/v1/list_files"
|
||||
API_UTI_STANDARD_OPENAI_RETRIEVE_FILE = "/v1//files/{file_id}"
|
||||
API_UTI_STANDARD_OPENAI_RETRIEVE_FILE_CONTENT = "/v1//files/{file_id}/content"
|
||||
API_UTI_STANDARD_OPENAI_DELETE_FILE = "/v1//files/{file_id}"
|
||||
|
||||
|
||||
class StandardOpenaiClient(ApiClient):
|
||||
|
||||
def list_models(self) -> dict:
|
||||
response = self._get(API_UTI_STANDARD_OPENAI_LIST_MODELS)
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def chat_completions(self, chat_input: OpenAIChatInput) -> dict:
|
||||
response = self._post(API_UTI_STANDARD_OPENAI_CHAT_COMPLETIONS, json=chat_input.dict(), stream=True)
|
||||
return self._httpx_stream2generator(response, as_json=True)
|
||||
|
||||
def completions(self, chat_input: OpenAIChatInput) -> dict:
|
||||
response = self._post(API_UTI_STANDARD_OPENAI_COMPLETIONS, json=chat_input.dict(), stream=True)
|
||||
return self._httpx_stream2generator(response, as_json=True)
|
||||
|
||||
def embeddings(self, embeddings_input: OpenAIEmbeddingsInput):
|
||||
response = self._post(API_UTI_STANDARD_OPENAI_EMBEDDINGS, json=embeddings_input.dict())
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def image_generations(
|
||||
self,
|
||||
data: OpenAIImageGenerationsInput,
|
||||
):
|
||||
response = self._post(API_UTI_STANDARD_OPENAI_IMAGE_GENERATIONS, json=data.dict())
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def image_variations(
|
||||
self,
|
||||
data: OpenAIImageVariationsInput,
|
||||
):
|
||||
response = self._post(API_UTI_STANDARD_OPENAI_IMAGE_VARIATIONS, json=data.dict())
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def image_edit(
|
||||
self,
|
||||
data: OpenAIImageEditsInput,
|
||||
):
|
||||
response = self._post(API_UTI_STANDARD_OPENAI_IMAGE_EDIT, json=data.dict())
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def audio_translations(
|
||||
self,
|
||||
data: OpenAIAudioTranslationsInput,
|
||||
):
|
||||
response = self._post(API_UTI_STANDARD_OPENAI_AUDIO_TRANSLATIONS, json=data.dict())
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def audio_transcriptions(
|
||||
self,
|
||||
data: OpenAIAudioTranscriptionsInput,
|
||||
):
|
||||
response = self._post(API_UTI_STANDARD_OPENAI_AUDIO_TRANSCRIPTIONS, json=data.dict())
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def audio_speech(
|
||||
self,
|
||||
data: OpenAIAudioSpeechInput,
|
||||
):
|
||||
response = self._post(API_UTI_STANDARD_OPENAI_AUDIO_SPEECH, json=data.dict())
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
# todo 待完成
|
||||
async def files(
|
||||
self,
|
||||
file: str,
|
||||
purpose: str = "assistants",
|
||||
) -> Dict:
|
||||
response = self._post(API_UTI_STANDARD_OPENAI_FILES)
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def list_files(self, purpose: str) -> Dict[str, List[Dict]]:
|
||||
response = self._get(API_UTI_STANDARD_OPENAI_LIST_FILES)
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def retrieve_file(self, file_id: str) -> Dict:
|
||||
response = self._get(API_UTI_STANDARD_OPENAI_RETRIEVE_FILE.format(file_id=file_id))
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def retrieve_file_content(self, file_id: str) -> Dict:
|
||||
response = self._get(API_UTI_STANDARD_OPENAI_RETRIEVE_FILE_CONTENT.format(file_id=file_id))
|
||||
return self._get_response_value(response, as_json=True)
|
||||
|
||||
def delete_file(self, file_id: str) -> Dict:
|
||||
response = self._delete(API_UTI_STANDARD_OPENAI_DELETE_FILE.format(file_id=file_id))
|
||||
return self._get_response_value(response, as_json=True)
|
||||
@@ -0,0 +1,26 @@
|
||||
from open_chatcaht.api_client import ApiClient
|
||||
from open_chatcaht.types.tools.call_tool_param import CallToolParam
|
||||
|
||||
API_URI_TOOL_CALL = "/tools/call"
|
||||
API_URI_TOOL_LIST = "/tools"
|
||||
|
||||
|
||||
class ToolClient(ApiClient):
|
||||
def list(self) -> dict:
|
||||
"""
|
||||
列出所有工具
|
||||
"""
|
||||
resp = self._get(API_URI_TOOL_LIST)
|
||||
return self._get_response_value(resp, as_json=True, value_func=lambda r: r.get("data", {}))
|
||||
|
||||
def call(
|
||||
self,
|
||||
name: str,
|
||||
tool_input: dict = {},
|
||||
):
|
||||
"""
|
||||
调用工具
|
||||
"""
|
||||
data = CallToolParam(name=name, tool_input=tool_input).dict()
|
||||
resp = self._post(API_URI_TOOL_CALL, json=data)
|
||||
return self._get_response_value(resp, as_json=True, value_func=lambda r: r.get("data"))
|
||||
@@ -0,0 +1,322 @@
|
||||
import contextlib
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
from typing import *
|
||||
|
||||
from open_chatcaht._constants import API_BASE_URI
|
||||
from open_chatcaht.utils import set_httpx_config, get_httpx_client, get_variable, get_function_default_params, \
|
||||
merge_dicts
|
||||
from functools import wraps
|
||||
from typing import Type, get_type_hints
|
||||
|
||||
import httpx
|
||||
import requests
|
||||
from pydantic import BaseModel
|
||||
|
||||
set_httpx_config()
|
||||
|
||||
CHATCHAT_API_BASE = get_variable(os.getenv('CHATCHAT_API_BASE'), 'http://127.0.0.1:8000')
|
||||
CHATCHAT_CLIENT_TIME_OUT = get_variable(os.getenv('CHATCHAT_CLIENT_TIME_OUT'), 60)
|
||||
CHATCHAT_CLIENT_DEFAULT_RETRY_COUNT = get_variable(os.getenv('CHATCHAT_CLIENT_DEFAULT_RETRY'), 3)
|
||||
CHATCHAT_CLIENT_DEFAULT_RETRY_INTERVAL = get_variable(os.getenv('CHATCHAT_CLIENT_DEFAULT_RETRY_INTERVAL'), 60)
|
||||
|
||||
|
||||
class ApiClient:
|
||||
"""
|
||||
api.py调用的封装(同步模式),简化api调用方式
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
base_url: str = API_BASE_URI,
|
||||
timeout: float = 60,
|
||||
use_async: bool = False,
|
||||
use_proxy: bool = False,
|
||||
proxies=None,
|
||||
log_level: int = logging.INFO,
|
||||
retry: int = 3,
|
||||
retry_interval: int = 1,
|
||||
):
|
||||
if proxies is None:
|
||||
proxies = {}
|
||||
self.base_url = get_variable(base_url, CHATCHAT_API_BASE)
|
||||
self.timeout = get_variable(timeout, CHATCHAT_CLIENT_TIME_OUT)
|
||||
self._use_async = use_async
|
||||
self.use_proxy = use_proxy
|
||||
self.default_retry_count = get_variable(retry, CHATCHAT_CLIENT_DEFAULT_RETRY_COUNT)
|
||||
self.default_retry_interval = get_variable(retry_interval, CHATCHAT_CLIENT_DEFAULT_RETRY_INTERVAL)
|
||||
self.proxies = proxies
|
||||
self._client = None
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.logger.setLevel(log_level)
|
||||
|
||||
@property
|
||||
def client(self):
|
||||
if self._client is None or self._client.is_closed:
|
||||
self._client = get_httpx_client(
|
||||
base_url=self.base_url, use_async=self._use_async, timeout=self.timeout
|
||||
)
|
||||
return self._client
|
||||
|
||||
def _get(
|
||||
self,
|
||||
url: str,
|
||||
params: Union[Dict, List[Tuple], bytes] = None,
|
||||
retry: int = 3,
|
||||
stream: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> Union[httpx.Response, Iterator[httpx.Response], None]:
|
||||
while retry > 0:
|
||||
try:
|
||||
if stream:
|
||||
return self.client.stream("GET", url, params=params, **kwargs)
|
||||
else:
|
||||
return self.client.get(url, params=params, **kwargs)
|
||||
except Exception as e:
|
||||
msg = f"error when get {url}: {e}"
|
||||
self.logger.error(f"{e.__class__.__name__}: {msg}")
|
||||
retry -= 1
|
||||
|
||||
def _post(
|
||||
self,
|
||||
url: str,
|
||||
data: Dict = None,
|
||||
json: Dict = None,
|
||||
retry: int = 3,
|
||||
stream: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> Union[httpx.Response, Iterator[httpx.Response], None]:
|
||||
while retry > 0:
|
||||
try:
|
||||
# print(kwargs)
|
||||
if stream:
|
||||
|
||||
return self.client.stream(
|
||||
"POST", url, data=data, json=json, **kwargs
|
||||
)
|
||||
else:
|
||||
self.logger.debug(f"post {url} with data: {data}")
|
||||
return self.client.post(url, data=data, json=json, **kwargs)
|
||||
except Exception as e:
|
||||
msg = f"error when post {url}: {e}"
|
||||
self.logger.error(f"{e.__class__.__name__}: {msg}")
|
||||
retry -= 1
|
||||
|
||||
def _delete(
|
||||
self,
|
||||
url: str,
|
||||
data: Dict = None,
|
||||
json: Dict = None,
|
||||
retry: int = 3,
|
||||
stream: bool = False,
|
||||
**kwargs: Any,
|
||||
) -> Union[httpx.Response, Iterator[httpx.Response], None]:
|
||||
while retry > 0:
|
||||
try:
|
||||
if stream:
|
||||
return self.client.stream(
|
||||
"DELETE", url, data=data, json=json, **kwargs
|
||||
)
|
||||
else:
|
||||
return self.client.delete(url, data=data, json=json, **kwargs)
|
||||
except Exception as e:
|
||||
msg = f"error when delete {url}: {e}"
|
||||
self.logger.error(f"{e.__class__.__name__}: {msg}")
|
||||
retry -= 1
|
||||
|
||||
def _httpx_stream2generator(
|
||||
self,
|
||||
response: contextlib._GeneratorContextManager,
|
||||
as_json: bool = False,
|
||||
):
|
||||
"""
|
||||
将httpx.stream返回的GeneratorContextManager转化为普通生成器
|
||||
"""
|
||||
|
||||
async def ret_async(response, as_json):
|
||||
try:
|
||||
async with response as r:
|
||||
chunk_cache = ""
|
||||
async for chunk in r.aiter_text(None):
|
||||
if not chunk: # fastchat api yield empty bytes on start and end
|
||||
continue
|
||||
if as_json:
|
||||
try:
|
||||
if chunk.startswith("data: "):
|
||||
data = json.loads(chunk_cache + chunk[6:-2])
|
||||
elif chunk.startswith(":"): # skip sse comment line
|
||||
continue
|
||||
else:
|
||||
data = json.loads(chunk_cache + chunk)
|
||||
|
||||
chunk_cache = ""
|
||||
yield data
|
||||
except Exception as e:
|
||||
msg = f"接口返回json错误: ‘{chunk}’。错误信息是:{e}。"
|
||||
self.logger.error(f"{e.__class__.__name__}: {msg}")
|
||||
|
||||
if chunk.startswith("data: "):
|
||||
chunk_cache += chunk[6:-2]
|
||||
elif chunk.startswith(":"): # skip sse comment line
|
||||
continue
|
||||
else:
|
||||
chunk_cache += chunk
|
||||
continue
|
||||
else:
|
||||
# print(chunk, end="", flush=True)
|
||||
yield chunk
|
||||
except httpx.ConnectError as e:
|
||||
msg = f"无法连接API服务器,请确认 ‘api.py’ 已正常启动。({e})"
|
||||
self.logger.error(msg)
|
||||
yield {"code": 500, "msg": msg}
|
||||
except httpx.ReadTimeout as e:
|
||||
msg = f"API通信超时,请确认已启动FastChat与API服务(详见Wiki '5. 启动 API 服务或 Web UI')。({e})"
|
||||
self.logger.error(msg)
|
||||
yield {"code": 500, "msg": msg}
|
||||
except Exception as e:
|
||||
msg = f"API通信遇到错误:{e}"
|
||||
self.logger.error(f"{e.__class__.__name__}: {msg}")
|
||||
yield {"code": 500, "msg": msg}
|
||||
|
||||
def ret_sync(response, as_json):
|
||||
try:
|
||||
with response as r:
|
||||
chunk_cache = ""
|
||||
for chunk in r.iter_text(None):
|
||||
if not chunk: # fastchat api yield empty bytes on start and end
|
||||
continue
|
||||
if as_json:
|
||||
try:
|
||||
if chunk.startswith("data: "):
|
||||
data = json.loads(chunk_cache + chunk[6:-2])
|
||||
elif chunk.startswith(":"): # skip sse comment line
|
||||
continue
|
||||
else:
|
||||
data = json.loads(chunk_cache + chunk)
|
||||
|
||||
chunk_cache = ""
|
||||
yield data
|
||||
except Exception as e:
|
||||
msg = f"接口返回json错误: ‘{chunk}’。错误信息是:{e}。"
|
||||
self.logger.error(f"{e.__class__.__name__}: {msg}")
|
||||
|
||||
if chunk.startswith("data: "):
|
||||
chunk_cache += chunk[6:-2]
|
||||
elif chunk.startswith(":"): # skip sse comment line
|
||||
continue
|
||||
else:
|
||||
chunk_cache += chunk
|
||||
continue
|
||||
else:
|
||||
# print(chunk, end="", flush=True)
|
||||
yield chunk
|
||||
except httpx.ConnectError as e:
|
||||
msg = f"无法连接API服务器,请确认 ‘api.py’ 已正常启动。({e})"
|
||||
self.logger.error(msg)
|
||||
yield {"code": 500, "msg": msg}
|
||||
except httpx.ReadTimeout as e:
|
||||
msg = f"API通信超时,请确认已启动FastChat与API服务(详见Wiki '5. 启动 API 服务或 Web UI')。({e})"
|
||||
self.logger.error(msg)
|
||||
yield {"code": 500, "msg": msg}
|
||||
except Exception as e:
|
||||
msg = f"API通信遇到错误:{e}"
|
||||
self.logger.error(f"{e.__class__.__name__}: {msg}")
|
||||
yield {"code": 500, "msg": msg}
|
||||
|
||||
if self._use_async:
|
||||
return ret_async(response, as_json)
|
||||
else:
|
||||
return ret_sync(response, as_json)
|
||||
|
||||
def _get_response_value(
|
||||
self,
|
||||
response: httpx.Response,
|
||||
as_json: bool = False,
|
||||
value_func: Callable = None,
|
||||
):
|
||||
"""
|
||||
转换同步或异步请求返回的响应
|
||||
`as_json`: 返回json
|
||||
`value_func`: 用户可以自定义返回值,该函数接受response或json
|
||||
"""
|
||||
|
||||
def to_json(r):
|
||||
try:
|
||||
return r.json()
|
||||
except Exception as e:
|
||||
msg = "API未能返回正确的JSON。" + str(e)
|
||||
self.logger.error(f"{e.__class__.__name__}: {msg}")
|
||||
return {"code": 500, "msg": msg, "data": None}
|
||||
|
||||
if value_func is None:
|
||||
value_func = lambda r: r
|
||||
|
||||
async def ret_async(response):
|
||||
if as_json:
|
||||
return value_func(to_json(await response))
|
||||
else:
|
||||
return value_func(await response)
|
||||
|
||||
if self._use_async:
|
||||
return ret_async(response)
|
||||
else:
|
||||
if as_json:
|
||||
return value_func(to_json(response))
|
||||
else:
|
||||
return value_func(response)
|
||||
|
||||
|
||||
def get_request_method(api_client_obj: ApiClient, method):
|
||||
if method is httpx.post:
|
||||
return getattr(api_client_obj, "_post")
|
||||
elif method is httpx.get:
|
||||
return getattr(api_client_obj, "_get")
|
||||
# elif method is httpx.put:
|
||||
# return api_client_obj.put
|
||||
elif method is httpx.delete:
|
||||
return getattr(api_client_obj, "_delete")
|
||||
|
||||
|
||||
def http_request(method):
|
||||
def decorator(url, base_url='', headers=None, body_model: Type[BaseModel] = None, **options):
|
||||
headers = headers or {}
|
||||
|
||||
def wrapper(func):
|
||||
@wraps(func)
|
||||
def inner(*args, **kwargs):
|
||||
try:
|
||||
default_param: dict = get_function_default_params(func)
|
||||
|
||||
api_client_obj: ApiClient = args[0] if len(args) > 0 and isinstance(args[0], ApiClient) else None
|
||||
return_type = get_type_hints(func).get('return')
|
||||
full_url = base_url + url
|
||||
param = merge_dicts(kwargs, default_param)
|
||||
if body_model is not None:
|
||||
param = body_model(**kwargs).dict()
|
||||
# Send the HTTP request
|
||||
response = None
|
||||
if api_client_obj is not None:
|
||||
_method = get_request_method(api_client_obj, method)
|
||||
response = _method(full_url, headers=headers, json=param)
|
||||
else:
|
||||
response = method(full_url, headers=headers, json=param)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.HTTPError as http_err:
|
||||
print(f"HTTP error occurred: {http_err}")
|
||||
except Exception as err:
|
||||
print(f"An error occurred: {err}")
|
||||
|
||||
return inner
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
post = http_request(httpx.post)
|
||||
get = http_request(httpx.get)
|
||||
delete = http_request(httpx.delete)
|
||||
put = http_request(httpx.put)
|
||||
@@ -0,0 +1,43 @@
|
||||
import logging
|
||||
|
||||
from open_chatcaht._constants import API_BASE_URI
|
||||
from open_chatcaht.api.chat.chat_client import ChatClient
|
||||
from open_chatcaht.api.knowledge_base.knowledge_base_client import KbClient
|
||||
from open_chatcaht.api.server.server_client import ServerClient
|
||||
from open_chatcaht.api.standard_openai.standard_openai_client import StandardOpenaiClient
|
||||
from open_chatcaht.api.tools.tool_client import ToolClient
|
||||
|
||||
|
||||
class ChatChat:
|
||||
knowledge: KbClient = None
|
||||
tool: ToolClient = None
|
||||
server: ServerClient = None
|
||||
chat: ChatClient = None
|
||||
openai_adapter: StandardOpenaiClient = None
|
||||
|
||||
def __init__(self,
|
||||
base_url: str = API_BASE_URI,
|
||||
timeout: float = 60,
|
||||
use_async: bool = False,
|
||||
use_proxy: bool = False,
|
||||
proxies=None,
|
||||
log_level: int = logging.INFO,
|
||||
retry: int = 3,
|
||||
retry_interval: int = 1, ):
|
||||
param = {
|
||||
'log_level': log_level,
|
||||
'retry': retry,
|
||||
'retry_interval': retry_interval,
|
||||
'base_url': base_url,
|
||||
'timeout': timeout,
|
||||
'use_async': use_async,
|
||||
'use_proxy': use_proxy,
|
||||
'proxies': proxies
|
||||
}
|
||||
|
||||
self.knowledge = KbClient(**param)
|
||||
self.tool = ToolClient(**param)
|
||||
self.server = ServerClient(**param)
|
||||
self.chat = ChatClient(**param)
|
||||
self.openai_adapter = StandardOpenaiClient(**param)
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from open_chatcaht.utils import is_dict
|
||||
from typing import Any, Optional, cast
|
||||
from typing_extensions import Literal
|
||||
|
||||
import httpx
|
||||
|
||||
__all__ = [
|
||||
"BadRequestError",
|
||||
"AuthenticationError",
|
||||
"PermissionDeniedError",
|
||||
"NotFoundError",
|
||||
"ConflictError",
|
||||
"UnprocessableEntityError",
|
||||
"RateLimitError",
|
||||
"InternalServerError",
|
||||
]
|
||||
|
||||
|
||||
class ChatChatError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class APIError(ChatChatError):
|
||||
message: str
|
||||
request: httpx.Request
|
||||
|
||||
body: object | None
|
||||
"""
|
||||
API响应体。
|
||||
如果API响应了一个有效的JSON结构,那么这个属性将是
|
||||
解码结果。
|
||||
如果它不是一个有效的JSON结构,那么这将是原始响应。
|
||||
如果没有与此错误相关的响应,那么它将是' None '。
|
||||
"""
|
||||
|
||||
code: Optional[str] = None
|
||||
param: Optional[str] = None
|
||||
type: Optional[str]
|
||||
|
||||
def __init__(self, message: str, request: httpx.Request, *, body: object | None) -> None:
|
||||
super().__init__(message)
|
||||
self.request = request
|
||||
self.message = message
|
||||
self.body = body
|
||||
|
||||
if is_dict(body):
|
||||
self.code = cast(str, body.get("code"))
|
||||
self.param = cast(str, body.get("param"))
|
||||
self.type = cast(str, body.get("type"))
|
||||
else:
|
||||
self.code = None
|
||||
self.param = None
|
||||
self.type = None
|
||||
|
||||
|
||||
class APIResponseValidationError(APIError):
|
||||
response: httpx.Response
|
||||
status_code: int
|
||||
|
||||
def __init__(self, response: httpx.Response, body: object | None, *, message: str | None = None) -> None:
|
||||
super().__init__(message or "API返回的数据对预期的模式无效。", response.request, body=body)
|
||||
self.response = response
|
||||
self.status_code = response.status_code
|
||||
|
||||
|
||||
class APIStatusError(APIError):
|
||||
"""当API响应的状态码为4xx或5xx时引发。"""
|
||||
|
||||
response: httpx.Response
|
||||
status_code: int
|
||||
request_id: str | None
|
||||
|
||||
def __init__(self, message: str, *, response: httpx.Response, body: object | None) -> None:
|
||||
super().__init__(message, response.request, body=body)
|
||||
self.response = response
|
||||
self.status_code = response.status_code
|
||||
self.request_id = response.headers.get("x-request-id")
|
||||
|
||||
|
||||
class APIConnectionError(APIError):
|
||||
def __init__(self, *, message: str = "连接错误", request: httpx.Request) -> None:
|
||||
super().__init__(message, request, body=None)
|
||||
|
||||
|
||||
class APITimeoutError(APIConnectionError):
|
||||
def __init__(self, request: httpx.Request) -> None:
|
||||
super().__init__(message="请求超时", request=request)
|
||||
|
||||
|
||||
class BadRequestError(APIStatusError):
|
||||
status_code: Literal[400] = 400
|
||||
|
||||
|
||||
class AuthenticationError(APIStatusError):
|
||||
status_code: Literal[401] = 401
|
||||
|
||||
|
||||
class PermissionDeniedError(APIStatusError):
|
||||
status_code: Literal[403] = 403
|
||||
|
||||
|
||||
class NotFoundError(APIStatusError):
|
||||
status_code: Literal[404] = 404
|
||||
|
||||
|
||||
class ConflictError(APIStatusError):
|
||||
status_code: Literal[409] = 409
|
||||
|
||||
|
||||
class UnprocessableEntityError(APIStatusError):
|
||||
status_code: Literal[422] = 422
|
||||
|
||||
|
||||
class RateLimitError(APIStatusError):
|
||||
status_code: Literal[429] = 429
|
||||
|
||||
|
||||
class InternalServerError(APIStatusError):
|
||||
pass
|
||||
@@ -0,0 +1,5 @@
|
||||
from chatchat.server.api.api_schemas import OpenAIChatInput
|
||||
|
||||
|
||||
class ChatCompletions(OpenAIChatInput):
|
||||
...
|
||||
@@ -0,0 +1,7 @@
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
|
||||
class ChatFeedbackParam(BaseModel):
|
||||
message_id: str = Field("", max_length=32, description="聊天记录id"),
|
||||
score: int = Field(0, max=100, description="用户评分,满分100,越大表示评价越高"),
|
||||
reason: str = Field("", description="用户评分理由,比如不符合事实等"),
|
||||
@@ -0,0 +1,6 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ChatMessage(BaseModel):
|
||||
role: str = Field(...)
|
||||
content: str = Field(...)
|
||||
@@ -0,0 +1,40 @@
|
||||
from typing import Optional, List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from open_chatcaht._constants import VECTOR_SEARCH_TOP_K, SCORE_THRESHOLD
|
||||
from open_chatcaht.types.chat.chat_message import ChatMessage
|
||||
|
||||
|
||||
class FileChatParam(BaseModel):
|
||||
"""文件对话类"""
|
||||
|
||||
query: str = Field(..., description="用户输入", examples=["你好"]),
|
||||
knowledge_id: str = Field(..., description="临时知识库ID"),
|
||||
top_k: int = Field(VECTOR_SEARCH_TOP_K, description="匹配向量数"),
|
||||
score_threshold: float = Field(
|
||||
SCORE_THRESHOLD,
|
||||
description="知识库匹配相关度阈值,取值范围在0-1之间,SCORE越小,相关度越高,取到1相当于不筛选,建议设置在0.5左右",
|
||||
ge=0,
|
||||
le=2,
|
||||
),
|
||||
history: List[ChatMessage] = Field(
|
||||
[],
|
||||
description="历史对话",
|
||||
examples=[
|
||||
[
|
||||
{"role": "user", "content": "我们来玩成语接龙,我先来,生龙活虎"},
|
||||
{"role": "assistant", "content": "虎头虎脑"},
|
||||
]
|
||||
],
|
||||
),
|
||||
stream: bool = Field(False, description="流式输出"),
|
||||
model_name: str = Field(None, description="LLM 模型名称。"),
|
||||
temperature: float = Field(0.01, description="LLM 采样温度", ge=0.0, le=1.0),
|
||||
max_tokens: Optional[int] = Field(
|
||||
None, description="限制LLM生成Token数量,默认None代表模型最大值"
|
||||
),
|
||||
prompt_name: str = Field(
|
||||
"default",
|
||||
description="使用的prompt模板名称(在 prompt_settings.yaml 中配置)",
|
||||
),
|
||||
@@ -0,0 +1,42 @@
|
||||
from typing import Optional, List, Literal
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from open_chatcaht._constants import MAX_TOKENS, TEMPERATURE, SCORE_THRESHOLD, VECTOR_SEARCH_TOP_K, LLM_MODEL
|
||||
from open_chatcaht.types.chat.chat_message import ChatMessage
|
||||
|
||||
|
||||
class KbChatParam(BaseModel):
|
||||
query: str = Field(..., description="用户输入", examples=["你好"]),
|
||||
mode: Literal["local_kb", "temp_kb", "search_engine"] = Field("local_kb", description="知识来源"),
|
||||
kb_name: str = Field("",
|
||||
description="mode=local_kb时为知识库名称;temp_kb时为临时知识库ID,search_engine时为搜索引擎名称",
|
||||
examples=["samples"]),
|
||||
top_k: int = Field(VECTOR_SEARCH_TOP_K, description="匹配向量数"),
|
||||
score_threshold: float = Field(
|
||||
SCORE_THRESHOLD,
|
||||
description="知识库匹配相关度阈值,取值范围在0-1之间,SCORE越小,相关度越高,取到1相当于不筛选,建议设置在0.5左右",
|
||||
ge=0,
|
||||
le=2,
|
||||
),
|
||||
history: List[ChatMessage] = Field(
|
||||
[],
|
||||
description="历史对话",
|
||||
examples=[[
|
||||
{"role": "user",
|
||||
"content": "我们来玩成语接龙,我先来,生龙活虎"},
|
||||
{"role": "assistant",
|
||||
"content": "虎头虎脑"}]]
|
||||
),
|
||||
stream: bool = Field(True, description="流式输出"),
|
||||
model: str = Field(LLM_MODEL, description="LLM 模型名称。"),
|
||||
temperature: float = Field(TEMPERATURE, description="LLM 采样温度", ge=0.0, le=2.0),
|
||||
max_tokens: Optional[int] = Field(
|
||||
MAX_TOKENS,
|
||||
description="限制LLM生成Token数量,默认None代表模型最大值"
|
||||
),
|
||||
prompt_name: str = Field(
|
||||
"default",
|
||||
description="使用的prompt模板名称(在prompt_settings.yaml中配置)"
|
||||
),
|
||||
return_direct: bool = Field(False, description="直接返回检索结果,不送入 LLM"),
|
||||
@@ -0,0 +1,11 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
|
||||
class CreateKnowledgeBaseParam(BaseModel):
|
||||
knowledge_base_name: str = Field(default=None, description="知识库名称")
|
||||
vector_store_type: str = Field(default=None, description="向量存储类型")
|
||||
kb_info: Optional[str] = Field(default=None, description="知识库信息")
|
||||
vs_type: Optional[str] = Field(default=None, description="向量库类型")
|
||||
embed_model: Optional[str] = Field(default=None, description="向量化模型")
|
||||
@@ -0,0 +1,5 @@
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
|
||||
class DeleteKnowledgeBaseParam(BaseModel):
|
||||
knowledge_base_name: str = Field(..., description="知识库名称")
|
||||
@@ -0,0 +1,10 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class DeleteKbDocsParam(BaseModel):
|
||||
knowledge_base_name: str = Field(..., examples=["samples"]),
|
||||
file_names: List[str] = Field(..., examples=[["file_name.md", "test.txt"]]),
|
||||
delete_content: bool = Field(False),
|
||||
not_refresh_vs_cache: bool = Field(False, description="暂不保存向量库(用于FAISS)"),
|
||||
@@ -0,0 +1,11 @@
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class DownloadKbDocParam(BaseModel):
|
||||
knowledge_base_name: str = Field(
|
||||
..., description="知识库名称", examples=["samples"]
|
||||
),
|
||||
file_name: str = Field(..., description="文件名称", examples=["test.txt"]),
|
||||
preview: bool = Field(False, description="是:浏览器内预览;否:下载"),
|
||||
@@ -0,0 +1,5 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class ListKbDocsFileParam(BaseModel):
|
||||
knowledge_base_name: str = Field(description="知识库名称")
|
||||
@@ -0,0 +1,17 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from open_chatcaht._constants import VECTOR_SEARCH_TOP_K, SCORE_THRESHOLD
|
||||
|
||||
|
||||
class SearchKbDocsParam(BaseModel):
|
||||
query: str = Field(description="检索内容")
|
||||
knowledge_base_name: str = Field(description="知识库名称")
|
||||
top_k: int = Field(default=VECTOR_SEARCH_TOP_K, description="匹配向量数")
|
||||
score_threshold: float = Field(default=SCORE_THRESHOLD,
|
||||
ge=0.0,
|
||||
le=1.0,
|
||||
description="知识库匹配相关度阈值,取值范围在0-1之间,"
|
||||
"SCORE越小,相关度越高,"
|
||||
"取到1相当于不筛选,建议设置在0.5左右")
|
||||
file_name: str = Field("", description="文件名称,支持 sql 通配符"),
|
||||
metadata: dict = Field({}, description="根据 metadata 进行过滤,仅支持一级键"),
|
||||
@@ -0,0 +1,15 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from open_chatcaht._constants import VECTOR_SEARCH_TOP_K, SCORE_THRESHOLD
|
||||
|
||||
|
||||
class SearchTempDocsParam(BaseModel):
|
||||
knowledge_id: str
|
||||
query: str
|
||||
top_k: int = Field(default=VECTOR_SEARCH_TOP_K, description="匹配向量数")
|
||||
score_threshold: float = Field(default=SCORE_THRESHOLD,
|
||||
ge=0.0,
|
||||
le=1.0,
|
||||
description="知识库匹配相关度阈值,取值范围在0-1之间,"
|
||||
"SCORE越小,相关度越高,"
|
||||
"取到1相当于不筛选,建议设置在0.5左右")
|
||||
@@ -0,0 +1,15 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from open_chatcaht._constants import CHUNK_SIZE, OVERLAP_SIZE, ZH_TITLE_ENHANCE
|
||||
|
||||
|
||||
class UploadKbDocsParam(BaseModel):
|
||||
knowledge_base_name: str = Field(
|
||||
..., description="知识库名称", examples=["samples"]
|
||||
),
|
||||
override: bool = Field(False, description="覆盖已有文件"),
|
||||
to_vector_store: bool = Field(True, description="上传文件后是否进行向量化"),
|
||||
chunk_size: int = Field(CHUNK_SIZE, description="知识库中单段文本最大长度"),
|
||||
chunk_overlap: int = Field(OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
|
||||
zh_title_enhance: bool = Field(ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
|
||||
docs: str = Field("", description="自定义的docs,需要转为json字符串"),
|
||||
not_refresh_vs_cache: bool = Field(False, description="暂不保存向量库(用于FAISS)"),
|
||||
@@ -0,0 +1,11 @@
|
||||
from typing import Union, List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from open_chatcaht._constants import CHUNK_SIZE, OVERLAP_SIZE, ZH_TITLE_ENHANCE
|
||||
|
||||
|
||||
class UploadTempDocsParam(BaseModel):
|
||||
prev_id: str = Field(None, description="前知识库ID"),
|
||||
chunk_size: int = Field(CHUNK_SIZE, description="知识库中单段文本最大长度"),
|
||||
chunk_overlap: int = Field(OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
|
||||
zh_title_enhance: bool = Field(ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
|
||||
@@ -0,0 +1,14 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class KnowledgeBaseInfo(BaseModel):
|
||||
id: int = Field(default=None, description="知识库id")
|
||||
kb_name: str = Field(default=None, description="知识库名称")
|
||||
kb_info: Optional[str] = Field(default=None, description="知识库信息")
|
||||
vs_type: Optional[str] = Field(default=None, description="向量库类型")
|
||||
embed_model: Optional[str] = Field(default=None, description="向量化模型")
|
||||
file_count: Optional[int] = Field(default=None, description="文件数量")
|
||||
create_time: Optional[datetime] = Field(default=None, description="创建时间")
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from open_chatcaht._constants import VS_TYPE, EMBEDDING_MODEL, CHUNK_SIZE, OVERLAP_SIZE, ZH_TITLE_ENHANCE
|
||||
|
||||
|
||||
class RecreateVectorStoreParam(BaseModel):
|
||||
knowledge_base_name: str = Field(..., examples=["samples"], description='知识库名称'),
|
||||
allow_empty_kb: bool = Field(True),
|
||||
vs_type: str = Field(VS_TYPE, description='向量库类型'),
|
||||
embed_model: str = Field(EMBEDDING_MODEL, description="向量模型"),
|
||||
chunk_size: int = Field(CHUNK_SIZE, description="知识库中单段文本最大长度"),
|
||||
chunk_overlap: int = Field(OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
|
||||
zh_title_enhance: bool = Field(ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
|
||||
not_refresh_vs_cache: bool = Field(False, description="暂不保存向量库(用于FAISS)")
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
from open_chatcaht._constants import VS_TYPE, EMBEDDING_MODEL
|
||||
|
||||
|
||||
class RecreateSummaryVectorStoreParam(BaseModel):
|
||||
knowledge_base_name: str = Field(..., examples=["samples"]),
|
||||
allow_empty_kb: bool = Field(True),
|
||||
vs_type: str = Field(VS_TYPE),
|
||||
embed_model: str = Field(EMBEDDING_MODEL),
|
||||
file_description: str = Field(""),
|
||||
model_name: str = Field(None, description="LLM 模型名称。"),
|
||||
temperature: float = Field(0.01, description="LLM 采样温度", ge=0.0, le=1.0),
|
||||
max_tokens: Optional[int] = Field(
|
||||
None, description="限制LLM生成Token数量,默认None代表模型最大值"
|
||||
),
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
from typing import Optional, List
|
||||
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
from open_chatcaht._constants import VS_TYPE, EMBEDDING_MODEL
|
||||
|
||||
|
||||
class SummaryDocIdsToVectorStoreParam(BaseModel):
|
||||
knowledge_base_name: str = Field(..., examples=["samples"]),
|
||||
doc_ids: List = Field([], examples=[["uuid"]]),
|
||||
vs_type: str = Field(VS_TYPE),
|
||||
embed_model: str = Field(EMBEDDING_MODEL),
|
||||
file_description: str = Field(""),
|
||||
model_name: str = Field(None, description="LLM 模型名称。"),
|
||||
temperature: float = Field(0.01, description="LLM 采样温度", ge=0.0, le=1.0),
|
||||
max_tokens: Optional[int] = Field(
|
||||
None, description="限制LLM生成Token数量,默认None代表模型最大值"
|
||||
),
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
from open_chatcaht._constants import VS_TYPE, EMBEDDING_MODEL
|
||||
|
||||
|
||||
class SummaryFileToVectorStoreParam(BaseModel):
|
||||
knowledge_base_name: str = Field(..., examples=["samples"]),
|
||||
file_name: str = Field(..., examples=["test.pdf"]),
|
||||
allow_empty_kb: bool = Field(True),
|
||||
vs_type: str = Field(VS_TYPE),
|
||||
embed_model: str = Field(EMBEDDING_MODEL),
|
||||
file_description: str = Field(""),
|
||||
model_name: str = Field(None, description="LLM 模型名称。"),
|
||||
temperature: float = Field(0.01, description="LLM 采样温度", ge=0.0, le=1.0),
|
||||
max_tokens: Optional[int] = Field(
|
||||
None, description="限制LLM生成Token数量,默认None代表模型最大值"
|
||||
),
|
||||
@@ -0,0 +1,8 @@
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class UpdateKbInfoParam(BaseModel):
|
||||
knowledge_base_name: str = Field(
|
||||
..., description="知识库名称", examples=["samples"]
|
||||
),
|
||||
kb_info: str = Field(..., description="知识库介绍", examples=["这是一个知识库"]),
|
||||
@@ -0,0 +1,30 @@
|
||||
from typing import Any, List
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class BaseResponse(BaseModel):
|
||||
code: int = Field(200, description="API status code")
|
||||
msg: str = Field("success", description="API status message")
|
||||
data: Any = Field(None, description="API data")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class ListResponse(BaseResponse):
|
||||
data: List[Any] = Field(..., description="List of data")
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
"data": ["doc1.docx", "doc2.pdf", "doc3.txt"],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
from typing import Optional, Literal
|
||||
|
||||
from open_chatcaht.types.standard_openai.base import OpenAIBaseInput
|
||||
|
||||
|
||||
class OpenAIAudioSpeechInput(OpenAIBaseInput):
|
||||
input: str
|
||||
model: str
|
||||
voice: str
|
||||
response_format: Optional[
|
||||
Literal["mp3", "opus", "aac", "flac", "pcm", "wav"]
|
||||
] = None
|
||||
speed: Optional[float] = None
|
||||
@@ -0,0 +1,8 @@
|
||||
from typing import Optional, List, Literal
|
||||
|
||||
from open_chatcaht.types.standard_openai.audio_translations_input import OpenAIAudioTranslationsInput
|
||||
|
||||
|
||||
class OpenAIAudioTranscriptionsInput(OpenAIAudioTranslationsInput):
|
||||
language: Optional[str] = None
|
||||
timestamp_granularities: Optional[List[Literal["word", "segment"]]] = None
|
||||
@@ -0,0 +1,14 @@
|
||||
from typing import Union, Optional, Any
|
||||
|
||||
from pydantic import AnyUrl
|
||||
|
||||
from open_chatcaht._constants import TEMPERATURE
|
||||
from open_chatcaht.types.standard_openai.base import OpenAIBaseInput
|
||||
|
||||
|
||||
class OpenAIAudioTranslationsInput(OpenAIBaseInput):
|
||||
file: Union[Any, AnyUrl]
|
||||
model: str
|
||||
prompt: Optional[str] = None
|
||||
response_format: Optional[str] = None
|
||||
temperature: float = TEMPERATURE
|
||||
@@ -0,0 +1,16 @@
|
||||
from typing import Optional, Dict
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class OpenAIBaseInput(BaseModel):
|
||||
user: Optional[str] = None
|
||||
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
|
||||
# The extra values given here take precedence over values defined on the client or passed to this method.
|
||||
extra_headers: Optional[Dict] = None
|
||||
extra_query: Optional[Dict] = None
|
||||
extra_json: Optional[Dict] = Field(None, alias="extra_body")
|
||||
timeout: Optional[float] = None
|
||||
|
||||
class Config:
|
||||
extra = "allow"
|
||||
@@ -0,0 +1,32 @@
|
||||
from typing import List, Optional, Dict, Union
|
||||
|
||||
from open_chatcaht._constants import LLM_MODEL, TEMPERATURE
|
||||
from open_chatcaht.types.standard_openai.base import OpenAIBaseInput
|
||||
from openai.types.chat import (
|
||||
ChatCompletionMessageParam,
|
||||
ChatCompletionToolChoiceOptionParam,
|
||||
ChatCompletionToolParam,
|
||||
completion_create_params,
|
||||
)
|
||||
|
||||
|
||||
class OpenAIChatInput(OpenAIBaseInput):
|
||||
messages: List[Union[Dict, ChatCompletionMessageParam]] = []
|
||||
model: str = LLM_MODEL
|
||||
frequency_penalty: Optional[float] = None
|
||||
function_call: Optional[completion_create_params.FunctionCall] = []
|
||||
functions: List[completion_create_params.Function] = None
|
||||
logit_bias: Optional[Dict[str, int]] = None
|
||||
logprobs: Optional[bool] = None
|
||||
max_tokens: Optional[int] = None
|
||||
n: Optional[int] = None
|
||||
presence_penalty: Optional[float] = None
|
||||
response_format: completion_create_params.ResponseFormat = None
|
||||
seed: Optional[int] = None
|
||||
stop: Union[Optional[str], List[str]] = None
|
||||
stream: Optional[bool] = True
|
||||
temperature: Optional[float] = TEMPERATURE
|
||||
tool_choice: Optional[Union[ChatCompletionToolChoiceOptionParam, str]] = None
|
||||
tools: List[Union[ChatCompletionToolParam, str]] = None
|
||||
top_logprobs: Optional[int] = None
|
||||
top_p: Optional[float] = None
|
||||
@@ -0,0 +1,10 @@
|
||||
from typing import Union, List, Optional, Literal
|
||||
|
||||
from open_chatcaht.types.standard_openai.base import OpenAIBaseInput
|
||||
|
||||
|
||||
class OpenAIEmbeddingsInput(OpenAIBaseInput):
|
||||
input: Union[str, List[str]]
|
||||
model: str
|
||||
dimensions: Optional[int] = None
|
||||
encoding_format: Optional[Literal["float", "base64"]] = None
|
||||
@@ -0,0 +1,12 @@
|
||||
from typing import Optional, Literal
|
||||
|
||||
from open_chatcaht.types.standard_openai.base import OpenAIBaseInput
|
||||
|
||||
|
||||
class OpenAIImageBaseInput(OpenAIBaseInput):
|
||||
model: str
|
||||
n: int = 1
|
||||
response_format: Optional[Literal["url", "b64_json"]] = None
|
||||
size: Optional[
|
||||
Literal["256x256", "512x512", "1024x1024", "1792x1024", "1024x1792"]
|
||||
] = "256x256"
|
||||
@@ -0,0 +1,10 @@
|
||||
from typing import Any, Union
|
||||
|
||||
from pydantic import AnyUrl
|
||||
|
||||
from open_chatcaht.types.standard_openai.image_variations_input import OpenAIImageVariationsInput
|
||||
|
||||
|
||||
class OpenAIImageEditsInput(OpenAIImageVariationsInput):
|
||||
prompt: str
|
||||
mask: Union[Any, AnyUrl]
|
||||
@@ -0,0 +1,9 @@
|
||||
from typing import Literal, Optional
|
||||
|
||||
from open_chatcaht.types.standard_openai.image_base_input import OpenAIImageBaseInput
|
||||
|
||||
|
||||
class OpenAIImageGenerationsInput(OpenAIImageBaseInput):
|
||||
prompt: str
|
||||
quality: Literal["standard", "hd"] = None
|
||||
style: Optional[Literal["vivid", "natural"]] = None
|
||||
@@ -0,0 +1,9 @@
|
||||
from typing import Union, Any
|
||||
|
||||
from pydantic import AnyUrl
|
||||
|
||||
from open_chatcaht.types.standard_openai.image_base_input import OpenAIImageBaseInput
|
||||
|
||||
|
||||
class OpenAIImageVariationsInput(OpenAIImageBaseInput):
|
||||
image: Union[Any, AnyUrl]
|
||||
@@ -0,0 +1,6 @@
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
|
||||
class CallToolParam(BaseModel):
|
||||
name: str = Field(..., description="工具名称")
|
||||
tool_input: dict = Field({}, description="知识库信息"),
|
||||
@@ -0,0 +1,254 @@
|
||||
import base64
|
||||
import inspect
|
||||
import os
|
||||
from io import BytesIO
|
||||
from pathlib import Path
|
||||
from typing import Union, List, Dict
|
||||
|
||||
import httpx
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import TypeGuard
|
||||
|
||||
from open_chatcaht._constants import HTTPX_TIMEOUT
|
||||
|
||||
|
||||
def get_httpx_client(
|
||||
use_async: bool = False,
|
||||
proxies: Union[str, Dict] = None,
|
||||
timeout: float = HTTPX_TIMEOUT,
|
||||
unused_proxies: List[str] = [],
|
||||
**kwargs,
|
||||
) -> Union[httpx.Client, httpx.AsyncClient]:
|
||||
"""
|
||||
helper to get httpx client with default proxies that bypass local addesses.
|
||||
"""
|
||||
default_proxies = {
|
||||
# do not use proxy for locahost
|
||||
"all://127.0.0.1": None,
|
||||
"all://localhost": None,
|
||||
}
|
||||
# do not use proxy for user deployed fastchat servers
|
||||
for x in unused_proxies:
|
||||
host = ":".join(x.split(":")[:2])
|
||||
default_proxies.update({host: None})
|
||||
|
||||
# get proxies from system envionrent
|
||||
# proxy not str empty string, None, False, 0, [] or {}
|
||||
default_proxies.update(
|
||||
{
|
||||
"http://": (
|
||||
os.environ.get("http_proxy")
|
||||
if os.environ.get("http_proxy")
|
||||
and len(os.environ.get("http_proxy").strip())
|
||||
else None
|
||||
),
|
||||
"https://": (
|
||||
os.environ.get("https_proxy")
|
||||
if os.environ.get("https_proxy")
|
||||
and len(os.environ.get("https_proxy").strip())
|
||||
else None
|
||||
),
|
||||
"all://": (
|
||||
os.environ.get("all_proxy")
|
||||
if os.environ.get("all_proxy")
|
||||
and len(os.environ.get("all_proxy").strip())
|
||||
else None
|
||||
),
|
||||
}
|
||||
)
|
||||
for host in os.environ.get("no_proxy", "").split(","):
|
||||
if host := host.strip():
|
||||
# default_proxies.update({host: None}) # Origin code
|
||||
default_proxies.update(
|
||||
{"all://" + host: None}
|
||||
) # PR 1838 fix, if not add 'all://', httpx will raise error
|
||||
|
||||
# merge default proxies with user provided proxies
|
||||
if isinstance(proxies, str):
|
||||
proxies = {"all://": proxies}
|
||||
|
||||
if isinstance(proxies, dict):
|
||||
default_proxies.update(proxies)
|
||||
|
||||
# construct Client
|
||||
kwargs.update(timeout=timeout, proxies=default_proxies)
|
||||
|
||||
if use_async:
|
||||
return httpx.AsyncClient(**kwargs)
|
||||
else:
|
||||
return httpx.Client(**kwargs)
|
||||
|
||||
|
||||
def set_httpx_config(
|
||||
timeout: float = HTTPX_TIMEOUT,
|
||||
proxy: Union[str, Dict] = None,
|
||||
unused_proxies: List[str] = [],
|
||||
):
|
||||
"""
|
||||
设置httpx默认timeout。httpx默认timeout是5秒,在请求LLM回答时不够用。
|
||||
将本项目相关服务加入无代理列表,避免fastchat的服务器请求错误。(windows下无效)
|
||||
对于chatgpt等在线API,如要使用代理需要手动配置。搜索引擎的代理如何处置还需考虑。
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import httpx
|
||||
|
||||
httpx._config.DEFAULT_TIMEOUT_CONFIG.connect = timeout
|
||||
httpx._config.DEFAULT_TIMEOUT_CONFIG.read = timeout
|
||||
httpx._config.DEFAULT_TIMEOUT_CONFIG.write = timeout
|
||||
|
||||
# 在进程范围内设置系统级代理
|
||||
proxies = {}
|
||||
if isinstance(proxy, str):
|
||||
for n in ["http", "https", "all"]:
|
||||
proxies[n + "_proxy"] = proxy
|
||||
elif isinstance(proxy, dict):
|
||||
for n in ["http", "https", "all"]:
|
||||
if p := proxy.get(n):
|
||||
proxies[n + "_proxy"] = p
|
||||
elif p := proxy.get(n + "_proxy"):
|
||||
proxies[n + "_proxy"] = p
|
||||
|
||||
for k, v in proxies.items():
|
||||
os.environ[k] = v
|
||||
|
||||
# set host to bypass proxy
|
||||
no_proxy = [
|
||||
x.strip() for x in os.environ.get("no_proxy", "").split(",") if x.strip()
|
||||
]
|
||||
no_proxy += [
|
||||
# do not use proxy for locahost
|
||||
"http://127.0.0.1",
|
||||
"http://localhost",
|
||||
]
|
||||
# do not use proxy for user deployed fastchat servers
|
||||
for x in unused_proxies:
|
||||
host = ":".join(x.split(":")[:2])
|
||||
if host not in no_proxy:
|
||||
no_proxy.append(host)
|
||||
os.environ["NO_PROXY"] = ",".join(no_proxy)
|
||||
|
||||
def _get_proxies():
|
||||
return proxies
|
||||
|
||||
import urllib.request
|
||||
|
||||
urllib.request.getproxies = _get_proxies
|
||||
|
||||
|
||||
def get_img_base64(file_path: str) -> str:
|
||||
"""
|
||||
get_img_base64 used in streamlit.
|
||||
"""
|
||||
image = file_path
|
||||
# 读取图片
|
||||
with open(image, "rb") as f:
|
||||
buffer = BytesIO(f.read())
|
||||
base_str = base64.b64encode(buffer.getvalue()).decode()
|
||||
return f"data:image/png;base64,{base_str}"
|
||||
|
||||
|
||||
def check_success_msg(data: Union[str, dict, list], key: str = "msg") -> str:
|
||||
"""
|
||||
return error message if error occured when requests API
|
||||
"""
|
||||
if (
|
||||
isinstance(data, dict)
|
||||
and key in data
|
||||
and "code" in data
|
||||
and data["code"] == 200
|
||||
):
|
||||
return data[key]
|
||||
return ""
|
||||
|
||||
|
||||
def check_error_msg(data: Union[str, dict, list], key: str = "errorMsg") -> str:
|
||||
"""
|
||||
return error message if error occured when requests API
|
||||
"""
|
||||
if isinstance(data, dict):
|
||||
if key in data:
|
||||
return data[key]
|
||||
if "code" in data and data["code"] != 200:
|
||||
return data["msg"]
|
||||
return ""
|
||||
|
||||
|
||||
def get_variable(*args):
|
||||
for var in args:
|
||||
if var:
|
||||
return var
|
||||
return None
|
||||
|
||||
|
||||
def is_dict(obj: object) -> TypeGuard[dict[object, object]]:
|
||||
return isinstance(obj, dict)
|
||||
|
||||
|
||||
def model_to_dict(model: BaseModel) -> dict[str, object]:
|
||||
return model.dict()
|
||||
|
||||
|
||||
def get_function_default_params(func) -> dict:
|
||||
"""
|
||||
获取函数的参数及其默认值。
|
||||
|
||||
参数:
|
||||
func (function): 要分析的函数。
|
||||
|
||||
返回:
|
||||
dict: 一个包含参数名称及其默认值的字典。
|
||||
"""
|
||||
signature = inspect.signature(func)
|
||||
params = signature.parameters
|
||||
params_dict = {}
|
||||
|
||||
for param_name, param in params.items():
|
||||
if param.default is inspect.Parameter.empty:
|
||||
params_dict[param_name] = None
|
||||
else:
|
||||
params_dict[param_name] = param.default
|
||||
|
||||
return params_dict
|
||||
|
||||
|
||||
def merge_dicts(dict1, dict2) -> dict:
|
||||
"""
|
||||
合并两个字典,优先使用第一个字典中的非空值。
|
||||
|
||||
参数:
|
||||
dict1 (dict): 第一个字典。
|
||||
dict2 (dict): 第二个字典。
|
||||
|
||||
返回:
|
||||
dict: 合并后的字典。
|
||||
"""
|
||||
merged_dict = {}
|
||||
|
||||
# 遍历两个字典的键集合
|
||||
all_keys = set(dict1.keys()).union(set(dict2.keys()))
|
||||
|
||||
for key in all_keys:
|
||||
value1 = dict1.get(key)
|
||||
value2 = dict2.get(key)
|
||||
|
||||
# 如果第一个字典中的值不为空,使用第一个字典的值
|
||||
if value1:
|
||||
merged_dict[key] = value1
|
||||
else:
|
||||
# 否则使用第二个字典中的值
|
||||
merged_dict[key] = value2
|
||||
|
||||
return merged_dict
|
||||
|
||||
|
||||
def convert_file(file, filename=None):
|
||||
if isinstance(file, bytes): # raw bytes
|
||||
file = BytesIO(file)
|
||||
elif hasattr(file, "read"): # a file io like object
|
||||
filename = filename or file.name
|
||||
else: # a local path
|
||||
file = Path(file).absolute().open("rb")
|
||||
filename = filename or os.path.split(file.name)[-1]
|
||||
return filename, file
|
||||
@@ -0,0 +1,3 @@
|
||||
from ._version import __version__
|
||||
|
||||
VERSION: str = __version__
|
||||
@@ -0,0 +1,16 @@
|
||||
[tool.poetry]
|
||||
name = "open_langchain_chatchat"
|
||||
version = "0.3.0.20240708"
|
||||
description = "Langchain-Chatchat sdk"
|
||||
authors = ["chatchat"]
|
||||
packages = [
|
||||
{include = "open_chatcaht"}
|
||||
]
|
||||
|
||||
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = ">=3.8.1,<3.12,!=3.9.7"
|
||||
pydantic = "^2.8.2"
|
||||
openai = "^1.35.13"
|
||||
httpx = "^0.27.0"
|
||||
@@ -0,0 +1,12 @@
|
||||
from open_chatcaht.chatchat_api import ChatChat
|
||||
|
||||
# todo 之后改为标准测试
|
||||
# chatchat = ChatChat()
|
||||
# for data in chatchat.chat.kb_chat(query='你好', kb_name="example_kb", model='glm-4'):
|
||||
# print(data)
|
||||
# for data in chatchat.chat.kb_chat(query='你好', kb_name="example_kb", model='glm-4'):
|
||||
# print(data)
|
||||
#
|
||||
# for data in chatchat.chat.file_chat(query='你好', knowledge_id="16d57480d9654104b405648f54d2485e", model_name='glm-4'):
|
||||
# print(data)
|
||||
# print(chatchat.chat.chat_feedback(message_id='a9bb673176cd4e34a827c63fd72945f2'))
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,53 @@
|
||||
import logging
|
||||
|
||||
from open_chatcaht.chatchat_api import ChatChat
|
||||
from open_chatcaht.types.knowledge_base.doc.upload_temp_docs_param import UploadTempDocsParam
|
||||
|
||||
# chatchat = ChatChat()
|
||||
# print('create_kb', chatchat.knowledge.create_kb(knowledge_base_name="example_kb"))
|
||||
# print('update_kb_info', chatchat.knowledge.update_kb_info(knowledge_base_name="example_kb", kb_info='aaaaaaa'))
|
||||
# print('list_kb', chatchat.knowledge.list_kb())
|
||||
# print('list_kb_docs_file', chatchat.knowledge.list_kb_docs_file(knowledge_base_name="samples"))
|
||||
# print('delete_kb', chatchat.knowledge.delete_kb(knowledge_base_name="example_kb"))
|
||||
# print('search_kb_docs', chatchat.knowledge.search_kb_docs(knowledge_base_name="example_kb", query="hello"))
|
||||
# print('upload_kb_docs', chatchat.knowledge.upload_kb_docs(
|
||||
# files=["data/upload_file1.txt", "data/upload_file2.txt"],
|
||||
# knowledge_base_name="example_kb",
|
||||
# ))
|
||||
# print('search_kb_docs', chatchat.knowledge.search_kb_docs(knowledge_base_name="example_kb", query="hello"))
|
||||
# print('recreate_vector_store', chatchat.knowledge.recreate_vector_store(
|
||||
# knowledge_base_name="samples",
|
||||
# ))
|
||||
# print('recreate_summary_vector_store', chatchat.knowledge.recreate_summary_vector_store(
|
||||
# knowledge_base_name="example_kb",
|
||||
# embed_model="embedding-2",
|
||||
# model_name="glm-4",
|
||||
# ))
|
||||
# for data in chatchat.knowledge.summary_file_to_vector_store(
|
||||
# knowledge_base_name="samples",
|
||||
# file_name="data/upload_file1.txt",
|
||||
# embed_model="embedding-2",
|
||||
# max_tokens=10000):
|
||||
# print(data)
|
||||
# print('summary_file_to_vector_store', chatchat.knowledge.summary_doc_ids_to_vector_store(
|
||||
# knowledge_base_name="samples",
|
||||
# file_name="data/upload_file1.txt",
|
||||
# ))
|
||||
# print('delete_kb_docs', chatchat.knowledge.delete_kb_docs(
|
||||
# knowledge_base_name="samples",
|
||||
# file_names=["upload_file1.txt"],
|
||||
# ))
|
||||
|
||||
# print(chatchat.knowledge.download_kb_doc_file(
|
||||
# knowledge_base_name='example_kb',
|
||||
# file_name='README.md'
|
||||
# ))
|
||||
# print(chatchat.knowledge.kb_doc_file_content(
|
||||
# knowledge_base_name='example_kb',
|
||||
# file_name='README.md'
|
||||
# ))
|
||||
# print(chatchat.knowledge.upload_temp_docs(
|
||||
# files=["README.md", ],
|
||||
# knowledge_id="4",
|
||||
# ))
|
||||
# print(chatchat.knowledge.search_temp_kb_docs(knowledge_id="cf414f74bca24fbdaece1ae8bb4d3970", query="hello"))
|
||||
@@ -0,0 +1,5 @@
|
||||
from open_chatcaht.chatchat_api import ChatChat
|
||||
|
||||
# chatchat = ChatChat()
|
||||
# print(chatchat.server.get_server_configs())
|
||||
# print(chatchat.server.get_prompt_template())
|
||||
@@ -0,0 +1,4 @@
|
||||
from open_chatcaht.chatchat_api import ChatChat
|
||||
from open_chatcaht.types.standard_openai.chat_input import OpenAIChatInput
|
||||
#
|
||||
# chatchat = ChatChat()
|
||||
@@ -0,0 +1,5 @@
|
||||
from open_chatcaht.chatchat_api import ChatChat
|
||||
|
||||
chatchat = ChatChat()
|
||||
print(chatchat.tool.list())
|
||||
print(chatchat.tool.call('calculate', {"text": "3+5/2"}))
|
||||
@@ -0,0 +1,86 @@
|
||||
from functools import wraps
|
||||
from typing import Type, get_type_hints
|
||||
|
||||
import httpx
|
||||
import requests
|
||||
from pydantic import BaseModel
|
||||
|
||||
from open_chatcaht.api_client import ApiClient
|
||||
from open_chatcaht.types.knowledge_base.delete_knowledge_base_param import DeleteKnowledgeBaseParam
|
||||
from open_chatcaht.types.response.base import ListResponse
|
||||
|
||||
base_url = "https://api.example.com"
|
||||
headers = {"Authorization": "Bearer token"}
|
||||
|
||||
|
||||
def http_request(method):
|
||||
def decorator(url, base_url='', headers=None, body_model: Type[BaseModel] = None, **options):
|
||||
headers = headers or {}
|
||||
|
||||
def wrapper(func):
|
||||
@wraps(func)
|
||||
def inner(*args, **kwargs):
|
||||
try:
|
||||
|
||||
print("args", args)
|
||||
print("kwargs", kwargs)
|
||||
# Prepare the request URL
|
||||
full_url = base_url + url
|
||||
|
||||
# Prepare the request data
|
||||
data = kwargs
|
||||
return_type = get_type_hints(func).get('return')
|
||||
print(f"Return type: {return_type}")
|
||||
print(body_model)
|
||||
print(f"body_model: {body_model}")
|
||||
# Send the HTTP request
|
||||
response = method(full_url, headers=headers, json=data)
|
||||
response.raise_for_status()
|
||||
|
||||
# Return the response JSON
|
||||
return response.json()
|
||||
except requests.exceptions.HTTPError as http_err:
|
||||
print(f"HTTP error occurred: {http_err}")
|
||||
except Exception as err:
|
||||
print(f"An error occurred: {err}")
|
||||
|
||||
return inner
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
# Usage example
|
||||
post = http_request(httpx.post)
|
||||
|
||||
|
||||
class MyAPIClient(ApiClient):
|
||||
|
||||
@post(url='/api/kb/recreate_summary_vector_store', base_url=base_url, headers=headers,
|
||||
body_model=DeleteKnowledgeBaseParam)
|
||||
def recreate_summary_vector_store(
|
||||
self,
|
||||
a: int,
|
||||
b: int
|
||||
) -> ListResponse:
|
||||
pass
|
||||
|
||||
|
||||
@post(url='/api/kb/recreate_summary_vector_store', base_url=base_url, headers=headers,
|
||||
body_model=DeleteKnowledgeBaseParam)
|
||||
def recreate_summary_vector_store(
|
||||
a: int,
|
||||
b: int
|
||||
) -> ListResponse:
|
||||
pass
|
||||
|
||||
|
||||
# Example usage
|
||||
if __name__ == "__main__":
|
||||
# Call the decorated function
|
||||
# response = recreate_summary_vector_store(a=1, b=1)
|
||||
# print(response)
|
||||
api_client = MyAPIClient()
|
||||
response = api_client.recreate_summary_vector_store(a=1, b=2)
|
||||
print("response", response)
|
||||
@@ -0,0 +1,70 @@
|
||||
import inspect
|
||||
from functools import wraps
|
||||
import requests
|
||||
|
||||
|
||||
class HTTPClient:
|
||||
def __init__(self, base_url='', headers=None):
|
||||
self.base_url = base_url
|
||||
self.headers = headers or {}
|
||||
|
||||
def http_request(self, method):
|
||||
def decorator(url, **options):
|
||||
headers = options.get('headers', self.headers)
|
||||
|
||||
def wrapper(func):
|
||||
@wraps(func)
|
||||
def inner(*args, **kwargs):
|
||||
try:
|
||||
# Prepare the request URL
|
||||
full_url = self.base_url + url
|
||||
instance = args[0] # Assuming func is a method of the class
|
||||
print(f"Instance: {instance}")
|
||||
# Prepare the request data from function arguments
|
||||
data = kwargs
|
||||
print(kwargs)
|
||||
# Send the HTTP request
|
||||
response = method(full_url, headers=headers, json=data)
|
||||
response.raise_for_status()
|
||||
|
||||
# Return the response JSON
|
||||
return response.json()
|
||||
except requests.exceptions.HTTPError as http_err:
|
||||
print(f"HTTP error occurred: {http_err}")
|
||||
except Exception as err:
|
||||
print(f"An error occurred: {err}")
|
||||
|
||||
return inner
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
def post(self, url, **options):
|
||||
print(self)
|
||||
|
||||
# Define a function that applies the decorator
|
||||
def decorator(func):
|
||||
return self.http_request(requests.post)(url, **options)(func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
app: HTTPClient = HTTPClient()
|
||||
|
||||
|
||||
# Example usage of the class and its decorators
|
||||
class MyAPIClient(HTTPClient):
|
||||
def __init__(self):
|
||||
super().__init__(base_url="https://api.example.com", headers={"Authorization": "Bearer token"})
|
||||
|
||||
@app.post(url='/api/kb/recreate_summary_vector_store')
|
||||
def recreate_summary_vector_store(self, a: int, b: int):
|
||||
...
|
||||
|
||||
|
||||
# Example call to the decorated method
|
||||
if __name__ == "__main__":
|
||||
client = MyAPIClient()
|
||||
response = client.recreate_summary_vector_store(a=1, b=1)
|
||||
print(response)
|
||||
Reference in New Issue
Block a user