Repository: chatchat-space/Langchain-Chatchat (language: python)

Issue:
[FEATURE] 添加配置项，以便使用云服务器或反向代理时生成正确的公网 API 地址
**解决的问题 / Problem Solved**  
在点击检索到的文档, 进行下载时, 目前的逻辑是跳转到api服务去.
![image](https://github.com/user-attachments/assets/d4b7f3ef-6cdc-414c-92b4-a9f8ff282d5a)

但是在从webui服务跳转到api服务时存在两个问题, 会导致访问失败:
1. 跳转链接直接取的api服务的host地址, 如果api.host配的是0.0.0.0, 那么在api_address()方法里面会被转成127.0.0.1; 如果在云服务器上配成公网IP, 则会bind失败(因为公网IP并不在云服务器的网卡上). 这两种情况导致没办法让webui能够正确跳转到api服务的公网ip上.
2. port后面多了一个"/", 也会导致访问失败.

(如果是本机启动服务, 然后在本地开浏览器, 是不会发现这个问题的. 但是在云服务器上启动服务, 要从本地访问, 就有问题了)

![image](https://github.com/user-attachments/assets/de88f386-bb49-4aec-8333-ce41c99079b7)

经检查源码, 主要是这里的问题.
![image](https://github.com/user-attachments/assets/d1d334ed-2ac0-4fb9-a33e-20bed3d811c7)

**实现建议 / Implementation Suggestions**  
在api服务的host配置之外额外增加一个public_host之类的配置, 如果要从webui服务跳转到api服务, 需要使用这个IP地址, 而不是直接取host地址(很多时候都是0.0.0.0).

![image](https://github.com/user-attachments/assets/44ed9463-2711-4ebd-b8dd-529b04605a26)
Fix bug of ChromaDB Collection has no attribute 'as_retriever'
Fix issue #4525 
Updated ChromaDB service refering to langchain document:
https://python.langchain.com/v0.1/docs/integrations/vectorstores/chroma/#retriever-options
* Passing a Chroma Client into Langchain instead of using Chroma Client directly
* Use Chroma as a retriever

Install langchain_chroma with:
`pip install langchain-chroma`


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index 411637a66e..70ea5fc9ef 100644
--- a/README.md
+++ b/README.md
@@ -303,9 +303,9 @@ chatchat start -a
 ### Docker 部署
 
 ```shell
-docker pull chatimage/chatchat:0.3.1.1-2024-0714
+docker pull chatimage/chatchat:0.3.1.2
 
-docker pull ccr.ccs.tencentyun.com/chatchat/chatchat:0.3.1.1-2024-0714 # 国内镜像
+docker pull ccr.ccs.tencentyun.com/chatchat/chatchat:0.3.1.2 # 国内镜像
 ```
 
 > [!important]
diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml
index 8c8cbbaf73..c7cdeac1d0 100644
--- a/docker/docker-compose.yaml
+++ b/docker/docker-compose.yaml
@@ -25,8 +25,8 @@ services:
     # environment:
     #   - XINFERENCE_MODEL_SRC=modelscope
   chatchat:
-    image: chatimage/chatchat:0.3.1.1-2024-0714
-    # image: ccr.ccs.tencentyun.com/chatchat/chatchat:0.3.1.1-2024-0714
+    image: chatimage/chatchat:0.3.1.2
+    # image: ccr.ccs.tencentyun.com/chatchat/chatchat:0.3.1.2
     restart: always
     # ports: # 不使用 host network 时可打开.
     #   - "7861:7861"
diff --git a/docs/install/README_docker.md b/docs/install/README_docker.md
index af4b0354be..c627e84fc8 100644
--- a/docs/install/README_docker.md
+++ b/docs/install/README_docker.md
@@ -59,7 +59,7 @@ docker-compose up -d
 ```text
 WARN[0000] /root/docker-compose.yaml: `version` is obsolete 
 NAME                IMAGE                           COMMAND                  SERVICE      CREATED         STATUS         PORTS
-root-chatchat-1     chatimage/chatchat:0.3.1.1-2024-0714   "chatchat -a"            chatchat     3 minutes ago   Up 3 minutes   
+root-chatchat-1     chatimage/chatchat:0.3.1.2   "chatchat -a"            chatchat     3 minutes ago   Up 3 minutes   
 root-xinference-1   xprobe/xinference:v0.12.1       "/opt/nvidia/nvidia_…"   xinference   3 minutes ago   Up 3 minutes
 ```
 ```shell
@@ -95,7 +95,7 @@ mkdir -p ~/chatchat
 ```yaml
   (上文 ...)
   chatchat:
-    image: chatimage/chatchat:0.3.1.1-2024-0714
+    image: chatimage/chatchat:0.3.1.2
     (省略 ...)
     # 将本地路径(~/chatchat/data)挂载到容器默认数据路径(/usr/local/lib/python3.11/site-packages/chatchat/data)中
     # volumes:
@@ -106,7 +106,7 @@ mkdir -p ~/chatchat
 ```yaml
   (上文 ...)
   chatchat:
-    image: chatimage/chatchat:0.3.1.1-2024-0714
+    image: chatimage/chatchat:0.3.1.2
     (省略 ...)
     # 将本地路径(~/chatchat/data)挂载到容器默认数据路径(/usr/local/lib/python3.11/site-packages/chatchat/data)中
     volumes:
@@ -163,7 +163,7 @@ WARN[0000] /root/docker-compose.yaml: `version` is obsolete
 [root@VM-2-15-centos ~]# docker-compose ps
 WARN[0000] /root/docker-compose.yaml: `version` is obsolete 
 NAME                IMAGE                           COMMAND                  SERVICE      CREATED          STATUS          PORTS
-root-chatchat-1     chatimage/chatchat:0.3.1.1-2024-0714   "chatchat -a"            chatchat     33 seconds ago   Up 32 seconds   
+root-chatchat-1     chatimage/chatchat:0.3.1.2   "chatchat -a"            chatchat     33 seconds ago   Up 32 seconds   
 root-xinference-1   xprobe/xinference:v0.12.1       "/opt/nvidia/nvidia_…"   xinference   45 minutes ago   Up 45 minutes   
 [root@VM-2-15-centos ~]# ss -anptl | grep -E '(8501|7861|9997)'
 LISTEN 0      128          0.0.0.0:9997       0.0.0.0:*    users:(("pt_main_thread",pid=1489804,fd=21))
diff --git a/libs/chatchat-server/README.md b/libs/chatchat-server/README.md
index 98ec04f8ca..a3895c8bc2 100644
--- a/libs/chatchat-server/README.md
+++ b/libs/chatchat-server/README.md
@@ -43,9 +43,9 @@ pip install langchain-chatchat
 3. Docker
 
 ```shell
-docker pull chatimage/chatchat:0.3.1.1-2024-0714
+docker pull chatimage/chatchat:0.3.1.2-2024-0720
 
-docker pull ccr.ccs.tencentyun.com/chatchat/chatchat:0.3.1.1-2024-0714 # 国内镜像
+docker pull ccr.ccs.tencentyun.com/chatchat/chatchat:0.3.1.2-2024-0720 # 国内镜像
 ```
 
 > [!important]
@@ -82,6 +82,34 @@ chatchat start -a
 更多命令可以通过 `chatchat --help` 查看。
 
 ### 更新日志：
+
+#### 0.3.1.2 (2024-07-20)
+- 新功能：
+    - Model Platform 支持配置代理 by @liunux4odoo (#4492)
+    - 给定一个默认可用的 searx 服务器 by @liunux4odoo (#4504)
+    - 更新 docker 镜像 by @yuehua-s @imClumsyPanda (#4511)
+    - 新增URL内容阅读器：通过jina-ai/reader项目，将url内容处理为llm易于理解的文本形式 by @ganwumeng @imClumsyPanda (#4547)
+    - 优化qwen模型下对tools的json修复成功率 by @ganwumeng (#4554)
+    - 允许用户在 basic_settings.API_SERVER 中配置 public_host,public_port，以便使用云服务器或反向代理时生成正确的公网 API
+      地址 by @liunux4odoo (#4567)
+    - 添加模型和服务自动化脚本 by @glide-the (#4573)
+    - 添加单元测试 by @glide-the (#4573)
+- 修复：
+    - WEBUI 中设置 System message 无效 by @liunux4odoo (#4491)
+    - 移除无效的 vqa_processor & aqa_processor 工具 by @liunux4odoo (#4498)
+    - KeyError of 'template' 错误 by @liunux4odoo (#4501)
+    - 执行 chatchat init 时 nltk_data 目录设置错误 by @liunux4odoo (#4523)
+    - 执行 chatchat init 时 出现 xinference-client 连接错误 by @imClumsyPanda (#4573)
+    - xinference 自动检测模型使用缓存，提高 UI 响应速度 by @liunux4odoo (#4510)
+    - chatchat.log 中重复记录 by @liunux4odoo (#4517)
+    - 优化错误信息的传递和前端显示 by @liunux4odoo (#4531)
+    - 修正 openai.chat.completions.create 参数构造方式，提高兼容性  by @liunux4odoo (#4540)
+    - Milvus retriever NotImplementedError by @kwunhang (#4536)
+    - Fix bug of ChromaDB Collection as retriever by @kwunhang (#4541)
+    - langchain 版本升级后，DocumentWithVsId 出现 id 重复问题 by @liunux4odoo (#4548)
+    - 重建知识库时只处理了一个知识库 by @liunux4odoo (#4549)
+    - chat api error because openapi set max_tokens to 0 by default by @liunux4odoo (#4564)
+
 #### 0.3.1.1 (2024-07-15)
 - 修复：
   - WEBUI 中设置 system message 无效([#4491](https://github.com/chatchat-space/Langchain-Chatchat/pull/4491))
diff --git a/libs/chatchat-server/README_en.md b/libs/chatchat-server/README_en.md
index 0001fdabb9..9a1475e61b 100644
--- a/libs/chatchat-server/README_en.md
+++ b/libs/chatchat-server/README_en.md
@@ -37,9 +37,9 @@ In addition to installing through Pypi, you can also choose to use [source code
 
 3. Docker
 ```shell
-docker pull chatimage/chatchat:0.3.1.1-2024-0714
+docker pull chatimage/chatchat:0.3.1.2
 
-docker pull ccr.ccs.tencentyun.com/chatchat/chatchat:0.3.1.1-2024-0714 # 国内镜像
+docker pull ccr.ccs.tencentyun.com/chatchat/chatchat:0.3.1.2 # 国内镜像
 ```
 > [!important]
 > Strong recommendation: Use docker compose for deployment, refer to [README.docker](https://github.com/chatchat-space/Langchain-Chatchat/blob/master/docs/install/README_docker.md) for details 
@@ -66,6 +66,7 @@ chatchat start -a
 ```
 If there are no errors, the browser page will automatically pop up.
 ### Update log:
+ 
 #### 0.3.1.1 (2024-07-15)
 - Fix:
   - Invalid system message setting in WEBUI ([# 4491](https://github.com/chatchat-space/Langchain-Chatchat/pull/4491 ))
diff --git a/libs/chatchat-server/chatchat/__init__.py b/libs/chatchat-server/chatchat/__init__.py
index 260c070a89..7ec2aea660 100644
--- a/libs/chatchat-server/chatchat/__init__.py
+++ b/libs/chatchat-server/chatchat/__init__.py
@@ -1,1 +1,1 @@
-__version__ = "0.3.1"
+__version__ = "0.3.1.2"
diff --git a/libs/chatchat-server/chatchat/cli.py b/libs/chatchat-server/chatchat/cli.py
index c32149d3f1..be63055148 100644
--- a/libs/chatchat-server/chatchat/cli.py
+++ b/libs/chatchat-server/chatchat/cli.py
@@ -46,15 +46,16 @@ def init(
     Settings.set_auto_reload(False)
     bs = Settings.basic_settings
     kb_names = [x.strip() for x in kb_names.split(",")]
-    logger.info(f"开始初始化项目数据目录：{Settings.CHATCHAT_ROOT}")
+    logger.success(f"开始初始化项目数据目录：{Settings.CHATCHAT_ROOT}")
     Settings.basic_settings.make_dirs()
-    logger.info("创建所有数据目录：成功。")
+    logger.success("创建所有数据目录：成功。")
     if(bs.PACKAGE_ROOT / "data/knowledge_base/samples" != Path(bs.KB_ROOT_PATH) / "samples"):
         shutil.copytree(bs.PACKAGE_ROOT / "data/knowledge_base/samples", Path(bs.KB_ROOT_PATH) / "samples", dirs_exist_ok=True)
-    logger.info("复制 samples 知识库文件：成功。")
+    logger.success("复制 samples 知识库文件：成功。")
     nltk.data.path.append(str(bs.PACKAGE_ROOT / "data/nltk_data"))
+    logger.success("设置 nltk_data 路径：成功。")
     create_tables()
-    logger.info("初始化知识库数据库：成功。")
+    logger.success("初始化知识库数据库：成功。")
 
     if xf_endpoint:
         Settings.model_settings.MODEL_PLATFORMS[0].api_base_url = xf_endpoint
@@ -66,8 +67,8 @@ def init(
     Settings.createl_all_templates()
     Settings.set_auto_reload(True)
 
-    logger.info("生成默认配置文件：成功。")
-    logger.warning("<red>请先检查 model_settings.yaml 里模型平台、LLM模型和Embed模型信息正确</red>")
+    logger.success("生成默认配置文件：成功。")
+    logger.success("请先检查确认 model_settings.yaml 里模型平台、LLM模型和Embed模型信息已经正确")
 
     if recreate_kb:
         folder2db(kb_names=kb_names,
@@ -76,7 +77,7 @@ def init(
                   embed_model=get_default_embedding())
         logger.success("<green>所有初始化已完成，执行 chatchat start -a 启动服务。</green>")
     else:
-        logger.success("<green>执行 chatchat kb -r 初始化知识库，然后 chatchat start -a 启动服务。</green>")
+        logger.success("执行 chatchat kb -r 初始化知识库，然后 chatchat start -a 启动服务。")
 
 
 main.add_command(startup_main, "start")
diff --git a/libs/chatchat-server/chatchat/init_database.py b/libs/chatchat-server/chatchat/init_database.py
index bdd94817d3..506df79e85 100644
--- a/libs/chatchat-server/chatchat/init_database.py
+++ b/libs/chatchat-server/chatchat/init_database.py
@@ -58,7 +58,7 @@ def worker(args: dict):
         end_time = datetime.now()
         print(f"总计用时\t：{end_time-start_time}\n")
     except Exception as e:
-        logger.error(e, exc_info=True)
+        logger.exception(e)
 
 
 @click.command(help="知识库相关功能")
diff --git a/libs/chatchat-server/chatchat/server/agent/agent_factory/qwen_agent.py b/libs/chatchat-server/chatchat/server/agent/agent_factory/qwen_agent.py
index 4141a9bfad..c957f3cd3c 100644
--- a/libs/chatchat-server/chatchat/server/agent/agent_factory/qwen_agent.py
+++ b/libs/chatchat-server/chatchat/server/agent/agent_factory/qwen_agent.py
@@ -92,6 +92,12 @@ def format_messages(self, **kwargs) -> str:
         formatted = self.template.format(**kwargs)
         return [HumanMessage(content=formatted)]
 
+def validate_json(json_data: str):
+    try:
+        json.loads(json_data)
+        return True
+    except ValueError:
+        return False
 
 class QwenChatAgentOutputParserCustom(StructuredChatOutputParser):
     """Output parser with retries for the structured chat agent with custom qwen prompt."""
@@ -109,9 +115,26 @@ def parse(self, text: str) -> Union[AgentAction, AgentFinish]:
                 # ollama部署的qwen，返回的json键值可能为单引号，可能缺少最后的引号和括号
                 if not json_string.endswith('"}'):
                     print("尝试修复格式不正确的json输出:" + json_string)
-                    json_string = (json_string + '"}').replace("'", '"')
-                    print("修复后的json:" + json_string)
+                    fixed_json_string = (json_string + '"}').replace("'", '"')
+
+                    fixed = True
+                    if not validate_json(fixed_json_string):
+                        # ollama部署的qwen，返回的json可能有注释，需要去掉注释
+                        fixed_json_string = (re.sub(r'//.*', '', (json_string + '"}').replace("'", '"'))
+                                             .strip()
+                                             .replace('\n', ''))
+                        if not validate_json(fixed_json_string):
+                            fixed = False
+                            print("尝试修复json格式失败：" + json_string)
+                    if fixed:
+                        json_string = fixed_json_string
+                        print("修复后的json输出:" + json_string)
+
                     json_input = json.loads(json_string)
+            # 有概率key为command而非query，需修改
+            if "command" in json_input:
+                json_input["query"] = json_input.pop("command")
+
             return AgentAction(tool=s[0].strip(), tool_input=json_input, log=text)
         elif s := re.findall(r"\nFinal\sAnswer:\s*(.+)", text, flags=re.DOTALL):
             s = s[-1]
diff --git a/libs/chatchat-server/chatchat/server/agent/tools_factory/__init__.py b/libs/chatchat-server/chatchat/server/agent/tools_factory/__init__.py
index 05153eac20..64e87ea4f0 100644
--- a/libs/chatchat-server/chatchat/server/agent/tools_factory/__init__.py
+++ b/libs/chatchat-server/chatchat/server/agent/tools_factory/__init__.py
@@ -12,3 +12,4 @@
 from .amap_weather import amap_weather
 from .wikipedia_search import wikipedia_search
 from .text2promql import text2promql
+from .url_reader import url_reader
diff --git a/libs/chatchat-server/chatchat/server/agent/tools_factory/url_reader.py b/libs/chatchat-server/chatchat/server/agent/tools_factory/url_reader.py
new file mode 100644
index 0000000000..b690271508
--- /dev/null
+++ b/libs/chatchat-server/chatchat/server/agent/tools_factory/url_reader.py
@@ -0,0 +1,43 @@
+"""
+通过jina-ai/reader项目，将url内容处理为llm易于理解的文本形式
+"""
+import requests
+
+import re
+
+from chatchat.server.pydantic_v1 import Field
+from chatchat.server.utils import get_tool_config
+
+from chatchat.server.agent.tools_factory.tools_registry import format_context
+
+from .tools_registry import BaseToolOutput, regist_tool
+
+
+@regist_tool(title="URL内容阅读")
+def url_reader(
+        url: str = Field(
+            description="The URL to be processed, so that its web content can be made more clear to read. Then provide a detailed description of the content in about 500 words. As structured as possible. ONLY THE LINK SHOULD BE PASSED IN."),
+):
+    """Use this tool to get the clear content of a URL."""
+
+    tool_config = get_tool_config("url_reader")
+    timeout = tool_config.get("timeout")
+
+    # 提取url文本中的网页链接部分。url文本可能是一句话
+    url_pattern = r'http[s]?://[a-zA-Z0-9./?&=_%#-]+'
+    match = re.search(url_pattern, url)
+    url = match.group(0) if match else None
+
+    if url is None:
+        return BaseToolOutput({"error": "No URL"})
+
+    reader_url = "https://r.jina.ai/{url}".format(url=url)
+
+    response = requests.get(reader_url, timeout=timeout)
+
+    if response.status_code == 200:
+        return BaseToolOutput(
+            {"result": response.text, "docs": [{"page_content": response.text, "metadata": {'source': url, 'id': ''}}]},
+            format=format_context)
+    else:
+        return BaseToolOutput({"error": "Timeout"})
diff --git a/libs/chatchat-server/chatchat/server/api_server/chat_routes.py b/libs/chatchat-server/chatchat/server/api_server/chat_routes.py
index 30dc79a2fd..4eb8fe76d4 100644
--- a/libs/chatchat-server/chatchat/server/api_server/chat_routes.py
+++ b/libs/chatchat-server/chatchat/server/api_server/chat_routes.py
@@ -42,7 +42,6 @@
 chat_router.post("/file_chat", summary="文件对话")(file_chat)
 
 
-
 @chat_router.post("/chat/completions", summary="兼容 openai 的统一 chat 接口")
 async def chat_completions(
     request: Request,
@@ -60,11 +59,11 @@ async def chat_completions(
     以后还要考虑其它的组合（如文件对话）
     返回与 openai 兼容的 Dict
     """
-    import rich
-    rich.print(body)
+    # import rich
+    # rich.print(body)
 
     # 当调用本接口且 body 中没有传入 "max_tokens" 参数时, 默认使用配置中定义的值
-    if body.max_tokens == None:
+    if body.max_tokens in [None, 0]:
         body.max_tokens = Settings.model_settings.MAX_TOKENS
 
     client = get_OpenAIClient(model_name=body.model, is_async=True)
@@ -106,15 +105,19 @@ async def chat_completions(
                 }
             ]
         if tool_input := extra.get("tool_input"):
-            message_id = (
-                add_message_to_db(
-                    chat_type="tool_call",
-                    query=body.messages[-1]["content"],
-                    conversation_id=conversation_id,
+            try:
+                message_id = (
+                    add_message_to_db(
+                        chat_type="tool_call",
+                        query=body.messages[-1]["content"],
+                        conversation_id=conversation_id,
+                    )
+                    if conversation_id
+                    else None
                 )
-                if conversation_id
-                else None
-            )
+            except Exception as e:
+                logger.warning(f"failed to add message to db: {e}")
+                message_id = None
 
             tool_result = await tool.ainvoke(tool_input)
             prompt_template = PromptTemplate.from_template(
@@ -153,15 +156,19 @@ def temp_gen():
 
     # agent chat with tool calls
     if body.tools:
-        message_id = (
-            add_message_to_db(
-                chat_type="agent_chat",
-                query=body.messages[-1]["content"],
-                conversation_id=conversation_id,
+        try:
+            message_id = (
+                add_message_to_db(
+                    chat_type="agent_chat",
+                    query=body.messages[-1]["content"],
+                    conversation_id=conversation_id,
+                )
+                if conversation_id
+                else None
             )
-            if conversation_id
-            else None
-        )
+        except Exception as e:
+            logger.warning(f"failed to add message to db: {e}")
+            message_id = None
 
         chat_model_config = {}  # TODO: 前端支持配置模型
         tool_names = [x["function"]["name"] for x in body.tools]
@@ -191,7 +198,7 @@ def temp_gen():
                 else None
             )
         except Exception as e:
-            logger.error(f"failed to add message to db")
+            logger.warning(f"failed to add message to db: {e}")
             message_id = None
 
         extra_json = {
diff --git a/libs/chatchat-server/chatchat/server/api_server/kb_routes.py b/libs/chatchat-server/chatchat/server/api_server/kb_routes.py
index f720e76cd2..bf1d5ee553 100644
--- a/libs/chatchat-server/chatchat/server/api_server/kb_routes.py
+++ b/libs/chatchat-server/chatchat/server/api_server/kb_routes.py
@@ -44,6 +44,9 @@ async def kb_chat_endpoint(
     # import rich
     # rich.print(body)
 
+    if body.max_tokens in [None, 0]:
+        body.max_tokens = Settings.model_settings.MAX_TOKENS
+
     extra = body.model_extra
     ret = await kb_chat(
         query=body.messages[-1]["content"],
diff --git a/libs/chatchat-server/chatchat/server/api_server/openai_routes.py b/libs/chatchat-server/chatchat/server/api_server/openai_routes.py
index 3f7b316933..a73e197b31 100644
--- a/libs/chatchat-server/chatchat/server/api_server/openai_routes.py
+++ b/libs/chatchat-server/chatchat/server/api_server/openai_routes.py
@@ -2,7 +2,6 @@
 
 import asyncio
 import base64
-import logging
 import os
 import shutil
 from contextlib import asynccontextmanager
@@ -10,11 +9,10 @@
 from pathlib import Path
 from typing import AsyncGenerator, Dict, Iterable, Tuple
 
-from fastapi import APIRouter, Request
+from fastapi import APIRouter, Request, HTTPException
 from fastapi.responses import FileResponse
 from openai import AsyncClient
-from openai.types.file_object import FileObject
-from sse_starlette.sse import EventSourceResponse
+from sse_starlette.sse import EventSourceResponse, ServerSentEvent
 
 from chatchat.settings import Settings
 from chatchat.server.utils import get_config_platforms, get_model_info, get_OpenAIClient
@@ -60,7 +58,7 @@ async def get_model_client(model_name: str) -> AsyncGenerator[AsyncClient]:
         await semaphore.acquire()
         yield get_OpenAIClient(platform_name=selected_platform, is_async=True)
     except Exception:
-        logger.error(f"failed when request to {key}", exc_info=True)
+        logger.exception(f"failed when request to {key}")
     finally:
         semaphore.release()
 
@@ -73,34 +71,43 @@ async def openai_request(
     """
 
     async def generator():
-        for x in header:
-            if isinstance(x, str):
-                x = OpenAIChatOutput(content=x, object="chat.completion.chunk")
-            elif isinstance(x, dict):
-                x = OpenAIChatOutput.model_validate(x)
-            else:
-                raise RuntimeError(f"unsupported value: {header}")
-            for k, v in extra_json.items():
-                setattr(x, k, v)
-            yield x.model_dump_json()
-
-        async for chunk in await method(**params):
-            for k, v in extra_json.items():
-                setattr(chunk, k, v)
-            yield chunk.model_dump_json()
-
-        for x in tail:
-            if isinstance(x, str):
-                x = OpenAIChatOutput(content=x, object="chat.completion.chunk")
-            elif isinstance(x, dict):
-                x = OpenAIChatOutput.model_validate(x)
-            else:
-                raise RuntimeError(f"unsupported value: {tail}")
-            for k, v in extra_json.items():
-                setattr(x, k, v)
-            yield x.model_dump_json()
+        try:
+            for x in header:
+                if isinstance(x, str):
+                    x = OpenAIChatOutput(content=x, object="chat.completion.chunk")
+                elif isinstance(x, dict):
+                    x = OpenAIChatOutput.model_validate(x)
+                else:
+                    raise RuntimeError(f"unsupported value: {header}")
+                for k, v in extra_json.items():
+                    setattr(x, k, v)
+                yield x.model_dump_json()
+
+            async for chunk in await method(**params):
+                for k, v in extra_json.items():
+                    setattr(chunk, k, v)
+                yield chunk.model_dump_json()
+
+            for x in tail:
+                if isinstance(x, str):
+                    x = OpenAIChatOutput(content=x, object="chat.completion.chunk")
+                elif isinstance(x, dict):
+                    x = OpenAIChatOutput.model_validate(x)
+                else:
+                    raise RuntimeError(f"unsupported value: {tail}")
+                for k, v in extra_json.items():
+                    setattr(x, k, v)
+                yield x.model_dump_json()
+        except asyncio.exceptions.CancelledError:
+            logger.warning("streaming progress has been interrupted by user.")
+            return
+        except Exception as e:
+            logger.error(f"openai request error: {e}")
+            yield {"data": json.dumps({"error": str(e)})}
 
     params = body.model_dump(exclude_unset=True)
+    if params.get("max_tokens") == 0:
+        params["max_tokens"] = Settings.model_settings.MAX_TOKENS
 
     if hasattr(body, "stream") and body.stream:
         return EventSourceResponse(generator())
@@ -123,7 +130,7 @@ async def task(name: str, config: Dict):
             models = await client.models.list()
             return [{**x.model_dump(), "platform_name": name} for x in models.data]
         except Exception:
-            logger.error(f"failed request to platform: {name}", exc_info=True)
+            logger.exception(f"failed request to platform: {name}")
             return []
 
     result = []
@@ -139,10 +146,8 @@ async def task(name: str, config: Dict):
 
 @openai_router.post("/chat/completions")
 async def create_chat_completions(
-    request: Request,
     body: OpenAIChatInput,
 ):
-    logger.debug(body)
     async with get_model_client(body.model) as client:
         result = await openai_request(client.chat.completions.create, body)
         return result
diff --git a/libs/chatchat-server/chatchat/server/api_server/server_routes.py b/libs/chatchat-server/chatchat/server/api_server/server_routes.py
index f77cfb273d..22f8df34dc 100644
--- a/libs/chatchat-server/chatchat/server/api_server/server_routes.py
+++ b/libs/chatchat-server/chatchat/server/api_server/server_routes.py
@@ -2,15 +2,14 @@
 
 from fastapi import APIRouter, Body
 
+from chatchat.server.types.server.response.base import BaseResponse
 from chatchat.settings import Settings
 from chatchat.server.utils import get_prompt_template, get_server_configs
 
 server_router = APIRouter(prefix="/server", tags=["Server State"])
 
-
 available_template_types = list(Settings.prompt_settings.model_fields.keys())
 
-
 # 服务器相关接口
 server_router.post(
     "/configs",
@@ -18,11 +17,14 @@
 )(get_server_configs)
 
 
-@server_router.post("/get_prompt_template", summary="获取服务区配置的 prompt 模板")
+@server_router.post("/get_prompt_template", summary="获取服务区配置的 prompt 模板", response_model=BaseResponse)
 def get_server_prompt_template(
-    type: str = Body(
-        "llm_model", description="模板类型，可选值：{available_template_types}"
-    ),
-    name: str = Body("default", description="模板名称"),
-) -> str:
-    return get_prompt_template(type=type, name=name)
+        type: str = Body(
+            "llm_model", description="模板类型，可选值：{available_template_types}"
+        ),
+        name: str = Body("default", description="模板名称"),
+):
+    prompt_template = get_prompt_template(type=type, name=name)
+    if prompt_template is None:
+        return BaseResponse.error("Prompt template not found")
+    return BaseResponse.success(prompt_template)
diff --git a/libs/chatchat-server/chatchat/server/api_server/tool_routes.py b/libs/chatchat-server/chatchat/server/api_server/tool_routes.py
index 933b08a93a..92f7458457 100644
--- a/libs/chatchat-server/chatchat/server/api_server/tool_routes.py
+++ b/libs/chatchat-server/chatchat/server/api_server/tool_routes.py
@@ -41,7 +41,7 @@ async def call_tool(
             return {"data": result}
         except Exception:
             msg = f"failed to call tool '{name}'"
-            logger.error(msg, exc_info=True)
+            logger.exception(msg)
             return {"code": 500, "msg": msg}
     else:
         return {"code": 500, "msg": f"no tool named '{name}'"}
diff --git a/libs/chatchat-server/chatchat/server/chat/chat.py b/libs/chatchat-server/chatchat/server/chat/chat.py
index b43710427b..f1df170dee 100644
--- a/libs/chatchat-server/chatchat/server/chat/chat.py
+++ b/libs/chatchat-server/chatchat/server/chat/chat.py
@@ -1,15 +1,12 @@
 import asyncio
 import json
-import time
 import uuid
 from typing import AsyncIterable, List
 
 from fastapi import Body
 from langchain.chains import LLMChain
-from langchain.prompts import PromptTemplate
 from langchain.prompts.chat import ChatPromptTemplate
 from langchain_core.messages import AIMessage, HumanMessage, convert_to_messages
-from langchain_core.output_parsers import StrOutputParser
 from sse_starlette.sse import EventSourceResponse
 
 from chatchat.settings import Settings
@@ -30,9 +27,13 @@
     get_tool,
     wrap_done,
     get_default_llm,
+    build_logger,
 )
 
 
+logger = build_logger()
+
+
 def create_models_from_config(configs, callbacks, stream, max_tokens):
     configs = configs or Settings.model_settings.LLM_MODEL_CONFIG
     models = {}
@@ -122,119 +123,127 @@ async def chat(
     """Agent 对话"""
 
     async def chat_iterator() -> AsyncIterable[OpenAIChatOutput]:
-        callback = AgentExecutorAsyncIteratorCallbackHandler()
-        callbacks = [callback]
+        try:
+            callback = AgentExecutorAsyncIteratorCallbackHandler()
+            callbacks = [callback]
 
-        # Enable langchain-chatchat to support langfuse
-        import os
+            # Enable langchain-chatchat to support langfuse
+            import os
 
-        langfuse_secret_key = os.environ.get("LANGFUSE_SECRET_KEY")
-        langfuse_public_key = os.environ.get("LANGFUSE_PUBLIC_KEY")
-        langfuse_host = os.environ.get("LANGFUSE_HOST")
-        if langfuse_secret_key and langfuse_public_key and langfuse_host:
-            from langfuse import Langfuse
-            from langfuse.callback import CallbackHandler
+            langfuse_secret_key = os.environ.get("LANGFUSE_SECRET_KEY")
+            langfuse_public_key = os.environ.get("LANGFUSE_PUBLIC_KEY")
+            langfuse_host = os.environ.get("LANGFUSE_HOST")
+            if langfuse_secret_key and langfuse_public_key and langfuse_host:
+                from langfuse import Langfuse
+                from langfuse.callback import CallbackHandler
 
-            langfuse_handler = CallbackHandler()
-            callbacks.append(langfuse_handler)
+                langfuse_handler = CallbackHandler()
+                callbacks.append(langfuse_handler)
 
-        models, prompts = create_models_from_config(
-            callbacks=callbacks, configs=chat_model_config, stream=stream, max_tokens=max_tokens
-        )
-        all_tools = get_tool().values()
-        tools = [tool for tool in all_tools if tool.name in tool_config]
-        tools = [t.copy(update={"callbacks": callbacks}) for t in tools]
-        full_chain = create_models_chains(
-            prompts=prompts,
-            models=models,
-            conversation_id=conversation_id,
-            tools=tools,
-            callbacks=callbacks,
-            history=history,
-            history_len=history_len,
-            metadata=metadata,
-        )
+            models, prompts = create_models_from_config(
+                callbacks=callbacks, configs=chat_model_config, stream=stream, max_tokens=max_tokens
+            )
+            all_tools = get_tool().values()
+            tools = [tool for tool in all_tools if tool.name in tool_config]
+            tools = [t.copy(update={"callbacks": callbacks}) for t in tools]
+            full_chain = create_models_chains(
+                prompts=prompts,
+                models=models,
+                conversation_id=conversation_id,
+                tools=tools,
+                callbacks=callbacks,
+                history=history,
+                history_len=history_len,
+                metadata=metadata,
+            )
 
-        _history = [History.from_data(h) for h in history]
-        chat_history = [h.to_msg_tuple() for h in _history]
+            _history = [History.from_data(h) for h in history]
+            chat_history = [h.to_msg_tuple() for h in _history]
 
-        history_message = convert_to_messages(chat_history)
+            history_message = convert_to_messages(chat_history)
 
-        task = asyncio.create_task(
-            wrap_done(
-                full_chain.ainvoke(
-                    {
-                        "input": query,
-                        "chat_history": history_message,
-                    }
-                ),
-                callback.done,
+            task = asyncio.create_task(
+                wrap_done(
+                    full_chain.ainvoke(
+                        {
+                            "input": query,
+                            "chat_history": history_message,
+                        }
+                    ),
+                    callback.done,
+                )
             )
-        )
 
-        last_tool = {}
-        async for chunk in callback.aiter():
-            data = json.loads(chunk)
-            data["tool_calls"] = []
-            data["message_type"] = MsgType.TEXT
+            last_tool = {}
+            async for chunk in callback.aiter():
+                data = json.loads(chunk)
+                data["tool_calls"] = []
+                data["message_type"] = MsgType.TEXT
 
-            if data["status"] == AgentStatus.tool_start:
-                last_tool = {
-                    "index": 0,
-                    "id": data["run_id"],
-                    "type": "function",
-                    "function": {
-                        "name": data["tool"],
-                        "arguments": data["tool_input"],
-                    },
-                    "tool_output": None,
-                    "is_error": False,
-                }
-                data["tool_calls"].append(last_tool)
-            if data["status"] in [AgentStatus.tool_end]:
-                last_tool.update(
-                    tool_output=data["tool_output"],
-                    is_error=data.get("is_error", False),
-                )
-                data["tool_calls"] = [last_tool]
-                last_tool = {}
-                try:
-                    tool_output = json.loads(data["tool_output"])
-                    if message_type := tool_output.get("message_type"):
-                        data["message_type"] = message_type
-                except:
-                    ...
-            elif data["status"] == AgentStatus.agent_finish:
-                try:
-                    tool_output = json.loads(data["text"])
-                    if message_type := tool_output.get("message_type"):
-                        data["message_type"] = message_type
-                except:
-                    ...
+                if data["status"] == AgentStatus.tool_start:
+                    last_tool = {
+                        "index": 0,
+                        "id": data["run_id"],
+                        "type": "function",
+                        "function": {
+                            "name": data["tool"],
+                            "arguments": data["tool_input"],
+                        },
+                        "tool_output": None,
+                        "is_error": False,
+                    }
+                    data["tool_calls"].append(last_tool)
+                if data["status"] in [AgentStatus.tool_end]:
+                    last_tool.update(
+                        tool_output=data["tool_output"],
+                        is_error=data.get("is_error", False),
+                    )
+                    data["tool_calls"] = [last_tool]
+                    last_tool = {}
+                    try:
+                        tool_output = json.loads(data["tool_output"])
+                        if message_type := tool_output.get("message_type"):
+                            data["message_type"] = message_type
+                    except:
+                        ...
+                elif data["status"] == AgentStatus.agent_finish:
+                    try:
+                        tool_output = json.loads(data["text"])
+                        if message_type := tool_output.get("message_type"):
+                            data["message_type"] = message_type
+                    except:
+                        ...
 
-            ret = OpenAIChatOutput(
-                id=f"chat{uuid.uuid4()}",
-                object="chat.completion.chunk",
-                content=data.get("text", ""),
-                role="assistant",
-                tool_calls=data["tool_calls"],
-                model=models["llm_model"].model_name,
-                status=data["status"],
-                message_type=data["message_type"],
-                message_id=message_id,
-            )
-            yield ret.model_dump_json()
-        # yield OpenAIChatOutput( # return blank text lastly
-        #         id=f"chat{uuid.uuid4()}",
-        #         object="chat.completion.chunk",
-        #         content="",
-        #         role="assistant",
-        #         model=models["llm_model"].model_name,
-        #         status = data["status"],
-        #         message_type = data["message_type"],
-        #         message_id=message_id,
-        # )
-        await task
+                ret = OpenAIChatOutput(
+                    id=f"chat{uuid.uuid4()}",
+                    object="chat.completion.chunk",
+                    content=data.get("text", ""),
+                    role="assistant",
+                    tool_calls=data["tool_calls"],
+                    model=models["llm_model"].model_name,
+                    status=data["status"],
+                    message_type=data["message_type"],
+                    message_id=message_id,
+                )
+                yield ret.model_dump_json()
+            # yield OpenAIChatOutput( # return blank text lastly
+            #         id=f"chat{uuid.uuid4()}",
+            #         object="chat.completion.chunk",
+            #         content="",
+            #         role="assistant",
+            #         model=models["llm_model"].model_name,
+            #         status = data["status"],
+            #         message_type = data["message_type"],
+            #         message_id=message_id,
+            # )
+            await task
+        except asyncio.exceptions.CancelledError:
+            logger.warning("streaming progress has been interrupted by user.")
+            return
+        except Exception as e:
+            logger.error(f"error in chat: {e}")
+            yield {"data": json.dumps({"error": str(e)})}
+            return
 
     if stream:
         return EventSourceResponse(chat_iterator())
diff --git a/libs/chatchat-server/chatchat/server/chat/completion.py b/libs/chatchat-server/chatchat/server/chat/completion.py
index ae4023b116..7342c800d2 100644
--- a/libs/chatchat-server/chatchat/server/chat/completion.py
+++ b/libs/chatchat-server/chatchat/server/chat/completion.py
@@ -7,7 +7,10 @@
 from langchain.prompts import PromptTemplate
 from sse_starlette.sse import EventSourceResponse
 
-from chatchat.server.utils import get_OpenAI, get_prompt_template, wrap_done
+from chatchat.server.utils import get_OpenAI, get_prompt_template, wrap_done, build_logger
+
+
+logger = build_logger()
 
 
 async def completion(
@@ -31,40 +34,44 @@ async def completion_iterator(
         prompt_name: str = prompt_name,
         echo: bool = echo,
     ) -> AsyncIterable[str]:
-        nonlocal max_tokens
-        callback = AsyncIteratorCallbackHandler()
-        if isinstance(max_tokens, int) and max_tokens <= 0:
-            max_tokens = None
+        try:
+            nonlocal max_tokens
+            callback = AsyncIteratorCallbackHandler()
+            if isinstance(max_tokens, int) and max_tokens <= 0:
+                max_tokens = None
 
-        model = get_OpenAI(
-            model_name=model_name,
-            temperature=temperature,
-            max_tokens=max_tokens,
-            callbacks=[callback],
-            echo=echo,
-            local_wrap=True,
-        )
+            model = get_OpenAI(
+                model_name=model_name,
+                temperature=temperature,
+                max_tokens=max_tokens,
+                callbacks=[callback],
+                echo=echo,
+                local_wrap=True,
+            )
 
-        prompt_template = get_prompt_template("llm_model", prompt_name)
-        prompt = PromptTemplate.from_template(prompt_template, template_format="jinja2")
-        chain = LLMChain(prompt=prompt, llm=model)
+            prompt_template = get_prompt_template("llm_model", prompt_name)
+            prompt = PromptTemplate.from_template(prompt_template, template_format="jinja2")
+            chain = LLMChain(prompt=prompt, llm=model)
 
-        # Begin a task that runs in the background.
-        task = asyncio.create_task(
-            wrap_done(chain.acall({"input": query}), callback.done),
-        )
+            # Begin a task that runs in the background.
+            task = asyncio.create_task(
+                wrap_done(chain.acall({"input": query}), callback.done),
+            )
 
-        if stream:
-            async for token in callback.aiter():
-                # Use server-sent-events to stream the response
-                yield token
-        else:
-            answer = ""
-            async for token in callback.aiter():
-                answer += token
-            yield answer
+            if stream:
+                async for token in callback.aiter():
+                    # Use server-sent-events to stream the response
+                    yield token
+            else:
+                answer = ""
+                async for token in callback.aiter():
+                    answer += token
+                yield answer
 
-        await task
+            await task
+        except asyncio.exceptions.CancelledError:
+            logger.warning("streaming progress has been interrupted by user.")
+            return
 
     return EventSourceResponse(
         completion_iterator(
diff --git a/libs/chatchat-server/chatchat/server/chat/file_chat.py b/libs/chatchat-server/chatchat/server/chat/file_chat.py
index 73eae7fee7..c164b999e6 100644
--- a/libs/chatchat-server/chatchat/server/chat/file_chat.py
+++ b/libs/chatchat-server/chatchat/server/chat/file_chat.py
@@ -152,81 +152,85 @@ async def file_chat(
     history = [History.from_data(h) for h in history]
 
     async def knowledge_base_chat_iterator() -> AsyncIterable[str]:
-        nonlocal max_tokens
-        callback = AsyncIteratorCallbackHandler()
-        if isinstance(max_tokens, int) and max_tokens <= 0:
-            max_tokens = None
-
-        callbacks = [callback]
-        # Enable langchain-chatchat to support langfuse
-        import os
-
-        langfuse_secret_key = os.environ.get("LANGFUSE_SECRET_KEY")
-        langfuse_public_key = os.environ.get("LANGFUSE_PUBLIC_KEY")
-        langfuse_host = os.environ.get("LANGFUSE_HOST")
-        if langfuse_secret_key and langfuse_public_key and langfuse_host:
-            from langfuse import Langfuse
-            from langfuse.callback import CallbackHandler
-
-            langfuse_handler = CallbackHandler()
-            callbacks.append(langfuse_handler)
-
-        model = get_ChatOpenAI(
-            model_name=model_name,
-            temperature=temperature,
-            max_tokens=max_tokens,
-            callbacks=callbacks,
-        )
-        embed_func = get_Embeddings()
-        embeddings = await embed_func.aembed_query(query)
-        with memo_faiss_pool.acquire(knowledge_id) as vs:
-            docs = vs.similarity_search_with_score_by_vector(
-                embeddings, k=top_k, score_threshold=score_threshold
+        try:
+            nonlocal max_tokens
+            callback = AsyncIteratorCallbackHandler()
+            if isinstance(max_tokens, int) and max_tokens <= 0:
+                max_tokens = None
+
+            callbacks = [callback]
+            # Enable langchain-chatchat to support langfuse
+            import os
+
+            langfuse_secret_key = os.environ.get("LANGFUSE_SECRET_KEY")
+            langfuse_public_key = os.environ.get("LANGFUSE_PUBLIC_KEY")
+            langfuse_host = os.environ.get("LANGFUSE_HOST")
+            if langfuse_secret_key and langfuse_public_key and langfuse_host:
+                from langfuse import Langfuse
+                from langfuse.callback import CallbackHandler
+
+                langfuse_handler = CallbackHandler()
+                callbacks.append(langfuse_handler)
+
+            model = get_ChatOpenAI(
+                model_name=model_name,
+                temperature=temperature,
+                max_tokens=max_tokens,
+                callbacks=callbacks,
+            )
+            embed_func = get_Embeddings()
+            embeddings = await embed_func.aembed_query(query)
+            with memo_faiss_pool.acquire(knowledge_id) as vs:
+                docs = vs.similarity_search_with_score_by_vector(
+                    embeddings, k=top_k, score_threshold=score_threshold
+                )
+                docs = [x[0] for x in docs]
+
+            context = "\n".join([doc.page_content for doc in docs])
+            if len(docs) == 0:  # 如果没有找到相关文档，使用Empty模板
+                prompt_template = get_prompt_template("rag", "empty")
+            else:
+                prompt_template = get_prompt_template("rag", "default")
+            input_msg = History(role="user", content=prompt_template).to_msg_template(False)
+            chat_prompt = ChatPromptTemplate.from_messages(
+                [i.to_msg_template() for i in history] + [input_msg]
             )
-            docs = [x[0] for x in docs]
-
-        context = "\n".join([doc.page_content for doc in docs])
-        if len(docs) == 0:  # 如果没有找到相关文档，使用Empty模板
-            prompt_template = get_prompt_template("rag", "empty")
-        else:
-            prompt_template = get_prompt_template("rag", "default")
-        input_msg = History(role="user", content=prompt_template).to_msg_template(False)
-        chat_prompt = ChatPromptTemplate.from_messages(
-            [i.to_msg_template() for i in history] + [input_msg]
-        )
-
-        chain = LLMChain(prompt=chat_prompt, llm=model)
-
-        # Begin a task that runs in the background.
-        task = asyncio.create_task(
-            wrap_done(
-                chain.acall({"context": context, "question": query}), callback.done
-            ),
-        )
 
-        source_documents = []
-        for inum, doc in enumerate(docs):
-            filename = doc.metadata.get("source")
-            text = f"""出处 [{inum + 1}] [{filename}] \n\n{doc.page_content}\n\n"""
-            source_documents.append(text)
+            chain = LLMChain(prompt=chat_prompt, llm=model)
 
-        if len(source_documents) == 0:  # 没有找到相关文档
-            source_documents.append(
-                f"""<span style='color:red'>未找到相关文档,该回答为大模型自身能力解答！</span>"""
+            # Begin a task that runs in the background.
+            task = asyncio.create_task(
+                wrap_done(
+                    chain.acall({"context": context, "question": query}), callback.done
+                ),
             )
 
-        if stream:
-            async for token in callback.aiter():
-                # Use server-sent-events to stream the response
-                yield json.dumps({"answer": token}, ensure_ascii=False)
-            yield json.dumps({"docs": source_documents}, ensure_ascii=False)
-        else:
-            answer = ""
-            async for token in callback.aiter():
-                answer += token
-            yield json.dumps(
-                {"answer": answer, "docs": source_documents}, ensure_ascii=False
-            )
-        await task
+            source_documents = []
+            for inum, doc in enumerate(docs):
+                filename = doc.metadata.get("source")
+                text = f"""出处 [{inum + 1}] [{filename}] \n\n{doc.page_content}\n\n"""
+                source_documents.append(text)
+
+            if len(source_documents) == 0:  # 没有找到相关文档
+                source_documents.append(
+                    f"""<span style='color:red'>未找到相关文档,该回答为大模型自身能力解答！</span>"""
+                )
+
+            if stream:
+                async for token in callback.aiter():
+                    # Use server-sent-events to stream the response
+                    yield json.dumps({"answer": token}, ensure_ascii=False)
+                yield json.dumps({"docs": source_documents}, ensure_ascii=False)
+            else:
+                answer = ""
+                async for token in callback.aiter():
+                    answer += token
+                yield json.dumps(
+                    {"answer": answer, "docs": source_documents}, ensure_ascii=False
+                )
+            await task
+        except asyncio.exceptions.CancelledError:
+            logger.warning("streaming progress has been interrupted by user.")
+            return
 
     return EventSourceResponse(knowledge_base_chat_iterator())
diff --git a/libs/chatchat-server/chatchat/server/chat/kb_chat.py b/libs/chatchat-server/chatchat/server/chat/kb_chat.py
index dd90811e0f..3aa1ce4e54 100644
--- a/libs/chatchat-server/chatchat/server/chat/kb_chat.py
+++ b/libs/chatchat-server/chatchat/server/chat/kb_chat.py
@@ -1,7 +1,6 @@
 from __future__ import annotations
 
 import asyncio, json
-from urllib.parse import urlencode
 import uuid
 from typing import AsyncIterable, List, Optional, Literal
 
@@ -20,10 +19,14 @@
 from chatchat.server.knowledge_base.kb_doc_api import search_docs, search_temp_docs
 from chatchat.server.knowledge_base.utils import format_reference
 from chatchat.server.utils import (wrap_done, get_ChatOpenAI, get_default_llm,
-                                   BaseResponse, get_prompt_template,
+                                   BaseResponse, get_prompt_template, build_logger,
+                                   check_embed_model, api_address
                                 )
 
 
+logger = build_logger()
+
+
 async def kb_chat(query: str = Body(..., description="用户输入", examples=["你好"]),
                 mode: Literal["local_kb", "temp_kb", "search_engine"] = Body("local_kb", description="知识来源"),
                 kb_name: str = Body("", description="mode=local_kb时为知识库名称；temp_kb时为临时知识库ID，search_engine时为搜索引擎名称", examples=["samples"]),
@@ -63,143 +66,161 @@ async def kb_chat(query: str = Body(..., description="用户输入", examples=["
             return BaseResponse(code=404, msg=f"未找到知识库 {kb_name}")
     
     async def knowledge_base_chat_iterator() -> AsyncIterable[str]:
-        nonlocal history, prompt_name
-
-        history = [History.from_data(h) for h in history]
-
-        if mode == "local_kb":
-            docs = await run_in_threadpool(search_docs,
-                                            query=query,
-                                            knowledge_base_name=kb_name,
-                                            top_k=top_k,
-                                            score_threshold=score_threshold,
-                                            file_name="",
-                                            metadata={})
-            source_documents = format_reference(kb_name, docs, request.base_url)
-        elif mode == "temp_kb":
-            docs = await run_in_threadpool(search_temp_docs,
-                                            kb_name,
-                                            query=query,
-                                            top_k=top_k,
-                                            score_threshold=score_threshold)
-            source_documents = format_reference(kb_name, docs, request.base_url)
-        elif mode == "search_engine":
-            result = await run_in_threadpool(search_engine, query, top_k, kb_name)
-            docs = [x.dict() for x in result.get("docs", [])]
-            source_documents = [f"""出处 [{i + 1}] [{d['metadata']['filename']}]({d['metadata']['source']}) \n\n{d['page_content']}\n\n""" for i,d in enumerate(docs)]
-        else:
-            docs = []
-            source_documents = []
-        # import rich
-        # rich.print(dict(
-        #     mode=mode,
-        #     query=query,
-        #     knowledge_base_name=kb_name,
-        #     top_k=top_k,
-        #     score_threshold=score_threshold,
-        # ))
-        # rich.print(docs)
-        if return_direct:
-            yield OpenAIChatOutput(
-                id=f"chat{uuid.uuid4()}",
-                model=None,
-                object="chat.completion",
-                content="",
-                role="assistant",
-                finish_reason="stop",
-                docs=source_documents,
-            ) .model_dump_json()
-            return
-
-        callback = AsyncIteratorCallbackHandler()
-        callbacks = [callback]
-
-        # Enable langchain-chatchat to support langfuse
-        import os
-        langfuse_secret_key = os.environ.get('LANGFUSE_SECRET_KEY')
-        langfuse_public_key = os.environ.get('LANGFUSE_PUBLIC_KEY')
-        langfuse_host = os.environ.get('LANGFUSE_HOST')
-        if langfuse_secret_key and langfuse_public_key and langfuse_host :
-            from langfuse import Langfuse
-            from langfuse.callback import CallbackHandler
-            langfuse_handler = CallbackHandler()
-            callbacks.append(langfuse_handler)
-
-        llm = get_ChatOpenAI(
-            model_name=model,
-            temperature=temperature,
-            max_tokens=max_tokens,
-            callbacks=callbacks,
-        )
-        # TODO： 视情况使用 API
-        # # 加入reranker
-        # if Settings.kb_settings.USE_RERANKER:
-        #     reranker_model_path = get_model_path(Settings.kb_settings.RERANKER_MODEL)
-        #     reranker_model = LangchainReranker(top_n=top_k,
-        #                                     device=embedding_device(),
-        #                                     max_length=Settings.kb_settings.RERANKER_MAX_LENGTH,
-        #                                     model_name_or_path=reranker_model_path
-        #                                     )
-        #     print("-------------before rerank-----------------")
-        #     print(docs)
-        #     docs = reranker_model.compress_documents(documents=docs,
-        #                                              query=query)
-        #     print("------------after rerank------------------")
-        #     print(docs)
-        context = "\n\n".join([doc["page_content"] for doc in docs])
-
-        if len(docs) == 0:  # 如果没有找到相关文档，使用empty模板
-            prompt_name = "empty"
-        prompt_template = get_prompt_template("rag", prompt_name)
-        input_msg = History(role="user", content=prompt_template).to_msg_template(False)
-        chat_prompt = ChatPromptTemplate.from_messages(
-            [i.to_msg_template() for i in history] + [input_msg])
-
-        chain = chat_prompt | llm
-
-        # Begin a task that runs in the background.
-        task = asyncio.create_task(wrap_done(
-            chain.ainvoke({"context": context, "question": query}),
-            callback.done),
-        )
-
-        if len(source_documents) == 0:  # 没有找到相关文档
-            source_documents.append(f"<span style='color:red'>未找到相关文档,该回答为大模型自身能力解答！</span>")
-
-        if stream:
-            # yield documents first
-            ret = OpenAIChatOutput(
-                id=f"chat{uuid.uuid4()}",
-                object="chat.completion.chunk",
-                content="",
-                role="assistant",
-                model=model,
-                docs=source_documents,
+        try:
+            nonlocal history, prompt_name, max_tokens
+
+            history = [History.from_data(h) for h in history]
+
+            if mode == "local_kb":
+                kb = KBServiceFactory.get_service_by_name(kb_name)
+                ok, msg = kb.check_embed_model()
+                if not ok:
+                    raise ValueError(msg)
+                docs = await run_in_threadpool(search_docs,
+                                                query=query,
+                                                knowledge_base_name=kb_name,
+                                                top_k=top_k,
+                                                score_threshold=score_threshold,
+                                                file_name="",
+                                                metadata={})
+                source_documents = format_reference(kb_name, docs, api_address(is_public=True))
+            elif mode == "temp_kb":
+                ok, msg = check_embed_model()
+                if not ok:
+                    raise ValueError(msg)
+                docs = await run_in_threadpool(search_temp_docs,
+                                                kb_name,
+                                                query=query,
+                                                top_k=top_k,
+                                                score_threshold=score_threshold)
+                source_documents = format_reference(kb_name, docs, api_address(is_public=True))
+            elif mode == "search_engine":
+                result = await run_in_threadpool(search_engine, query, top_k, kb_name)
+                docs = [x.dict() for x in result.get("docs", [])]
+                source_documents = [f"""出处 [{i + 1}] [{d['metadata']['filename']}]({d['metadata']['source']}) \n\n{d['page_content']}\n\n""" for i,d in enumerate(docs)]
+            else:
+                docs = []
+                source_documents = []
+            # import rich
+            # rich.print(dict(
+            #     mode=mode,
+            #     query=query,
+            #     knowledge_base_name=kb_name,
+            #     top_k=top_k,
+            #     score_threshold=score_threshold,
+            # ))
+            # rich.print(docs)
+            if return_direct:
+                yield OpenAIChatOutput(
+                    id=f"chat{uuid.uuid4()}",
+                    model=None,
+                    object="chat.completion",
+                    content="",
+                    role="assistant",
+                    finish_reason="stop",
+                    docs=source_documents,
+                ) .model_dump_json()
+                return
+
+            callback = AsyncIteratorCallbackHandler()
+            callbacks = [callback]
+
+            # Enable langchain-chatchat to support langfuse
+            import os
+            langfuse_secret_key = os.environ.get('LANGFUSE_SECRET_KEY')
+            langfuse_public_key = os.environ.get('LANGFUSE_PUBLIC_KEY')
+            langfuse_host = os.environ.get('LANGFUSE_HOST')
+            if langfuse_secret_key and langfuse_public_key and langfuse_host :
+                from langfuse import Langfuse
+                from langfuse.callback import CallbackHandler
+                langfuse_handler = CallbackHandler()
+                callbacks.append(langfuse_handler)
+
+            if max_tokens in [None, 0]:
+                max_tokens = Settings.model_settings.MAX_TOKENS
+
+            llm = get_ChatOpenAI(
+                model_name=model,
+                temperature=temperature,
+                max_tokens=max_tokens,
+                callbacks=callbacks,
+            )
+            # TODO： 视情况使用 API
+            # # 加入reranker
+            # if Settings.kb_settings.USE_RERANKER:
+            #     reranker_model_path = get_model_path(Settings.kb_settings.RERANKER_MODEL)
+            #     reranker_model = LangchainReranker(top_n=top_k,
+            #                                     device=embedding_device(),
+            #                                     max_length=Settings.kb_settings.RERANKER_MAX_LENGTH,
+            #                                     model_name_or_path=reranker_model_path
+            #                                     )
+            #     print("-------------before rerank-----------------")
+            #     print(docs)
+            #     docs = reranker_model.compress_documents(documents=docs,
+            #                                              query=query)
+            #     print("------------after rerank------------------")
+            #     print(docs)
+            context = "\n\n".join([doc["page_content"] for doc in docs])
+
+            if len(docs) == 0:  # 如果没有找到相关文档，使用empty模板
+                prompt_name = "empty"
+            prompt_template = get_prompt_template("rag", prompt_name)
+            input_msg = History(role="user", content=prompt_template).to_msg_template(False)
+            chat_prompt = ChatPromptTemplate.from_messages(
+                [i.to_msg_template() for i in history] + [input_msg])
+
+            chain = chat_prompt | llm
+
+            # Begin a task that runs in the background.
+            task = asyncio.create_task(wrap_done(
+                chain.ainvoke({"context": context, "question": query}),
+                callback.done),
             )
-            yield ret.model_dump_json()
 
-            async for token in callback.aiter():
+            if len(source_documents) == 0:  # 没有找到相关文档
+                source_documents.append(f"<span style='color:red'>未找到相关文档,该回答为大模型自身能力解答！</span>")
+
+            if stream:
+                # yield documents first
                 ret = OpenAIChatOutput(
                     id=f"chat{uuid.uuid4()}",
                     object="chat.completion.chunk",
-                    content=token,
+                    content="",
                     role="assistant",
                     model=model,
+                    docs=source_documents,
                 )
                 yield ret.model_dump_json()
-        else:
-            answer = ""
-            async for token in callback.aiter():
-                answer += token
-            ret = OpenAIChatOutput(
-                id=f"chat{uuid.uuid4()}",
-                object="chat.completion",
-                content=answer,
-                role="assistant",
-                model=model,
-            )
-            yield ret.model_dump_json()
-        await task
+
+                async for token in callback.aiter():
+                    ret = OpenAIChatOutput(
+                        id=f"chat{uuid.uuid4()}",
+                        object="chat.completion.chunk",
+                        content=token,
+                        role="assistant",
+                        model=model,
+                    )
+                    yield ret.model_dump_json()
+            else:
+                answer = ""
+                async for token in callback.aiter():
+                    answer += token
+                ret = OpenAIChatOutput(
+                    id=f"chat{uuid.uuid4()}",
+                    object="chat.completion",
+                    content=answer,
+                    role="assistant",
+                    model=model,
+                )
+                yield ret.model_dump_json()
+            await task
+        except asyncio.exceptions.CancelledError:
+            logger.warning("streaming progress has been interrupted by user.")
+            return
+        except Exception as e:
+            logger.error(f"error in knowledge chat: {e}")
+            yield {"data": json.dumps({"error": str(e)})}
+            return
 
     if stream:
         return EventSourceResponse(knowledge_base_chat_iterator())
diff --git a/libs/chatchat-server/chatchat/server/constant/__init__.py b/libs/chatchat-server/chatchat/server/constant/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/chatchat-server/chatchat/server/constant/response_code.py b/libs/chatchat-server/chatchat/server/constant/response_code.py
new file mode 100644
index 0000000000..8dafd53655
--- /dev/null
+++ b/libs/chatchat-server/chatchat/server/constant/response_code.py
@@ -0,0 +1,5 @@
+class ResponseCode:
+    SUCCESS = 200
+    BAD_REQUEST = 400
+    NOT_FOUND = 404
+    INTERNAL_SERVER_ERROR = 500
diff --git a/libs/chatchat-server/chatchat/server/file_rag/retrievers/__init__.py b/libs/chatchat-server/chatchat/server/file_rag/retrievers/__init__.py
index ef78fac775..7ef2f45278 100644
--- a/libs/chatchat-server/chatchat/server/file_rag/retrievers/__init__.py
+++ b/libs/chatchat-server/chatchat/server/file_rag/retrievers/__init__.py
@@ -1,3 +1,4 @@
 from chatchat.server.file_rag.retrievers.base import BaseRetrieverService
 from chatchat.server.file_rag.retrievers.ensemble import EnsembleRetrieverService
 from chatchat.server.file_rag.retrievers.vectorstore import VectorstoreRetrieverService
+from chatchat.server.file_rag.retrievers.milvus_vectorstore import MilvusVectorstoreRetrieverService
\ No newline at end of file
diff --git a/libs/chatchat-server/chatchat/server/file_rag/retrievers/base.py b/libs/chatchat-server/chatchat/server/file_rag/retrievers/base.py
index 6cda595825..e1491578dd 100644
--- a/libs/chatchat-server/chatchat/server/file_rag/retrievers/base.py
+++ b/libs/chatchat-server/chatchat/server/file_rag/retrievers/base.py
@@ -1,3 +1,6 @@
+from __future__ import annotations
+
+
 from abc import ABCMeta, abstractmethod
 
 from langchain.vectorstores import VectorStore
@@ -15,7 +18,7 @@ def do_init(self, **kwargs):
     def from_vectorstore(
         vectorstore: VectorStore,
         top_k: int,
-        score_threshold: int or float,
+        score_threshold: int | float,
     ):
         pass
 
diff --git a/libs/chatchat-server/chatchat/server/file_rag/retrievers/ensemble.py b/libs/chatchat-server/chatchat/server/file_rag/retrievers/ensemble.py
index 31a6aaea78..36b234f5bf 100644
--- a/libs/chatchat-server/chatchat/server/file_rag/retrievers/ensemble.py
+++ b/libs/chatchat-server/chatchat/server/file_rag/retrievers/ensemble.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
 from langchain.retrievers import EnsembleRetriever
 from langchain.vectorstores import VectorStore
 from langchain_community.retrievers import BM25Retriever
@@ -20,7 +22,7 @@ def do_init(
     def from_vectorstore(
         vectorstore: VectorStore,
         top_k: int,
-        score_threshold: int or float,
+        score_threshold: int | float,
     ):
         faiss_retriever = vectorstore.as_retriever(
             search_type="similarity_score_threshold",
diff --git a/libs/chatchat-server/chatchat/server/file_rag/retrievers/milvus_vectorstore.py b/libs/chatchat-server/chatchat/server/file_rag/retrievers/milvus_vectorstore.py
new file mode 100644
index 0000000000..d0de13baba
--- /dev/null
+++ b/libs/chatchat-server/chatchat/server/file_rag/retrievers/milvus_vectorstore.py
@@ -0,0 +1,124 @@
+import warnings
+
+from langchain.vectorstores import VectorStore
+from langchain_core.retrievers import BaseRetriever
+from langchain_core.vectorstores import VectorStoreRetriever
+
+from chatchat.server.file_rag.retrievers.base import BaseRetrieverService
+
+from langchain.docstore.document import Document
+from langchain_core.callbacks.manager import (
+        AsyncCallbackManagerForRetrieverRun,
+        CallbackManagerForRetrieverRun
+)
+
+from typing import List
+
+class MilvusRetriever(VectorStoreRetriever):
+    def _get_relevant_documents(
+        self, query: str, *, run_manager: CallbackManagerForRetrieverRun
+    ) -> List[Document]:
+        if self.search_type == "similarity":
+            docs = self.vectorstore.similarity_search(query, **self.search_kwargs)
+        elif self.search_type == "similarity_score_threshold":
+            docs_and_similarities = self.vectorstore.similarity_search_with_score(query)
+            
+            score_threshold = self.search_kwargs.get("score_threshold", None)
+            k = self.search_kwargs.get("k", 4)
+            
+            if any(
+                similarity < 0.0 or similarity > 1.0
+                for _, similarity in docs_and_similarities
+            ):
+                warnings.warn(
+                    "Relevance scores must be between"
+                    f" 0 and 1, got {docs_and_similarities}"
+                )
+            if score_threshold is not None:
+                docs_and_similarities = [
+                doc
+                for doc, similarity in docs_and_similarities
+                if similarity >= score_threshold
+            ]
+            if len(docs_and_similarities) == 0:
+                warnings.warn(
+                    "No relevant docs were retrieved using the relevance score"
+                    f" threshold {score_threshold}"
+                )
+            return docs_and_similarities
+        elif self.search_type == "mmr":
+            docs = self.vectorstore.max_marginal_relevance_search(
+                query, **self.search_kwargs
+            )
+        else:
+            raise ValueError(f"search_type of {self.search_type} not allowed.")
+        return docs
+
+    async def _aget_relevant_documents(
+        self, query: str, *, run_manager: AsyncCallbackManagerForRetrieverRun
+    ) -> List[Document]:
+        if self.search_type == "similarity":
+            docs = await self.vectorstore.asimilarity_search(
+                query, **self.search_kwargs
+            )
+        elif self.search_type == "similarity_score_threshold":
+            docs_and_similarities = (
+                await self.vectorstore.asimilarity_search_with_score(query)
+            )
+            
+            score_threshold = self.search_kwargs.get("score_threshold", None)
+            k = self.search_kwargs.get("k", 4)
+            
+            if any(
+                similarity < 0.0 or similarity > 1.0
+                for _, similarity in docs_and_similarities
+            ):
+                warnings.warn(
+                    "Relevance scores must be between"
+                    f" 0 and 1, got {docs_and_similarities}"
+                )
+            if score_threshold is not None:
+                docs_and_similarities = [
+                (doc, similarity)
+                for doc, similarity in docs_and_similarities
+                if similarity >= score_threshold
+            ]
+            if len(docs_and_similarities) == 0:
+                warnings.warn(
+                    "No relevant docs were retrieved using the relevance score"
+                    f" threshold {score_threshold}"
+                )
+            return docs_and_similarities
+        elif self.search_type == "mmr":
+            docs = await self.vectorstore.amax_marginal_relevance_search(
+                query, **self.search_kwargs
+            )
+        else:
+            raise ValueError(f"search_type of {self.search_type} not allowed.")
+        return docs 
+
+class MilvusVectorstoreRetrieverService(BaseRetrieverService):
+    def do_init(
+        self,
+        retriever: BaseRetriever = None,
+        top_k: int = 5,
+    ):
+        self.vs = None
+        self.top_k = top_k
+        self.retriever = retriever
+
+    @staticmethod
+    def from_vectorstore(
+        vectorstore: VectorStore,
+        top_k: int,
+        score_threshold: int or float,
+    ):
+        retriever = MilvusRetriever(vectorstore=vectorstore, 
+                                    search_type="similarity_score_threshold",
+                                    search_kwargs={"score_threshold": score_threshold, "k": top_k}
+                                    )
+        
+        return MilvusVectorstoreRetrieverService(retriever=retriever)
+
+    def get_relevant_documents(self, query: str):
+        return self.retriever.get_relevant_documents(query)[: self.top_k]
diff --git a/libs/chatchat-server/chatchat/server/file_rag/retrievers/vectorstore.py b/libs/chatchat-server/chatchat/server/file_rag/retrievers/vectorstore.py
index ba65353c3d..018e3e506f 100644
--- a/libs/chatchat-server/chatchat/server/file_rag/retrievers/vectorstore.py
+++ b/libs/chatchat-server/chatchat/server/file_rag/retrievers/vectorstore.py
@@ -1,3 +1,6 @@
+from __future__ import annotations
+
+
 from langchain.vectorstores import VectorStore
 from langchain_core.retrievers import BaseRetriever
 
@@ -18,7 +21,7 @@ def do_init(
     def from_vectorstore(
         vectorstore: VectorStore,
         top_k: int,
-        score_threshold: int or float,
+        score_threshold: int | float,
     ):
         retriever = vectorstore.as_retriever(
             search_type="similarity_score_threshold",
diff --git a/libs/chatchat-server/chatchat/server/file_rag/utils.py b/libs/chatchat-server/chatchat/server/file_rag/utils.py
index cd767451a4..2f35245c45 100644
--- a/libs/chatchat-server/chatchat/server/file_rag/utils.py
+++ b/libs/chatchat-server/chatchat/server/file_rag/utils.py
@@ -2,9 +2,11 @@
     BaseRetrieverService,
     EnsembleRetrieverService,
     VectorstoreRetrieverService,
+    MilvusVectorstoreRetrieverService,
 )
 
 Retrivals = {
+    "milvusvectorstore": MilvusVectorstoreRetrieverService,
     "vectorstore": VectorstoreRetrieverService,
     "ensemble": EnsembleRetrieverService,
 }
diff --git a/libs/chatchat-server/chatchat/server/knowledge_base/kb_cache/faiss_cache.py b/libs/chatchat-server/chatchat/server/knowledge_base/kb_cache/faiss_cache.py
index 0ce85f2074..5c9bc0d07a 100644
--- a/libs/chatchat-server/chatchat/server/knowledge_base/kb_cache/faiss_cache.py
+++ b/libs/chatchat-server/chatchat/server/knowledge_base/kb_cache/faiss_cache.py
@@ -137,7 +137,7 @@ def load_vector_store(
         except Exception as e:
             if locked:  # we don't know exception raised before or after atomic.release
                 self.atomic.release()
-            logger.error(e, exc_info=True)
+            logger.exception(e)
             raise RuntimeError(f"向量库 {kb_name} 加载失败。")
         return self.get((kb_name, vector_name))
 
diff --git a/libs/chatchat-server/chatchat/server/knowledge_base/kb_doc_api.py b/libs/chatchat-server/chatchat/server/knowledge_base/kb_doc_api.py
index aa250180bd..fd5ea6bb05 100644
--- a/libs/chatchat-server/chatchat/server/knowledge_base/kb_doc_api.py
+++ b/libs/chatchat-server/chatchat/server/knowledge_base/kb_doc_api.py
@@ -1,3 +1,4 @@
+import asyncio
 import json
 import os
 import urllib
@@ -32,11 +33,13 @@
 )
 from chatchat.utils import build_logger
 
-
 logger = build_logger()
 
 
-def search_temp_docs(knowledge_id: str, query: str, top_k:int, score_threshold: float) -> List[Dict]:
+def search_temp_docs(knowledge_id: str = Body(..., description="知识库 ID", examples=["example_id"]),
+                     query: str = Body("", description="用户输入", examples=["你好"]),
+                     top_k: int = Body(..., description="返回的文档数量", examples=[5]),
+                     score_threshold: float = Body(..., description="分数阈值", examples=[0.8])) -> List[Dict]:
     '''从临时 FAISS 知识库中检索文档，用于文件对话'''
     with memo_faiss_pool.acquire(knowledge_id) as vs:
         docs = vs.similarity_search_with_score(
@@ -47,21 +50,21 @@ def search_temp_docs(knowledge_id: str, query: str, top_k:int, score_threshold:
 
 
 def search_docs(
-    query: str = Body("", description="用户输入", examples=["你好"]),
-    knowledge_base_name: str = Body(
-        ..., description="知识库名称", examples=["samples"]
-    ),
-    top_k: int = Body(Settings.kb_settings.VECTOR_SEARCH_TOP_K, description="匹配向量数"),
-    score_threshold: float = Body(
-        Settings.kb_settings.SCORE_THRESHOLD,
-        description="知识库匹配相关度阈值，取值范围在0-1之间，"
-        "SCORE越小，相关度越高，"
-        "取到2相当于不筛选，建议设置在0.5左右",
-        ge=0.0,
-        le=2.0,
-    ),
-    file_name: str = Body("", description="文件名称，支持 sql 通配符"),
-    metadata: dict = Body({}, description="根据 metadata 进行过滤，仅支持一级键"),
+        query: str = Body("", description="用户输入", examples=["你好"]),
+        knowledge_base_name: str = Body(
+            ..., description="知识库名称", examples=["samples"]
+        ),
+        top_k: int = Body(Settings.kb_settings.VECTOR_SEARCH_TOP_K, description="匹配向量数"),
+        score_threshold: float = Body(
+            Settings.kb_settings.SCORE_THRESHOLD,
+            description="知识库匹配相关度阈值，取值范围在0-1之间，"
+                        "SCORE越小，相关度越高，"
+                        "取到2相当于不筛选，建议设置在0.5左右",
+            ge=0.0,
+            le=2.0,
+        ),
+        file_name: str = Body("", description="文件名称，支持 sql 通配符"),
+        metadata: dict = Body({}, description="根据 metadata 进行过滤，仅支持一级键"),
 ) -> List[Dict]:
     kb = KBServiceFactory.get_service_by_name(knowledge_base_name)
     data = []
@@ -69,7 +72,7 @@ def search_docs(
         if query:
             docs = kb.search_docs(query, top_k, score_threshold)
             # data = [DocumentWithVSId(**x[0].dict(), score=x[1], id=x[0].metadata.get("id")) for x in docs]
-            data = [DocumentWithVSId(**x.dict(), id=x.metadata.get("id")) for x in docs]
+            data = [DocumentWithVSId(**{"id": x.metadata.get("id"), **x.dict()}) for x in docs]
         elif file_name or metadata:
             data = kb.list_docs(file_name=file_name, metadata=metadata)
             for d in data:
@@ -94,7 +97,7 @@ def list_files(knowledge_base_name: str) -> ListResponse:
 
 
 def _save_files_in_thread(
-    files: List[UploadFile], knowledge_base_name: str, override: bool
+        files: List[UploadFile], knowledge_base_name: str, override: bool
 ):
     """
     通过多线程将上传的文件保存到对应知识库目录内。
@@ -114,9 +117,9 @@ def save_file(file: UploadFile, knowledge_base_name: str, override: bool) -> dic
 
             file_content = file.file.read()  # 读取上传文件的内容
             if (
-                os.path.isfile(file_path)
-                and not override
-                and os.path.getsize(file_path) == len(file_content)
+                    os.path.isfile(file_path)
+                    and not override
+                    and os.path.getsize(file_path) == len(file_content)
             ):
                 file_status = f"文件 {filename} 已存在。"
                 logger.warn(file_status)
@@ -154,17 +157,17 @@ def save_file(file: UploadFile, knowledge_base_name: str, override: bool) -> dic
 
 
 def upload_docs(
-    files: List[UploadFile] = File(..., description="上传文件，支持多文件"),
-    knowledge_base_name: str = Form(
-        ..., description="知识库名称", examples=["samples"]
-    ),
-    override: bool = Form(False, description="覆盖已有文件"),
-    to_vector_store: bool = Form(True, description="上传文件后是否进行向量化"),
-    chunk_size: int = Form(Settings.kb_settings.CHUNK_SIZE, description="知识库中单段文本最大长度"),
-    chunk_overlap: int = Form(Settings.kb_settings.OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
-    zh_title_enhance: bool = Form(Settings.kb_settings.ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
-    docs: str = Form("", description="自定义的docs，需要转为json字符串"),
-    not_refresh_vs_cache: bool = Form(False, description="暂不保存向量库（用于FAISS）"),
+        files: List[UploadFile] = File(..., description="上传文件，支持多文件"),
+        knowledge_base_name: str = Form(
+            ..., description="知识库名称", examples=["samples"]
+        ),
+        override: bool = Form(False, description="覆盖已有文件"),
+        to_vector_store: bool = Form(True, description="上传文件后是否进行向量化"),
+        chunk_size: int = Form(Settings.kb_settings.CHUNK_SIZE, description="知识库中单段文本最大长度"),
+        chunk_overlap: int = Form(Settings.kb_settings.OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
+        zh_title_enhance: bool = Form(Settings.kb_settings.ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
+        docs: str = Form("", description="自定义的docs，需要转为json字符串"),
+        not_refresh_vs_cache: bool = Form(False, description="暂不保存向量库（用于FAISS）"),
 ) -> BaseResponse:
     """
     API接口：上传文件，并/或向量化
@@ -182,7 +185,7 @@ def upload_docs(
 
     # 先将上传的文件保存到磁盘
     for result in _save_files_in_thread(
-        files, knowledge_base_name=knowledge_base_name, override=override
+            files, knowledge_base_name=knowledge_base_name, override=override
     ):
         filename = result["data"]["file_name"]
         if result["code"] != 200:
@@ -213,10 +216,10 @@ def upload_docs(
 
 
 def delete_docs(
-    knowledge_base_name: str = Body(..., examples=["samples"]),
-    file_names: List[str] = Body(..., examples=[["file_name.md", "test.txt"]]),
-    delete_content: bool = Body(False),
-    not_refresh_vs_cache: bool = Body(False, description="暂不保存向量库（用于FAISS）"),
+        knowledge_base_name: str = Body(..., examples=["samples"]),
+        file_names: List[str] = Body(..., examples=[["file_name.md", "test.txt"]]),
+        delete_content: bool = Body(False),
+        not_refresh_vs_cache: bool = Body(False, description="暂不保存向量库（用于FAISS）"),
 ) -> BaseResponse:
     if not validate_kb_name(knowledge_base_name):
         return BaseResponse(code=403, msg="Don't attack me")
@@ -250,10 +253,10 @@ def delete_docs(
 
 
 def update_info(
-    knowledge_base_name: str = Body(
-        ..., description="知识库名称", examples=["samples"]
-    ),
-    kb_info: str = Body(..., description="知识库介绍", examples=["这是一个知识库"]),
+        knowledge_base_name: str = Body(
+            ..., description="知识库名称", examples=["samples"]
+        ),
+        kb_info: str = Body(..., description="知识库介绍", examples=["这是一个知识库"]),
 ):
     if not validate_kb_name(knowledge_base_name):
         return BaseResponse(code=403, msg="Don't attack me")
@@ -267,18 +270,18 @@ def update_info(
 
 
 def update_docs(
-    knowledge_base_name: str = Body(
-        ..., description="知识库名称", examples=["samples"]
-    ),
-    file_names: List[str] = Body(
-        ..., description="文件名称，支持多文件", examples=[["file_name1", "text.txt"]]
-    ),
-    chunk_size: int = Body(Settings.kb_settings.CHUNK_SIZE, description="知识库中单段文本最大长度"),
-    chunk_overlap: int = Body(Settings.kb_settings.OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
-    zh_title_enhance: bool = Body(Settings.kb_settings.ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
-    override_custom_docs: bool = Body(False, description="是否覆盖之前自定义的docs"),
-    docs: str = Body("", description="自定义的docs，需要转为json字符串"),
-    not_refresh_vs_cache: bool = Body(False, description="暂不保存向量库（用于FAISS）"),
+        knowledge_base_name: str = Body(
+            ..., description="知识库名称", examples=["samples"]
+        ),
+        file_names: List[str] = Body(
+            ..., description="文件名称，支持多文件", examples=[["file_name1", "text.txt"]]
+        ),
+        chunk_size: int = Body(Settings.kb_settings.CHUNK_SIZE, description="知识库中单段文本最大长度"),
+        chunk_overlap: int = Body(Settings.kb_settings.OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
+        zh_title_enhance: bool = Body(Settings.kb_settings.ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
+        override_custom_docs: bool = Body(False, description="是否覆盖之前自定义的docs"),
+        docs: str = Body("", description="自定义的docs，需要转为json字符串"),
+        not_refresh_vs_cache: bool = Body(False, description="暂不保存向量库（用于FAISS）"),
 ) -> BaseResponse:
     """
     更新知识库文档
@@ -315,10 +318,10 @@ def update_docs(
     # 从文件生成docs，并进行向量化。
     # 这里利用了KnowledgeFile的缓存功能，在多线程中加载Document，然后传给KnowledgeFile
     for status, result in files2docs_in_thread(
-        kb_files,
-        chunk_size=chunk_size,
-        chunk_overlap=chunk_overlap,
-        zh_title_enhance=zh_title_enhance,
+            kb_files,
+            chunk_size=chunk_size,
+            chunk_overlap=chunk_overlap,
+            zh_title_enhance=zh_title_enhance,
     ):
         if status:
             kb_name, file_name, new_docs = result
@@ -353,11 +356,11 @@ def update_docs(
 
 
 def download_doc(
-    knowledge_base_name: str = Query(
-        ..., description="知识库名称", examples=["samples"]
-    ),
-    file_name: str = Query(..., description="文件名称", examples=["test.txt"]),
-    preview: bool = Query(False, description="是：浏览器内预览；否：下载"),
+        knowledge_base_name: str = Query(
+            ..., description="知识库名称", examples=["samples"]
+        ),
+        file_name: str = Query(..., description="文件名称", examples=["test.txt"]),
+        preview: bool = Query(False, description="是：浏览器内预览；否：下载"),
 ):
     """
     下载知识库文档
@@ -395,14 +398,14 @@ def download_doc(
 
 
 def recreate_vector_store(
-    knowledge_base_name: str = Body(..., examples=["samples"]),
-    allow_empty_kb: bool = Body(True),
-    vs_type: str = Body(Settings.kb_settings.DEFAULT_VS_TYPE),
-    embed_model: str = Body(get_default_embedding()),
-    chunk_size: int = Body(Settings.kb_settings.CHUNK_SIZE, description="知识库中单段文本最大长度"),
-    chunk_overlap: int = Body(Settings.kb_settings.OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
-    zh_title_enhance: bool = Body(Settings.kb_settings.ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
-    not_refresh_vs_cache: bool = Body(False, description="暂不保存向量库（用于FAISS）"),
+        knowledge_base_name: str = Body(..., examples=["samples"]),
+        allow_empty_kb: bool = Body(True),
+        vs_type: str = Body(Settings.kb_settings.DEFAULT_VS_TYPE),
+        embed_model: str = Body(get_default_embedding()),
+        chunk_size: int = Body(Settings.kb_settings.CHUNK_SIZE, description="知识库中单段文本最大长度"),
+        chunk_overlap: int = Body(Settings.kb_settings.OVERLAP_SIZE, description="知识库中相邻文本重合长度"),
+        zh_title_enhance: bool = Body(Settings.kb_settings.ZH_TITLE_ENHANCE, description="是否开启中文标题加强"),
+        not_refresh_vs_cache: bool = Body(False, description="暂不保存向量库（用于FAISS）"),
 ):
     """
     recreate vector store from the content.
@@ -412,57 +415,59 @@ def recreate_vector_store(
     """
 
     def output():
-        kb = KBServiceFactory.get_service(knowledge_base_name, vs_type, embed_model)
-        if not kb.exists() and not allow_empty_kb:
-            yield {"code": 404, "msg": f"未找到知识库 ‘{knowledge_base_name}’"}
-        else:
-            error_msg = (
-                f"could not recreate vector store because failed to access embed model."
-            )
-            if not kb.check_embed_model(error_msg):
-                yield {"code": 404, "msg": error_msg}
+        try:
+            kb = KBServiceFactory.get_service(knowledge_base_name, vs_type, embed_model)
+            if not kb.exists() and not allow_empty_kb:
+                yield {"code": 404, "msg": f"未找到知识库 ‘{knowledge_base_name}’"}
             else:
-                if kb.exists():
-                    kb.clear_vs()
-                kb.create_kb()
-                files = list_files_from_folder(knowledge_base_name)
-                kb_files = [(file, knowledge_base_name) for file in files]
-                i = 0
-                for status, result in files2docs_in_thread(
-                    kb_files,
-                    chunk_size=chunk_size,
-                    chunk_overlap=chunk_overlap,
-                    zh_title_enhance=zh_title_enhance,
-                ):
-                    if status:
-                        kb_name, file_name, docs = result
-                        kb_file = KnowledgeFile(
-                            filename=file_name, knowledge_base_name=kb_name
-                        )
-                        kb_file.splited_docs = docs
-                        yield json.dumps(
-                            {
-                                "code": 200,
-                                "msg": f"({i + 1} / {len(files)}): {file_name}",
-                                "total": len(files),
-                                "finished": i + 1,
-                                "doc": file_name,
-                            },
-                            ensure_ascii=False,
-                        )
-                        kb.add_doc(kb_file, not_refresh_vs_cache=True)
-                    else:
-                        kb_name, file_name, error = result
-                        msg = f"添加文件‘{file_name}’到知识库‘{knowledge_base_name}’时出错：{error}。已跳过。"
-                        logger.error(msg)
-                        yield json.dumps(
-                            {
-                                "code": 500,
-                                "msg": msg,
-                            }
-                        )
-                    i += 1
-                if not not_refresh_vs_cache:
-                    kb.save_vector_store()
+                ok, msg = kb.check_embed_model()
+                if not ok:
+                    yield {"code": 404, "msg": msg}
+                else:
+                    if kb.exists():
+                        kb.clear_vs()
+                    kb.create_kb()
+                    files = list_files_from_folder(knowledge_base_name)
+                    kb_files = [(file, knowledge_base_name) for file in files]
+                    i = 0
+                    for status, result in files2docs_in_thread(
+                            kb_files,
+                            chunk_size=chunk_size,
+                            chunk_overlap=chunk_overlap,
+                            zh_title_enhance=zh_title_enhance,
+                    ):
+                        if status:
+                            kb_name, file_name, docs = result
+                            kb_file = KnowledgeFile(
+                                filename=file_name, knowledge_base_name=kb_name
+                            )
+                            kb_file.splited_docs = docs
+                            yield json.dumps(
+                                {
+                                    "code": 200,
+                                    "msg": f"({i + 1} / {len(files)}): {file_name}",
+                                    "total": len(files),
+                                    "finished": i + 1,
+                                    "doc": file_name,
+                                },
+                                ensure_ascii=False,
+                            )
+                            kb.add_doc(kb_file, not_refresh_vs_cache=True)
+                        else:
+                            kb_name, file_name, error = result
+                            msg = f"添加文件‘{file_name}’到知识库‘{knowledge_base_name}’时出错：{error}。已跳过。"
+                            logger.error(msg)
+                            yield json.dumps(
+                                {
+                                    "code": 500,
+                                    "msg": msg,
+                                }
+                            )
+                        i += 1
+                    if not not_refresh_vs_cache:
+                        kb.save_vector_store()
+        except asyncio.exceptions.CancelledError:
+            logger.warning("streaming progress has been interrupted by user.")
+            return
 
     return EventSourceResponse(output())
diff --git a/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/base.py b/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/base.py
index fe2c23f7f0..7d5c4c4791 100644
--- a/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/base.py
+++ b/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/base.py
@@ -80,12 +80,8 @@ def save_vector_store(self):
         """
         pass
 
-    def check_embed_model(self, error_msg: str) -> bool:
-        if not _check_embed_model(self.embed_model):
-            logger.error(error_msg, exc_info=True)
-            return False
-        else:
-            return True
+    def check_embed_model(self) -> Tuple[bool, str]:
+        return _check_embed_model(self.embed_model)
 
     def create_kb(self):
         """
@@ -123,9 +119,7 @@ def add_doc(self, kb_file: KnowledgeFile, docs: List[Document] = [], **kwargs):
         向知识库添加文件
         如果指定了docs，则不再将文本向量化，并将数据库对应条目标为custom_docs=True
         """
-        if not self.check_embed_model(
-            f"could not add docs because failed to access embed model."
-        ):
+        if not self.check_embed_model()[0]:
             return False
 
         if docs:
@@ -186,9 +180,7 @@ def update_doc(self, kb_file: KnowledgeFile, docs: List[Document] = [], **kwargs
         使用content中的文件更新向量库
         如果指定了docs，则使用自定义docs，并将数据库对应条目标为custom_docs=True
         """
-        if not self.check_embed_model(
-            f"could not update docs because failed to access embed model."
-        ):
+        if not self.check_embed_model()[0]:
             return False
 
         if os.path.exists(kb_file.filepath):
@@ -212,10 +204,9 @@ def search_docs(
         top_k: int = Settings.kb_settings.VECTOR_SEARCH_TOP_K,
         score_threshold: float = Settings.kb_settings.SCORE_THRESHOLD,
     ) -> List[Document]:
-        if not self.check_embed_model(
-            f"could not search docs because failed to access embed model."
-        ):
+        if not self.check_embed_model()[0]:
             return []
+
         docs = self.do_search(query, top_k, score_threshold)
         return docs
 
@@ -230,9 +221,7 @@ def update_doc_by_ids(self, docs: Dict[str, Document]) -> bool:
         传入参数为： {doc_id: Document, ...}
         如果对应 doc_id 的值为 None，或其 page_content 为空，则删除该文档
         """
-        if not self.check_embed_model(
-            f"could not update docs because failed to access embed model."
-        ):
+        if not self.check_embed_model()[0]:
             return False
 
         self.del_doc_by_ids(list(docs.keys()))
@@ -260,7 +249,7 @@ def list_docs(
             doc_info = self.get_doc_by_ids([x["id"]])[0]
             if doc_info is not None:
                 # 处理非空的情况
-                doc_with_id = DocumentWithVSId(**doc_info.dict(), id=x["id"])
+                doc_with_id = DocumentWithVSId(**{**doc_info.dict(), "id":x["id"]})
                 docs.append(doc_with_id)
             else:
                 # 处理空的情况
diff --git a/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/chromadb_kb_service.py b/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/chromadb_kb_service.py
index ed0cf42b1c..bdcf1cf7d3 100644
--- a/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/chromadb_kb_service.py
+++ b/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/chromadb_kb_service.py
@@ -4,6 +4,7 @@
 import chromadb
 from chromadb.api.types import GetResult, QueryResult
 from langchain.docstore.document import Document
+from langchain_chroma import Chroma
 
 from chatchat.settings import Settings
 from chatchat.server.file_rag.utils import get_Retriever
@@ -49,9 +50,9 @@ def _results_to_docs_and_scores(results: Any) -> List[Tuple[Document, float]]:
 class ChromaKBService(KBService):
     vs_path: str
     kb_path: str
+    chroma: Chroma
 
     client = None
-    collection = None
 
     def vs_type(self) -> str:
         return SupportedVSType.CHROMADB
@@ -61,16 +62,23 @@ def get_vs_path(self) -> str:
 
     def get_kb_path(self) -> str:
         return get_kb_path(self.kb_name)
+    
+    def _load_chroma(self):
+        self.chroma = Chroma(
+            client=self.client,
+            collection_name=self.kb_name,
+            embedding_function=get_Embeddings(self.embed_model),
+        )
 
     def do_init(self) -> None:
         self.kb_path = self.get_kb_path()
         self.vs_path = self.get_vs_path()
         self.client = chromadb.PersistentClient(path=self.vs_path)
-        self.collection = self.client.get_or_create_collection(self.kb_name)
+        collection = self.client.get_or_create_collection(self.kb_name)
+        self._load_chroma()
 
     def do_create_kb(self) -> None:
-        # In ChromaDB, creating a KB is equivalent to creating a collection
-        self.collection = self.client.get_or_create_collection(self.kb_name)
+        pass
 
     def do_drop_kb(self):
         # Dropping a KB is equivalent to deleting a collection in ChromaDB
@@ -84,7 +92,7 @@ def do_search(
         self, query: str, top_k: int, score_threshold: float = Settings.kb_settings.SCORE_THRESHOLD
     ) -> List[Tuple[Document, float]]:
         retriever = get_Retriever("vectorstore").from_vectorstore(
-            self.collection,
+            self.chroma,
             top_k=top_k,
             score_threshold=score_threshold,
         )
@@ -99,18 +107,18 @@ def do_add_doc(self, docs: List[Document], **kwargs) -> List[Dict]:
         embeddings = embed_func.embed_documents(texts=texts)
         ids = [str(uuid.uuid1()) for _ in range(len(texts))]
         for _id, text, embedding, metadata in zip(ids, texts, embeddings, metadatas):
-            self.collection.add(
+            self.chroma._collection.add(
                 ids=_id, embeddings=embedding, metadatas=metadata, documents=text
             )
             doc_infos.append({"id": _id, "metadata": metadata})
         return doc_infos
 
     def get_doc_by_ids(self, ids: List[str]) -> List[Document]:
-        get_result: GetResult = self.collection.get(ids=ids)
+        get_result: GetResult = self.chroma._collection.get(ids=ids)
         return _get_result_to_documents(get_result)
 
     def del_doc_by_ids(self, ids: List[str]) -> bool:
-        self.collection.delete(ids=ids)
+        self.chroma._collection.delete(ids=ids)
         return True
 
     def do_clear_vs(self):
@@ -118,4 +126,4 @@ def do_clear_vs(self):
         self.do_drop_kb()
 
     def do_delete_doc(self, kb_file: KnowledgeFile, **kwargs):
-        return self.collection.delete(where={"source": kb_file.filepath})
+        return self.chroma._collection.delete(where={"source": kb_file.filepath})
diff --git a/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/milvus_kb_service.py b/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/milvus_kb_service.py
index e6bd9c4b06..ef06c242e7 100644
--- a/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/milvus_kb_service.py
+++ b/libs/chatchat-server/chatchat/server/knowledge_base/kb_service/milvus_kb_service.py
@@ -80,7 +80,7 @@ def do_search(self, query: str, top_k: int, score_threshold: float):
         # embed_func = get_Embeddings(self.embed_model)
         # embeddings = embed_func.embed_query(query)
         # docs = self.milvus.similarity_search_with_score_by_vector(embeddings, top_k)
-        retriever = get_Retriever("vectorstore").from_vectorstore(
+        retriever = get_Retriever("milvusvectorstore").from_vectorstore(
             self.milvus,
             top_k=top_k,
             score_threshold=score_threshold,
diff --git a/libs/chatchat-server/chatchat/server/knowledge_base/kb_summary_api.py b/libs/chatchat-server/chatchat/server/knowledge_base/kb_summary_api.py
index a49c58d0e2..22c6a1d3c5 100644
--- a/libs/chatchat-server/chatchat/server/knowledge_base/kb_summary_api.py
+++ b/libs/chatchat-server/chatchat/server/knowledge_base/kb_summary_api.py
@@ -1,3 +1,4 @@
+import asyncio
 import json
 from typing import List, Optional
 
@@ -16,7 +17,6 @@
 
 logger = build_logger()
 
-
 def recreate_summary_vector_store(
     knowledge_base_name: str = Body(..., examples=["samples"]),
     allow_empty_kb: bool = Body(True),
@@ -41,71 +41,77 @@ def recreate_summary_vector_store(
     :param embed_model:
     :return:
     """
+    if max_tokens in [None, 0]:
+        max_tokens = Settings.model_settings.MAX_TOKENS
 
     def output():
-        kb = KBServiceFactory.get_service(knowledge_base_name, vs_type, embed_model)
-        if not kb.exists() and not allow_empty_kb:
-            yield {"code": 404, "msg": f"未找到知识库 ‘{knowledge_base_name}’"}
-        else:
-            error_msg = f"could not recreate summary vector store because failed to access embed model."
-            if not kb.check_embed_model(error_msg):
-                yield {"code": 404, "msg": error_msg}
+        try:
+            kb = KBServiceFactory.get_service(knowledge_base_name, vs_type, embed_model)
+            if not kb.exists() and not allow_empty_kb:
+                yield {"code": 404, "msg": f"未找到知识库 ‘{knowledge_base_name}’"}
             else:
-                # 重新创建知识库
-                kb_summary = KBSummaryService(knowledge_base_name, embed_model)
-                kb_summary.drop_kb_summary()
-                kb_summary.create_kb_summary()
+                ok, msg = kb.check_embed_model()
+                if not ok:
+                    yield {"code": 404, "msg": msg}
+                else:
+                    # 重新创建知识库
+                    kb_summary = KBSummaryService(knowledge_base_name, embed_model)
+                    kb_summary.drop_kb_summary()
+                    kb_summary.create_kb_summary()
 
-                llm = get_ChatOpenAI(
-                    model_name=model_name,
-                    temperature=temperature,
-                    max_tokens=max_tokens,
-                    local_wrap=True,
-                )
-                reduce_llm = get_ChatOpenAI(
-                    model_name=model_name,
-                    temperature=temperature,
-                    max_tokens=max_tokens,
-                    local_wrap=True,
-                )
-                # 文本摘要适配器
-                summary = SummaryAdapter.form_summary(
-                    llm=llm, reduce_llm=reduce_llm, overlap_size=Settings.kb_settings.OVERLAP_SIZE
-                )
-                files = list_files_from_folder(knowledge_base_name)
-
-                i = 0
-                for i, file_name in enumerate(files):
-                    doc_infos = kb.list_docs(file_name=file_name)
-                    docs = summary.summarize(
-                        file_description=file_description, docs=doc_infos
+                    llm = get_ChatOpenAI(
+                        model_name=model_name,
+                        temperature=temperature,
+                        max_tokens=max_tokens,
+                        local_wrap=True,
                     )
-
-                    status_kb_summary = kb_summary.add_kb_summary(
-                        summary_combine_docs=docs
+                    reduce_llm = get_ChatOpenAI(
+                        model_name=model_name,
+                        temperature=temperature,
+                        max_tokens=max_tokens,
+                        local_wrap=True,
                     )
-                    if status_kb_summary:
-                        logger.info(f"({i + 1} / {len(files)}): {file_name} 总结完成")
-                        yield json.dumps(
-                            {
-                                "code": 200,
-                                "msg": f"({i + 1} / {len(files)}): {file_name}",
-                                "total": len(files),
-                                "finished": i + 1,
-                                "doc": file_name,
-                            },
-                            ensure_ascii=False,
+                    # 文本摘要适配器
+                    summary = SummaryAdapter.form_summary(
+                        llm=llm, reduce_llm=reduce_llm, overlap_size=Settings.kb_settings.OVERLAP_SIZE
+                    )
+                    files = list_files_from_folder(knowledge_base_name)
+
+                    i = 0
+                    for i, file_name in enumerate(files):
+                        doc_infos = kb.list_docs(file_name=file_name)
+                        docs = summary.summarize(
+                            file_description=file_description, docs=doc_infos
                         )
-                    else:
-                        msg = f"知识库'{knowledge_base_name}'总结文件‘{file_name}’时出错。已跳过。"
-                        logger.error(msg)
-                        yield json.dumps(
-                            {
-                                "code": 500,
-                                "msg": msg,
-                            }
+
+                        status_kb_summary = kb_summary.add_kb_summary(
+                            summary_combine_docs=docs
                         )
-                    i += 1
+                        if status_kb_summary:
+                            logger.info(f"({i + 1} / {len(files)}): {file_name} 总结完成")
+                            yield json.dumps(
+                                {
+                                    "code": 200,
+                                    "msg": f"({i + 1} / {len(files)}): {file_name}",
+                                    "total": len(files),
+                                    "finished": i + 1,
+                                    "doc": file_name,
+                                },
+                                ensure_ascii=False,
+                            )
+                        else:
+                            msg = f"知识库'{knowledge_base_name}'总结文件‘{file_name}’时出错。已跳过。"
+                            logger.error(msg)
+                            yield json.dumps(
+                                {
+                                    "code": 500,
+                                    "msg": msg,
+                                }
+                            )
+                        i += 1
+        except asyncio.exceptions.CancelledError:
+            logger.warning("streaming progress has been interrupted by user.")
+            return
 
     return EventSourceResponse(output())
 
@@ -138,55 +144,59 @@ def summary_file_to_vector_store(
     """
 
     def output():
-        kb = KBServiceFactory.get_service(knowledge_base_name, vs_type, embed_model)
-        if not kb.exists() and not allow_empty_kb:
-            yield {"code": 404, "msg": f"未找到知识库 ‘{knowledge_base_name}’"}
-        else:
-            # 重新创建知识库
-            kb_summary = KBSummaryService(knowledge_base_name, embed_model)
-            kb_summary.create_kb_summary()
-
-            llm = get_ChatOpenAI(
-                model_name=model_name,
-                temperature=temperature,
-                max_tokens=max_tokens,
-                local_wrap=True,
-            )
-            reduce_llm = get_ChatOpenAI(
-                model_name=model_name,
-                temperature=temperature,
-                max_tokens=max_tokens,
-                local_wrap=True,
-            )
-            # 文本摘要适配器
-            summary = SummaryAdapter.form_summary(
-                llm=llm, reduce_llm=reduce_llm, overlap_size=Settings.kb_settings.OVERLAP_SIZE
-            )
-
-            doc_infos = kb.list_docs(file_name=file_name)
-            docs = summary.summarize(file_description=file_description, docs=doc_infos)
+        try:
+            kb = KBServiceFactory.get_service(knowledge_base_name, vs_type, embed_model)
+            if not kb.exists() and not allow_empty_kb:
+                yield {"code": 404, "msg": f"未找到知识库 ‘{knowledge_base_name}’"}
+            else:
+                # 重新创建知识库
+                kb_summary = KBSummaryService(knowledge_base_name, embed_model)
+                kb_summary.create_kb_summary()
 
-            status_kb_summary = kb_summary.add_kb_summary(summary_combine_docs=docs)
-            if status_kb_summary:
-                logger.info(f" {file_name} 总结完成")
-                yield json.dumps(
-                    {
-                        "code": 200,
-                        "msg": f"{file_name} 总结完成",
-                        "doc": file_name,
-                    },
-                    ensure_ascii=False,
+                llm = get_ChatOpenAI(
+                    model_name=model_name,
+                    temperature=temperature,
+                    max_tokens=max_tokens,
+                    local_wrap=True,
                 )
-            else:
-                msg = f"知识库'{knowledge_base_name}'总结文件‘{file_name}’时出错。已跳过。"
-                logger.error(msg)
-                yield json.dumps(
-                    {
-                        "code": 500,
-                        "msg": msg,
-                    }
+                reduce_llm = get_ChatOpenAI(
+                    model_name=model_name,
+                    temperature=temperature,
+                    max_tokens=max_tokens,
+                    local_wrap=True,
+                )
+                # 文本摘要适配器
+                summary = SummaryAdapter.form_summary(
+                    llm=llm, reduce_llm=reduce_llm, overlap_size=Settings.kb_settings.OVERLAP_SIZE
                 )
 
+                doc_infos = kb.list_docs(file_name=file_name)
+                docs = summary.summarize(file_description=file_description, docs=doc_infos)
+
+                status_kb_summary = kb_summary.add_kb_summary(summary_combine_docs=docs)
+                if status_kb_summary:
+                    logger.info(f" {file_name} 总结完成")
+                    yield json.dumps(
+                        {
+                            "code": 200,
+                            "msg": f"{file_name} 总结完成",
+                            "doc": file_name,
+                        },
+                        ensure_ascii=False,
+                    )
+                else:
+                    msg = f"知识库'{knowledge_base_name}'总结文件‘{file_name}’时出错。已跳过。"
+                    logger.error(msg)
+                    yield json.dumps(
+                        {
+                            "code": 500,
+                            "msg": msg,
+                        }
+                    )
+        except asyncio.exceptions.CancelledError:
+            logger.warning("streaming progress has been interrupted by user.")
+            return
+
     return EventSourceResponse(output())
 
 
@@ -240,7 +250,7 @@ def summary_doc_ids_to_vector_store(
         doc_infos = kb.get_doc_by_ids(ids=doc_ids)
         # doc_infos转换成DocumentWithVSId包装的对象
         doc_info_with_ids = [
-            DocumentWithVSId(**doc.dict(), id=with_id)
+            DocumentWithVSId(**{**doc.dict(), "id":with_id})
             for with_id, doc in zip(doc_ids, doc_infos)
         ]
 
diff --git a/libs/chatchat-server/chatchat/server/knowledge_base/utils.py b/libs/chatchat-server/chatchat/server/knowledge_base/utils.py
index f10e9cdf6b..f7b27276b1 100644
--- a/libs/chatchat-server/chatchat/server/knowledge_base/utils.py
+++ b/libs/chatchat-server/chatchat/server/knowledge_base/utils.py
@@ -475,6 +475,7 @@ def format_reference(kb_name: str, docs: List[Dict], api_base_url: str="") -> Li
                 "file_name": filename,
             }
         )
+        api_base_url = api_base_url.strip(" /")
         url = (
             f"{api_base_url}/knowledge_base/download_doc?" + parameters
         )
diff --git a/libs/chatchat-server/chatchat/server/localai_embeddings.py b/libs/chatchat-server/chatchat/server/localai_embeddings.py
index e27041c211..559a4a4fd6 100644
--- a/libs/chatchat-server/chatchat/server/localai_embeddings.py
+++ b/libs/chatchat-server/chatchat/server/localai_embeddings.py
@@ -156,7 +156,7 @@ class LocalAIEmbeddings(BaseModel, Embeddings):
     disallowed_special: Union[Literal["all"], Set[str], Sequence[str]] = "all"
     chunk_size: int = 1000
     """Maximum number of texts to embed in each batch"""
-    max_retries: int = 6
+    max_retries: int = 3
     """Maximum number of retries to make when generating."""
     request_timeout: Union[float, Tuple[float, float], Any, None] = Field(
         default=None, alias="timeout"
diff --git a/libs/chatchat-server/chatchat/server/types/__init__.py b/libs/chatchat-server/chatchat/server/types/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/chatchat-server/chatchat/server/types/server/__init__.py b/libs/chatchat-server/chatchat/server/types/server/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/chatchat-server/chatchat/server/types/server/response/__init__.py b/libs/chatchat-server/chatchat/server/types/server/response/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/chatchat-server/chatchat/server/types/server/response/base.py b/libs/chatchat-server/chatchat/server/types/server/response/base.py
new file mode 100644
index 0000000000..126a8debb7
--- /dev/null
+++ b/libs/chatchat-server/chatchat/server/types/server/response/base.py
@@ -0,0 +1,32 @@
+from typing import Any, List, Optional, Union
+
+from pydantic import BaseModel, Field
+
+from chatchat.server.constant.response_code import ResponseCode
+
+
+def default_data():
+    return ""
+
+
+class BaseResponse(BaseModel):
+    code: int = Field(200, description="API status code")
+    msg: str = Field("success", description="API status message")
+    data: Optional[Union[Any, None]] = Field(default_factory=default_data, description="API data")
+
+    class Config:
+        json_schema_extra = {
+            "example": {
+                "code": 200,
+                "msg": "success",
+                "data": None
+            }
+        }
+
+    @classmethod
+    def success(cls, data: Optional[Any] = "", message: str = "success"):
+        return BaseResponse(code=ResponseCode.SUCCESS, msg=message, data=data)
+
+    @classmethod
+    def error(cls, data: Optional[Any] = "", message: str = "error", code=ResponseCode.INTERNAL_SERVER_ERROR):
+        return BaseResponse(code=code, msg=message, data=data)
diff --git a/libs/chatchat-server/chatchat/server/utils.py b/libs/chatchat-server/chatchat/server/utils.py
index fc762ab0ff..d58d4a4c0e 100644
--- a/libs/chatchat-server/chatchat/server/utils.py
+++ b/libs/chatchat-server/chatchat/server/utils.py
@@ -27,6 +27,7 @@
 from langchain_core.embeddings import Embeddings
 from langchain_openai.chat_models import ChatOpenAI
 from langchain_openai.llms import OpenAI
+from memoization import cached, CachingAlgorithmFlag
 
 from chatchat.settings import Settings, XF_MODELS_TYPES
 from chatchat.server.pydantic_v2 import BaseModel, Field
@@ -62,11 +63,58 @@ def get_config_platforms() -> Dict[str, Dict]:
     return {m["platform_name"]: m for m in platforms}
 
 
+@cached(max_size=10, ttl=60, algorithm=CachingAlgorithmFlag.LRU)
+def detect_xf_models(xf_url: str) -> Dict[str, List[str]]:
+    '''
+    use cache for xinference model detecting to avoid:
+    - too many requests in short intervals
+    - multiple requests to one platform for every model
+    the cache will be invalidated after one minute
+    '''
+    xf_model_type_maps = {
+        "llm_models": lambda xf_models: [k for k, v in xf_models.items()
+                                        if "LLM" == v["model_type"]
+                                        and "vision" not in v["model_ability"]],
+        "embed_models": lambda xf_models: [k for k, v in xf_models.items()
+                                        if "embedding" == v["model_type"]],
+        "text2image_models": lambda xf_models: [k for k, v in xf_models.items()
+                                                if "image" == v["model_type"]],
+        "image2image_models": lambda xf_models: [k for k, v in xf_models.items()
+                                                if "image" == v["model_type"]],
+        "image2text_models": lambda xf_models: [k for k, v in xf_models.items()
+                                                if "LLM" == v["model_type"]
+                                                and "vision" in v["model_ability"]],
+        "rerank_models": lambda xf_models: [k for k, v in xf_models.items()
+                                            if "rerank" == v["model_type"]],
+        "speech2text_models": lambda xf_models: [k for k, v in xf_models.items()
+                                                if v.get(list(XF_MODELS_TYPES["speech2text"].keys())[0])
+                                                in XF_MODELS_TYPES["speech2text"].values()],
+        "text2speech_models": lambda xf_models: [k for k, v in xf_models.items()
+                                                if v.get(list(XF_MODELS_TYPES["text2speech"].keys())[0])
+                                                in XF_MODELS_TYPES["text2speech"].values()],
+    }
+    models = {}
+    try:
+        from xinference_client import RESTfulClient as Client
+        xf_client = Client(xf_url)
+        xf_models = xf_client.list_models()
+        for m_type, filter in xf_model_type_maps.items():
+            models[m_type] = filter(xf_models)
+    except ImportError:
+        logger.warning('auto_detect_model needs xinference-client installed. '
+                        'Please try "pip install xinference-client". ')
+    except requests.exceptions.ConnectionError:
+        logger.warning(f"cannot connect to xinference host: {xf_url}, please check your configuration.")
+    except Exception as e:
+        logger.warning(f"error when connect to xinference server({xf_url}): {e}")
+    return models
+
+
 def get_config_models(
         model_name: str = None,
-        model_type: Literal[
+        model_type: Optional[Literal[
             "llm", "embed", "text2image", "image2image", "image2text", "rerank", "speech2text", "text2speech"
-        ] = None,
+        ]] = None,
         platform_name: str = None,
 ) -> Dict[str, Dict]:
     """
@@ -96,29 +144,6 @@ def get_config_models(
     else:
         model_types = [f"{model_type}_models"]
 
-    xf_model_type_maps = {
-        "llm_models": lambda xf_models: [k for k, v in xf_models.items()
-                                         if "LLM" == v["model_type"]
-                                         and "vision" not in v["model_ability"]],
-        "embed_models": lambda xf_models: [k for k, v in xf_models.items()
-                                           if "embedding" == v["model_type"]],
-        "text2image_models": lambda xf_models: [k for k, v in xf_models.items()
-                                                if "image" == v["model_type"]],
-        "image2image_models": lambda xf_models: [k for k, v in xf_models.items()
-                                                 if "image" == v["model_type"]],
-        "image2text_models": lambda xf_models: [k for k, v in xf_models.items()
-                                                if "LLM" == v["model_type"]
-                                                and "vision" in v["model_ability"]],
-        "rerank_models": lambda xf_models: [k for k, v in xf_models.items()
-                                            if "rerank" == v["model_type"]],
-        "speech2text_models": lambda xf_models: [k for k, v in xf_models.items()
-                                                 if v.get(list(XF_MODELS_TYPES["speech2text"].keys())[0])
-                                                 in XF_MODELS_TYPES["speech2text"].values()],
-        "text2speech_models": lambda xf_models: [k for k, v in xf_models.items()
-                                                 if v.get(list(XF_MODELS_TYPES["text2speech"].keys())[0])
-                                                 in XF_MODELS_TYPES["text2speech"].values()],
-    }
-
     for m in list(get_config_platforms().values()):
         if platform_name is not None and platform_name != m.get("platform_name"):
             continue
@@ -127,22 +152,12 @@ def get_config_models(
             if not m.get("platform_type") == "xinference":  # TODO：当前仅支持 xf 自动检测模型
                 logger.warning(f"auto_detect_model not supported for {m.get('platform_type')} yet")
                 continue
+            xf_url = get_base_url(m.get("api_base_url"))
+            xf_models = detect_xf_models(xf_url)
             for m_type in model_types:
                 # if m.get(m_type) != "auto":
                 #     continue
-                try:
-                    from xinference_client import RESTfulClient as Client
-                    xf_url = get_base_url(m.get("api_base_url"))
-                    xf_client = Client(xf_url)
-                    xf_models = xf_client.list_models()
-                    m[m_type] = xf_model_type_maps[m_type](xf_models)
-                except ImportError:
-                    logger.warning('auto_detect_model needs xinference-client installed. '
-                                   'Please try "pip install xinference-client". ')
-                except requests.exceptions.ConnectionError:
-                    logger.warning(f"cannot connect to xinference host: {xf_url}")
-                except Exception as e:
-                    logger.warning(f"error when connect to xinference server({xf_url}): {e}")
+                m[m_type] = xf_models.get(m_type, [])
 
         for m_type in model_types:
             models = m.get(m_type, [])
@@ -240,9 +255,7 @@ def get_ChatOpenAI(
             )
         model = ChatOpenAI(**params)
     except Exception as e:
-        logger.error(
-            f"failed to create ChatOpenAI for model: {model_name}.", exc_info=True
-        )
+        logger.exception(f"failed to create ChatOpenAI for model: {model_name}.")
         model = None
     return model
 
@@ -284,7 +297,7 @@ def get_OpenAI(
             )
         model = OpenAI(**params)
     except Exception as e:
-        logger.error(f"failed to create OpenAI for model: {model_name}.", exc_info=True)
+        logger.exception(f"failed to create OpenAI for model: {model_name}.")
         model = None
     return model
 
@@ -325,21 +338,22 @@ def get_Embeddings(
         else:
             return LocalAIEmbeddings(**params)
     except Exception as e:
-        logger.error(
-            f"failed to create Embeddings for model: {embed_model}.", exc_info=True
-        )
+        logger.exception(f"failed to create Embeddings for model: {embed_model}.")
 
 
-def check_embed_model(embed_model: str = get_default_embedding()) -> bool:
+def check_embed_model(embed_model: str = None) -> Tuple[bool, str]:
+    '''
+    check weather embed_model accessable, use default embed model if None
+    '''
+    embed_model = embed_model or get_default_embedding()
     embeddings = get_Embeddings(embed_model=embed_model)
     try:
         embeddings.embed_query("this is a test")
-        return True
+        return True, ""
     except Exception as e:
-        logger.error(
-            f"failed to access embed model '{embed_model}': {e}", exc_info=True
-        )
-        return False
+        msg = f"failed to access embed model '{embed_model}': {e}"
+        logger.error(msg)
+        return False, msg
 
 
 def get_OpenAIClient(
@@ -603,13 +617,22 @@ async def redoc_html(request: Request) -> HTMLResponse:
 #         return path_str  # THUDM/chatglm06b
 
 
-def api_address() -> str:
+def api_address(is_public: bool = False) -> str:
+    '''
+    允许用户在 basic_settings.API_SERVER 中配置 public_host, public_port
+    以便使用云服务器或反向代理时生成正确的公网 API 地址（如知识库文档下载链接）
+    '''
     from chatchat.settings import Settings
 
-    host = Settings.basic_settings.API_SERVER["host"]
-    if host == "0.0.0.0":
-        host = "127.0.0.1"
-    port = Settings.basic_settings.API_SERVER["port"]
+    server = Settings.basic_settings.API_SERVER
+    if is_public:
+        host = server.get("public_host", "127.0.0.1")
+        port = server.get("public_port", "7861")
+    else:
+        host = server.get("host", "127.0.0.1")
+        port = server.get("port", "7861")
+        if host == "0.0.0.0":
+            host = "127.0.0.1"
     return f"http://{host}:{port}"
 
 
@@ -707,7 +730,7 @@ def run_in_thread_pool(
             try:
                 yield obj.result()
             except Exception as e:
-                logger.error(f"error in sub thread: {e}", exc_info=True)
+                logger.exception(f"error in sub thread: {e}")
 
 
 def run_in_process_pool(
@@ -732,7 +755,7 @@ def run_in_process_pool(
             try:
                 yield obj.result()
             except Exception as e:
-                logger.error(f"error in sub process: {e}", exc_info=True)
+                logger.exception(f"error in sub process: {e}")
 
 
 def get_httpx_client(
diff --git a/libs/chatchat-server/chatchat/settings.py b/libs/chatchat-server/chatchat/settings.py
index fa30bafaea..6bd7575ab6 100644
--- a/libs/chatchat-server/chatchat/settings.py
+++ b/libs/chatchat-server/chatchat/settings.py
@@ -23,7 +23,8 @@
 class BasicSettings(BaseFileSettings):
     """
     服务器基本配置信息
-    除 log_verbose/HTTPX_DEFAULT_TIMEOUT 修改后即时生效，其它配置项修改后都需要重启服务器才能生效
+    除 log_verbose/HTTPX_DEFAULT_TIMEOUT 修改后即时生效
+    其它配置项修改后都需要重启服务器才能生效，服务运行期间请勿修改
     """
 
     model_config = SettingsConfigDict(yaml_file=CHATCHAT_ROOT / "basic_settings.yaml")
@@ -34,9 +35,6 @@ class BasicSettings(BaseFileSettings):
     log_verbose: bool = False
     """是否开启日志详细信息"""
 
-    LOG_FORMAT: str = "%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s"
-    """日志格式"""
-
     HTTPX_DEFAULT_TIMEOUT: float = 300
     """httpx 请求默认超时时间（秒）。如果加载模型或对话较慢，出现超时错误，可以适当加大该值。"""
 
@@ -101,14 +99,14 @@ def BASE_TEMP_DIR(self) -> Path:
     OPEN_CROSS_DOMAIN: bool = False
     """API 是否开启跨域"""
 
-    DEFAULT_BIND_HOST: str = "127.0.0.1" if sys.platform != "win32" else "127.0.0.1"
+    DEFAULT_BIND_HOST: str = "0.0.0.0" if sys.platform != "win32" else "127.0.0.1"
     """
     各服务器默认绑定host。如改为"0.0.0.0"需要修改下方所有XX_SERVER的host
     Windows 下 WEBUI 自动弹出浏览器时，如果地址为 "0.0.0.0" 是无法访问的，需要手动修改地址栏
     """
 
-    API_SERVER: dict = {"host": DEFAULT_BIND_HOST, "port": 7861}
-    """API 服务器地址"""
+    API_SERVER: dict = {"host": DEFAULT_BIND_HOST, "port": 7861, "public_host": "127.0.0.1", "public_port": 7861}
+    """API 服务器地址。其中 public_host 用于生成云服务公网访问链接（如知识库文档链接）"""
 
     WEBUI_SERVER: dict = {"host": DEFAULT_BIND_HOST, "port": 8501}
     """WEBUI 服务器地址"""
@@ -275,18 +273,10 @@ class PlatformConfig(MyBaseModel):
     auto_detect_model: bool = False
     """是否自动获取平台可用模型列表。设为 True 时下方不同模型类型可自动检测"""
 
-    llm_models: t.Union[t.Literal["auto"], t.List[str]] = [
-        "glm4-chat",
-        "qwen1.5-chat",
-        "qwen2-instruct",
-        "gpt-3.5-turbo",
-        "gpt-4o",
-    ]
+    llm_models: t.Union[t.Literal["auto"], t.List[str]] = []
     """该平台支持的大语言模型列表，auto_detect_model 设为 True 时自动检测"""
 
-    embed_models: t.Union[t.Literal["auto"], t.List[str]] = [
-        "bge-large-zh-v1.5",
-    ]
+    embed_models: t.Union[t.Literal["auto"], t.List[str]] = []
     """该平台支持的嵌入模型列表，auto_detect_model 设为 True 时自动检测"""
 
     text2image_models: t.Union[t.Literal["auto"], t.List[str]] = []
@@ -385,7 +375,7 @@ class ApiModelSettings(BaseFileSettings):
 
     MODEL_PLATFORMS: t.List[PlatformConfig] = [
             PlatformConfig(**{
-                "platform_name": "xinference-auto",
+                "platform_name": "xinference",
                 "platform_type": "xinference",
                 "api_base_url": "http://127.0.0.1:9997/v1",
                 "api_key": "EMPTY",
@@ -399,26 +389,6 @@ class ApiModelSettings(BaseFileSettings):
                 "speech2text_models": [],
                 "text2speech_models": [],
             }),
-            PlatformConfig(**{
-                "platform_name": "xinference",
-                "platform_type": "xinference",
-                "api_base_url": "http://127.0.0.1:9997/v1",
-                "api_key": "EMPTY",
-                "api_concurrencies": 5,
-                "llm_models": [
-                    "glm4-chat",
-                    "qwen1.5-chat",
-                    "qwen2-instruct",
-                ],
-                "embed_models": [
-                    "bge-large-zh-v1.5",
-                ],
-                "text2image_models": [],
-                "image2text_models": [],
-                "rerank_models": [],
-                "speech2text_models": [],
-                "text2speech_models": [],
-            }),
             PlatformConfig(**{
                 "platform_name": "ollama",
                 "platform_type": "ollama",
@@ -630,6 +600,14 @@ class ToolSettings(BaseFileSettings):
     3、当前仅支持 单prometheus 查询, 后续考虑支持 多prometheus 查询.
     '''
 
+    url_reader: dict = {
+        "use": False,
+        "timeout": "10000",
+    }
+    '''URL内容阅读（https://r.jina.ai/）工具配置项
+    请确保部署的网络环境良好，以免造成超时等问题'''
+
+
 
 class PromptSettings(BaseFileSettings):
     """Prompt 模板.除 Agent 模板使用 f-string 外，其它均使用 jinja2 格式"""
diff --git a/libs/chatchat-server/chatchat/utils.py b/libs/chatchat-server/chatchat/utils.py
index d349487291..7821c5d66a 100644
--- a/libs/chatchat-server/chatchat/utils.py
+++ b/libs/chatchat-server/chatchat/utils.py
@@ -10,6 +10,16 @@
 from chatchat.settings import Settings
 
 
+def _filter_logs(record: dict) -> bool:
+    # hide debug logs if Settings.basic_settings.log_verbose=False 
+    if record["level"].no <= 10 and not Settings.basic_settings.log_verbose:
+        return False
+    # hide traceback logs if Settings.basic_settings.log_verbose=False 
+    if record["level"].no == 40 and not Settings.basic_settings.log_verbose:
+        record["exception"] = None
+    return True
+
+
 # 默认每调用一次 build_logger 就会添加一次 hanlder，导致 chatchat.log 里重复输出
 @cached(max_size=100, algorithm=CachingAlgorithmFlag.LRU)
 def build_logger(log_file: str = "chatchat"):
@@ -20,27 +30,19 @@ def build_logger(log_file: str = "chatchat"):
     logger.info("<green>some message</green>")
 
     user can set basic_settings.log_verbose=True to output debug logs
+    use logger.exception to log errors with exceptions
     """
+    loguru.logger._core.handlers[0]._filter = _filter_logs
     logger = loguru.logger.opt(colors=True)
     logger.opt = partial(loguru.logger.opt, colors=True)
+    # logger.error = partial(logger.exception)
 
     if log_file:
         if not log_file.endswith(".log"):
             log_file = f"{log_file}.log"
         if not os.path.isabs(log_file):
             log_file = str((Settings.basic_settings.LOG_PATH / log_file).resolve())
-        logger.add(log_file, colorize=False)
-
-    logger.error = logger.opt(exception=True).error
-
-    _debug = logger.debug
-
-    def debug(*args, **kwds):
-        if (Settings.basic_settings.log_verbose
-                and _debug is not debug):
-            _debug(*args, **kwds)
-
-    logger.debug = debug
+        logger.add(log_file, colorize=False, filter=_filter_logs)
 
     return logger
 
diff --git a/libs/chatchat-server/chatchat/webui_pages/dialogue/dialogue.py b/libs/chatchat-server/chatchat/webui_pages/dialogue/dialogue.py
index 2f22a0957a..75da7c3b0b 100644
--- a/libs/chatchat-server/chatchat/webui_pages/dialogue/dialogue.py
+++ b/libs/chatchat-server/chatchat/webui_pages/dialogue/dialogue.py
@@ -19,8 +19,8 @@
 from chatchat.settings import Settings
 from chatchat.server.callback_handler.agent_callback_handler import AgentStatus
 from chatchat.server.knowledge_base.model.kb_document_model import DocumentWithVSId
+from chatchat.server.knowledge_base.utils import format_reference
 from chatchat.server.utils import MsgType, get_config_models, get_config_platforms, get_default_llm
-from chatchat.webui_pages.dialogue.utils import process_files
 from chatchat.webui_pages.utils import *
 
 
@@ -206,6 +206,8 @@ def rename_conversation():
             use_agent = st.checkbox(
                 "启用Agent", help="请确保选择的模型具备Agent能力", key="use_agent"
             )
+            output_agent = st.checkbox("显示 Agent 过程", key="output_agent")
+
             # 选择工具
             tools = list_tools(api)
             tool_names = ["None"] + list(tools)
@@ -422,119 +424,114 @@ def on_conv_change():
             upload_image=upload_image,
         )
         stream = not is_vision_chat
+        params = dict(
+            messages=messages,
+            model=llm_model,
+            stream=stream, # TODO：xinference qwen-vl-chat 流式输出会出错，后续看更新
+            extra_body=extra_body,
+        )
+        if tools:
+            params["tools"] = tools
+        if tool_choice:
+            params["tool_choice"] = tool_choice
+        if Settings.model_settings.MAX_TOKENS:
+            params["max_tokens"] = Settings.model_settings.MAX_TOKENS
+
         if stream:
-            for d in client.chat.completions.create(
-                messages=messages,
-                model=llm_model,
-                stream=stream, # TODO：xinference qwen-vl-chat 流式输出会出错，后续看更新
-                tools=tools or openai.NOT_GIVEN,
-                tool_choice=tool_choice,
-                extra_body=extra_body,
-                max_tokens=Settings.model_settings.MAX_TOKENS,
-            ):
-                # import rich
-                # rich.print(d)
-                message_id = d.message_id
-                metadata = {
-                    "message_id": message_id,
-                }
-
-                # clear initial message
-                if not started:
-                    chat_box.update_msg("", streaming=False)
-                    started = True
-
-                if d.status == AgentStatus.error:
-                    st.error(d.choices[0].delta.content)
-                elif d.status == AgentStatus.llm_start:
-                    chat_box.insert_msg("正在解读工具输出结果...")
-                    text = d.choices[0].delta.content or ""
-                elif d.status == AgentStatus.llm_new_token:
-                    text += d.choices[0].delta.content or ""
-                    chat_box.update_msg(
-                        text.replace("\n", "\n\n"), streaming=True, metadata=metadata
-                    )
-                elif d.status == AgentStatus.llm_end:
-                    text += d.choices[0].delta.content or ""
-                    chat_box.update_msg(
-                        text.replace("\n", "\n\n"), streaming=False, metadata=metadata
-                    )
-                # tool 的输出与 llm 输出重复了
-                # elif d.status == AgentStatus.tool_start:
-                #     formatted_data = {
-                #         "Function": d.choices[0].delta.tool_calls[0].function.name,
-                #         "function_input": d.choices[0].delta.tool_calls[0].function.arguments,
-                #     }
-                #     formatted_json = json.dumps(formatted_data, indent=2, ensure_ascii=False)
-                #     text = """\n```{}\n```\n""".format(formatted_json)
-                #     chat_box.insert_msg( # TODO: insert text directly not shown
-                #         Markdown(text, title="Function call", in_expander=True, expanded=True, state="running"))
-                # elif d.status == AgentStatus.tool_end:
-                #     tool_output = d.choices[0].delta.tool_calls[0].tool_output
-                #     if d.message_type == MsgType.IMAGE:
-                #         for url in json.loads(tool_output).get("images", []):
-                #             url = f"{api.base_url}/media/{url}"
-                #             chat_box.insert_msg(Image(url))
-                #         chat_box.update_msg(expanded=False, state="complete")
-                #     else:
-                #         text += """\n```\nObservation:\n{}\n```\n""".format(tool_output)
-                #         chat_box.update_msg(text, streaming=False, expanded=False, state="complete")
-                elif d.status == AgentStatus.agent_finish:
-                    text = d.choices[0].delta.content or ""
-                    chat_box.update_msg(text.replace("\n", "\n\n"))
-                elif d.status is None:  # not agent chat
-                    if getattr(d, "is_ref", False):
-                        context = str(d.tool_output)
-                        if isinstance(d.tool_output, dict):
-                            docs = d.tool_output.get("docs", [])
-                            source_documents = []
-                            for inum, doc in enumerate(docs):
-                                doc = DocumentWithVSId.parse_obj(doc)
-                                filename = doc.metadata.get("source")
-                                parameters = urlencode(
-                                    {
-                                        "knowledge_base_name": d.tool_output.get(
-                                            "knowledge_base"
-                                        ),
-                                        "file_name": filename,
-                                    }
-                                )
-                                url = (
-                                    f"{api.base_url}/knowledge_base/download_doc?" + parameters
-                                )
-                                ref = f"""出处 [{inum + 1}] [{filename}]({url}) \n\n{doc.page_content}\n\n"""
-                                source_documents.append(ref)
-                            context = "\n".join(source_documents)
-
-                        chat_box.insert_msg(
-                            Markdown(
-                                context,
-                                in_expander=True,
-                                state="complete",
-                                title="参考资料",
-                            )
-                        )
-                        chat_box.insert_msg("")
-                    elif getattr(d, "tool_call", None) == "text2images":  # TODO：特定工具特别处理，需要更通用的处理方式
-                        for img in d.tool_output.get("images", []):
-                            chat_box.insert_msg(Image(f"{api.base_url}/media/{img}"), pos=-2)
-                    else:
+            try:
+                for d in client.chat.completions.create(**params):
+                    # import rich
+                    # rich.print(d)
+                    message_id = d.message_id
+                    metadata = {
+                        "message_id": message_id,
+                    }
+
+                    # clear initial message
+                    if not started:
+                        chat_box.update_msg("", streaming=False)
+                        started = True
+
+                    if d.status == AgentStatus.error:
+                        st.error(d.choices[0].delta.content)
+                    elif d.status == AgentStatus.llm_start:
+                        if not output_agent:
+                            continue
+                        chat_box.insert_msg("正在解读工具输出结果...")
+                        text = d.choices[0].delta.content or ""
+                    elif d.status == AgentStatus.llm_new_token:
+                        if not output_agent:
+                            continue
                         text += d.choices[0].delta.content or ""
                         chat_box.update_msg(
                             text.replace("\n", "\n\n"), streaming=True, metadata=metadata
                         )
-            chat_box.update_msg(text, streaming=False, metadata=metadata)
+                    elif d.status == AgentStatus.llm_end:
+                        if not output_agent:
+                            continue
+                        text += d.choices[0].delta.content or ""
+                        chat_box.update_msg(
+                            text.replace("\n", "\n\n"), streaming=False, metadata=metadata
+                        )
+                    # tool 的输出与 llm 输出重复了
+                    # elif d.status == AgentStatus.tool_start:
+                    #     formatted_data = {
+                    #         "Function": d.choices[0].delta.tool_calls[0].function.name,
+                    #         "function_input": d.choices[0].delta.tool_calls[0].function.arguments,
+                    #     }
+                    #     formatted_json = json.dumps(formatted_data, indent=2, ensure_ascii=False)
+                    #     text = """\n```{}\n```\n""".format(formatted_json)
+                    #     chat_box.insert_msg( # TODO: insert text directly not shown
+                    #         Markdown(text, title="Function call", in_expander=True, expanded=True, state="running"))
+                    # elif d.status == AgentStatus.tool_end:
+                    #     tool_output = d.choices[0].delta.tool_calls[0].tool_output
+                    #     if d.message_type == MsgType.IMAGE:
+                    #         for url in json.loads(tool_output).get("images", []):
+                    #             url = f"{api.base_url}/media/{url}"
+                    #             chat_box.insert_msg(Image(url))
+                    #         chat_box.update_msg(expanded=False, state="complete")
+                    #     else:
+                    #         text += """\n```\nObservation:\n{}\n```\n""".format(tool_output)
+                    #         chat_box.update_msg(text, streaming=False, expanded=False, state="complete")
+                    elif d.status == AgentStatus.agent_finish:
+                        text = d.choices[0].delta.content or ""
+                        chat_box.update_msg(text.replace("\n", "\n\n"))
+                    elif d.status is None:  # not agent chat
+                        if getattr(d, "is_ref", False):
+                            context = str(d.tool_output)
+                            if isinstance(d.tool_output, dict):
+                                docs = d.tool_output.get("docs", [])
+                                source_documents = format_reference(kb_name=d.tool_output.get("knowledge_base"),
+                                                                    docs=docs,
+                                                                    api_base_url=api_address(is_public=True))
+                                context = "\n".join(source_documents)
+
+                            chat_box.insert_msg(
+                                Markdown(
+                                    context,
+                                    in_expander=True,
+                                    state="complete",
+                                    title="参考资料",
+                                )
+                            )
+                            chat_box.insert_msg("")
+                        elif getattr(d, "tool_call", None) == "text2images":  # TODO：特定工具特别处理，需要更通用的处理方式
+                            for img in d.tool_output.get("images", []):
+                                chat_box.insert_msg(Image(f"{api.base_url}/media/{img}"), pos=-2)
+                        else:
+                            text += d.choices[0].delta.content or ""
+                            chat_box.update_msg(
+                                text.replace("\n", "\n\n"), streaming=True, metadata=metadata
+                            )
+                    chat_box.update_msg(text, streaming=False, metadata=metadata)
+            except Exception as e:
+                st.error(e.body)
         else:
-            d =client.chat.completions.create(
-                messages=messages,
-                model=llm_model,
-                stream=stream, # TODO：xinference qwen-vl-chat 流式输出会出错，后续看更新
-                tools=tools or openai.NOT_GIVEN,
-                tool_choice=tool_choice,
-                extra_body=extra_body,
-                max_tokens=Settings.model_settings.MAX_TOKENS,
-            )
-            chat_box.update_msg(d.choices[0].message.content or "", streaming=False)
+            try:
+                d =client.chat.completions.create(**params)
+                chat_box.update_msg(d.choices[0].message.content or "", streaming=False)
+            except Exception as e:
+                st.error(e.body)
 
         # if os.path.exists("tmp/image.jpg"):
         #     with open("tmp/image.jpg", "rb") as image_file:
diff --git a/libs/chatchat-server/chatchat/webui_pages/kb_chat.py b/libs/chatchat-server/chatchat/webui_pages/kb_chat.py
index 2c54447e7a..bcadb82778 100644
--- a/libs/chatchat-server/chatchat/webui_pages/kb_chat.py
+++ b/libs/chatchat-server/chatchat/webui_pages/kb_chat.py
@@ -10,7 +10,7 @@
 
 from chatchat.settings import Settings
 from chatchat.server.knowledge_base.utils import LOADER_DICT
-from chatchat.server.utils import get_config_models, get_config_platforms, get_default_llm
+from chatchat.server.utils import get_config_models, get_config_platforms, get_default_llm, api_address
 from chatchat.webui_pages.dialogue.dialogue import (save_session, restore_session, rerun,
                                                     get_messages_history, upload_temp_docs,
                                                     add_conv, del_conv, clear_conv)
@@ -194,8 +194,9 @@ def on_conv_change():
             return_direct=return_direct,
         )
     
+        api_url = api_address(is_public=True)
         if dialogue_mode == "知识库问答":
-            client = openai.Client(base_url=f"{api_address()}/knowledge_base/local_kb/{selected_kb}", api_key="NONE")
+            client = openai.Client(base_url=f"{api_url}/knowledge_base/local_kb/{selected_kb}", api_key="NONE")
             chat_box.ai_say([
                 Markdown("...", in_expander=True, title="知识库匹配结果", state="running", expanded=return_direct),
                 f"正在查询知识库 `{selected_kb}` ...",
@@ -205,13 +206,13 @@ def on_conv_change():
                 st.error("请先上传文件再进行对话")
                 st.stop()
             knowledge_id=st.session_state.get("file_chat_id")
-            client = openai.Client(base_url=f"{api_address()}/knowledge_base/temp_kb/{knowledge_id}", api_key="NONE")
+            client = openai.Client(base_url=f"{api_url}/knowledge_base/temp_kb/{knowledge_id}", api_key="NONE")
             chat_box.ai_say([
                 Markdown("...", in_expander=True, title="知识库匹配结果", state="running", expanded=return_direct),
                 f"正在查询文件 `{st.session_state.get('file_chat_id')}` ...",
             ])
         else:
-            client = openai.Client(base_url=f"{api_address()}/knowledge_base/search_engine/{search_engine}", api_key="NONE")
+            client = openai.Client(base_url=f"{api_url}/knowledge_base/search_engine/{search_engine}", api_key="NONE")
             chat_box.ai_say([
                 Markdown("...", in_expander=True, title="知识库匹配结果", state="running", expanded=return_direct),
                 f"正在执行 `{search_engine}` 搜索...",
@@ -220,16 +221,19 @@ def on_conv_change():
         text = ""
         first = True
 
-        for d in client.chat.completions.create(messages=messages, model=llm_model, stream=True, extra_body=extra_body):
-            if first:
-                chat_box.update_msg("\n\n".join(d.docs), element_index=0, streaming=False, state="complete")
-                chat_box.update_msg("", streaming=False)
-                first = False
-                continue
-            text += d.choices[0].delta.content or ""
-            chat_box.update_msg(text.replace("\n", "\n\n"), streaming=True)
-        chat_box.update_msg(text, streaming=False)
-        # TODO: 搜索未配置API KEY时产生报错
+        try:
+            for d in client.chat.completions.create(messages=messages, model=llm_model, stream=True, extra_body=extra_body):
+                if first:
+                    chat_box.update_msg("\n\n".join(d.docs), element_index=0, streaming=False, state="complete")
+                    chat_box.update_msg("", streaming=False)
+                    first = False
+                    continue
+                text += d.choices[0].delta.content or ""
+                chat_box.update_msg(text.replace("\n", "\n\n"), streaming=True)
+            chat_box.update_msg(text, streaming=False)
+            # TODO: 搜索未配置API KEY时产生报错
+        except Exception as e:
+            st.error(e.body)
 
     now = datetime.now()
     with tabs[1]:
diff --git a/libs/chatchat-server/pyproject.toml b/libs/chatchat-server/pyproject.toml
index 09d712dbd7..9974bed5d0 100644
--- a/libs/chatchat-server/pyproject.toml
+++ b/libs/chatchat-server/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "langchain-chatchat"
-version = "0.3.1.1"
+version = "0.3.1.2"
 description = "Langchain-Chatchat (formerly langchain-ChatGLM), local knowledge based LLM (like ChatGLM, Qwen and Llama) RAG and Agent app with langchain"
 authors = ["chatchat"]
 readme = "README.md"
diff --git a/libs/python-sdk/open_chatcaht/__init__.py b/libs/python-sdk/open_chatcaht/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/_constants.py b/libs/python-sdk/open_chatcaht/_constants.py
new file mode 100644
index 0000000000..53121b7663
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/_constants.py
@@ -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
+
diff --git a/libs/python-sdk/open_chatcaht/_version.py b/libs/python-sdk/open_chatcaht/_version.py
new file mode 100644
index 0000000000..fe34e93d76
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/_version.py
@@ -0,0 +1,3 @@
+
+__title__ = "open_chatcaht"
+__version__ = "1.35.13"
diff --git a/libs/python-sdk/open_chatcaht/api/__init__.py b/libs/python-sdk/open_chatcaht/api/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/api/chat/__init__.py b/libs/python-sdk/open_chatcaht/api/chat/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/api/chat/chat_client.py b/libs/python-sdk/open_chatcaht/api/chat/chat_client.py
new file mode 100644
index 0000000000..4dc8aee9db
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/api/chat/chat_client.py
@@ -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)
diff --git a/libs/python-sdk/open_chatcaht/api/knowledge_base/__init__.py b/libs/python-sdk/open_chatcaht/api/knowledge_base/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/api/knowledge_base/knowledge_base_client.py b/libs/python-sdk/open_chatcaht/api/knowledge_base/knowledge_base_client.py
new file mode 100644
index 0000000000..074413ad75
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/api/knowledge_base/knowledge_base_client.py
@@ -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')
diff --git a/libs/python-sdk/open_chatcaht/api/server/__init__.py b/libs/python-sdk/open_chatcaht/api/server/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/api/server/server_client.py b/libs/python-sdk/open_chatcaht/api/server/server_client.py
new file mode 100644
index 0000000000..f07cf13f9e
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/api/server/server_client.py
@@ -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)
diff --git a/libs/python-sdk/open_chatcaht/api/standard_openai/__init__.py b/libs/python-sdk/open_chatcaht/api/standard_openai/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/api/standard_openai/standard_openai_client.py b/libs/python-sdk/open_chatcaht/api/standard_openai/standard_openai_client.py
new file mode 100644
index 0000000000..6f80f4243c
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/api/standard_openai/standard_openai_client.py
@@ -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)
diff --git a/libs/python-sdk/open_chatcaht/api/tools/__init__.py b/libs/python-sdk/open_chatcaht/api/tools/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/api/tools/tool_client.py b/libs/python-sdk/open_chatcaht/api/tools/tool_client.py
new file mode 100644
index 0000000000..8ee0c55854
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/api/tools/tool_client.py
@@ -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"))
diff --git a/libs/python-sdk/open_chatcaht/api_client.py b/libs/python-sdk/open_chatcaht/api_client.py
new file mode 100644
index 0000000000..895736bb26
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/api_client.py
@@ -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)
diff --git a/libs/python-sdk/open_chatcaht/chatchat_api.py b/libs/python-sdk/open_chatcaht/chatchat_api.py
new file mode 100644
index 0000000000..48bfd2f5d9
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/chatchat_api.py
@@ -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)
+
diff --git a/libs/python-sdk/open_chatcaht/exceptions.py b/libs/python-sdk/open_chatcaht/exceptions.py
new file mode 100644
index 0000000000..0bae734652
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/exceptions.py
@@ -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
diff --git a/libs/python-sdk/open_chatcaht/extra/__init__.py b/libs/python-sdk/open_chatcaht/extra/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/extra/langchain/__init__.py b/libs/python-sdk/open_chatcaht/extra/langchain/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/extra/langchain/chatchat_kb_retrieve.py b/libs/python-sdk/open_chatcaht/extra/langchain/chatchat_kb_retrieve.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/extra/llmaindex/__init__.py b/libs/python-sdk/open_chatcaht/extra/llmaindex/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/extra/llmaindex/chatchat_kb_retrieve.py b/libs/python-sdk/open_chatcaht/extra/llmaindex/chatchat_kb_retrieve.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/types/chat/__init__.py b/libs/python-sdk/open_chatcaht/types/chat/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/types/chat/chat_completions.py b/libs/python-sdk/open_chatcaht/types/chat/chat_completions.py
new file mode 100644
index 0000000000..3871f65000
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/chat/chat_completions.py
@@ -0,0 +1,5 @@
+from chatchat.server.api.api_schemas import OpenAIChatInput
+
+
+class ChatCompletions(OpenAIChatInput):
+    ...
diff --git a/libs/python-sdk/open_chatcaht/types/chat/chat_feedback_param.py b/libs/python-sdk/open_chatcaht/types/chat/chat_feedback_param.py
new file mode 100644
index 0000000000..252475c8c8
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/chat/chat_feedback_param.py
@@ -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="用户评分理由，比如不符合事实等"),
diff --git a/libs/python-sdk/open_chatcaht/types/chat/chat_message.py b/libs/python-sdk/open_chatcaht/types/chat/chat_message.py
new file mode 100644
index 0000000000..af996d31d3
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/chat/chat_message.py
@@ -0,0 +1,6 @@
+from pydantic import BaseModel, Field
+
+
+class ChatMessage(BaseModel):
+    role: str = Field(...)
+    content: str = Field(...)
diff --git a/libs/python-sdk/open_chatcaht/types/chat/file_chat_param.py b/libs/python-sdk/open_chatcaht/types/chat/file_chat_param.py
new file mode 100644
index 0000000000..5ed7aada96
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/chat/file_chat_param.py
@@ -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 中配置)",
+    ),
diff --git a/libs/python-sdk/open_chatcaht/types/chat/kb_chat_param.py b/libs/python-sdk/open_chatcaht/types/chat/kb_chat_param.py
new file mode 100644
index 0000000000..6bedee8955
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/chat/kb_chat_param.py
@@ -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"),
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/__init__.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/create_knowledge_base_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/create_knowledge_base_param.py
new file mode 100644
index 0000000000..b451a41c75
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/create_knowledge_base_param.py
@@ -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="向量化模型")
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/delete_knowledge_base_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/delete_knowledge_base_param.py
new file mode 100644
index 0000000000..ea7abdee01
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/delete_knowledge_base_param.py
@@ -0,0 +1,5 @@
+from pydantic import Field, BaseModel
+
+
+class DeleteKnowledgeBaseParam(BaseModel):
+    knowledge_base_name: str = Field(..., description="知识库名称")
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/__init__.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/delete_kb_docs_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/delete_kb_docs_param.py
new file mode 100644
index 0000000000..8c510f0d8b
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/delete_kb_docs_param.py
@@ -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）"),
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/download_kb_doc_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/download_kb_doc_param.py
new file mode 100644
index 0000000000..e980153334
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/download_kb_doc_param.py
@@ -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="是：浏览器内预览；否：下载"),
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/list_kb_docs_file_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/list_kb_docs_file_param.py
new file mode 100644
index 0000000000..3a75138f70
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/list_kb_docs_file_param.py
@@ -0,0 +1,5 @@
+from pydantic import BaseModel, Field
+
+
+class ListKbDocsFileParam(BaseModel):
+    knowledge_base_name: str = Field(description="知识库名称")
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/search_kb_docs_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/search_kb_docs_param.py
new file mode 100644
index 0000000000..22d78b5941
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/search_kb_docs_param.py
@@ -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 进行过滤，仅支持一级键"),
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/search_temp_docs_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/search_temp_docs_param.py
new file mode 100644
index 0000000000..a3ead33d07
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/search_temp_docs_param.py
@@ -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左右")
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/upload_kb_docs_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/upload_kb_docs_param.py
new file mode 100644
index 0000000000..3f376f0c3a
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/upload_kb_docs_param.py
@@ -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）"),
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/upload_temp_docs_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/upload_temp_docs_param.py
new file mode 100644
index 0000000000..5a7c266ac5
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/doc/upload_temp_docs_param.py
@@ -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="是否开启中文标题加强"),
\ No newline at end of file
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/knowledge_base.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/knowledge_base.py
new file mode 100644
index 0000000000..7f73056641
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/knowledge_base.py
@@ -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="创建时间")
\ No newline at end of file
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/recreate_vector_store_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/recreate_vector_store_param.py
new file mode 100644
index 0000000000..4f8822bcb2
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/recreate_vector_store_param.py
@@ -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）")
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/summary/__init__.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/summary/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/summary/recreate_summary_vector_store_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/summary/recreate_summary_vector_store_param.py
new file mode 100644
index 0000000000..7286c1893f
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/summary/recreate_summary_vector_store_param.py
@@ -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代表模型最大值"
+    ),
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/summary/summary_doc_ids_to_vector_store_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/summary/summary_doc_ids_to_vector_store_param.py
new file mode 100644
index 0000000000..af3307f4e7
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/summary/summary_doc_ids_to_vector_store_param.py
@@ -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代表模型最大值"
+    ),
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/summary/summary_file_to_vector_store_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/summary/summary_file_to_vector_store_param.py
new file mode 100644
index 0000000000..5e78014195
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/summary/summary_file_to_vector_store_param.py
@@ -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代表模型最大值"
+    ),
diff --git a/libs/python-sdk/open_chatcaht/types/knowledge_base/update_kb_info_param.py b/libs/python-sdk/open_chatcaht/types/knowledge_base/update_kb_info_param.py
new file mode 100644
index 0000000000..89f8fb49b8
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/knowledge_base/update_kb_info_param.py
@@ -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=["这是一个知识库"]),
diff --git a/libs/python-sdk/open_chatcaht/types/response/__init__.py b/libs/python-sdk/open_chatcaht/types/response/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/types/response/base.py b/libs/python-sdk/open_chatcaht/types/response/base.py
new file mode 100644
index 0000000000..2c6dd31e32
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/response/base.py
@@ -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"],
+            }
+        }
diff --git a/libs/python-sdk/open_chatcaht/types/server/__init__.py b/libs/python-sdk/open_chatcaht/types/server/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/types/standard_openai/__init__.py b/libs/python-sdk/open_chatcaht/types/standard_openai/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/types/standard_openai/audio_speech_input.py b/libs/python-sdk/open_chatcaht/types/standard_openai/audio_speech_input.py
new file mode 100644
index 0000000000..141abe1480
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/standard_openai/audio_speech_input.py
@@ -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
diff --git a/libs/python-sdk/open_chatcaht/types/standard_openai/audio_transcriptions_input.py b/libs/python-sdk/open_chatcaht/types/standard_openai/audio_transcriptions_input.py
new file mode 100644
index 0000000000..4d3442b995
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/standard_openai/audio_transcriptions_input.py
@@ -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
diff --git a/libs/python-sdk/open_chatcaht/types/standard_openai/audio_translations_input.py b/libs/python-sdk/open_chatcaht/types/standard_openai/audio_translations_input.py
new file mode 100644
index 0000000000..60a74f4077
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/standard_openai/audio_translations_input.py
@@ -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
diff --git a/libs/python-sdk/open_chatcaht/types/standard_openai/base.py b/libs/python-sdk/open_chatcaht/types/standard_openai/base.py
new file mode 100644
index 0000000000..1996b102ff
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/standard_openai/base.py
@@ -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"
diff --git a/libs/python-sdk/open_chatcaht/types/standard_openai/chat_input.py b/libs/python-sdk/open_chatcaht/types/standard_openai/chat_input.py
new file mode 100644
index 0000000000..b472624db5
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/standard_openai/chat_input.py
@@ -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
diff --git a/libs/python-sdk/open_chatcaht/types/standard_openai/embeddings_Input.py b/libs/python-sdk/open_chatcaht/types/standard_openai/embeddings_Input.py
new file mode 100644
index 0000000000..e563ce2bb7
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/standard_openai/embeddings_Input.py
@@ -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
diff --git a/libs/python-sdk/open_chatcaht/types/standard_openai/image_base_input.py b/libs/python-sdk/open_chatcaht/types/standard_openai/image_base_input.py
new file mode 100644
index 0000000000..e3ff9b78b2
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/standard_openai/image_base_input.py
@@ -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"
\ No newline at end of file
diff --git a/libs/python-sdk/open_chatcaht/types/standard_openai/image_edits_input.py b/libs/python-sdk/open_chatcaht/types/standard_openai/image_edits_input.py
new file mode 100644
index 0000000000..d148eff0dc
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/standard_openai/image_edits_input.py
@@ -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]
\ No newline at end of file
diff --git a/libs/python-sdk/open_chatcaht/types/standard_openai/image_generations_input.py b/libs/python-sdk/open_chatcaht/types/standard_openai/image_generations_input.py
new file mode 100644
index 0000000000..73ebd3483e
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/standard_openai/image_generations_input.py
@@ -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
\ No newline at end of file
diff --git a/libs/python-sdk/open_chatcaht/types/standard_openai/image_variations_input.py b/libs/python-sdk/open_chatcaht/types/standard_openai/image_variations_input.py
new file mode 100644
index 0000000000..494763145b
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/standard_openai/image_variations_input.py
@@ -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]
diff --git a/libs/python-sdk/open_chatcaht/types/tools/__init__.py b/libs/python-sdk/open_chatcaht/types/tools/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/open_chatcaht/types/tools/call_tool_param.py b/libs/python-sdk/open_chatcaht/types/tools/call_tool_param.py
new file mode 100644
index 0000000000..0aff467e46
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/types/tools/call_tool_param.py
@@ -0,0 +1,6 @@
+from pydantic import Field, BaseModel
+
+
+class CallToolParam(BaseModel):
+    name: str = Field(..., description="工具名称")
+    tool_input: dict = Field({}, description="知识库信息"),
diff --git a/libs/python-sdk/open_chatcaht/utils.py b/libs/python-sdk/open_chatcaht/utils.py
new file mode 100644
index 0000000000..458ee2286d
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/utils.py
@@ -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
\ No newline at end of file
diff --git a/libs/python-sdk/open_chatcaht/version.py b/libs/python-sdk/open_chatcaht/version.py
new file mode 100644
index 0000000000..01a08ab5a9
--- /dev/null
+++ b/libs/python-sdk/open_chatcaht/version.py
@@ -0,0 +1,3 @@
+from ._version import __version__
+
+VERSION: str = __version__
diff --git a/libs/python-sdk/poetry.toml b/libs/python-sdk/poetry.toml
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/pyproject.toml b/libs/python-sdk/pyproject.toml
new file mode 100644
index 0000000000..54f1428c41
--- /dev/null
+++ b/libs/python-sdk/pyproject.toml
@@ -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"
diff --git a/tools/autodl_start_script/start_chatchat.sh b/tools/autodl_start_script/start_chatchat.sh
index 634800b6c8..6e934de337 100644
--- a/tools/autodl_start_script/start_chatchat.sh
+++ b/tools/autodl_start_script/start_chatchat.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-conda run -n chatchat  --no-capture-output chatchat -a > >(tee chatchat-output.log) 2>&1 &
+conda run -n chatchat  --no-capture-output export CHATCHAT_ROOT=/root/chatchat-data && chatchat start -a > >(tee chatchat-output.log) 2>&1 &
 PID=$!
 echo "Started chatchat with PID $PID"
 
diff --git a/tools/autodl_start_script/start_models_emb.sh b/tools/autodl_start_script/start_models_emb.sh
index a9854bd388..cd722f28c7 100755
--- a/tools/autodl_start_script/start_models_emb.sh
+++ b/tools/autodl_start_script/start_models_emb.sh
@@ -13,4 +13,4 @@ curl 'http://127.0.0.1:9997/v1/models' \
   -H 'sec-ch-ua: "Chromium";v="124", "Google Chrome";v="124", "Not-A.Brand";v="99"' \
   -H 'sec-ch-ua-mobile: ?0' \
   -H 'sec-ch-ua-platform: "Linux"' \
-  --data-raw '{"model_uid":"bge-large-zh-v1.5","model_name":"autodl-tmp-bge-large-zh","model_type":"embedding","replica":1,"n_gpu":"auto","worker_ip":null,"gpu_idx":null}'
\ No newline at end of file
+  --data-raw '{"model_uid":"bge-large-zh-v1.5","model_name":"autodl-tmp-bge-large-zh","model_type":"embedding","replica":1,"n_gpu":null,"worker_ip":null,"gpu_idx":null}'
\ No newline at end of file


Test patch:
diff --git a/libs/python-sdk/tests/__init__.py b/libs/python-sdk/tests/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/libs/python-sdk/tests/chat_test.py b/libs/python-sdk/tests/chat_test.py
new file mode 100644
index 0000000000..9051a49961
--- /dev/null
+++ b/libs/python-sdk/tests/chat_test.py
@@ -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'))
diff --git a/libs/python-sdk/tests/data/upload_file1.txt b/libs/python-sdk/tests/data/upload_file1.txt
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/libs/python-sdk/tests/data/upload_file1.txt
@@ -0,0 +1,1 @@
+{}
\ No newline at end of file
diff --git a/libs/python-sdk/tests/data/upload_file2.txt b/libs/python-sdk/tests/data/upload_file2.txt
new file mode 100644
index 0000000000..9e26dfeeb6
--- /dev/null
+++ b/libs/python-sdk/tests/data/upload_file2.txt
@@ -0,0 +1,1 @@
+{}
\ No newline at end of file
diff --git a/libs/python-sdk/tests/kb_test.py b/libs/python-sdk/tests/kb_test.py
new file mode 100644
index 0000000000..a9b6562946
--- /dev/null
+++ b/libs/python-sdk/tests/kb_test.py
@@ -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"))
diff --git a/libs/python-sdk/tests/server_test.py b/libs/python-sdk/tests/server_test.py
new file mode 100644
index 0000000000..7547c29330
--- /dev/null
+++ b/libs/python-sdk/tests/server_test.py
@@ -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())
diff --git a/libs/python-sdk/tests/standard_openai_test.py b/libs/python-sdk/tests/standard_openai_test.py
new file mode 100644
index 0000000000..5f0cc12826
--- /dev/null
+++ b/libs/python-sdk/tests/standard_openai_test.py
@@ -0,0 +1,4 @@
+from open_chatcaht.chatchat_api import ChatChat
+from open_chatcaht.types.standard_openai.chat_input import OpenAIChatInput
+#
+# chatchat = ChatChat()
diff --git a/libs/python-sdk/tests/tools_test.py b/libs/python-sdk/tests/tools_test.py
new file mode 100644
index 0000000000..df3a0c6abc
--- /dev/null
+++ b/libs/python-sdk/tests/tools_test.py
@@ -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"}))
\ No newline at end of file
diff --git "a/libs/python-sdk/tests/\350\243\205\351\245\260\345\231\250\345\243\260\346\230\216\350\257\267\346\261\202_test.py" "b/libs/python-sdk/tests/\350\243\205\351\245\260\345\231\250\345\243\260\346\230\216\350\257\267\346\261\202_test.py"
new file mode 100644
index 0000000000..388ab72648
--- /dev/null
+++ "b/libs/python-sdk/tests/\350\243\205\351\245\260\345\231\250\345\243\260\346\230\216\350\257\267\346\261\202_test.py"
@@ -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)
diff --git "a/libs/python-sdk/tests/\350\243\205\351\245\260\345\231\250\345\243\260\346\230\216\350\257\267\346\261\202_test1.py" "b/libs/python-sdk/tests/\350\243\205\351\245\260\345\231\250\345\243\260\346\230\216\350\257\267\346\261\202_test1.py"
new file mode 100644
index 0000000000..cb80bf24bc
--- /dev/null
+++ "b/libs/python-sdk/tests/\350\243\205\351\245\260\345\231\250\345\243\260\346\230\216\350\257\267\346\261\202_test1.py"
@@ -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)