diff --git a/.env.template b/.env.template
index 0b660b575..fc5911ce4 100644
--- a/.env.template
+++ b/.env.template
@@ -235,4 +235,11 @@ SUMMARY_CONFIG=FAST
# FATAL, ERROR, WARNING, WARNING, INFO, DEBUG, NOTSET
DBGPT_LOG_LEVEL=INFO
# LOG dir, default: ./logs
-#DBGPT_LOG_DIR=
\ No newline at end of file
+#DBGPT_LOG_DIR=
+
+
+#*******************************************************************#
+#** API_KEYS **#
+#*******************************************************************#
+# API_KEYS - The list of API keys that are allowed to access the API. Each of the below are an option, separated by commas.
+# API_KEYS=dbgpt
\ No newline at end of file
diff --git a/.mypy.ini b/.mypy.ini
index bba90802a..5fb04d0d7 100644
--- a/.mypy.ini
+++ b/.mypy.ini
@@ -5,6 +5,9 @@ exclude = /tests/
[mypy-dbgpt.app.*]
follow_imports = skip
+[mypy-dbgpt.agent.*]
+follow_imports = skip
+
[mypy-dbgpt.serve.*]
follow_imports = skip
@@ -80,4 +83,7 @@ ignore_missing_imports = True
ignore_missing_imports = True
[mypy-clickhouse_connect.*]
+ignore_missing_imports = True
+
+[mypy-fastchat.protocol.api_protocol]
ignore_missing_imports = True
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 72cc22c01..a0b2bd217 100644
--- a/Makefile
+++ b/Makefile
@@ -48,7 +48,7 @@ fmt: setup ## Format Python code
$(VENV_BIN)/blackdoc examples
# TODO: Use flake8 to enforce Python style guide.
# https://flake8.pycqa.org/en/latest/
- $(VENV_BIN)/flake8 dbgpt/core/ dbgpt/rag/ dbgpt/storage/ dbgpt/datasource/
+ $(VENV_BIN)/flake8 dbgpt/core/ dbgpt/rag/ dbgpt/storage/ dbgpt/datasource/ dbgpt/client/
# TODO: More package checks with flake8.
.PHONY: fmt-check
@@ -57,7 +57,7 @@ fmt-check: setup ## Check Python code formatting and style without making change
$(VENV_BIN)/isort --check-only --extend-skip="examples/notebook" examples
$(VENV_BIN)/black --check --extend-exclude="examples/notebook" .
$(VENV_BIN)/blackdoc --check dbgpt examples
- $(VENV_BIN)/flake8 dbgpt/core/ dbgpt/rag/ dbgpt/storage/ dbgpt/datasource/
+ $(VENV_BIN)/flake8 dbgpt/core/ dbgpt/rag/ dbgpt/storage/ dbgpt/datasource/ dbgpt/client/
.PHONY: pre-commit
pre-commit: fmt-check test test-doc mypy ## Run formatting and unit tests before committing
@@ -73,7 +73,7 @@ test-doc: $(VENV)/.testenv ## Run doctests
.PHONY: mypy
mypy: $(VENV)/.testenv ## Run mypy checks
# https://github.com/python/mypy
- $(VENV_BIN)/mypy --config-file .mypy.ini dbgpt/rag/ dbgpt/datasource/
+ $(VENV_BIN)/mypy --config-file .mypy.ini dbgpt/rag/ dbgpt/datasource/ dbgpt/client/
# rag depends on core and storage, so we not need to check it again.
# $(VENV_BIN)/mypy --config-file .mypy.ini dbgpt/storage/
# $(VENV_BIN)/mypy --config-file .mypy.ini dbgpt/core/
@@ -107,4 +107,4 @@ upload: package ## Upload the package to PyPI
.PHONY: help
help: ## Display this help screen
@echo "Available commands:"
- @grep -E '^[a-z.A-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}' | sort
+ @grep -E '^[a-z.A-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}' | sort
\ No newline at end of file
diff --git a/assets/wechat.jpg b/assets/wechat.jpg
index cfc353527..3dde06d24 100644
Binary files a/assets/wechat.jpg and b/assets/wechat.jpg differ
diff --git a/dbgpt/_private/config.py b/dbgpt/_private/config.py
index e573da7ad..893388b34 100644
--- a/dbgpt/_private/config.py
+++ b/dbgpt/_private/config.py
@@ -286,6 +286,8 @@ class Config(metaclass=Singleton):
self.MODEL_CACHE_STORAGE_DISK_DIR: Optional[str] = os.getenv(
"MODEL_CACHE_STORAGE_DISK_DIR"
)
+ # global dbgpt api key
+ self.API_KEYS = os.getenv("API_KEYS", None)
@property
def local_db_manager(self) -> "ConnectorManager":
diff --git a/dbgpt/app/dbgpt_server.py b/dbgpt/app/dbgpt_server.py
index df6cdb9d4..c78c82f9d 100644
--- a/dbgpt/app/dbgpt_server.py
+++ b/dbgpt/app/dbgpt_server.py
@@ -89,13 +89,17 @@ def mount_routers(app: FastAPI):
router as api_editor_route_v1,
)
from dbgpt.app.openapi.api_v1.feedback.api_fb_v1 import router as api_fb_v1
+ from dbgpt.app.openapi.api_v2 import router as api_v2
from dbgpt.serve.agent.app.controller import router as gpts_v1
+ from dbgpt.serve.agent.app.endpoints import router as app_v2
app.include_router(api_v1, prefix="/api", tags=["Chat"])
+ app.include_router(api_v2, prefix="/api", tags=["ChatV2"])
app.include_router(api_editor_route_v1, prefix="/api", tags=["Editor"])
app.include_router(llm_manage_api, prefix="/api", tags=["LLM Manage"])
app.include_router(api_fb_v1, prefix="/api", tags=["FeedBack"])
app.include_router(gpts_v1, prefix="/api", tags=["GptsApp"])
+ app.include_router(app_v2, prefix="/api", tags=["App"])
app.include_router(knowledge_router, tags=["Knowledge"])
diff --git a/dbgpt/app/initialization/db_model_initialization.py b/dbgpt/app/initialization/db_model_initialization.py
index f2243dfc1..4d6353bcc 100644
--- a/dbgpt/app/initialization/db_model_initialization.py
+++ b/dbgpt/app/initialization/db_model_initialization.py
@@ -2,12 +2,12 @@
"""
from dbgpt.app.knowledge.chunk_db import DocumentChunkEntity
from dbgpt.app.knowledge.document_db import KnowledgeDocumentEntity
-from dbgpt.app.knowledge.space_db import KnowledgeSpaceEntity
from dbgpt.app.openapi.api_v1.feedback.feed_back_db import ChatFeedBackEntity
from dbgpt.datasource.manages.connect_config_db import ConnectConfigEntity
from dbgpt.serve.agent.db.my_plugin_db import MyPluginEntity
from dbgpt.serve.agent.db.plugin_hub_db import PluginHubEntity
from dbgpt.serve.prompt.models.models import ServeEntity as PromptManageEntity
+from dbgpt.serve.rag.models.models import KnowledgeSpaceEntity
from dbgpt.storage.chat_history.chat_history_db import (
ChatHistoryEntity,
ChatHistoryMessageEntity,
diff --git a/dbgpt/app/initialization/serve_initialization.py b/dbgpt/app/initialization/serve_initialization.py
index 5d8f06709..4e757fca5 100644
--- a/dbgpt/app/initialization/serve_initialization.py
+++ b/dbgpt/app/initialization/serve_initialization.py
@@ -5,6 +5,8 @@ from dbgpt.component import SystemApp
def register_serve_apps(system_app: SystemApp, cfg: Config):
"""Register serve apps"""
system_app.config.set("dbgpt.app.global.language", cfg.LANGUAGE)
+ if cfg.API_KEYS:
+ system_app.config.set("dbgpt.app.global.api_keys", cfg.API_KEYS)
# ################################ Prompt Serve Register Begin ######################################
from dbgpt.serve.prompt.serve import (
@@ -42,4 +44,12 @@ def register_serve_apps(system_app: SystemApp, cfg: Config):
# Register serve app
system_app.register(FlowServe)
+
+ from dbgpt.serve.rag.serve import (
+ SERVE_CONFIG_KEY_PREFIX as RAG_SERVE_CONFIG_KEY_PREFIX,
+ )
+ from dbgpt.serve.rag.serve import Serve as RagServe
+
+ # Register serve app
+ system_app.register(RagServe)
# ################################ AWEL Flow Serve Register End ########################################
diff --git a/dbgpt/app/knowledge/api.py b/dbgpt/app/knowledge/api.py
index 875dbcb18..a891c46ea 100644
--- a/dbgpt/app/knowledge/api.py
+++ b/dbgpt/app/knowledge/api.py
@@ -4,7 +4,7 @@ import shutil
import tempfile
from typing import List
-from fastapi import APIRouter, File, Form, UploadFile
+from fastapi import APIRouter, Depends, File, Form, UploadFile
from dbgpt._private.config import Config
from dbgpt.app.knowledge.request.request import (
@@ -16,7 +16,6 @@ from dbgpt.app.knowledge.request.request import (
KnowledgeDocumentRequest,
KnowledgeQueryRequest,
KnowledgeSpaceRequest,
- KnowledgeSyncRequest,
SpaceArgumentRequest,
)
from dbgpt.app.knowledge.request.response import KnowledgeQueryResponse
@@ -31,6 +30,8 @@ from dbgpt.rag.embedding.embedding_factory import EmbeddingFactory
from dbgpt.rag.knowledge.base import ChunkStrategy
from dbgpt.rag.knowledge.factory import KnowledgeFactory
from dbgpt.rag.retriever.embedding import EmbeddingRetriever
+from dbgpt.serve.rag.api.schemas import KnowledgeSyncRequest
+from dbgpt.serve.rag.service.service import Service
from dbgpt.storage.vector_store.base import VectorStoreConfig
from dbgpt.storage.vector_store.connector import VectorStoreConnector
from dbgpt.util.tracer import SpanType, root_tracer
@@ -44,6 +45,11 @@ router = APIRouter()
knowledge_space_service = KnowledgeService()
+def get_rag_service() -> Service:
+ """Get Rag Service."""
+ return Service.get_instance(CFG.SYSTEM_APP)
+
+
@router.post("/knowledge/space/add")
def space_add(request: KnowledgeSpaceRequest):
print(f"/space/add params: {request}")
@@ -226,12 +232,20 @@ def document_sync(space_name: str, request: DocumentSyncRequest):
@router.post("/knowledge/{space_name}/document/sync_batch")
-def batch_document_sync(space_name: str, request: List[KnowledgeSyncRequest]):
+def batch_document_sync(
+ space_name: str,
+ request: List[KnowledgeSyncRequest],
+ service: Service = Depends(get_rag_service),
+):
logger.info(f"Received params: {space_name}, {request}")
try:
- doc_ids = knowledge_space_service.batch_document_sync(
- space_name=space_name, sync_requests=request
- )
+ space = service.get({"name": space_name})
+ for sync_request in request:
+ sync_request.space_id = space.id
+ doc_ids = service.sync_document(requests=request)
+ # doc_ids = service.sync_document(
+ # space_name=space_name, sync_requests=request
+ # )
return Result.succ({"tasks": doc_ids})
except Exception as e:
return Result.failed(code="E000X", msg=f"document sync error {e}")
diff --git a/dbgpt/app/knowledge/document_db.py b/dbgpt/app/knowledge/document_db.py
index 7e08d0733..1165b7296 100644
--- a/dbgpt/app/knowledge/document_db.py
+++ b/dbgpt/app/knowledge/document_db.py
@@ -1,9 +1,11 @@
from datetime import datetime
-from typing import List
+from typing import Any, Dict, List, Union
from sqlalchemy import Column, DateTime, Integer, String, Text, func
from dbgpt._private.config import Config
+from dbgpt.serve.conversation.api.schemas import ServeRequest
+from dbgpt.serve.rag.api.schemas import DocumentServeRequest, DocumentServeResponse
from dbgpt.storage.metadata import BaseDao, Model
CFG = Config()
@@ -218,3 +220,70 @@ class KnowledgeDocumentDao(BaseDao):
knowledge_documents.delete()
session.commit()
session.close()
+
+ def from_request(
+ self, request: Union[ServeRequest, Dict[str, Any]]
+ ) -> KnowledgeDocumentEntity:
+ """Convert the request to an entity
+
+ Args:
+ request (Union[ServeRequest, Dict[str, Any]]): The request
+
+ Returns:
+ T: The entity
+ """
+ request_dict = (
+ request.dict() if isinstance(request, DocumentServeRequest) else request
+ )
+ entity = KnowledgeDocumentEntity(**request_dict)
+ return entity
+
+ def to_request(self, entity: KnowledgeDocumentEntity) -> DocumentServeResponse:
+ """Convert the entity to a request
+
+ Args:
+ entity (T): The entity
+
+ Returns:
+ REQ: The request
+ """
+ return DocumentServeResponse(
+ id=entity.id,
+ doc_name=entity.doc_name,
+ doc_type=entity.doc_type,
+ space=entity.space,
+ chunk_size=entity.chunk_size,
+ status=entity.status,
+ last_sync=entity.last_sync,
+ content=entity.content,
+ result=entity.result,
+ vector_ids=entity.vector_ids,
+ summary=entity.summary,
+ gmt_created=entity.gmt_created,
+ gmt_modified=entity.gmt_modified,
+ )
+
+ def to_response(self, entity: KnowledgeDocumentEntity) -> DocumentServeResponse:
+ """Convert the entity to a response
+
+ Args:
+ entity (T): The entity
+
+ Returns:
+ REQ: The request
+ """
+ return DocumentServeResponse(
+ id=entity.id,
+ doc_name=entity.doc_name,
+ doc_type=entity.doc_type,
+ space=entity.space,
+ chunk_size=entity.chunk_size,
+ status=entity.status,
+ last_sync=entity.last_sync,
+ content=entity.content,
+ result=entity.result,
+ vector_ids=entity.vector_ids,
+ summary=entity.summary,
+ gmt_created=entity.gmt_created,
+ gmt_modified=entity.gmt_modified,
+ )
diff --git a/dbgpt/app/knowledge/request/request.py b/dbgpt/app/knowledge/request/request.py
index fbd6a697c..14e12ce90 100644
--- a/dbgpt/app/knowledge/request/request.py
+++ b/dbgpt/app/knowledge/request/request.py
@@ -17,6 +17,8 @@ class KnowledgeQueryRequest(BaseModel):
class KnowledgeSpaceRequest(BaseModel):
"""name: knowledge space name"""
+ """vector_type: vector type"""
+ id: int = None
name: str = None
"""vector_type: vector type"""
vector_type: str = None
@@ -37,9 +39,6 @@ class KnowledgeDocumentRequest(BaseModel):
"""content: content"""
source: str = None
- """text_chunk_size: text_chunk_size"""
- # text_chunk_size: int
-
class DocumentQueryRequest(BaseModel):
"""doc_name: doc path"""
@@ -80,20 +79,6 @@ class DocumentSyncRequest(BaseModel):
chunk_overlap: Optional[int] = None
-class KnowledgeSyncRequest(BaseModel):
- """Sync request"""
-
- """doc_ids: doc ids"""
- doc_id: int
-
- """model_name: model name"""
- model_name: Optional[str] = None
-
- """chunk_parameters: chunk parameters
- """
- chunk_parameters: ChunkParameters
-
-
class ChunkQueryRequest(BaseModel):
"""id: id"""
diff --git a/dbgpt/app/knowledge/service.py b/dbgpt/app/knowledge/service.py
index c6bf61fc8..5b59bc803 100644
--- a/dbgpt/app/knowledge/service.py
+++ b/dbgpt/app/knowledge/service.py
@@ -1,7 +1,6 @@
import json
import logging
from datetime import datetime
-from enum import Enum
from typing import List
from dbgpt._private.config import Config
@@ -17,7 +16,6 @@ from dbgpt.app.knowledge.request.request import (
DocumentSyncRequest,
KnowledgeDocumentRequest,
KnowledgeSpaceRequest,
- KnowledgeSyncRequest,
SpaceArgumentRequest,
)
from dbgpt.app.knowledge.request.response import (
@@ -25,7 +23,6 @@ from dbgpt.app.knowledge.request.response import (
DocumentQueryResponse,
SpaceQueryResponse,
)
-from dbgpt.app.knowledge.space_db import KnowledgeSpaceDao, KnowledgeSpaceEntity
from dbgpt.component import ComponentType
from dbgpt.configs.model_config import EMBEDDING_MODEL_CONFIG
from dbgpt.core import Chunk
@@ -38,8 +35,11 @@ from dbgpt.rag.text_splitter.text_splitter import (
RecursiveCharacterTextSplitter,
SpacyTextSplitter,
)
+from dbgpt.serve.rag.api.schemas import KnowledgeSyncRequest
from dbgpt.serve.rag.assembler.embedding import EmbeddingAssembler
from dbgpt.serve.rag.assembler.summary import SummaryAssembler
+from dbgpt.serve.rag.models.models import KnowledgeSpaceDao, KnowledgeSpaceEntity
+from dbgpt.serve.rag.service.service import Service, SyncStatus
from dbgpt.storage.vector_store.base import VectorStoreConfig
from dbgpt.storage.vector_store.connector import VectorStoreConnector
from dbgpt.util.executor_utils import ExecutorFactory, blocking_func_to_async
@@ -53,13 +53,6 @@ logger = logging.getLogger(__name__)
CFG = Config()
-class SyncStatus(Enum):
- TODO = "TODO"
- FAILED = "FAILED"
- RUNNING = "RUNNING"
- FINISHED = "FINISHED"
-
-
# default summary max iteration call with llm.
DEFAULT_SUMMARY_MAX_ITERATION = 5
# default summary concurrency call with llm.
@@ -88,8 +81,8 @@ class KnowledgeService:
spaces = knowledge_space_dao.get_knowledge_space(query)
if len(spaces) > 0:
raise Exception(f"space name:{request.name} have already named")
- knowledge_space_dao.create_knowledge_space(request)
- return True
+ space_id = knowledge_space_dao.create_knowledge_space(request)
+ return space_id
def create_knowledge_document(self, space, request: KnowledgeDocumentRequest):
"""create knowledge document
@@ -199,7 +192,9 @@ class KnowledgeService:
return res
def batch_document_sync(
- self, space_name, sync_requests: List[KnowledgeSyncRequest]
+ self,
+ space_name,
+ sync_requests: List[KnowledgeSyncRequest],
) -> List[int]:
"""batch sync knowledge document chunk into vector store
Args:
diff --git a/dbgpt/app/openapi/api_v1/api_v1.py b/dbgpt/app/openapi/api_v1/api_v1.py
index 3441ab8e0..300f5f8ae 100644
--- a/dbgpt/app/openapi/api_v1/api_v1.py
+++ b/dbgpt/app/openapi/api_v1/api_v1.py
@@ -13,11 +13,8 @@ from dbgpt._private.config import Config
from dbgpt.app.knowledge.request.request import KnowledgeSpaceRequest
from dbgpt.app.knowledge.service import KnowledgeService
from dbgpt.app.openapi.api_view_model import (
- ChatCompletionResponseStreamChoice,
- ChatCompletionStreamResponse,
ChatSceneVo,
ConversationVo,
- DeltaMessage,
MessageVo,
Result,
)
@@ -25,6 +22,11 @@ from dbgpt.app.scene import BaseChat, ChatFactory, ChatScene
from dbgpt.component import ComponentType
from dbgpt.configs.model_config import KNOWLEDGE_UPLOAD_ROOT_PATH
from dbgpt.core.awel import CommonLLMHttpRequestBody, CommonLLMHTTPRequestContext
+from dbgpt.core.schema.api import (
+ ChatCompletionResponseStreamChoice,
+ ChatCompletionStreamResponse,
+ DeltaMessage,
+)
from dbgpt.datasource.db_conn_info import DBConfig, DbTypeInfo
from dbgpt.model.base import FlatSupportedModel
from dbgpt.model.cluster import BaseModelController, WorkerManager, WorkerManagerFactory
@@ -439,7 +441,6 @@ async def stream_generator(chat, incremental: bool, model_name: str):
span = root_tracer.start_span("stream_generator")
msg = "[LLM_ERROR]: llm server has no output, maybe your prompt template is wrong."
- stream_id = f"chatcmpl-{str(uuid.uuid1())}"
previous_response = ""
async for chunk in chat.stream_call():
if chunk:
@@ -451,7 +452,7 @@ async def stream_generator(chat, incremental: bool, model_name: str):
delta=DeltaMessage(role="assistant", content=incremental_output),
)
chunk = ChatCompletionStreamResponse(
- id=stream_id, choices=[choice_data], model=model_name
+ id=chat.chat_session_id, choices=[choice_data], model=model_name
)
yield f"data: {chunk.json(exclude_unset=True, ensure_ascii=False)}\n\n"
else:
diff --git a/dbgpt/app/openapi/api_v2.py b/dbgpt/app/openapi/api_v2.py
new file mode 100644
index 000000000..4ed8335d7
--- /dev/null
+++ b/dbgpt/app/openapi/api_v2.py
@@ -0,0 +1,333 @@
+import json
+import re
+import time
+import uuid
+from typing import Optional
+
+from fastapi import APIRouter, Body, Depends, HTTPException
+from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
+from starlette.responses import StreamingResponse
+
+from dbgpt.app.openapi.api_v1.api_v1 import (
+ CHAT_FACTORY,
+ __new_conversation,
+ get_chat_flow,
+ get_chat_instance,
+ get_executor,
+ stream_generator,
+)
+from dbgpt.app.scene import BaseChat, ChatScene
+from dbgpt.client.schema import ChatCompletionRequestBody, ChatMode
+from dbgpt.component import logger
+from dbgpt.core.awel import CommonLLMHttpRequestBody, CommonLLMHTTPRequestContext
+from dbgpt.core.schema.api import (
+ ChatCompletionResponse,
+ ChatCompletionResponseChoice,
+ ChatCompletionResponseStreamChoice,
+ ChatCompletionStreamResponse,
+ ChatMessage,
+ DeltaMessage,
+ UsageInfo,
+)
+from dbgpt.model.cluster.apiserver.api import APISettings
+from dbgpt.serve.agent.agents.controller import multi_agents
+from dbgpt.serve.flow.api.endpoints import get_service
+from dbgpt.serve.flow.service.service import Service as FlowService
+from dbgpt.util.executor_utils import blocking_func_to_async
+from dbgpt.util.tracer import SpanType, root_tracer
+
+router = APIRouter()
+api_settings = APISettings()
+get_bearer_token = HTTPBearer(auto_error=False)
+
+
+async def check_api_key(
+ auth: Optional[HTTPAuthorizationCredentials] = Depends(get_bearer_token),
+ service=Depends(get_service),
+) -> Optional[str]:
+ """Check the api key
+ Args:
+ auth (Optional[HTTPAuthorizationCredentials]): The bearer token.
+ service (Service): The flow service.
+ """
+ if service.config.api_keys:
+ api_keys = [key.strip() for key in service.config.api_keys.split(",")]
+ if auth is None or (token := auth.credentials) not in api_keys:
+ raise HTTPException(
+ status_code=401,
+ detail={
+ "error": {
+ "message": "",
+ "type": "invalid_request_error",
+ "param": None,
+ "code": "invalid_api_key",
+ }
+ },
+ )
+ return token
+ else:
+ return None
+
+
+@router.post("/v2/chat/completions", dependencies=[Depends(check_api_key)])
+async def chat_completions(
+ request: ChatCompletionRequestBody = Body(),
+):
+ """Chat V2 completions
+ Args:
+ request (ChatCompletionRequestBody): The chat request.
+ flow_service (FlowService): The flow service.
+ Raises:
+ HTTPException: If the request is invalid.
+ """
+ logger.info(
+ f"chat_completions:{request.chat_mode},{request.chat_param},{request.model}"
+ )
+ headers = {
+ "Content-Type": "text/event-stream",
+ "Cache-Control": "no-cache",
+ "Connection": "keep-alive",
+ "Transfer-Encoding": "chunked",
+ }
+ # check chat request
+ check_chat_request(request)
+ if request.conv_uid is None:
+ request.conv_uid = str(uuid.uuid4())
+ if request.chat_mode == ChatMode.CHAT_APP.value:
+ if request.stream is False:
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": {
+ "message": "chat app now not support no stream",
+ "type": "invalid_request_error",
+ "param": None,
+ "code": "invalid_request_error",
+ }
+ },
+ )
+ return StreamingResponse(
+ chat_app_stream_wrapper(
+ request=request,
+ ),
+ headers=headers,
+ media_type="text/event-stream",
+ )
+ elif request.chat_mode == ChatMode.CHAT_AWEL_FLOW.value:
+ return StreamingResponse(
+ chat_flow_stream_wrapper(request),
+ headers=headers,
+ media_type="text/event-stream",
+ )
+ elif (
+ request.chat_mode is None
+ or request.chat_mode == ChatMode.CHAT_NORMAL.value
+ or request.chat_mode == ChatMode.CHAT_KNOWLEDGE.value
+ ):
+ with root_tracer.start_span(
+ "get_chat_instance", span_type=SpanType.CHAT, metadata=request.dict()
+ ):
+ chat: BaseChat = await get_chat_instance(request)
+
+ if not request.stream:
+ return await no_stream_wrapper(request, chat)
+ else:
+ return StreamingResponse(
+ stream_generator(chat, request.incremental, request.model),
+ headers=headers,
+ media_type="text/plain",
+ )
+ else:
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": {
+ "message": "chat mode now only support chat_normal, chat_app, chat_flow, chat_knowledge",
+ "type": "invalid_request_error",
+ "param": None,
+ "code": "invalid_chat_mode",
+ }
+ },
+ )
+
+
+async def get_chat_instance(dialogue: ChatCompletionRequestBody = Body()) -> BaseChat:
+ """
+ Get chat instance
+ Args:
+ dialogue (OpenAPIChatCompletionRequest): The chat request.
+ """
+ logger.info(f"get_chat_instance:{dialogue}")
+ if not dialogue.chat_mode:
+ dialogue.chat_mode = ChatScene.ChatNormal.value()
+ if not dialogue.conv_uid:
+ conv_vo = __new_conversation(
+ dialogue.chat_mode, dialogue.user_name, dialogue.sys_code
+ )
+ dialogue.conv_uid = conv_vo.conv_uid
+
+ if not ChatScene.is_valid_mode(dialogue.chat_mode):
+ raise StopAsyncIteration(f"Unsupported Chat Mode,{dialogue.chat_mode}!")
+
+ chat_param = {
+ "chat_session_id": dialogue.conv_uid,
+ "user_name": dialogue.user_name,
+ "sys_code": dialogue.sys_code,
+ "current_user_input": dialogue.messages,
+ "select_param": dialogue.chat_param,
+ "model_name": dialogue.model,
+ }
+ chat: BaseChat = await blocking_func_to_async(
+ get_executor(),
+ CHAT_FACTORY.get_implementation,
+ dialogue.chat_mode,
+ **{"chat_param": chat_param},
+ )
+ return chat
+
+
+async def no_stream_wrapper(
+ request: ChatCompletionRequestBody, chat: BaseChat
+) -> ChatCompletionResponse:
+ """
+ no stream wrapper
+ Args:
+ request (OpenAPIChatCompletionRequest): request
+ chat (BaseChat): chat
+ """
+ with root_tracer.start_span("no_stream_generator"):
+ response = await chat.nostream_call()
+ msg = response.replace("\ufffd", "")
+ choice_data = ChatCompletionResponseChoice(
+ index=0,
+ message=ChatMessage(role="assistant", content=msg),
+ )
+ usage = UsageInfo()
+ return ChatCompletionResponse(
+ id=request.conv_uid, choices=[choice_data], model=request.model, usage=usage
+ )
+
+
+async def chat_app_stream_wrapper(request: ChatCompletionRequestBody = None):
+ """chat app stream
+ Args:
+ request (OpenAPIChatCompletionRequest): request
+ token (APIToken): token
+ """
+ async for output in multi_agents.app_agent_chat(
+ conv_uid=request.conv_uid,
+ gpts_name=request.chat_param,
+ user_query=request.messages,
+ user_code=request.user_name,
+ sys_code=request.sys_code,
+ ):
+ match = re.search(r"data:\s*({.*})", output)
+ if match:
+ json_str = match.group(1)
+ vis = json.loads(json_str)
+ vis_content = vis.get("vis", None)
+ if vis_content != "[DONE]":
+ choice_data = ChatCompletionResponseStreamChoice(
+ index=0,
+ delta=DeltaMessage(role="assistant", content=vis.get("vis", None)),
+ )
+ chunk = ChatCompletionStreamResponse(
+ id=request.conv_uid,
+ choices=[choice_data],
+ model=request.model,
+ created=int(time.time()),
+ )
+ content = (
+ f"data: {chunk.json(exclude_unset=True, ensure_ascii=False)}\n\n"
+ )
+ yield content
+ yield "data: [DONE]\n\n"
+
+
+async def chat_flow_stream_wrapper(
+ request: ChatCompletionRequestBody = None,
+):
+ """chat app stream
+ Args:
+ request (OpenAPIChatCompletionRequest): request
+ token (APIToken): token
+ """
+ flow_service = get_chat_flow()
+ flow_ctx = CommonLLMHTTPRequestContext(
+ conv_uid=request.conv_uid,
+ chat_mode=request.chat_mode,
+ user_name=request.user_name,
+ sys_code=request.sys_code,
+ )
+ flow_req = CommonLLMHttpRequestBody(
+ model=request.model,
+ messages=request.chat_param,
+ stream=True,
+ context=flow_ctx,
+ )
+ async for output in flow_service.chat_flow(request.chat_param, flow_req):
+ if output.startswith("data: [DONE]"):
+ yield output
+ if output.startswith("data:"):
+ output = output[len("data: ") :]
+ choice_data = ChatCompletionResponseStreamChoice(
+ index=0,
+ delta=DeltaMessage(role="assistant", content=output),
+ )
+ chunk = ChatCompletionStreamResponse(
+ id=request.conv_uid,
+ choices=[choice_data],
+ model=request.model,
+ created=int(time.time()),
+ )
+ chat_completion_response = (
+ f"data: {chunk.json(exclude_unset=True, ensure_ascii=False)}\n\n"
+ )
+ yield chat_completion_response
+
+
+def check_chat_request(request: ChatCompletionRequestBody = Body()):
+ """
+ Check the chat request
+ Args:
+ request (ChatCompletionRequestBody): The chat request.
+ Raises:
+ HTTPException: If the request is invalid.
+ """
+ if request.chat_mode and request.chat_mode != ChatScene.ChatNormal.value():
+ if request.chat_param is None:
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": {
+ "message": "chart param is None",
+ "type": "invalid_request_error",
+ "param": None,
+ "code": "invalid_chat_param",
+ }
+ },
+ )
+ if request.model is None:
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": {
+ "message": "model is None",
+ "type": "invalid_request_error",
+ "param": None,
+ "code": "invalid_model",
+ }
+ },
+ )
+ if request.messages is None:
+ raise HTTPException(
+ status_code=400,
+ detail={
+ "error": {
+ "message": "messages is None",
+ "type": "invalid_request_error",
+ "param": None,
+ "code": "invalid_messages",
+ }
+ },
+ )
diff --git a/dbgpt/app/openapi/api_view_model.py b/dbgpt/app/openapi/api_view_model.py
index cd4ccadfd..f3655e30a 100644
--- a/dbgpt/app/openapi/api_view_model.py
+++ b/dbgpt/app/openapi/api_view_model.py
@@ -89,21 +89,3 @@ class MessageVo(BaseModel):
model_name
"""
model_name: str
-
-
-class DeltaMessage(BaseModel):
- role: Optional[str] = None
- content: Optional[str] = None
-
-
-class ChatCompletionResponseStreamChoice(BaseModel):
- index: int
- delta: DeltaMessage
- finish_reason: Optional[Literal["stop", "length"]] = None
-
-
-class ChatCompletionStreamResponse(BaseModel):
- id: str = Field(default_factory=lambda: f"chatcmpl-{str(uuid.uuid1())}")
- created: int = Field(default_factory=lambda: int(time.time()))
- model: str
- choices: List[ChatCompletionResponseStreamChoice]
diff --git a/dbgpt/app/scene/chat_knowledge/v1/prompt.py b/dbgpt/app/scene/chat_knowledge/v1/prompt.py
index b32e8f5e4..433f62f45 100644
--- a/dbgpt/app/scene/chat_knowledge/v1/prompt.py
+++ b/dbgpt/app/scene/chat_knowledge/v1/prompt.py
@@ -14,14 +14,22 @@ PROMPT_SCENE_DEFINE = """A chat between a curious user and an artificial intelli
The assistant gives helpful, detailed, professional and polite answers to the user's questions. """
-_DEFAULT_TEMPLATE_ZH = """ 基于以下已知的信息, 专业、简要的回答用户的问题,
- 如果无法从提供的内容中获取答案, 请说: "知识库中提供的内容不足以回答此问题" 禁止胡乱编造, 回答的时候最好按照1.2.3.点进行总结。
+_DEFAULT_TEMPLATE_ZH = """ 基于以下给出的已知信息, 准守规范约束,专业、简要回答用户的问题.
+规范约束:
+ 1.如果已知信息包含的图片、链接、表格、代码块等特殊markdown标签格式的信息,确保在答案中包含原文这些图片、链接、表格和代码标签,不要丢弃不要修改,如:图片格式:, 链接格式:[xxx](xxx), 表格格式:|xxx|xxx|xxx|, 代码格式:```xxx```.
+ 2.如果无法从提供的内容中获取答案, 请说: "知识库中提供的内容不足以回答此问题" 禁止胡乱编造.
+ 3.回答的时候最好按照1.2.3.点进行总结.
已知内容:
{context}
问题:
{question},请使用和用户相同的语言进行回答.
"""
-_DEFAULT_TEMPLATE_EN = """ Based on the known information below, provide users with professional and concise answers to their questions. If the answer cannot be obtained from the provided content, please say: "The information provided in the knowledge base is not sufficient to answer this question." It is forbidden to make up information randomly. When answering, it is best to summarize according to points 1.2.3.
+_DEFAULT_TEMPLATE_EN = """ Based on the known information below, provide users with professional and concise answers to their questions.
+constraints:
+ 1.Ensure to include original markdown formatting elements such as images, links, tables, or code blocks without alteration in the response if they are present in the provided information.
+ For example, image format should be , link format [xxx](xxx), table format should be represented with |xxx|xxx|xxx|, and code format with xxx.
+ 2.If the information available in the knowledge base is insufficient to answer the question, state clearly: "The content provided in the knowledge base is not enough to answer this question," and avoid making up answers.
+ 3.When responding, it is best to summarize the points in the order of 1, 2, 3.
known information:
{context}
question:
diff --git a/dbgpt/client/__init__.py b/dbgpt/client/__init__.py
new file mode 100644
index 000000000..7ddf7e9eb
--- /dev/null
+++ b/dbgpt/client/__init__.py
@@ -0,0 +1,5 @@
+"""This module is the client of the dbgpt package."""
+
+from .client import Client, ClientException # noqa: F401
+
+__ALL__ = ["Client", "ClientException"]
diff --git a/dbgpt/client/app.py b/dbgpt/client/app.py
new file mode 100644
index 000000000..23f2f90ea
--- /dev/null
+++ b/dbgpt/client/app.py
@@ -0,0 +1,50 @@
+"""App Client API."""
+from typing import List
+
+from dbgpt.core.schema.api import Result
+
+from .client import Client, ClientException
+from .schema import AppModel
+
+
+async def get_app(client: Client, app_id: str) -> AppModel:
+ """Get an app.
+
+ Args:
+ client (Client): The dbgpt client.
+ app_id (str): The app id.
+ Returns:
+ AppModel: The app model.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.get("/apps/" + app_id)
+ result: Result = res.json()
+ if result["success"]:
+ return AppModel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to get app: {e}")
+
+
+async def list_app(client: Client) -> List[AppModel]:
+ """List apps.
+
+ Args:
+ client (Client): The dbgpt client.
+ Returns:
+ List[AppModel]: The list of app models.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.get("/apps")
+ result: Result = res.json()
+ if result["success"]:
+ return [AppModel(**app) for app in result["data"]["app_list"]]
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to list apps: {e}")
diff --git a/dbgpt/client/client.py b/dbgpt/client/client.py
new file mode 100644
index 000000000..eccbd26ae
--- /dev/null
+++ b/dbgpt/client/client.py
@@ -0,0 +1,374 @@
+"""This module contains the client for the DB-GPT API."""
+import json
+import os
+from typing import Any, AsyncGenerator, List, Optional, Union
+from urllib.parse import urlparse
+
+import httpx
+
+from dbgpt.core.schema.api import ChatCompletionResponse, ChatCompletionStreamResponse
+
+from .schema import ChatCompletionRequestBody
+
+CLIENT_API_PATH = "api"
+CLIENT_SERVE_PATH = "serve"
+
+
+class ClientException(Exception):
+ """ClientException is raised when an error occurs in the client."""
+
+ def __init__(self, status=None, reason=None, http_resp=None):
+ """Initialize the ClientException.
+
+ Args:
+ status: Optional[int], the HTTP status code.
+ reason: Optional[str], the reason for the exception.
+ http_resp: Optional[httpx.Response], the HTTP response object.
+ """
+ self.status = status
+ self.reason = reason
+ self.http_resp = http_resp
+ self.headers = http_resp.headers if http_resp else None
+ self.body = http_resp.text if http_resp else None
+
+ def __str__(self):
+ """Return the error message."""
+ error_message = "({0})\n" "Reason: {1}\n".format(self.status, self.reason)
+ if self.headers:
+ error_message += "HTTP response headers: {0}\n".format(self.headers)
+
+ if self.body:
+ error_message += "HTTP response body: {0}\n".format(self.body)
+
+ return error_message
+
+
+"""Client API."""
+
+
+class Client:
+ """The client for the DB-GPT API."""
+
+ def __init__(
+ self,
+ api_base: Optional[str] = None,
+ api_key: Optional[str] = None,
+ version: str = "v2",
+ timeout: Optional[httpx._types.TimeoutTypes] = 120,
+ ):
+ """Create the client.
+
+ Args:
+ api_base: Optional[str], a full URL for the DB-GPT API.
+ Defaults to the `http://localhost:5000/api/v2`.
+ api_key: Optional[str], The dbgpt api key to use for authentication.
+ Defaults to None.
+ timeout: Optional[httpx._types.TimeoutTypes]: The timeout to use.
+ Defaults to None.
+ In most cases, pass in a float number to specify the timeout in seconds.
+ Returns:
+ None
+ Raise: ClientException
+
+ Examples:
+ --------
+ .. code-block:: python
+
+ from dbgpt.client import Client
+
+ DBGPT_API_BASE = "http://localhost:5000/api/v2"
+ DBGPT_API_KEY = "dbgpt"
+ client = Client(api_base=DBGPT_API_BASE, api_key=DBGPT_API_KEY)
+ client.chat(model="chatgpt_proxyllm", messages="Hello?")
+ """
+ if not api_base:
+ api_base = os.getenv(
+ "DBGPT_API_BASE", f"http://localhost:5000/{CLIENT_API_PATH}/{version}"
+ )
+ if not api_key:
+ api_key = os.getenv("DBGPT_API_KEY")
+ if api_base and is_valid_url(api_base):
+ self._api_url = api_base
+ else:
+ raise ValueError(f"api url {api_base} does not exist or is not accessible.")
+ self._api_key = api_key
+ self._version = version
+ self._timeout = timeout
+ headers = {"Authorization": f"Bearer {self._api_key}"} if self._api_key else {}
+ self._http_client = httpx.AsyncClient(
+ headers=headers, timeout=timeout if timeout else httpx.Timeout(None)
+ )
+
+ async def chat(
+ self,
+ model: str,
+ messages: Union[str, List[str]],
+ temperature: Optional[float] = None,
+ max_new_tokens: Optional[int] = None,
+ chat_mode: Optional[str] = None,
+ chat_param: Optional[str] = None,
+ conv_uid: Optional[str] = None,
+ user_name: Optional[str] = None,
+ sys_code: Optional[str] = None,
+ span_id: Optional[str] = None,
+ incremental: bool = True,
+ enable_vis: bool = True,
+ ) -> ChatCompletionResponse:
+ """
+ Chat Completion.
+
+ Args:
+ model: str, The model name.
+ messages: Union[str, List[str]], The user input messages.
+ temperature: Optional[float], What sampling temperature to use,between 0
+ and 2. Higher values like 0.8 will make the output more random,
+ while lower values like 0.2 will make it more focused and deterministic.
+ max_new_tokens: Optional[int].The maximum number of tokens that can be
+ generated in the chat completion.
+ chat_mode: Optional[str], The chat mode.
+ chat_param: Optional[str], The chat param of chat mode.
+ conv_uid: Optional[str], The conversation id of the model inference.
+ user_name: Optional[str], The user name of the model inference.
+ sys_code: Optional[str], The system code of the model inference.
+ span_id: Optional[str], The span id of the model inference.
+ incremental: bool, Used to control whether the content is returned
+ incrementally or in full each time. If this parameter is not provided,
+ the default is full return.
+ enable_vis: bool, Response content whether to output vis label.
+ Returns:
+ ChatCompletionResponse: The chat completion response.
+ Examples:
+ --------
+ .. code-block:: python
+
+ from dbgpt.client import Client
+
+ DBGPT_API_BASE = "http://localhost:5000/api/v2"
+ DBGPT_API_KEY = "dbgpt"
+ client = Client(api_base=DBGPT_API_BASE, api_key=DBGPT_API_KEY)
+ res = await client.chat(model="chatgpt_proxyllm", messages="Hello?")
+ """
+ request = ChatCompletionRequestBody(
+ model=model,
+ messages=messages,
+ stream=False,
+ temperature=temperature,
+ max_new_tokens=max_new_tokens,
+ chat_mode=chat_mode,
+ chat_param=chat_param,
+ conv_uid=conv_uid,
+ user_name=user_name,
+ sys_code=sys_code,
+ span_id=span_id,
+ incremental=incremental,
+ enable_vis=enable_vis,
+ )
+ response = await self._http_client.post(
+ self._api_url + "/chat/completions", json=request.dict()
+ )
+ if response.status_code == 200:
+ json_data = json.loads(response.text)
+ chat_completion_response = ChatCompletionResponse(**json_data)
+ return chat_completion_response
+ else:
+ return json.loads(response.content)
+
+ async def chat_stream(
+ self,
+ model: str,
+ messages: Union[str, List[str]],
+ temperature: Optional[float] = None,
+ max_new_tokens: Optional[int] = None,
+ chat_mode: Optional[str] = None,
+ chat_param: Optional[str] = None,
+ conv_uid: Optional[str] = None,
+ user_name: Optional[str] = None,
+ sys_code: Optional[str] = None,
+ span_id: Optional[str] = None,
+ incremental: bool = True,
+ enable_vis: bool = True,
+ ) -> AsyncGenerator[ChatCompletionStreamResponse, None]:
+ """
+ Chat Stream Completion.
+
+ Args:
+ model: str, The model name.
+ messages: Union[str, List[str]], The user input messages.
+ temperature: Optional[float], What sampling temperature to use, between 0
+ and 2.Higher values like 0.8 will make the output more random, while lower
+ values like 0.2 will make it more focused and deterministic.
+ max_new_tokens: Optional[int], The maximum number of tokens that can be
+ generated in the chat completion.
+ chat_mode: Optional[str], The chat mode.
+ chat_param: Optional[str], The chat param of chat mode.
+ conv_uid: Optional[str], The conversation id of the model inference.
+ user_name: Optional[str], The user name of the model inference.
+ sys_code: Optional[str], The system code of the model inference.
+ span_id: Optional[str], The span id of the model inference.
+ incremental: bool, Used to control whether the content is returned
+ incrementally or in full each time. If this parameter is not provided,
+ the default is full return.
+ enable_vis: bool, Response content whether to output vis label.
+ Returns:
+ ChatCompletionStreamResponse: The chat completion response.
+
+ Examples:
+ --------
+ .. code-block:: python
+
+ from dbgpt.client import Client
+
+ DBGPT_API_BASE = "http://localhost:5000/api/v2"
+ DBGPT_API_KEY = "dbgpt"
+ client = Client(api_base=DBGPT_API_BASE, api_key=DBGPT_API_KEY)
+ res = await client.chat_stream(model="chatgpt_proxyllm", messages="Hello?")
+ """
+ request = ChatCompletionRequestBody(
+ model=model,
+ messages=messages,
+ stream=True,
+ temperature=temperature,
+ max_new_tokens=max_new_tokens,
+ chat_mode=chat_mode,
+ chat_param=chat_param,
+ conv_uid=conv_uid,
+ user_name=user_name,
+ sys_code=sys_code,
+ span_id=span_id,
+ incremental=incremental,
+ enable_vis=enable_vis,
+ )
+ async with self._http_client.stream(
+ method="POST",
+ url=self._api_url + "/chat/completions",
+ json=request.dict(),
+ headers={},
+ ) as response:
+ if response.status_code == 200:
+ async for line in response.aiter_lines():
+ try:
+ if line.strip() == "data: [DONE]":
+ break
+ if line.startswith("data:"):
+ json_data = json.loads(line[len("data: ") :])
+ chat_completion_response = ChatCompletionStreamResponse(
+ **json_data
+ )
+ yield chat_completion_response
+ except Exception as e:
+ raise e
+
+ else:
+ try:
+ error = await response.aread()
+ yield json.loads(error)
+ except Exception as e:
+ raise e
+
+ async def get(self, path: str, *args):
+ """Get method.
+
+ Args:
+ path: str, The path to get.
+ args: Any, The arguments to pass to the get method.
+ """
+ try:
+ response = await self._http_client.get(
+ f"{self._api_url}/{CLIENT_SERVE_PATH}{path}",
+ *args,
+ )
+ return response
+ finally:
+ await self._http_client.aclose()
+
+ async def post(self, path: str, args):
+ """Post method.
+
+ Args:
+ path: str, The path to post.
+ args: Any, The arguments to pass to the post
+ """
+ try:
+ return await self._http_client.post(
+ f"{self._api_url}/{CLIENT_SERVE_PATH}{path}",
+ json=args,
+ )
+ finally:
+ await self._http_client.aclose()
+
+ async def post_param(self, path: str, args):
+ """Post method.
+
+ Args:
+ path: str, The path to post.
+ args: Any, The arguments to pass to the post
+ """
+ try:
+ return await self._http_client.post(
+ f"{self._api_url}/{CLIENT_SERVE_PATH}{path}",
+ params=args,
+ )
+ finally:
+ await self._http_client.aclose()
+
+ async def patch(self, path: str, *args):
+ """Patch method.
+
+ Args:
+ path: str, The path to patch.
+ args: Any, The arguments to pass to the patch.
+ """
+ return self._http_client.patch(
+ f"{self._api_url}/{CLIENT_SERVE_PATH}{path}", *args
+ )
+
+ async def put(self, path: str, args):
+ """Put method.
+
+ Args:
+ path: str, The path to put.
+ args: Any, The arguments to pass to the put.
+ """
+ try:
+ return await self._http_client.put(
+ f"{self._api_url}/{CLIENT_SERVE_PATH}{path}", json=args
+ )
+ finally:
+ await self._http_client.aclose()
+
+ async def delete(self, path: str, *args):
+ """Delete method.
+
+ Args:
+ path: str, The path to delete.
+ args: Any, The arguments to pass to the delete.
+ """
+ try:
+ return await self._http_client.delete(
+ f"{self._api_url}/{CLIENT_SERVE_PATH}{path}", *args
+ )
+ finally:
+ await self._http_client.aclose()
+
+ async def head(self, path: str, *args):
+ """Head method.
+
+ Args:
+ path: str, The path to head.
+ args: Any, The arguments to pass to the head
+ """
+ return self._http_client.head(self._api_url + path, *args)
+
+
+def is_valid_url(api_url: Any) -> bool:
+ """Check if the given URL is valid.
+
+ Args:
+ api_url: Any, The URL to check.
+ Returns:
+ bool: True if the URL is valid, False otherwise.
+ """
+ if not isinstance(api_url, str):
+ return False
+ parsed = urlparse(api_url)
+ return parsed.scheme != "" and parsed.netloc != ""
diff --git a/dbgpt/client/flow.py b/dbgpt/client/flow.py
new file mode 100644
index 000000000..f5206b2d1
--- /dev/null
+++ b/dbgpt/client/flow.py
@@ -0,0 +1,115 @@
+"""this module contains the flow client functions."""
+from typing import List
+
+from dbgpt.core.awel.flow.flow_factory import FlowPanel
+from dbgpt.core.schema.api import Result
+
+from .client import Client, ClientException
+
+
+async def create_flow(client: Client, flow: FlowPanel) -> FlowPanel:
+ """Create a new flow.
+
+ Args:
+ client (Client): The dbgpt client.
+ flow (FlowPanel): The flow panel.
+ """
+ try:
+ res = await client.get("/awel/flows", flow.dict())
+ result: Result = res.json()
+ if result["success"]:
+ return FlowPanel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to create flow: {e}")
+
+
+async def update_flow(client: Client, flow: FlowPanel) -> FlowPanel:
+ """Update a flow.
+
+ Args:
+ client (Client): The dbgpt client.
+ flow (FlowPanel): The flow panel.
+ Returns:
+ FlowPanel: The flow panel.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.put("/awel/flows", flow.dict())
+ result: Result = res.json()
+ if result["success"]:
+ return FlowPanel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to update flow: {e}")
+
+
+async def delete_flow(client: Client, flow_id: str) -> FlowPanel:
+ """
+ Delete a flow.
+
+ Args:
+ client (Client): The dbgpt client.
+ flow_id (str): The flow id.
+ Returns:
+ FlowPanel: The flow panel.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.delete("/awel/flows/" + flow_id)
+ result: Result = res.json()
+ if result["success"]:
+ return FlowPanel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to delete flow: {e}")
+
+
+async def get_flow(client: Client, flow_id: str) -> FlowPanel:
+ """
+ Get a flow.
+
+ Args:
+ client (Client): The dbgpt client.
+ flow_id (str): The flow id.
+ Returns:
+ FlowPanel: The flow panel.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.get("/awel/flows/" + flow_id)
+ result: Result = res.json()
+ if result["success"]:
+ return FlowPanel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to get flow: {e}")
+
+
+async def list_flow(client: Client) -> List[FlowPanel]:
+ """
+ List flows.
+
+ Args:
+ client (Client): The dbgpt client.
+ Returns:
+ List[FlowPanel]: The list of flow panels.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.get("/awel/flows")
+ result: Result = res.json()
+ if result["success"]:
+ return [FlowPanel(**flow) for flow in result["data"]["items"]]
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to list flows: {e}")
diff --git a/dbgpt/client/knowledge.py b/dbgpt/client/knowledge.py
new file mode 100644
index 000000000..f8b3404ec
--- /dev/null
+++ b/dbgpt/client/knowledge.py
@@ -0,0 +1,221 @@
+"""Knowledge API client."""
+import json
+from typing import List
+
+from dbgpt.core.schema.api import Result
+
+from .client import Client, ClientException
+from .schema import DocumentModel, SpaceModel, SyncModel
+
+
+async def create_space(client: Client, space_model: SpaceModel) -> SpaceModel:
+ """Create a new space.
+
+ Args:
+ client (Client): The dbgpt client.
+ space_model (SpaceModel): The space model.
+ Returns:
+ SpaceModel: The space model.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.post("/knowledge/spaces", space_model.dict())
+ result: Result = res.json()
+ if result["success"]:
+ return SpaceModel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to create space: {e}")
+
+
+async def update_space(client: Client, space_model: SpaceModel) -> SpaceModel:
+ """Update a document.
+
+ Args:
+ client (Client): The dbgpt client.
+ space_model (SpaceModel): The space model.
+ Returns:
+ SpaceModel: The space model.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.put("/knowledge/spaces", space_model.dict())
+ result: Result = res.json()
+ if result["success"]:
+ return SpaceModel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to update space: {e}")
+
+
+async def delete_space(client: Client, space_id: str) -> SpaceModel:
+ """Delete a space.
+
+ Args:
+ client (Client): The dbgpt client.
+ space_id (str): The space id.
+ Returns:
+ SpaceModel: The space model.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.delete("/knowledge/spaces/" + space_id)
+ result: Result = res.json()
+ if result["success"]:
+ return SpaceModel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to delete space: {e}")
+
+
+async def get_space(client: Client, space_id: str) -> SpaceModel:
+ """Get a document.
+
+ Args:
+ client (Client): The dbgpt client.
+ space_id (str): The space id.
+ Returns:
+ SpaceModel: The space model.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.get("/knowledge/spaces/" + space_id)
+ result: Result = res.json()
+ if result["success"]:
+ return SpaceModel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to get space: {e}")
+
+
+async def list_space(client: Client) -> List[SpaceModel]:
+ """List spaces.
+
+ Args:
+ client (Client): The dbgpt client.
+ Returns:
+ List[SpaceModel]: The list of space models.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.get("/knowledge/spaces")
+ result: Result = res.json()
+ if result["success"]:
+ return [SpaceModel(**space) for space in result["data"]["items"]]
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to list spaces: {e}")
+
+
+async def create_document(client: Client, doc_model: DocumentModel) -> DocumentModel:
+ """Create a new document.
+
+ Args:
+ client (Client): The dbgpt client.
+ doc_model (SpaceModel): The document model.
+
+ """
+ try:
+ res = await client.post_param("/knowledge/documents", doc_model.dict())
+ result: Result = res.json()
+ if result["success"]:
+ return DocumentModel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to create document: {e}")
+
+
+async def delete_document(client: Client, document_id: str) -> DocumentModel:
+ """Delete a document.
+
+ Args:
+ client (Client): The dbgpt client.
+ document_id (str): The document id.
+ Returns:
+ DocumentModel: The document model.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.delete("/knowledge/documents/" + document_id)
+ result: Result = res.json()
+ if result["success"]:
+ return DocumentModel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to delete document: {e}")
+
+
+async def get_document(client: Client, document_id: str) -> DocumentModel:
+ """Get a document.
+
+ Args:
+ client (Client): The dbgpt client.
+ document_id (str): The document id.
+ Returns:
+ DocumentModel: The document model.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.get("/knowledge/documents/" + document_id)
+ result: Result = res.json()
+ if result["success"]:
+ return DocumentModel(**result["data"])
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to get document: {e}")
+
+
+async def list_document(client: Client) -> List[DocumentModel]:
+ """List documents.
+
+ Args:
+ client (Client): The dbgpt client.
+ """
+ try:
+ res = await client.get("/knowledge/documents")
+ result: Result = res.json()
+ if result["success"]:
+ return [DocumentModel(**document) for document in result["data"]["items"]]
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to list documents: {e}")
+
+
+async def sync_document(client: Client, sync_model: SyncModel) -> List:
+ """Sync document.
+
+ Args:
+ client (Client): The dbgpt client.
+ sync_model (SyncModel): The sync model.
+ Returns:
+ List: The list of document ids.
+ Raises:
+ ClientException: If the request failed.
+ """
+ try:
+ res = await client.post(
+ "/knowledge/documents/sync", [json.loads(sync_model.json())]
+ )
+ result: Result = res.json()
+ if result["success"]:
+ return result["data"]
+ else:
+ raise ClientException(status=result["err_code"], reason=result)
+ except Exception as e:
+ raise ClientException(f"Failed to list documents: {e}")
diff --git a/dbgpt/client/schema.py b/dbgpt/client/schema.py
new file mode 100644
index 000000000..b4e8ad397
--- /dev/null
+++ b/dbgpt/client/schema.py
@@ -0,0 +1,280 @@
+"""this module contains the schemas for the dbgpt client."""
+import json
+from datetime import datetime
+from enum import Enum
+from typing import Any, Dict, List, Optional, Union
+
+from fastapi import File, UploadFile
+
+from dbgpt._private.pydantic import BaseModel, Field
+from dbgpt.rag.chunk_manager import ChunkParameters
+
+
+class ChatCompletionRequestBody(BaseModel):
+ """ChatCompletion LLM http request body."""
+
+ model: str = Field(
+ ..., description="The model name", examples=["gpt-3.5-turbo", "proxyllm"]
+ )
+ messages: Union[str, List[str]] = Field(
+ ..., description="User input messages", examples=["Hello", "How are you?"]
+ )
+ stream: bool = Field(default=True, description="Whether return stream")
+
+ temperature: Optional[float] = Field(
+ default=None,
+ description="What sampling temperature to use, between 0 and 2. Higher values "
+ "like 0.8 will make the output more random, "
+ "while lower values like 0.2 will "
+ "make it more focused and deterministic.",
+ )
+ max_new_tokens: Optional[int] = Field(
+ default=None,
+ description="The maximum number of tokens that can be generated in the chat "
+ "completion.",
+ )
+ conv_uid: Optional[str] = Field(
+ default=None, description="The conversation id of the model inference"
+ )
+ span_id: Optional[str] = Field(
+ default=None, description="The span id of the model inference"
+ )
+ chat_mode: Optional[str] = Field(
+ default="chat_normal",
+ description="The chat mode",
+ examples=["chat_awel_flow", "chat_normal"],
+ )
+ chat_param: Optional[str] = Field(
+ default=None,
+ description="The chat param of chat mode",
+ )
+ user_name: Optional[str] = Field(
+ default=None, description="The user name of the model inference"
+ )
+ sys_code: Optional[str] = Field(
+ default=None, description="The system code of the model inference"
+ )
+ incremental: bool = Field(
+ default=True,
+ description="Used to control whether the content is returned incrementally "
+ "or in full each time. "
+ "If this parameter is not provided, the default is full return.",
+ )
+ enable_vis: str = Field(
+ default=True, description="response content whether to output vis label"
+ )
+
+
+class ChatMode(Enum):
+ """Chat mode."""
+
+ CHAT_NORMAL = "chat_normal"
+ CHAT_APP = "chat_app"
+ CHAT_AWEL_FLOW = "chat_flow"
+ CHAT_KNOWLEDGE = "chat_knowledge"
+
+
+class AwelTeamModel(BaseModel):
+ """Awel team model."""
+
+ dag_id: str = Field(
+ ...,
+ description="The unique id of dag",
+ examples=["flow_dag_testflow_66d8e9d6-f32e-4540-a5bd-ea0648145d0e"],
+ )
+ uid: str = Field(
+ default=None,
+ description="The unique id of flow",
+ examples=["66d8e9d6-f32e-4540-a5bd-ea0648145d0e"],
+ )
+ name: Optional[str] = Field(
+ default=None,
+ description="The name of dag",
+ )
+ label: Optional[str] = Field(
+ default=None,
+ description="The label of dag",
+ )
+ version: Optional[str] = Field(
+ default=None,
+ description="The version of dag",
+ )
+ description: Optional[str] = Field(
+ default=None,
+ description="The description of dag",
+ )
+ editable: bool = Field(
+ default=False,
+ description="is the dag is editable",
+ examples=[True, False],
+ )
+ state: Optional[str] = Field(
+ default=None,
+ description="The state of dag",
+ )
+ user_name: Optional[str] = Field(
+ default=None,
+ description="The owner of current dag",
+ )
+ sys_code: Optional[str] = Field(
+ default=None,
+ description="The system code of current dag",
+ )
+ flow_category: Optional[str] = Field(
+ default="common",
+ description="The flow category of current dag",
+ )
+
+
+class AgentResourceType(Enum):
+ """Agent resource type."""
+
+ DB = "database"
+ Knowledge = "knowledge"
+ Internet = "internet"
+ Plugin = "plugin"
+ TextFile = "text_file"
+ ExcelFile = "excel_file"
+ ImageFile = "image_file"
+ AwelFlow = "awel_flow"
+
+
+class AgentResourceModel(BaseModel):
+ """Agent resource model."""
+
+ type: AgentResourceType
+ name: str
+ value: str
+ is_dynamic: bool = (
+ False # Is the current resource predefined or dynamically passed in?
+ )
+
+ @staticmethod
+ def from_dict(d: Dict[str, Any]):
+ """From dict."""
+ if d is None:
+ return None
+ return AgentResourceModel(
+ type=AgentResourceType(d.get("type")),
+ name=d.get("name"),
+ introduce=d.get("introduce"),
+ value=d.get("value", None),
+ is_dynamic=d.get("is_dynamic", False),
+ )
+
+ @staticmethod
+ def from_json_list_str(d: Optional[str]):
+ """From json list str."""
+ if d is None:
+ return None
+ try:
+ json_array = json.loads(d)
+ except Exception as e:
+ raise ValueError(f"Illegal AgentResource json string!{d},{e}")
+ return [AgentResourceModel.from_dict(item) for item in json_array]
+
+ def to_dict(self) -> Dict[str, Any]:
+ """To dict."""
+ temp = self.dict()
+ for field, value in temp.items():
+ if isinstance(value, Enum):
+ temp[field] = value.value
+ return temp
+
+
+class AppDetailModel(BaseModel):
+ """App detail model."""
+
+ app_code: Optional[str] = Field(None, description="app code")
+ app_name: Optional[str] = Field(None, description="app name")
+ agent_name: Optional[str] = Field(None, description="agent name")
+ node_id: Optional[str] = Field(None, description="node id")
+ resources: Optional[list[AgentResourceModel]] = Field(None, description="resources")
+ prompt_template: Optional[str] = Field(None, description="prompt template")
+ llm_strategy: Optional[str] = Field(None, description="llm strategy")
+ llm_strategy_value: Optional[str] = Field(None, description="llm strategy value")
+ created_at: datetime = datetime.now()
+ updated_at: datetime = datetime.now()
+
+
+class AppModel(BaseModel):
+ """App model."""
+
+ app_code: Optional[str] = Field(None, title="app code")
+ app_name: Optional[str] = Field(None, title="app name")
+ app_describe: Optional[str] = Field(None, title="app describe")
+ team_mode: Optional[str] = Field(None, title="team mode")
+ language: Optional[str] = Field("en", title="language")
+ team_context: Optional[Union[str, AwelTeamModel]] = Field(
+ None, title="team context"
+ )
+ user_code: Optional[str] = Field(None, title="user code")
+ sys_code: Optional[str] = Field(None, title="sys code")
+ is_collected: Optional[str] = Field(None, title="is collected")
+ icon: Optional[str] = Field(None, title="icon")
+ created_at: datetime = datetime.now()
+ updated_at: datetime = datetime.now()
+ details: List[AppDetailModel] = Field([], title="app details")
+
+
+class SpaceModel(BaseModel):
+ """Space model."""
+
+ id: str = Field(
+ default=None,
+ description="space id",
+ )
+ name: str = Field(
+ default=None,
+ description="knowledge space name",
+ )
+ vector_type: str = Field(
+ default=None,
+ description="vector type",
+ )
+ desc: str = Field(
+ default=None,
+ description="space description",
+ )
+ owner: str = Field(
+ default=None,
+ description="space owner",
+ )
+ context: Optional[str] = Field(
+ default=None,
+ description="space argument context",
+ )
+
+
+class DocumentModel(BaseModel):
+ """Document model."""
+
+ id: int = Field(None, description="The doc id")
+ doc_name: str = Field(None, description="doc name")
+ """doc_type: document type"""
+ doc_type: str = Field(None, description="The doc type")
+ """content: description"""
+ content: str = Field(None, description="content")
+ """doc file"""
+ doc_file: UploadFile = Field(File(None), description="doc file")
+ """doc_source: doc source"""
+ doc_source: str = Field(None, description="doc source")
+ """doc_source: doc source"""
+ space_id: str = Field(None, description="space_id")
+
+
+class SyncModel(BaseModel):
+ """Sync model."""
+
+ """doc_id: doc id"""
+ doc_id: str = Field(None, description="The doc id")
+
+ """space id"""
+ space_id: str = Field(None, description="The space id")
+
+ """model_name: model name"""
+ model_name: Optional[str] = Field(None, description="model name")
+
+ """chunk_parameters: chunk parameters
+ """
+ chunk_parameters: ChunkParameters = Field(None, description="chunk parameters")
diff --git a/dbgpt/client/tests/__init__.py b/dbgpt/client/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/dbgpt/core/schema/__init__.py b/dbgpt/core/schema/__init__.py
new file mode 100644
index 000000000..0acfbeaa4
--- /dev/null
+++ b/dbgpt/core/schema/__init__.py
@@ -0,0 +1 @@
+"""Module for common schemas."""
diff --git a/dbgpt/core/schema/api.py b/dbgpt/core/schema/api.py
new file mode 100644
index 000000000..647987b0f
--- /dev/null
+++ b/dbgpt/core/schema/api.py
@@ -0,0 +1,116 @@
+"""API schema module."""
+
+import time
+import uuid
+from typing import Any, Generic, List, Literal, Optional, TypeVar
+
+from dbgpt._private.pydantic import BaseModel, Field
+
+T = TypeVar("T")
+
+
+class Result(BaseModel, Generic[T]):
+ """Common result entity for API response."""
+
+ success: bool = Field(
+ ..., description="Whether it is successful, True: success, False: failure"
+ )
+ err_code: str | None = Field(None, description="Error code")
+ err_msg: str | None = Field(None, description="Error message")
+ data: T | None = Field(None, description="Return data")
+
+ @staticmethod
+ def succ(data: T) -> "Result[T]":
+ """Build a successful result entity.
+
+ Args:
+ data (T): Return data
+
+ Returns:
+ Result[T]: Result entity
+ """
+ return Result(success=True, err_code=None, err_msg=None, data=data)
+
+ @staticmethod
+ def failed(msg: str, err_code: Optional[str] = "E000X") -> "Result[Any]":
+ """Build a failed result entity.
+
+ Args:
+ msg (str): Error message
+ err_code (Optional[str], optional): Error code. Defaults to "E000X".
+ """
+ return Result(success=False, err_code=err_code, err_msg=msg, data=None)
+
+
+class DeltaMessage(BaseModel):
+ """Delta message entity for chat completion response."""
+
+ role: Optional[str] = None
+ content: Optional[str] = None
+
+
+class ChatCompletionResponseStreamChoice(BaseModel):
+ """Chat completion response choice entity."""
+
+ index: int = Field(..., description="Choice index")
+ delta: DeltaMessage = Field(..., description="Delta message")
+ finish_reason: Optional[Literal["stop", "length"]] = Field(
+ None, description="Finish reason"
+ )
+
+
+class ChatCompletionStreamResponse(BaseModel):
+ """Chat completion response stream entity."""
+
+ id: str = Field(
+ default_factory=lambda: f"chatcmpl-{str(uuid.uuid1())}", description="Stream ID"
+ )
+ created: int = Field(
+ default_factory=lambda: int(time.time()), description="Created time"
+ )
+ model: str = Field(..., description="Model name")
+ choices: List[ChatCompletionResponseStreamChoice] = Field(
+ ..., description="Chat completion response choices"
+ )
+
+
+class ChatMessage(BaseModel):
+ """Chat message entity."""
+
+ role: str = Field(..., description="Role of the message")
+ content: str = Field(..., description="Content of the message")
+
+
+class UsageInfo(BaseModel):
+ """Usage info entity."""
+
+ prompt_tokens: int = Field(0, description="Prompt tokens")
+ total_tokens: int = Field(0, description="Total tokens")
+ completion_tokens: Optional[int] = Field(0, description="Completion tokens")
+
+
+class ChatCompletionResponseChoice(BaseModel):
+ """Chat completion response choice entity."""
+
+ index: int = Field(..., description="Choice index")
+ message: ChatMessage = Field(..., description="Chat message")
+ finish_reason: Optional[Literal["stop", "length"]] = Field(
+ None, description="Finish reason"
+ )
+
+
+class ChatCompletionResponse(BaseModel):
+ """Chat completion response entity."""
+
+ id: str = Field(
+ default_factory=lambda: f"chatcmpl-{str(uuid.uuid1())}", description="Stream ID"
+ )
+ object: str = "chat.completion"
+ created: int = Field(
+ default_factory=lambda: int(time.time()), description="Created time"
+ )
+ model: str = Field(..., description="Model name")
+ choices: List[ChatCompletionResponseChoice] = Field(
+ ..., description="Chat completion response choices"
+ )
+ usage: UsageInfo = Field(..., description="Usage info")
diff --git a/dbgpt/model/utils/chatgpt_utils.py b/dbgpt/model/utils/chatgpt_utils.py
index d84861dd5..3dbb3f8a3 100644
--- a/dbgpt/model/utils/chatgpt_utils.py
+++ b/dbgpt/model/utils/chatgpt_utils.py
@@ -16,14 +16,7 @@ from typing import (
from dbgpt._private.pydantic import model_to_json
from dbgpt.core.awel import TransformStreamAbsOperator
-from dbgpt.core.awel.flow import (
- IOField,
- OperatorCategory,
- OperatorType,
- Parameter,
- ResourceCategory,
- ViewMetadata,
-)
+from dbgpt.core.awel.flow import IOField, OperatorCategory, OperatorType, ViewMetadata
from dbgpt.core.interface.llm import ModelOutput
from dbgpt.core.operators import BaseLLM
@@ -217,7 +210,8 @@ async def _to_openai_stream(
import json
import shortuuid
- from fastchat.protocol.openai_api_protocol import (
+
+ from dbgpt.core.schema.api import (
ChatCompletionResponseStreamChoice,
ChatCompletionStreamResponse,
DeltaMessage,
diff --git a/dbgpt/serve/agent/app/endpoints.py b/dbgpt/serve/agent/app/endpoints.py
new file mode 100644
index 000000000..8fb7934b8
--- /dev/null
+++ b/dbgpt/serve/agent/app/endpoints.py
@@ -0,0 +1,65 @@
+from typing import Optional
+
+from fastapi import APIRouter, Query
+
+from dbgpt.serve.agent.db.gpts_app import (
+ GptsApp,
+ GptsAppCollectionDao,
+ GptsAppDao,
+ GptsAppQuery,
+)
+from dbgpt.serve.core import Result
+
+router = APIRouter()
+gpts_dao = GptsAppDao()
+collection_dao = GptsAppCollectionDao()
+
+
+@router.get("/v2/serve/apps")
+async def app_list(
+ user_name: Optional[str] = Query(default=None, description="user name"),
+ sys_code: Optional[str] = Query(default=None, description="system code"),
+ is_collected: Optional[str] = Query(default=None, description="system code"),
+ page: int = Query(default=1, description="current page"),
+ page_size: int = Query(default=20, description="page size"),
+):
+ try:
+ query = GptsAppQuery(
+ page_no=page, page_size=page_size, is_collected=is_collected
+ )
+ return Result.succ(gpts_dao.app_list(query, True))
+ except Exception as ex:
+ return Result.failed(err_code="E000X", msg=f"query app error: {ex}")
+
+
+@router.get("/v2/serve/apps/{app_id}")
+async def app_detail(app_id: str):
+ try:
+ return Result.succ(gpts_dao.app_detail(app_id))
+ except Exception as ex:
+ return Result.failed(err_code="E000X", msg=f"query app error: {ex}")
+
+
+@router.put("/v2/serve/apps/{app_id}")
+async def app_update(app_id: str, gpts_app: GptsApp):
+ try:
+ return Result.succ(gpts_dao.edit(gpts_app))
+ except Exception as ex:
+ return Result.failed(err_code="E000X", msg=f"edit app error: {ex}")
+
+
+@router.post("/v2/serve/apps")
+async def app_create(gpts_app: GptsApp):
+ try:
+ return Result.succ(gpts_dao.create(gpts_app))
+ except Exception as ex:
+ return Result.failed(err_code="E000X", msg=f"edit app error: {ex}")
+
+
+@router.delete("/v2/serve/apps/{app_id}")
+async def app_delete(app_id: str, user_code: Optional[str], sys_code: Optional[str]):
+ try:
+ gpts_dao.delete(app_id, user_code, sys_code)
+ return Result.succ([])
+ except Exception as ex:
+ return Result.failed(err_code="E000X", msg=f"delete app error: {ex}")
diff --git a/dbgpt/serve/conversation/api/endpoints.py b/dbgpt/serve/conversation/api/endpoints.py
index 59b155aaf..00b7f08cf 100644
--- a/dbgpt/serve/conversation/api/endpoints.py
+++ b/dbgpt/serve/conversation/api/endpoints.py
@@ -2,7 +2,7 @@ import uuid
from functools import cache
from typing import List, Optional
-from fastapi import APIRouter, Depends, HTTPException, Query
+from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBearer
from dbgpt.component import SystemApp
@@ -45,6 +45,7 @@ def _parse_api_keys(api_keys: str) -> List[str]:
async def check_api_key(
auth: Optional[HTTPAuthorizationCredentials] = Depends(get_bearer_token),
+ request: Request = None,
service: Service = Depends(get_service),
) -> Optional[str]:
"""Check the api key
@@ -63,6 +64,9 @@ async def check_api_key(
assert res.status_code == 200
"""
+ if request.url.path.startswith(f"/api/v1"):
+ return None
+
if service.config.api_keys:
api_keys = _parse_api_keys(service.config.api_keys)
if auth is None or (token := auth.credentials) not in api_keys:
diff --git a/dbgpt/serve/core/config.py b/dbgpt/serve/core/config.py
index 9b78c44a2..306c778b3 100644
--- a/dbgpt/serve/core/config.py
+++ b/dbgpt/serve/core/config.py
@@ -16,7 +16,16 @@ class BaseServeConfig(BaseParameters):
config (AppConfig): Application configuration
config_prefix (str): Configuration prefix
"""
+ global_prefix = "dbgpt.app.global."
+ global_dict = config.get_all_by_prefix(global_prefix)
config_dict = config.get_all_by_prefix(config_prefix)
# remove prefix
- config_dict = {k[len(config_prefix) :]: v for k, v in config_dict.items()}
+ config_dict = {
+ k[len(config_prefix) :]: v
+ for k, v in config_dict.items()
+ if k.startswith(config_prefix)
+ }
+ for k, v in global_dict.items():
+ if k not in config_dict and k[len(global_prefix) :] in cls().__dict__:
+ config_dict[k[len(global_prefix) :]] = v
return cls(**config_dict)
diff --git a/dbgpt/serve/core/schemas.py b/dbgpt/serve/core/schemas.py
index 57d85503e..698ac6805 100644
--- a/dbgpt/serve/core/schemas.py
+++ b/dbgpt/serve/core/schemas.py
@@ -1,12 +1,12 @@
import logging
import sys
-from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar
+from typing import TYPE_CHECKING
from fastapi import HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
-from dbgpt._private.pydantic import BaseModel, Field
+from dbgpt.core.schema.api import Result
if sys.version_info < (3, 11):
try:
@@ -18,40 +18,6 @@ if TYPE_CHECKING:
from fastapi import FastAPI
logger = logging.getLogger(__name__)
-T = TypeVar("T")
-
-
-class Result(BaseModel, Generic[T]):
- """Common result entity class"""
-
- success: bool = Field(
- ..., description="Whether it is successful, True: success, False: failure"
- )
- err_code: str | None = Field(None, description="Error code")
- err_msg: str | None = Field(None, description="Error message")
- data: T | None = Field(None, description="Return data")
-
- @staticmethod
- def succ(data: T) -> "Result[T]":
- """Build a successful result entity
-
- Args:
- data (T): Return data
-
- Returns:
- Result[T]: Result entity
- """
- return Result(success=True, err_code=None, err_msg=None, data=data)
-
- @staticmethod
- def failed(msg: str, err_code: Optional[str] = "E000X") -> "Result[Any]":
- """Build a failed result entity
-
- Args:
- msg (str): Error message
- err_code (Optional[str], optional): Error code. Defaults to "E000X".
- """
- return Result(success=False, err_code=err_code, err_msg=msg, data=None)
async def validation_exception_handler(
@@ -69,11 +35,11 @@ async def validation_exception_handler(
async def http_exception_handler(request: Request, exc: HTTPException):
res = Result.failed(
- msg=exc.detail,
- err_code="E0002",
+ msg=str(exc.detail),
+ err_code=str(exc.status_code),
)
logger.error(f"http_exception_handler catch HTTPException: {res}")
- return JSONResponse(status_code=400, content=res.dict())
+ return JSONResponse(status_code=exc.status_code, content=res.dict())
async def common_exception_handler(request: Request, exc: Exception) -> JSONResponse:
diff --git a/dbgpt/serve/core/serve.py b/dbgpt/serve/core/serve.py
index ff0203b65..2280a2136 100644
--- a/dbgpt/serve/core/serve.py
+++ b/dbgpt/serve/core/serve.py
@@ -18,7 +18,7 @@ class BaseServe(BaseComponent, ABC):
def __init__(
self,
system_app: SystemApp,
- api_prefix: str,
+ api_prefix: str | List[str],
api_tags: List[str],
db_url_or_db: Union[str, URL, DatabaseManager] = None,
try_create_tables: Optional[bool] = False,
diff --git a/dbgpt/serve/flow/api/endpoints.py b/dbgpt/serve/flow/api/endpoints.py
index 8082f4984..c3939ceb8 100644
--- a/dbgpt/serve/flow/api/endpoints.py
+++ b/dbgpt/serve/flow/api/endpoints.py
@@ -1,7 +1,7 @@
from functools import cache
from typing import List, Optional, Union
-from fastapi import APIRouter, Depends, HTTPException, Query
+from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBearer
from dbgpt.component import SystemApp
@@ -9,7 +9,7 @@ from dbgpt.core.awel.flow import ResourceMetadata, ViewMetadata
from dbgpt.serve.core import Result
from dbgpt.util import PaginationResult
-from ..config import APP_NAME, SERVE_APP_NAME, SERVE_SERVICE_COMPONENT_NAME, ServeConfig
+from ..config import APP_NAME, SERVE_SERVICE_COMPONENT_NAME, ServeConfig
from ..service.service import Service
from .schemas import ServeRequest, ServerResponse
@@ -45,6 +45,7 @@ def _parse_api_keys(api_keys: str) -> List[str]:
async def check_api_key(
auth: Optional[HTTPAuthorizationCredentials] = Depends(get_bearer_token),
+ request: Request = None,
service: Service = Depends(get_service),
) -> Optional[str]:
"""Check the api key
@@ -63,6 +64,10 @@ async def check_api_key(
assert res.status_code == 200
"""
+ if request.url.path.startswith(f"/api/v1"):
+ return None
+
+ # for api_version in serve.serve_versions():
if service.config.api_keys:
api_keys = _parse_api_keys(service.config.api_keys)
if auth is None or (token := auth.credentials) not in api_keys:
@@ -142,8 +147,8 @@ async def delete(uid: str, service: Service = Depends(get_service)) -> Result[No
Returns:
Result[None]: The response
"""
- service.delete(uid)
- return Result.succ(None)
+ inst = service.delete(uid)
+ return Result.succ(inst)
@router.get("/flows/{uid}")
diff --git a/dbgpt/serve/flow/serve.py b/dbgpt/serve/flow/serve.py
index e661928e2..126841e57 100644
--- a/dbgpt/serve/flow/serve.py
+++ b/dbgpt/serve/flow/serve.py
@@ -27,11 +27,13 @@ class Serve(BaseServe):
def __init__(
self,
system_app: SystemApp,
- api_prefix: Optional[str] = f"/api/v1/serve/awel",
+ api_prefix: Optional[List[str]] = None,
api_tags: Optional[List[str]] = None,
db_url_or_db: Union[str, URL, DatabaseManager] = None,
try_create_tables: Optional[bool] = False,
):
+ if api_prefix is None:
+ api_prefix = [f"/api/v1/serve/awel", "/api/v2/serve/awel"]
if api_tags is None:
api_tags = [SERVE_APP_NAME_HUMP]
super().__init__(
@@ -43,9 +45,10 @@ class Serve(BaseServe):
if self._app_has_initiated:
return
self._system_app = system_app
- self._system_app.app.include_router(
- router, prefix=self._api_prefix, tags=self._api_tags
- )
+ for prefix in self._api_prefix:
+ self._system_app.app.include_router(
+ router, prefix=prefix, tags=self._api_tags
+ )
init_endpoints(self._system_app)
self._app_has_initiated = True
diff --git a/dbgpt/serve/flow/service/service.py b/dbgpt/serve/flow/service/service.py
index 6deeb8985..a8811fa81 100644
--- a/dbgpt/serve/flow/service/service.py
+++ b/dbgpt/serve/flow/service/service.py
@@ -485,9 +485,7 @@ async def _chat_with_dag_task(
if OpenAIStreamingOutputOperator and isinstance(
task, OpenAIStreamingOutputOperator
):
- from fastchat.protocol.openai_api_protocol import (
- ChatCompletionResponseStreamChoice,
- )
+ from dbgpt.core.schema.api import ChatCompletionResponseStreamChoice
previous_text = ""
async for output in await task.call_stream(request):
diff --git a/dbgpt/serve/prompt/api/endpoints.py b/dbgpt/serve/prompt/api/endpoints.py
index f78d0843c..2da890eb8 100644
--- a/dbgpt/serve/prompt/api/endpoints.py
+++ b/dbgpt/serve/prompt/api/endpoints.py
@@ -1,7 +1,7 @@
from functools import cache
from typing import List, Optional
-from fastapi import APIRouter, Depends, HTTPException, Query
+from fastapi import APIRouter, Depends, HTTPException, Query, Request
from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBearer
from dbgpt.component import SystemApp
@@ -44,6 +44,7 @@ def _parse_api_keys(api_keys: str) -> List[str]:
async def check_api_key(
auth: Optional[HTTPAuthorizationCredentials] = Depends(get_bearer_token),
+ request: Request = None,
service: Service = Depends(get_service),
) -> Optional[str]:
"""Check the api key
@@ -62,6 +63,9 @@ async def check_api_key(
assert res.status_code == 200
"""
+ if request.url.path.startswith(f"/api/v1"):
+ return None
+
if service.config.api_keys:
api_keys = _parse_api_keys(service.config.api_keys)
if auth is None or (token := auth.credentials) not in api_keys:
diff --git a/dbgpt/serve/rag/api/endpoints.py b/dbgpt/serve/rag/api/endpoints.py
new file mode 100644
index 000000000..392e70ab9
--- /dev/null
+++ b/dbgpt/serve/rag/api/endpoints.py
@@ -0,0 +1,300 @@
+from functools import cache
+from typing import List, Optional
+
+from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, UploadFile
+from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBearer
+
+from dbgpt.component import SystemApp
+from dbgpt.serve.core import Result
+from dbgpt.serve.rag.api.schemas import (
+ DocumentServeRequest,
+ DocumentServeResponse,
+ KnowledgeSyncRequest,
+ SpaceServeRequest,
+ SpaceServeResponse,
+)
+from dbgpt.serve.rag.config import SERVE_SERVICE_COMPONENT_NAME
+from dbgpt.serve.rag.service.service import Service
+from dbgpt.util import PaginationResult
+
+router = APIRouter()
+
+# Add your API endpoints here
+
+global_system_app: Optional[SystemApp] = None
+
+
+def get_service() -> Service:
+ """Get the service instance"""
+ return global_system_app.get_component(SERVE_SERVICE_COMPONENT_NAME, Service)
+
+
+get_bearer_token = HTTPBearer(auto_error=False)
+
+
+@cache
+def _parse_api_keys(api_keys: str) -> List[str]:
+ """Parse the string api keys to a list
+
+ Args:
+ api_keys (str): The string api keys
+
+ Returns:
+ List[str]: The list of api keys
+ """
+ if not api_keys:
+ return []
+ return [key.strip() for key in api_keys.split(",")]
+
+
+async def check_api_key(
+ auth: Optional[HTTPAuthorizationCredentials] = Depends(get_bearer_token),
+ service: Service = Depends(get_service),
+) -> Optional[str]:
+ """Check the api key
+
+ If the api key is not set, allow all.
+
+ Your can pass the token in you request header like this:
+
+ .. code-block:: python
+
+ import requests
+
+ client_api_key = "your_api_key"
+ headers = {"Authorization": "Bearer " + client_api_key}
+ res = requests.get("http://test/hello", headers=headers)
+ assert res.status_code == 200
+
+ """
+ if service.config.api_keys:
+ api_keys = _parse_api_keys(service.config.api_keys)
+ if auth is None or (token := auth.credentials) not in api_keys:
+ raise HTTPException(
+ status_code=401,
+ detail={
+ "error": {
+ "message": "",
+ "type": "invalid_request_error",
+ "param": None,
+ "code": "invalid_api_key",
+ }
+ },
+ )
+ return token
+ else:
+ # api_keys not set; allow all
+ return None
+
+
+@router.get("/health", dependencies=[Depends(check_api_key)])
+async def health():
+ """Health check endpoint"""
+ return {"status": "ok"}
+
+
+@router.get("/test_auth", dependencies=[Depends(check_api_key)])
+async def test_auth():
+ """Test auth endpoint"""
+ return {"status": "ok"}
+
+
+@router.post("/spaces", dependencies=[Depends(check_api_key)])
+async def create(
+ request: SpaceServeRequest, service: Service = Depends(get_service)
+) -> Result:
+ """Create a new Space entity
+
+ Args:
+ request (SpaceServeRequest): The request
+ service (Service): The service
+ Returns:
+ ServerResponse: The response
+ """
+ return Result.succ(service.create_space(request))
+
+
+@router.put("/spaces", dependencies=[Depends(check_api_key)])
+async def update(
+ request: SpaceServeRequest, service: Service = Depends(get_service)
+) -> Result:
+ """Update a Space entity
+
+ Args:
+ request (SpaceServeRequest): The request
+ service (Service): The service
+ Returns:
+ ServerResponse: The response
+ """
+ return Result.succ(service.update_space(request))
+
+
+@router.delete(
+ "/spaces/{space_id}",
+ response_model=Result[None],
+ dependencies=[Depends(check_api_key)],
+)
+async def delete(
+ space_id: str, service: Service = Depends(get_service)
+) -> Result[None]:
+ """Delete a Space entity
+
+ Args:
+ request (SpaceServeRequest): The request
+ service (Service): The service
+ Returns:
+ ServerResponse: The response
+ """
+ return Result.succ(service.delete(space_id))
+
+
+@router.get(
+ "/spaces/{space_id}",
+ dependencies=[Depends(check_api_key)],
+ response_model=Result[List],
+)
+async def query(
+ space_id: str, service: Service = Depends(get_service)
+) -> Result[List[SpaceServeResponse]]:
+ """Query Space entities
+
+ Args:
+ request (SpaceServeRequest): The request
+ service (Service): The service
+ Returns:
+ List[ServeResponse]: The response
+ """
+ request = {"id": space_id}
+ return Result.succ(service.get(request))
+
+
+@router.get(
+ "/spaces",
+ dependencies=[Depends(check_api_key)],
+ response_model=Result[PaginationResult[SpaceServeResponse]],
+)
+async def query_page(
+ page: int = Query(default=1, description="current page"),
+ page_size: int = Query(default=20, description="page size"),
+ service: Service = Depends(get_service),
+) -> Result[PaginationResult[SpaceServeResponse]]:
+ """Query Space entities
+
+ Args:
+ page (int): The page number
+ page_size (int): The page size
+ service (Service): The service
+ Returns:
+ ServerResponse: The response
+ """
+ return Result.succ(service.get_list_by_page({}, page, page_size))
+
+
+@router.post("/documents", dependencies=[Depends(check_api_key)])
+async def create_document(
+ doc_name: str = Form(...),
+ doc_type: str = Form(...),
+ space_id: str = Form(...),
+ content: Optional[str] = Form(None),
+ doc_file: Optional[UploadFile] = File(None),
+ service: Service = Depends(get_service),
+) -> Result:
+ """Create a new Document entity
+
+ Args:
+ request (SpaceServeRequest): The request
+ service (Service): The service
+ Returns:
+ ServerResponse: The response
+ """
+ request = DocumentServeRequest(
+ doc_name=doc_name,
+ doc_type=doc_type,
+ content=content,
+ doc_file=doc_file,
+ space_id=space_id,
+ )
+ return Result.succ(await service.create_document(request))
+
+
+@router.get(
+ "/documents/{document_id}",
+ dependencies=[Depends(check_api_key)],
+ response_model=Result[List],
+)
+async def query(
+ document_id: str, service: Service = Depends(get_service)
+) -> Result[List[SpaceServeResponse]]:
+ """Query Space entities
+
+ Args:
+ request (SpaceServeRequest): The request
+ service (Service): The service
+ Returns:
+ List[ServeResponse]: The response
+ """
+ request = {"id": document_id}
+ return Result.succ(service.get_document(request))
+
+
+@router.get(
+ "/documents",
+ dependencies=[Depends(check_api_key)],
+ response_model=Result[PaginationResult[SpaceServeResponse]],
+)
+async def query_page(
+ page: int = Query(default=1, description="current page"),
+ page_size: int = Query(default=20, description="page size"),
+ service: Service = Depends(get_service),
+) -> Result[PaginationResult[DocumentServeResponse]]:
+ """Query Space entities
+
+ Args:
+ page (int): The page number
+ page_size (int): The page size
+ service (Service): The service
+ Returns:
+ ServerResponse: The response
+ """
+ return Result.succ(service.get_document_list({}, page, page_size))
+
+
+@router.post("/documents/sync", dependencies=[Depends(check_api_key)])
+async def sync_documents(
+ requests: List[KnowledgeSyncRequest], service: Service = Depends(get_service)
+) -> Result:
+ """Create a new Document entity
+
+ Args:
+ request (SpaceServeRequest): The request
+ service (Service): The service
+ Returns:
+ ServerResponse: The response
+ """
+ return Result.succ(service.sync_document(requests))
+
+
+@router.delete(
+ "/documents/{document_id}",
+ dependencies=[Depends(check_api_key)],
+ response_model=Result[None],
+)
+async def delete_document(
+ document_id: str, service: Service = Depends(get_service)
+) -> Result[None]:
+ """Delete a Space entity
+
+ Args:
+ request (SpaceServeRequest): The request
+ service (Service): The service
+ Returns:
+ ServerResponse: The response
+ """
+ return Result.succ(service.delete_document(document_id))
+
+
+def init_endpoints(system_app: SystemApp) -> None:
+ """Initialize the endpoints"""
+ global global_system_app
+ system_app.register(Service)
+ global_system_app = system_app
diff --git a/dbgpt/serve/rag/api/schemas.py b/dbgpt/serve/rag/api/schemas.py
new file mode 100644
index 000000000..1b9a91357
--- /dev/null
+++ b/dbgpt/serve/rag/api/schemas.py
@@ -0,0 +1,99 @@
+from typing import Optional
+
+from fastapi import File, UploadFile
+from pydantic import BaseModel, Field
+
+from dbgpt.rag.chunk_manager import ChunkParameters
+
+from ..config import SERVE_APP_NAME_HUMP
+
+
+class SpaceServeRequest(BaseModel):
+ """name: knowledge space name"""
+
+ """vector_type: vector type"""
+ id: Optional[int] = Field(None, description="The space id")
+ name: str = Field(None, description="The space name")
+ """vector_type: vector type"""
+ vector_type: str = Field("Chroma", description="The vector type")
+ """desc: description"""
+ desc: Optional[str] = Field(None, description="The description")
+ """owner: owner"""
+ owner: Optional[str] = Field(None, description="The owner")
+ """context: argument context"""
+ context: Optional[str] = Field(None, description="The context")
+ """gmt_created: created time"""
+ gmt_created: Optional[str] = Field(None, description="The created time")
+ """gmt_modified: modified time"""
+ gmt_modified: Optional[str] = Field(None, description="The modified time")
+
+
+class DocumentServeRequest(BaseModel):
+ id: int = Field(None, description="The doc id")
+ doc_name: str = Field(None, description="doc name")
+ """doc_type: document type"""
+ doc_type: str = Field(None, description="The doc type")
+ """content: description"""
+ content: str = Field(None, description="content")
+ """doc file"""
+ doc_file: UploadFile = File(...)
+ """doc_source: doc source"""
+ doc_source: str = None
+ """doc_source: doc source"""
+ space_id: str = None
+
+
+class DocumentServeResponse(BaseModel):
+ id: int = Field(None, description="The doc id")
+ doc_name: str = Field(None, description="doc type")
+ """vector_type: vector type"""
+ doc_type: str = Field(None, description="The doc content")
+ """desc: description"""
+ content: str = Field(None, description="content")
+ """vector ids"""
+ vector_ids: str = Field(None, description="vector ids")
+ """doc_source: doc source"""
+ doc_source: str = None
+ """doc_source: doc source"""
+ space: str = None
+
+
+class KnowledgeSyncRequest(BaseModel):
+ """Sync request"""
+
+ """doc_ids: doc ids"""
+ doc_id: int = Field(None, description="The doc id")
+
+ """space id"""
+ space_id: str = Field(None, description="space id")
+
+ """model_name: model name"""
+ model_name: Optional[str] = Field(None, description="model name")
+
+ """chunk_parameters: chunk parameters
+ """
+ chunk_parameters: ChunkParameters = Field(None, description="chunk parameters")
+
+
+class SpaceServeResponse(BaseModel):
+ """Flow response model"""
+
+ """name: knowledge space name"""
+
+ """vector_type: vector type"""
+ id: int = Field(None, description="The space id")
+ name: str = Field(None, description="The space name")
+ """vector_type: vector type"""
+ vector_type: str = Field(None, description="The vector type")
+ """desc: description"""
+ desc: str = Field(None, description="The description")
+ """context: argument context"""
+ context: str = Field(None, description="The context")
+ """owner: owner"""
+ owner: str = Field(None, description="The owner")
+ """sys code"""
+ sys_code: str = Field(None, description="The sys code")
+
+ # TODO define your own fields here
+ class Config:
+ title = f"ServerResponse for {SERVE_APP_NAME_HUMP}"
diff --git a/dbgpt/serve/rag/config.py b/dbgpt/serve/rag/config.py
new file mode 100644
index 000000000..c0f10ab23
--- /dev/null
+++ b/dbgpt/serve/rag/config.py
@@ -0,0 +1,28 @@
+from dataclasses import dataclass, field
+from typing import Optional
+
+from dbgpt.serve.core import BaseServeConfig
+
+APP_NAME = "rag"
+SERVE_APP_NAME = "dbgpt_rag"
+SERVE_APP_NAME_HUMP = "dbgpt_rag"
+SERVE_CONFIG_KEY_PREFIX = "dbgpt_rag"
+SERVE_SERVICE_COMPONENT_NAME = f"{SERVE_APP_NAME}_service"
+
+
+@dataclass
+class ServeConfig(BaseServeConfig):
+ """Parameters for the serve command"""
+
+ api_keys: Optional[str] = field(
+ default=None, metadata={"help": "API keys for the endpoint, if None, allow all"}
+ )
+
+ default_user: Optional[str] = field(
+ default=None,
+ metadata={"help": "Default user name for prompt"},
+ )
+ default_sys_code: Optional[str] = field(
+ default=None,
+ metadata={"help": "Default system code for prompt"},
+ )
diff --git a/dbgpt/serve/rag/dependencies.py b/dbgpt/serve/rag/dependencies.py
new file mode 100644
index 000000000..8598ecd97
--- /dev/null
+++ b/dbgpt/serve/rag/dependencies.py
@@ -0,0 +1 @@
+# Define your dependencies here
diff --git a/dbgpt/serve/rag/models/__init__.py b/dbgpt/serve/rag/models/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/dbgpt/app/knowledge/space_db.py b/dbgpt/serve/rag/models/models.py
similarity index 56%
rename from dbgpt/app/knowledge/space_db.py
rename to dbgpt/serve/rag/models/models.py
index bc283db1d..f8fd2986c 100644
--- a/dbgpt/app/knowledge/space_db.py
+++ b/dbgpt/serve/rag/models/models.py
@@ -1,13 +1,11 @@
from datetime import datetime
+from typing import Any, Dict, List, Union
from sqlalchemy import Column, DateTime, Integer, String, Text
-from dbgpt._private.config import Config
-from dbgpt.app.knowledge.request.request import KnowledgeSpaceRequest
+from dbgpt.serve.rag.api.schemas import SpaceServeRequest, SpaceServeResponse
from dbgpt.storage.metadata import BaseDao, Model
-CFG = Config()
-
class KnowledgeSpaceEntity(Model):
__tablename__ = "knowledge_space"
@@ -25,11 +23,12 @@ class KnowledgeSpaceEntity(Model):
class KnowledgeSpaceDao(BaseDao):
- def create_knowledge_space(self, space: KnowledgeSpaceRequest):
+ def create_knowledge_space(self, space: SpaceServeRequest):
+ """Create knowledge space"""
session = self.get_raw_session()
knowledge_space = KnowledgeSpaceEntity(
name=space.name,
- vector_type=CFG.VECTOR_STORE_TYPE,
+ vector_type=space.vector_type,
desc=space.desc,
owner=space.owner,
gmt_created=datetime.now(),
@@ -37,9 +36,12 @@ class KnowledgeSpaceDao(BaseDao):
)
session.add(knowledge_space)
session.commit()
+ space_id = knowledge_space.id
session.close()
+ return self.to_response(knowledge_space)
def get_knowledge_space(self, query: KnowledgeSpaceEntity):
+ """Get knowledge space by query"""
session = self.get_raw_session()
knowledge_spaces = session.query(KnowledgeSpaceEntity)
if query.id is not None:
@@ -70,7 +72,6 @@ class KnowledgeSpaceDao(BaseDao):
knowledge_spaces = knowledge_spaces.filter(
KnowledgeSpaceEntity.gmt_modified == query.gmt_modified
)
-
knowledge_spaces = knowledge_spaces.order_by(
KnowledgeSpaceEntity.gmt_created.desc()
)
@@ -79,15 +80,79 @@ class KnowledgeSpaceDao(BaseDao):
return result
def update_knowledge_space(self, space: KnowledgeSpaceEntity):
+ """Update knowledge space"""
+
session = self.get_raw_session()
- session.merge(space)
+ request = SpaceServeRequest(id=space.id)
+ update_request = self.to_request(space)
+ query = self._create_query_object(session, request)
+ entry = query.first()
+ if entry is None:
+ raise Exception("Invalid request")
+ for key, value in update_request.dict().items(): # type: ignore
+ if value is not None:
+ setattr(entry, key, value)
+ session.merge(entry)
session.commit()
session.close()
- return True
+ return self.to_response(space)
def delete_knowledge_space(self, space: KnowledgeSpaceEntity):
+ """Delete knowledge space"""
session = self.get_raw_session()
if space:
session.delete(space)
session.commit()
session.close()
+
+ def from_request(
+ self, request: Union[SpaceServeRequest, Dict[str, Any]]
+ ) -> KnowledgeSpaceEntity:
+ """Convert the request to an entity
+
+ Args:
+ request (Union[ServeRequest, Dict[str, Any]]): The request
+
+ Returns:
+ T: The entity
+ """
+ request_dict = (
+ request.dict() if isinstance(request, SpaceServeRequest) else request
+ )
+ entity = KnowledgeSpaceEntity(**request_dict)
+ return entity
+
+ def to_request(self, entity: KnowledgeSpaceEntity) -> SpaceServeRequest:
+ """Convert the entity to a request
+
+ Args:
+ entity (T): The entity
+
+ Returns:
+ REQ: The request
+ """
+ return SpaceServeRequest(
+ id=entity.id,
+ name=entity.name,
+ vector_type=entity.vector_type,
+ desc=entity.desc,
+ owner=entity.owner,
+ context=entity.context,
+ )
+
+ def to_response(self, entity: KnowledgeSpaceEntity) -> SpaceServeResponse:
+ """Convert the entity to a response
+
+ Args:
+ entity (T): The entity
+
+ Returns:
+ REQ: The request
+ """
+ return SpaceServeResponse(
+ id=entity.id,
+ name=entity.name,
+ vector_type=entity.vector_type,
+ desc=entity.desc,
+ owner=entity.owner,
+ )
diff --git a/dbgpt/serve/rag/serve.py b/dbgpt/serve/rag/serve.py
new file mode 100644
index 000000000..305364614
--- /dev/null
+++ b/dbgpt/serve/rag/serve.py
@@ -0,0 +1,62 @@
+import logging
+from typing import List, Optional, Union
+
+from sqlalchemy import URL
+
+from dbgpt.component import SystemApp
+from dbgpt.serve.core import BaseServe
+from dbgpt.storage.metadata import DatabaseManager
+
+from .api.endpoints import init_endpoints, router
+from .config import (
+ APP_NAME,
+ SERVE_APP_NAME,
+ SERVE_APP_NAME_HUMP,
+ SERVE_CONFIG_KEY_PREFIX,
+)
+
+logger = logging.getLogger(__name__)
+
+
+class Serve(BaseServe):
+ """Serve component for DB-GPT"""
+
+ name = SERVE_APP_NAME
+
+ def __init__(
+ self,
+ system_app: SystemApp,
+ api_prefix: Optional[str] = f"/api/v2/serve/knowledge",
+ api_tags: Optional[List[str]] = None,
+ db_url_or_db: Union[str, URL, DatabaseManager] = None,
+ try_create_tables: Optional[bool] = False,
+ ):
+ if api_tags is None:
+ api_tags = [SERVE_APP_NAME_HUMP]
+ super().__init__(
+ system_app, api_prefix, api_tags, db_url_or_db, try_create_tables
+ )
+ self._db_manager: Optional[DatabaseManager] = None
+
+ def init_app(self, system_app: SystemApp):
+ if self._app_has_initiated:
+ return
+ self._system_app = system_app
+ self._system_app.app.include_router(
+ router, prefix=self._api_prefix, tags=self._api_tags
+ )
+ init_endpoints(self._system_app)
+ self._app_has_initiated = True
+
+ def on_init(self):
+ """Called when init the application.
+
+ You can do some initialization here. You can't get other components here because they may be not initialized yet
+ """
+ # import your own module here to ensure the module is loaded before the application starts
+ from .models.models import KnowledgeSpaceEntity
+
+ def before_start(self):
+ """Called before the start of the application."""
+ # TODO: Your code here
+ self._db_manager = self.create_or_get_db_manager()
diff --git a/dbgpt/serve/rag/service/__init__.py b/dbgpt/serve/rag/service/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/dbgpt/serve/rag/service/service.py b/dbgpt/serve/rag/service/service.py
new file mode 100644
index 000000000..4cb100c50
--- /dev/null
+++ b/dbgpt/serve/rag/service/service.py
@@ -0,0 +1,520 @@
+import json
+import logging
+import os
+import shutil
+import tempfile
+from datetime import datetime
+from enum import Enum
+from typing import List, Optional
+
+from fastapi import HTTPException
+
+from dbgpt._private.config import Config
+from dbgpt.app.knowledge.chunk_db import DocumentChunkDao, DocumentChunkEntity
+from dbgpt.app.knowledge.document_db import (
+ KnowledgeDocumentDao,
+ KnowledgeDocumentEntity,
+)
+from dbgpt.app.knowledge.request.request import KnowledgeSpaceRequest
+from dbgpt.component import ComponentType, SystemApp
+from dbgpt.configs.model_config import (
+ EMBEDDING_MODEL_CONFIG,
+ KNOWLEDGE_UPLOAD_ROOT_PATH,
+)
+from dbgpt.core import Chunk
+from dbgpt.core.awel.dag.dag_manager import DAGManager
+from dbgpt.rag.chunk_manager import ChunkParameters
+from dbgpt.rag.embedding import EmbeddingFactory
+from dbgpt.rag.knowledge import ChunkStrategy, KnowledgeFactory, KnowledgeType
+from dbgpt.serve.core import BaseService
+from dbgpt.storage.metadata import BaseDao
+from dbgpt.storage.metadata._base_dao import QUERY_SPEC
+from dbgpt.storage.vector_store.base import VectorStoreConfig
+from dbgpt.storage.vector_store.connector import VectorStoreConnector
+from dbgpt.util.dbgpts.loader import DBGPTsLoader
+from dbgpt.util.executor_utils import ExecutorFactory
+from dbgpt.util.pagination_utils import PaginationResult
+from dbgpt.util.tracer import root_tracer, trace
+
+from ..api.schemas import (
+ DocumentServeRequest,
+ DocumentServeResponse,
+ KnowledgeSyncRequest,
+ SpaceServeRequest,
+ SpaceServeResponse,
+)
+from ..assembler.embedding import EmbeddingAssembler
+from ..config import SERVE_CONFIG_KEY_PREFIX, SERVE_SERVICE_COMPONENT_NAME, ServeConfig
+from ..models.models import KnowledgeSpaceDao, KnowledgeSpaceEntity
+
+logger = logging.getLogger(__name__)
+CFG = Config()
+
+
+class SyncStatus(Enum):
+ TODO = "TODO"
+ FAILED = "FAILED"
+ RUNNING = "RUNNING"
+ FINISHED = "FINISHED"
+
+
+class Service(BaseService[KnowledgeSpaceEntity, SpaceServeRequest, SpaceServeResponse]):
+ """The service class for Flow"""
+
+ name = SERVE_SERVICE_COMPONENT_NAME
+
+ def __init__(
+ self,
+ system_app: SystemApp,
+ dao: Optional[KnowledgeSpaceDao] = None,
+ document_dao: Optional[KnowledgeDocumentDao] = None,
+ chunk_dao: Optional[DocumentChunkDao] = None,
+ ):
+ self._system_app = None
+ self._dao: KnowledgeSpaceDao = dao
+ self._document_dao: KnowledgeDocumentDao = document_dao
+ self._chunk_dao: DocumentChunkDao = chunk_dao
+ self._dag_manager: Optional[DAGManager] = None
+ self._dbgpts_loader: Optional[DBGPTsLoader] = None
+
+ super().__init__(system_app)
+
+ def init_app(self, system_app: SystemApp) -> None:
+ """Initialize the service
+
+ Args:
+ system_app (SystemApp): The system app
+ """
+ self._serve_config = ServeConfig.from_app_config(
+ system_app.config, SERVE_CONFIG_KEY_PREFIX
+ )
+ self._dao = self._dao or KnowledgeSpaceDao()
+ self._document_dao = self._document_dao or KnowledgeDocumentDao()
+ self._chunk_dao = self._chunk_dao or DocumentChunkDao()
+ self._system_app = system_app
+
+ def before_start(self):
+ """Execute before the application starts"""
+
+ def after_start(self):
+ """Execute after the application starts"""
+
+ @property
+ def dao(
+ self,
+ ) -> BaseDao[KnowledgeSpaceEntity, SpaceServeRequest, SpaceServeResponse]:
+ """Returns the internal DAO."""
+ return self._dao
+
+ @property
+ def config(self) -> ServeConfig:
+ """Returns the internal ServeConfig."""
+ return self._serve_config
+
+ def create_space(self, request: SpaceServeRequest) -> SpaceServeResponse:
+ """Create a new Space entity
+
+ Args:
+ request (KnowledgeSpaceRequest): The request
+
+ Returns:
+ SpaceServeResponse: The response
+ """
+ space = self.get(request)
+ if space is not None:
+ raise HTTPException(
+ status_code=400,
+ detail=f"space name:{request.name} have already named",
+ )
+ return self._dao.create_knowledge_space(request)
+
+ def update_space(self, request: SpaceServeRequest) -> SpaceServeResponse:
+ """Create a new Space entity
+
+ Args:
+ request (KnowledgeSpaceRequest): The request
+
+ Returns:
+ SpaceServeResponse: The response
+ """
+ spaces = self._dao.get_knowledge_space(
+ KnowledgeSpaceEntity(id=request.id, name=request.name)
+ )
+ if len(spaces) == 0:
+ raise HTTPException(
+ status_code=400,
+ detail=f"no space name named {request.name}",
+ )
+ update_obj = self._dao.update_knowledge_space(self._dao.from_request(request))
+ return update_obj
+
+ async def create_document(
+ self, request: DocumentServeRequest
+ ) -> SpaceServeResponse:
+ """Create a new document entity
+
+ Args:
+ request (KnowledgeSpaceRequest): The request
+
+ Returns:
+ SpaceServeResponse: The response
+ """
+ space = self.get({"id": request.space_id})
+ if space is None:
+ raise Exception(f"space id:{request.space_id} not found")
+ query = KnowledgeDocumentEntity(doc_name=request.doc_name, space=space.name)
+ documents = self._document_dao.get_knowledge_documents(query)
+ if len(documents) > 0:
+ raise Exception(f"document name:{request.doc_name} have already named")
+ if request.doc_file and request.doc_type == KnowledgeType.DOCUMENT.name:
+ doc_file = request.doc_file
+ if not os.path.exists(os.path.join(KNOWLEDGE_UPLOAD_ROOT_PATH, space.name)):
+ os.makedirs(os.path.join(KNOWLEDGE_UPLOAD_ROOT_PATH, space.name))
+ tmp_fd, tmp_path = tempfile.mkstemp(
+ dir=os.path.join(KNOWLEDGE_UPLOAD_ROOT_PATH, space.name)
+ )
+ with os.fdopen(tmp_fd, "wb") as tmp:
+ tmp.write(await request.doc_file.read())
+ shutil.move(
+ tmp_path,
+ os.path.join(KNOWLEDGE_UPLOAD_ROOT_PATH, space.name, doc_file.filename),
+ )
+ request.content = os.path.join(
+ KNOWLEDGE_UPLOAD_ROOT_PATH, space.name, doc_file.filename
+ )
+ document = KnowledgeDocumentEntity(
+ doc_name=request.doc_name,
+ doc_type=request.doc_type,
+ space=space.name,
+ chunk_size=0,
+ status=SyncStatus.TODO.name,
+ last_sync=datetime.now(),
+ content=request.content,
+ result="",
+ )
+ doc_id = self._document_dao.create_knowledge_document(document)
+ if doc_id is None:
+ raise Exception(f"create document failed, {request.doc_name}")
+ return doc_id
+
+ def sync_document(self, requests: List[KnowledgeSyncRequest]) -> List:
+ """Create a new document entity
+
+ Args:
+ request (KnowledgeSpaceRequest): The request
+
+ Returns:
+ SpaceServeResponse: The response
+ """
+ doc_ids = []
+ for sync_request in requests:
+ space_id = sync_request.space_id
+ docs = self._document_dao.documents_by_ids([sync_request.doc_id])
+ if len(docs) == 0:
+ raise Exception(
+ f"there are document called, doc_id: {sync_request.doc_id}"
+ )
+ doc = docs[0]
+ if (
+ doc.status == SyncStatus.RUNNING.name
+ or doc.status == SyncStatus.FINISHED.name
+ ):
+ raise Exception(
+ f" doc:{doc.doc_name} status is {doc.status}, can not sync"
+ )
+ chunk_parameters = sync_request.chunk_parameters
+ if chunk_parameters.chunk_strategy != ChunkStrategy.CHUNK_BY_SIZE.name:
+ space_context = self.get_space_context(space_id)
+ chunk_parameters.chunk_size = (
+ CFG.KNOWLEDGE_CHUNK_SIZE
+ if space_context is None
+ else int(space_context["embedding"]["chunk_size"])
+ )
+ chunk_parameters.chunk_overlap = (
+ CFG.KNOWLEDGE_CHUNK_OVERLAP
+ if space_context is None
+ else int(space_context["embedding"]["chunk_overlap"])
+ )
+ self._sync_knowledge_document(space_id, doc, chunk_parameters)
+ doc_ids.append(doc.id)
+ return doc_ids
+
+ def get(self, request: QUERY_SPEC) -> Optional[SpaceServeResponse]:
+ """Get a Flow entity
+
+ Args:
+ request (SpaceServeRequest): The request
+
+ Returns:
+ SpaceServeResponse: The response
+ """
+ # TODO: implement your own logic here
+ # Build the query request from the request
+ query_request = request
+ return self._dao.get_one(query_request)
+
+ def get_document(self, request: QUERY_SPEC) -> Optional[SpaceServeResponse]:
+ """Get a Flow entity
+
+ Args:
+ request (SpaceServeRequest): The request
+
+ Returns:
+ SpaceServeResponse: The response
+ """
+ # TODO: implement your own logic here
+ # Build the query request from the request
+ query_request = request
+ return self._document_dao.get_one(query_request)
+
+ def delete(self, space_id: str) -> Optional[SpaceServeResponse]:
+ """Delete a Flow entity
+
+ Args:
+ uid (str): The uid
+
+ Returns:
+ SpaceServeResponse: The data after deletion
+ """
+
+ # TODO: implement your own logic here
+ # Build the query request from the request
+ query_request = {"id": space_id}
+ space = self.get(query_request)
+ if space is None:
+ raise HTTPException(status_code=400, detail=f"Space {space_id} not found")
+ config = VectorStoreConfig(name=space.name)
+ vector_store_connector = VectorStoreConnector(
+ vector_store_type=CFG.VECTOR_STORE_TYPE,
+ vector_store_config=config,
+ )
+ # delete vectors
+ vector_store_connector.delete_vector_name(space.name)
+ document_query = KnowledgeDocumentEntity(space=space.name)
+ # delete chunks
+ documents = self._document_dao.get_documents(document_query)
+ for document in documents:
+ self._chunk_dao.raw_delete(document.id)
+ # delete documents
+ self._document_dao.raw_delete(document_query)
+ # delete space
+ self._dao.delete(query_request)
+ return space
+
+ def delete_document(self, document_id: str) -> Optional[DocumentServeResponse]:
+ """Delete a Flow entity
+
+ Args:
+ uid (str): The uid
+
+ Returns:
+ SpaceServeResponse: The data after deletion
+ """
+
+ query_request = {"id": document_id}
+ docuemnt = self._document_dao.get_one(query_request)
+ if docuemnt is None:
+ raise Exception(f"there are no or more than one document {document_id}")
+ vector_ids = docuemnt.vector_ids
+ if vector_ids is not None:
+ config = VectorStoreConfig(name=docuemnt.space)
+ vector_store_connector = VectorStoreConnector(
+ vector_store_type=CFG.VECTOR_STORE_TYPE,
+ vector_store_config=config,
+ )
+ # delete vector by ids
+ vector_store_connector.delete_by_ids(vector_ids)
+ # delete chunks
+ self._chunk_dao.raw_delete(docuemnt.id)
+ # delete document
+ self._document_dao.raw_delete(docuemnt)
+ return docuemnt
+
+ def get_list(self, request: SpaceServeRequest) -> List[SpaceServeResponse]:
+ """Get a list of Flow entities
+
+ Args:
+ request (SpaceServeRequest): The request
+
+ Returns:
+ List[SpaceServeResponse]: The response
+ """
+ # TODO: implement your own logic here
+ # Build the query request from the request
+ query_request = request
+ return self.dao.get_list(query_request)
+
+ def get_list_by_page(
+ self, request: QUERY_SPEC, page: int, page_size: int
+ ) -> PaginationResult[SpaceServeResponse]:
+ """Get a list of Flow entities by page
+
+ Args:
+ request (SpaceServeRequest): The request
+ page (int): The page number
+ page_size (int): The page size
+
+ Returns:
+ List[SpaceServeResponse]: The response
+ """
+ return self.dao.get_list_page(request, page, page_size)
+
+ def get_document_list(
+ self, request: QUERY_SPEC, page: int, page_size: int
+ ) -> PaginationResult[SpaceServeResponse]:
+ """Get a list of Flow entities by page
+
+ Args:
+ request (SpaceServeRequest): The request
+ page (int): The page number
+ page_size (int): The page size
+
+ Returns:
+ List[SpaceServeResponse]: The response
+ """
+ return self._document_dao.get_list_page(request, page, page_size)
+
+ def _batch_document_sync(
+ self, space_id, sync_requests: List[KnowledgeSyncRequest]
+ ) -> List[int]:
+ """batch sync knowledge document chunk into vector store
+ Args:
+ - space: Knowledge Space Name
+ - sync_requests: List[KnowledgeSyncRequest]
+ Returns:
+ - List[int]: document ids
+ """
+ doc_ids = []
+ for sync_request in sync_requests:
+ docs = self._document_dao.documents_by_ids([sync_request.doc_id])
+ if len(docs) == 0:
+ raise Exception(
+ f"there are document called, doc_id: {sync_request.doc_id}"
+ )
+ doc = docs[0]
+ if (
+ doc.status == SyncStatus.RUNNING.name
+ or doc.status == SyncStatus.FINISHED.name
+ ):
+ raise Exception(
+ f" doc:{doc.doc_name} status is {doc.status}, can not sync"
+ )
+ chunk_parameters = sync_request.chunk_parameters
+ if chunk_parameters.chunk_strategy != ChunkStrategy.CHUNK_BY_SIZE.name:
+ space_context = self.get_space_context(space_id)
+ chunk_parameters.chunk_size = (
+ CFG.KNOWLEDGE_CHUNK_SIZE
+ if space_context is None
+ else int(space_context["embedding"]["chunk_size"])
+ )
+ chunk_parameters.chunk_overlap = (
+ CFG.KNOWLEDGE_CHUNK_OVERLAP
+ if space_context is None
+ else int(space_context["embedding"]["chunk_overlap"])
+ )
+ self._sync_knowledge_document(space_id, doc, chunk_parameters)
+ doc_ids.append(doc.id)
+ return doc_ids
+
+ def _sync_knowledge_document(
+ self,
+ space_id,
+ doc: KnowledgeDocumentEntity,
+ chunk_parameters: ChunkParameters,
+ ) -> List[Chunk]:
+ """sync knowledge document chunk into vector store"""
+ embedding_factory = CFG.SYSTEM_APP.get_component(
+ "embedding_factory", EmbeddingFactory
+ )
+ embedding_fn = embedding_factory.create(
+ model_name=EMBEDDING_MODEL_CONFIG[CFG.EMBEDDING_MODEL]
+ )
+ from dbgpt.storage.vector_store.base import VectorStoreConfig
+
+ space = self.get({"id": space_id})
+ config = VectorStoreConfig(
+ name=space.name,
+ embedding_fn=embedding_fn,
+ max_chunks_once_load=CFG.KNOWLEDGE_MAX_CHUNKS_ONCE_LOAD,
+ )
+ vector_store_connector = VectorStoreConnector(
+ vector_store_type=CFG.VECTOR_STORE_TYPE,
+ vector_store_config=config,
+ )
+ knowledge = KnowledgeFactory.create(
+ datasource=doc.content,
+ knowledge_type=KnowledgeType.get_by_value(doc.doc_type),
+ )
+ assembler = EmbeddingAssembler.load_from_knowledge(
+ knowledge=knowledge,
+ chunk_parameters=chunk_parameters,
+ vector_store_connector=vector_store_connector,
+ )
+ chunk_docs = assembler.get_chunks()
+ doc.status = SyncStatus.RUNNING.name
+ doc.chunk_size = len(chunk_docs)
+ doc.gmt_modified = datetime.now()
+ self._document_dao.update_knowledge_document(doc)
+ executor = CFG.SYSTEM_APP.get_component(
+ ComponentType.EXECUTOR_DEFAULT, ExecutorFactory
+ ).create()
+ executor.submit(self.async_doc_embedding, assembler, chunk_docs, doc)
+ logger.info(f"begin save document chunks, doc:{doc.doc_name}")
+ return chunk_docs
+
+ @trace("async_doc_embedding")
+ def async_doc_embedding(self, assembler, chunk_docs, doc):
+ """async document embedding into vector db
+ Args:
+ - client: EmbeddingEngine Client
+ - chunk_docs: List[Document]
+ - doc: KnowledgeDocumentEntity
+ """
+
+ logger.info(
+ f"async doc embedding sync, doc:{doc.doc_name}, chunks length is {len(chunk_docs)}, begin embedding to vector store-{CFG.VECTOR_STORE_TYPE}"
+ )
+ try:
+ with root_tracer.start_span(
+ "app.knowledge.assembler.persist",
+ metadata={"doc": doc.doc_name, "chunks": len(chunk_docs)},
+ ):
+ vector_ids = assembler.persist()
+ doc.status = SyncStatus.FINISHED.name
+ doc.result = "document embedding success"
+ if vector_ids is not None:
+ doc.vector_ids = ",".join(vector_ids)
+ logger.info(f"async document embedding, success:{doc.doc_name}")
+ # save chunk details
+ chunk_entities = [
+ DocumentChunkEntity(
+ doc_name=doc.doc_name,
+ doc_type=doc.doc_type,
+ document_id=doc.id,
+ content=chunk_doc.content,
+ meta_info=str(chunk_doc.metadata),
+ gmt_created=datetime.now(),
+ gmt_modified=datetime.now(),
+ )
+ for chunk_doc in chunk_docs
+ ]
+ self._chunk_dao.create_documents_chunks(chunk_entities)
+ except Exception as e:
+ doc.status = SyncStatus.FAILED.name
+ doc.result = "document embedding failed" + str(e)
+ logger.error(f"document embedding, failed:{doc.doc_name}, {str(e)}")
+ return self._document_dao.update_knowledge_document(doc)
+
+ def get_space_context(self, space_id):
+ """get space contect
+ Args:
+ - space_name: space name
+ """
+ space = self.get({"id": space_id})
+ if space is None:
+ raise Exception(
+ f"have not found {space_id} space or found more than one space called {space_id}"
+ )
+ if space.context is not None:
+ return json.loads(space.context)
+ return None
diff --git a/dbgpt/serve/rag/tests/__init__.py b/dbgpt/serve/rag/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/docs/docs/api/app.md b/docs/docs/api/app.md
new file mode 100644
index 000000000..f2cbec390
--- /dev/null
+++ b/docs/docs/api/app.md
@@ -0,0 +1,268 @@
+# App
+
+Get started with the App API
+
+# Chat App
+
+```python
+POST /api/v2/chat/completions
+```
+### Examples
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+### Stream Chat App
+
+
+
+
+
+
+```shell
+ DBGPT_API_KEY=dbgpt
+ APP_ID={YOUR_APP_ID}
+
+ curl -X POST "http://localhost:5000/api/v2/chat/completions" \
+ -H "Authorization: Bearer $DBGPT_API_KEY" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d "{\"messages\":\"Hello\",\"model\":\"chatgpt_proxyllm\", \"chat_mode\": \"chat_app\", \"chat_param\": \"$APP_ID\"}"
+
+```
+
+
+
+
+```python
+from dbgpt.client import Client
+
+DBGPT_API_KEY = "dbgpt"
+APP_ID="{YOUR_APP_ID}"
+
+client = Client(api_key=DBGPT_API_KEY)
+
+async for data in client.chat_stream(
+ messages="Introduce AWEL",
+ model="chatgpt_proxyllm",
+ chat_mode="chat_app",
+ chat_param=APP_ID
+):
+ print(data)
+
+```
+
+
+
+### Chat Completion Stream Response
+```commandline
+data: {"id": "109bfc28-fe87-452c-8e1f-d4fe43283b7d", "created": 1710919480, "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "```agent-plans\n[{\"name\": \"Introduce Awel\", \"num\": 2, \"status\": \"complete\", \"agent\": \"Human\", \"markdown\": \"```agent-messages\\n[{\\\"sender\\\": \\\"Summarizer\\\", \\\"receiver\\\": \\\"Human\\\", \\\"model\\\": \\\"chatgpt_proxyllm\\\", \\\"markdown\\\": \\\"Agentic Workflow Expression Language (AWEL) is a specialized language designed for developing large model applications with intelligent agent workflows. It offers flexibility and functionality, allowing developers to focus on business logic for LLMs applications without getting bogged down in model and environment details. AWEL uses a layered API design architecture, making it easier to work with. You can find examples and source code to get started with AWEL, and it supports various operators and environments. AWEL is a powerful tool for building native data applications through workflows and agents.\"}]\n```"}}]}
+
+data: [DONE]
+```
+### Get App
+
+```python
+GET /api/v2/serve/apps/{app_id}
+```
+
+
+
+
+
+```shell
+DBGPT_API_KEY=dbgpt
+APP_ID={YOUR_APP_ID}
+curl -X GET "http://localhost:5000/api/v2/serve/apps/$APP_ID" -H "Authorization: Bearer $DBGPT_API_KEY"
+```
+
+
+
+
+
+```python
+from dbgpt.client import Client
+from dbgpt.client.app import get_app
+
+DBGPT_API_KEY = "dbgpt"
+app_id = "{your_app_id}"
+
+client = Client(api_key=DBGPT_API_KEY)
+res = await get_app(client=client, app_id=app_id)
+
+```
+
+
+
+
+
+#### Query Parameters
+________
+app_id string Required
+
+app id
+________
+
+#### Response body
+Return App Object
+
+### List App
+
+```python
+GET /api/v2/serve/apps
+```
+
+
+
+
+```shell
+DBGPT_API_KEY=dbgpt
+
+curl -X GET 'http://localhost:5000/api/v2/serve/apps' -H "Authorization: Bearer $DBGPT_API_KEY"
+```
+
+
+
+
+
+```python
+from dbgpt.client import Client
+from dbgpt.client.app import list_app
+
+DBGPT_API_KEY = "dbgpt"
+app_id = "{your_app_id}"
+
+client = Client(api_key=DBGPT_API_KEY)
+res = await list_app(client=client)
+
+```
+
+
+
+
+#### Response body
+Return App Object List
+
+### The App Model
+________
+id string
+
+space id
+________
+app_code string
+
+app code
+________
+app_name string
+
+app name
+________
+
+app_describe string
+
+app describe
+________
+team_mode string
+
+team mode
+________
+language string
+
+language
+________
+team_context string
+
+team context
+________
+user_code string
+
+user code
+________
+sys_code string
+
+sys code
+________
+is_collected string
+
+is collected
+________
+icon string
+
+icon
+________
+created_at string
+
+created at
+________
+updated_at string
+
+updated at
+________
+details string
+
+app details List[AppDetailModel]
+________
+
+### The App Detail Model
+________
+app_code string
+
+app code
+________
+app_name string
+
+app name
+________
+agent_name string
+
+agent name
+________
+node_id string
+
+node id
+________
+resources string
+
+resources
+________
+prompt_template string
+
+prompt template
+________
+llm_strategy string
+
+llm strategy
+________
+llm_strategy_value string
+
+llm strategy value
+________
+created_at string
+
+created at
+________
+updated_at string
+
+updated at
+________
diff --git a/docs/docs/api/chat.md b/docs/docs/api/chat.md
new file mode 100644
index 000000000..da64c99a8
--- /dev/null
+++ b/docs/docs/api/chat.md
@@ -0,0 +1,285 @@
+# Chat
+
+Given a list of messages comprising a conversation, the model will return a response.
+
+# Create Chat Completion
+
+```python
+POST /api/v2/chat/completions
+```
+
+### Examples
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+### Stream Chat Completion
+
+
+
+
+
+
+```shell
+ DBGPT_API_KEY="dbgpt"
+
+ curl -X POST "http://localhost:5000/api/v2/chat/completions" \
+ -H "Authorization: Bearer $DBGPT_API_KEY" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d "{\"messages\":\"Hello\",\"model\":\"chatgpt_proxyllm\", \"stream\": true}"
+
+```
+
+
+
+
+```python
+from dbgpt.client import Client
+
+DBGPT_API_KEY = "dbgpt"
+client = Client(api_key=DBGPT_API_KEY)
+
+async for data in client.chat_stream(
+ model="chatgpt_proxyllm",
+ messages="hello",
+):
+ print(data)
+```
+
+
+
+### Chat Completion Stream Response
+```commandline
+data: {"id": "chatcmpl-ba6fb52e-e5b2-11ee-b031-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "Hello"}}]}
+
+data: {"id": "chatcmpl-ba6fb52e-e5b2-11ee-b031-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "!"}}]}
+
+data: {"id": "chatcmpl-ba6fb52e-e5b2-11ee-b031-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " How"}}]}
+
+data: {"id": "chatcmpl-ba6fb52e-e5b2-11ee-b031-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " can"}}]}
+
+data: {"id": "chatcmpl-ba6fb52e-e5b2-11ee-b031-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " I"}}]}
+
+data: {"id": "chatcmpl-ba6fb52e-e5b2-11ee-b031-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " assist"}}]}
+
+data: {"id": "chatcmpl-ba6fb52e-e5b2-11ee-b031-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " you"}}]}
+
+data: {"id": "chatcmpl-ba6fb52e-e5b2-11ee-b031-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " today"}}]}
+
+data: {"id": "chatcmpl-ba6fb52e-e5b2-11ee-b031-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "?"}}]}
+
+data: [DONE]
+```
+
+### Chat Completion
+
+
+
+
+```shell
+ DBGPT_API_KEY="dbgpt"
+
+ curl -X POST "http://localhost:5000/api/v2/chat/completions" \
+ -H "Authorization: Bearer $DBGPT_API_KEY" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d "{\"messages\":\"Hello\",\"model\":\"chatgpt_proxyllm\", \"stream\": false}"
+```
+
+
+
+
+```python
+from dbgpt.client import Client
+
+DBGPT_API_KEY = "dbgpt"
+client = Client(api_key=DBGPT_API_KEY)
+response = await client.chat(model="chatgpt_proxyllm" ,messages="hello")
+```
+
+
+
+### Chat Completion Response
+```json
+{
+ "id": "a8321543-52e9-47a5-a0b6-3d997463f6a3",
+ "object": "chat.completion",
+ "created": 1710826792,
+ "model": "chatgpt_proxyllm",
+ "choices": [
+ {
+ "index": 0,
+ "message": {
+ "role": "assistant",
+ "content": "Hello! How can I assist you today?"
+ },
+ "finish_reason": null
+ }
+ ],
+ "usage": {
+ "prompt_tokens": 0,
+ "total_tokens": 0,
+ "completion_tokens": 0
+ }
+}
+```
+
+
+
+### Request body
+________
+messages string Required
+
+A list of messages comprising the conversation so far. Example Python code.
+________
+model string Required
+
+ID of the model to use. See the model endpoint compatibility table for details on which models work with the Chat API.
+________
+chat_mode string Optional
+
+The DB-GPT chat mode, which can be one of the following: `chat_normal`, `chat_app`, `chat_knowledge`, `chat_flow`, default is `chat_normal`.
+________
+chat_param string Optional
+
+The DB-GPT The chat param value of chat mode: `{app_id}`, `{space_id}`, `{flow_id}`, default is `None`.
+________
+max_new_tokens integer Optional
+
+The maximum number of tokens that can be generated in the chat completion.
+
+The total length of input tokens and generated tokens is limited by the model's context length.
+________
+stream integer Optional
+
+If set, partial message deltas will be sent.
+Tokens will be sent as data-only server-sent events as they become available, with the stream terminated by a `data: [DONE]`
+________
+temperature integer Optional
+
+What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.
+________
+conv_uid string Optional
+
+The conversation id of the model inference, default is `None`
+________
+span_id string Optional
+
+The span id of the model inference, default is `None`
+________
+sys_code string Optional
+
+The system code, default is `None`
+________
+user_name string Optional
+
+The web server user name, default is `None`
+________
+
+
+### Chat Stream Response Body
+________
+id string
+
+conv_uid of the convsersation.
+________
+model string
+
+The model used for the chat completion.
+
+________
+created string
+
+The Unix timestamp (in seconds) of when the chat completion was created.
+________
+choices array
+
+A list of chat completion choices. Can be more than one if n is greater than 1.
+
+ - index integer
+
+ The index of the choice in the list of choices.
+ - delta object
+
+ The chat completion delta.
+ - role string
+
+ The role of the speaker. Can be `user` or `assistant`.
+ - content string
+
+ The content of the message.
+ - finish_reason string
+
+ The reason the chat completion finished. Can be `max_tokens` or `stop`.
+________
+
+
+### Chat Response Body
+________
+id string
+
+conv_uid of the convsersation.
+________
+model string
+
+The model used for the chat completion.
+
+________
+created string
+
+The Unix timestamp (in seconds) of when the chat completion was created.
+________
+object string
+
+The object type of the chat completion.
+________
+choices array
+
+A list of chat completion choices. Can be more than one if n is greater than 1.
+
+ - index integer
+
+ The index of the choice in the list of choices.
+
+ - delta object
+
+ The chat completion delta.
+ - role string
+
+ The role of the speaker. Can be `user` or `assistant`.
+ - content string
+
+ The content of the message.
+ - finish_reason string
+
+ The reason the chat completion finished. Can be `max_tokens` or `stop`.
+________
+usage object
+
+ The usage statistics for the chat completion.
+ - prompt_tokens integer
+
+ The number of tokens in the prompt.
+ - total_tokens integer
+
+ The total number of tokens in the chat completion.
+ - completion_tokens integer
+
+ The number of tokens in the chat completion.
+
+
diff --git a/docs/docs/api/flow.md b/docs/docs/api/flow.md
new file mode 100644
index 000000000..88be326d2
--- /dev/null
+++ b/docs/docs/api/flow.md
@@ -0,0 +1,312 @@
+# Flow
+
+Get started with the App API
+
+# Chat Flow
+
+```python
+POST /api/v2/chat/completions
+```
+### Examples
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+### Stream Chat Flow
+
+
+
+
+
+
+```shell
+DBGPT_API_KEY=dbgpt
+FLOW_ID={YOUR_FLOW_ID}
+
+curl -X POST "http://localhost:5000/api/v2/chat/completions" \
+ -H "Authorization: Bearer $DBGPT_API_KEY" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d "{\"messages\":\"Hello\",\"model\":\"chatgpt_proxyllm\", \"chat_mode\": \"chat_flow\", \"chat_param\": \"$FLOW_ID\"}"
+
+```
+
+
+
+
+```python
+from dbgpt.client import Client
+
+DBGPT_API_KEY = "dbgpt"
+FLOW_ID="{YOUR_FLOW_ID}"
+
+client = Client(api_key=DBGPT_API_KEY)
+async for data in client.chat_stream(
+ messages="Introduce AWEL",
+ model="chatgpt_proxyllm",
+ chat_mode="chat_flow",
+ chat_param=FLOW_ID
+):
+ print(data)
+```
+
+
+
+#### Chat Completion Stream Response
+```commandline
+data: {"id": "579f8862-fc4b-481e-af02-a127e6d036c8", "created": 1710918094, "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "\n\n"}}]}
+```
+### Create Flow
+
+```python
+POST /api/v2/serve/awel/flows
+```
+#### Request body
+Request Flow Object
+
+#### Response body
+Return Flow Object
+
+
+### Update Flow
+
+PUT /api/v2/serve/awel/flows
+
+#### Request body
+Request Flow Object
+
+#### Response body
+Return Flow Object
+
+### Delete Flow
+
+```python
+DELETE /api/v2/serve/awel/flows
+```
+
+
+
+
+
+```shell
+DBGPT_API_KEY=dbgpt
+FLOW_ID={YOUR_FLOW_ID}
+
+ curl -X DELETE "http://localhost:5000/api/v2/serve/awel/flows/$FLOW_ID" \
+ -H "Authorization: Bearer $DBGPT_API_KEY" \
+
+```
+
+
+
+
+
+```python
+from dbgpt.client import Client
+from dbgpt.client.flow import delete_flow
+
+DBGPT_API_KEY = "dbgpt"
+flow_id = "{your_flow_id}"
+
+client = Client(api_key=DBGPT_API_KEY)
+res = await delete_flow(client=client, flow_id=flow_id)
+
+```
+
+
+
+
+#### Delete Parameters
+________
+uid string Required
+
+flow id
+________
+
+#### Response body
+Return Flow Object
+
+### Get Flow
+
+```python
+GET /api/v2/serve/awel/flows/{flow_id}
+```
+
+
+
+
+```shell
+DBGPT_API_KEY=dbgpt
+FLOW_ID={YOUR_FLOW_ID}
+
+curl -X GET "http://localhost:5000/api/v2/serve/awel/flows/$FLOW_ID" -H "Authorization: Bearer $DBGPT_API_KEY"
+
+```
+
+
+
+
+
+```python
+from dbgpt.client import Client
+from dbgpt.client.knowledge import get_flow
+
+DBGPT_API_KEY = "dbgpt"
+flow_id = "{your_flow_id}"
+
+client = Client(api_key=DBGPT_API_KEY)
+res = await get_flow(client=client, flow_id=flow_id)
+
+```
+
+
+
+
+#### Query Parameters
+________
+uid string Required
+
+flow id
+________
+
+#### Response body
+Return Flow Object
+
+### List Flow
+
+```python
+GET /api/v2/serve/awel/flows
+```
+
+
+
+
+
+
+```shell
+DBGPT_API_KEY=dbgpt
+
+curl -X GET "http://localhost:5000/api/v2/serve/awel/flows" -H "Authorization: Bearer $DBGPT_API_KEY"
+
+```
+
+
+
+
+
+```python
+from dbgpt.client import Client
+from dbgpt.client.flow import list_flow
+
+DBGPT_API_KEY = "dbgpt"
+
+client = Client(api_key=DBGPT_API_KEY)
+res = await list_flow(client=client)
+
+```
+
+
+
+
+#### Response body
+Return Flow Object
+
+### The Flow Object
+
+________
+uid string
+
+The unique id for the flow.
+________
+name string
+
+The name of the flow.
+________
+description string
+
+The description of the flow.
+________
+label string
+
+The label of the flow.
+________
+flow_category string
+
+The category of the flow. Default is FlowCategory.COMMON.
+________
+flow_data object
+
+The flow data.
+________
+state string
+
+The state of the flow.Default is INITIALIZING.
+________
+error_message string
+
+The error message of the flow.
+________
+source string
+
+The source of the flow. Default is DBGPT-WEB.
+________
+source_url string
+
+The source url of the flow.
+________
+version string
+
+The version of the flow. Default is 0.1.0.
+________
+editable boolean
+
+Whether the flow is editable. Default is True.
+________
+user_name string
+
+The user name of the flow.
+________
+sys_code string
+
+The system code of the flow.
+________
+dag_id string
+
+The dag id of the flow.
+________
+gmt_created string
+
+The created time of the flow.
+________
+gmt_modified string
+
+The modified time of the flow.
+________
\ No newline at end of file
diff --git a/docs/docs/api/introduction.md b/docs/docs/api/introduction.md
new file mode 100644
index 000000000..6354982ae
--- /dev/null
+++ b/docs/docs/api/introduction.md
@@ -0,0 +1,37 @@
+# Introduction
+
+This is the introduction to the DB-GPT API documentation. You can interact with the API through HTTP requests from any language, via our official Python Client bindings.
+
+# Authentication
+The DB-GPT API uses API keys for authentication. Visit your API Keys page to retrieve the API key you'll use in your requests.
+
+Production requests must be routed through your own backend server where your API key can be securely loaded from an environment variable or key management service.
+
+All API requests should include your API key in an Authorization HTTP header as follows:
+
+ ```http
+ Authorization: Bearer DBGPT_API_KEY
+ ```
+Example with the DB-GPT API curl command:
+
+ ```bash
+ curl "http://localhost:5000/api/v2/chat/completions" \
+ -H "Authorization: Bearer $DBGPT_API_KEY" \
+ ```
+Example with the DB-GPT Client Python package:
+
+ ```python
+ from dbgpt.client import Client
+
+ DBGPT_API_KEY = "dbgpt"
+ client = Client(api_key=DBGPT_API_KEY)
+ ```
+Set the API Key in .env file as follows:
+:::info note
+API_KEYS - The list of API keys that are allowed to access the API. Each of the below are an option, separated by commas.
+:::
+```python
+API_KEYS=dbgpt
+```
+
+
diff --git a/docs/docs/api/knowledge.md b/docs/docs/api/knowledge.md
new file mode 100644
index 000000000..8810f05fb
--- /dev/null
+++ b/docs/docs/api/knowledge.md
@@ -0,0 +1,654 @@
+# Knowledge
+
+Get started with the Knowledge API
+
+# Chat Knowledge Space
+
+```python
+POST /api/v2/chat/completions
+```
+### Examples
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+### Chat Knowledge
+
+
+
+
+
+
+```shell
+DBGPT_API_KEY=dbgpt
+SPACE_NAME={YOUR_SPACE_NAME}
+
+curl -X POST "http://localhost:5000/api/v2/chat/completions" \
+ -H "Authorization: Bearer $DBGPT_API_KEY" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+ -d "{\"messages\":\"Hello\",\"model\":\"chatgpt_proxyllm\", \"chat_mode\": \"chat_knowledge\", \"chat_param\": \"$SPACE_NAME\"}"
+```
+
+
+
+
+```python
+from dbgpt.client import Client
+
+DBGPT_API_KEY = "dbgpt"
+SPACE_NAME="{YOUR_SPACE_NAME}"
+
+client = Client(api_key=DBGPT_API_KEY)
+
+async for data in client.chat_stream(
+ messages="Introduce AWEL",
+ model="chatgpt_proxyllm",
+ chat_mode="chat_knowledge",
+ chat_param=SPACE_NAME
+):
+ print(data)
+```
+
+
+
+#### Chat Completion Response
+```json
+{
+ "id": "acb050ab-eb2c-4754-97e4-6f3b94b7dac2",
+ "object": "chat.completion",
+ "created": 1710917272,
+ "model": "chatgpt_proxyllm",
+ "choices": [
+ {
+ "index": 0,
+ "message": {
+ "role": "assistant",
+ "content": "Agentic Workflow Expression Language (AWEL) is a specialized language designed for developing large model applications with intelligent agent workflows. It offers flexibility and functionality, allowing developers to focus on business logic for LLMs applications without getting bogged down in model and environment details. AWEL uses a layered API design architecture, making it easier to work with. You can find examples and source code to get started with AWEL, and it supports various operators and environments. AWEL is a powerful tool for building native data applications through workflows and agents."
+ },
+ "finish_reason": null
+ }
+ ],
+ "usage": {
+ "prompt_tokens": 0,
+ "total_tokens": 0,
+ "completion_tokens": 0
+ }
+}
+```
+
+#### Chat Completion Stream Response
+```commandline
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "AW"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "EL"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": ","}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " which"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " stands"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " for"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " Ag"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "entic"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " Workflow"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " Expression"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " Language"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": ","}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " is"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " a"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " powerful"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " tool"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " designed"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " for"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " developing"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " large"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " model"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " applications"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "."}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " It"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " simpl"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "ifies"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " the"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " process"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " by"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " allowing"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " developers"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " to"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " focus"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " on"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " business"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " logic"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " without"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " getting"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " bog"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "ged"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " down"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " in"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " complex"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " model"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " and"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " environment"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " details"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "."}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " AW"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "EL"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " offers"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " great"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " functionality"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " and"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " flexibility"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " through"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " its"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " layered"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " API"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " design"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " architecture"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "."}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " It"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " provides"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " a"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " set"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " of"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " intelligent"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " agent"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " workflow"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " expression"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " language"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " that"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " enhances"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " efficiency"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " in"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " application"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " development"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "."}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " If"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " you"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " want"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " to"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " learn"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " more"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " about"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " AW"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "EL"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": ","}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " you"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " can"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " check"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " out"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " the"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " built"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "-in"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " examples"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " and"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " resources"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " available"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " on"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " platforms"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " like"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " Github"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": ","}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " Docker"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "hub"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": ","}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " and"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": " more"}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "."}}]}
+
+data: {"id": "chatcmpl-86f60a0c-e686-11ee-9322-acde48001122", "model": "chatgpt_proxyllm", "choices": [{"index": 0, "delta": {"role": "assistant", "content": "\n\n"}}]}
+
+data: [DONE]
+```
+### Create Knowledge Space
+
+```python
+POST /api/v2/serve/knowledge/spaces
+```
+
+
+
+
+
+
+
+```shell
+ DBGPT_API_KEY="dbgpt"
+
+ curl --location --request POST 'http://localhost:5000/api/v2/serve/knowledge/spaces' \
+--header 'Authorization: Bearer $DBGPT_API_KEY' \
+--header 'Content-Type: application/json' \
+--data-raw '{"desc": "for client space desc", "name": "test_space_2", "owner": "dbgpt", "vector_type": "Chroma"
+}'
+```
+
+
+
+
+```python
+from dbgpt.client import Client
+from dbgpt.client.knowledge import create_space
+from dbgpt.client.schema import SpaceModel
+
+DBGPT_API_KEY = "dbgpt"
+
+client = Client(api_key=DBGPT_API_KEY)
+res = await create_space(client, SpaceModel(
+ name="test_space",
+ vector_type="Chroma",
+ desc="for client space",
+ owner="dbgpt"
+))
+```
+
+
+
+
+#### Request body
+
+________
+name string Required
+
+knowledge space name
+________
+vector_type string Required
+
+vector db type, `Chroma`, `Milvus`, default is `Chroma`
+________
+desc string Optional
+
+description of the knowledge space
+________
+owner integer Optional
+
+The owner of the knowledge space
+________
+context integer Optional
+
+The context of the knowledge space argument
+________
+
+#### Response body
+Return Space Object
+
+### Update Knowledge Space
+
+```python
+PUT /api/v2/serve/knowledge/spaces
+```
+
+
+
+
+
+```shell
+ DBGPT_API_KEY="dbgpt"
+
+ curl --location --request PUT 'http://localhost:5000/api/v2/serve/knowledge/spaces' \
+--header 'Authorization: Bearer $DBGPT_API_KEY' \
+--header 'Content-Type: application/json' \
+--data-raw '{"desc": "for client space desc v2", "id": "49", "name": "test_space_2", "owner": "dbgpt", "vector_type": "Chroma"
+}'
+```
+
+
+
+
+```python
+from dbgpt.client import Client
+from dbgpt.client.knowledge import update_space
+from dbgpt.client.schema import SpaceModel
+
+DBGPT_API_KEY = "dbgpt"
+
+client = Client(api_key=DBGPT_API_KEY)
+res = await update_space(client, SpaceModel(
+ name="test_space",
+ vector_type="Chroma",
+ desc="for client space update",
+ owner="dbgpt"
+))
+
+```
+
+
+
+
+#### Request body
+
+________
+id string Required
+
+knowledge space id
+________
+name string Required
+
+knowledge space name
+________
+vector_type string Optional
+
+vector db type, `Chroma`, `Milvus`, default is `Chroma`
+________
+desc string Optional
+
+description of the knowledge space
+________
+owner integer Optional
+
+The owner of the knowledge space
+________
+context integer Optional
+
+The context of the knowledge space argument
+________
+
+#### Response body
+Return Space Object
+
+### Delete Knowledge Space
+
+```python
+DELETE /api/v2/serve/knowledge/spaces
+```
+
+
+
+
+
+
+```shell
+ DBGPT_API_KEY=dbgpt
+ SPACE_ID={YOUR_SPACE_ID}
+
+ curl -X DELETE "http://localhost:5000/api/v2/serve/knowledge/spaces/$SPACE_ID" \
+ -H "Authorization: Bearer $DBGPT_API_KEY" \
+ -H "accept: application/json" \
+ -H "Content-Type: application/json" \
+```
+
+
+
+
+
+```python
+from dbgpt.client import Client
+from dbgpt.client.knowledge import delete_space
+
+DBGPT_API_KEY = "dbgpt"
+space_id = "{your_space_id}"
+
+client = Client(api_key=DBGPT_API_KEY)
+res = await delete_space(client=client, space_id=space_id)
+
+```
+
+
+
+
+#### Delete Parameters
+________
+id string Required
+
+knowledge space id
+________
+
+#### Response body
+Return Space Object
+
+### Get Knowledge Space
+
+```python
+GET /api/v2/serve/knowledge/spaces/{space_id}
+```
+
+
+
+
+
+```shell
+DBGPT_API_KEY=dbgpt
+SPACE_ID={YOUR_SPACE_ID}
+curl -X GET "http://localhost:5000/api/v2/serve/knowledge/spaces/$SPACE_ID" -H "Authorization: Bearer $DBGPT_API_KEY"
+```
+
+
+
+
+
+```python
+from dbgpt.client import Client
+from dbgpt.client.knowledge import get_space
+
+DBGPT_API_KEY = "dbgpt"
+space_id = "{your_space_id}"
+
+client = Client(api_key=DBGPT_API_KEY)
+res = await get_space(client=client, space_id=space_id)
+
+```
+
+
+
+
+#### Query Parameters
+________
+id string Required
+
+knowledge space id
+________
+
+#### Response body
+Return Space Object
+
+### List Knowledge Space
+
+```python
+GET /api/v2/serve/knowledge/spaces
+```
+
+
+
+
+
+```shell
+ DBGPT_API_KEY=dbgpt
+
+curl -X GET 'http://localhost:5000/api/v2/serve/knowledge/spaces' -H "Authorization: Bearer $DBGPT_API_KEY"
+```
+
+
+
+
+
+```python
+from dbgpt.client import Client
+from dbgpt.client.knowledge import list_space
+
+DBGPT_API_KEY = "dbgpt"
+space_id = "{your_space_id}"
+
+client = Client(api_key=DBGPT_API_KEY)
+res = await list_space(client=client)
+
+```
+
+
+
+
+#### Response body
+Return Space Object List
+
+### The Space Object
+
+________
+id string
+
+space id
+________
+name string
+
+knowledge space name
+________
+vector_type string
+
+vector db type, `Chroma`, `Milvus`, default is `Chroma`
+________
+desc string Optional
+
+description of the knowledge space
+________
+owner integer Optional
+
+The owner of the knowledge space
+________
+context integer Optional
+
+The context of the knowledge space argument
+________
\ No newline at end of file
diff --git a/docs/sidebars.js b/docs/sidebars.js
index ecb2dbec6..4f76fdcc8 100755
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -356,6 +356,39 @@ const sidebars = {
},
},
+ {
+ type: "category",
+ label: "API Reference",
+ collapsed: false,
+ collapsible: false,
+ items: [
+ {
+ type: 'doc',
+ id: 'api/introduction'
+ },
+ {
+ type: 'doc',
+ id: 'api/chat'
+ },
+ {
+ type: 'doc',
+ id: 'api/app'
+ },
+ {
+ type: 'doc',
+ id: 'api/flow'
+ },
+ {
+ type: 'doc',
+ id: 'api/knowledge'
+ },
+ ],
+ link: {
+ type: 'generated-index',
+ slug: "api",
+ },
+ },
+
{
type: "category",
label: "Modules",
diff --git a/examples/client/app_crud_example.py b/examples/client/app_crud_example.py
new file mode 100644
index 000000000..000607841
--- /dev/null
+++ b/examples/client/app_crud_example.py
@@ -0,0 +1,29 @@
+"""Client: Simple App CRUD example.
+
+This example demonstrates how to use the dbgpt client to get, list apps.
+Example:
+ .. code-block:: python
+
+ DBGPT_API_KEY = "dbgpt"
+ client = Client(api_key=DBGPT_API_KEY)
+ # 1. List all apps
+ res = await list_app(client)
+ # 2. Get an app
+ res = await get_app(client, app_id="bf1c7561-13fc-4fe0-bf5d-c22e724766a8")
+"""
+import asyncio
+
+from dbgpt.client import Client
+from dbgpt.client.app import list_app
+
+
+async def main():
+ # initialize client
+ DBGPT_API_KEY = "dbgpt"
+ client = Client(api_key=DBGPT_API_KEY)
+ res = await list_app(client)
+ print(res)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/examples/client/client_chat_example.py b/examples/client/client_chat_example.py
new file mode 100644
index 000000000..266ef1bc7
--- /dev/null
+++ b/examples/client/client_chat_example.py
@@ -0,0 +1,72 @@
+"""Client: Simple Chat example.
+
+This example demonstrates how to use the dbgpt client to chat with the chatgpt model.
+
+Example:
+ .. code-block:: python
+
+ DBGPT_API_KEY = "dbgpt"
+ # chat with stream
+ client = Client(api_key=DBGPT_API_KEY)
+
+ # 1. chat normal
+ async for data in client.chat_stream(
+ model="chatgpt_proxyllm",
+ messages="hello",
+ ):
+ print(data.dict())
+
+ # chat with no stream
+ res = await client.chat(model="chatgpt_proxyllm", messages="Hello?")
+ print(res.json())
+
+ # 2. chat with app
+ async for data in client.chat_stream(
+ model="chatgpt_proxyllm",
+ chat_mode="chat_app",
+ chat_param="${app_code}",
+ messages="hello",
+ ):
+ print(data.dict())
+
+ # 3. chat with knowledge
+ async for data in client.chat_stream(
+ model="chatgpt_proxyllm",
+ chat_mode="chat_knowledge",
+ chat_param="${space_name}",
+ messages="hello",
+ ):
+ print(data.dict())
+
+ # 4. chat with flow
+ async for data in client.chat_stream(
+ model="chatgpt_proxyllm",
+ chat_mode="chat_flow",
+ chat_param="${flow_id}",
+ messages="hello",
+ ):
+ print(data.dict())
+"""
+
+import asyncio
+
+from dbgpt.client import Client
+
+
+async def main():
+ # initialize client
+ DBGPT_API_KEY = "dbgpt"
+ client = Client(api_key=DBGPT_API_KEY)
+
+ async for data in client.chat_stream(
+ model="chatgpt_proxyllm",
+ messages="hello",
+ ):
+ print(data)
+
+ # res = await client.chat(model="chatgpt_proxyllm" ,messages="hello")
+ # print(res)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/examples/client/flow_crud_example.py b/examples/client/flow_crud_example.py
new file mode 100644
index 000000000..7b83a00a0
--- /dev/null
+++ b/examples/client/flow_crud_example.py
@@ -0,0 +1,44 @@
+"""Client: Simple Flow CRUD example
+
+This example demonstrates how to use the dbgpt client to create, get, update, and
+delete flows.
+
+Example:
+ .. code-block:: python
+
+ DBGPT_API_KEY = "dbgpt"
+ client = Client(api_key=DBGPT_API_KEY)
+ # 1. Create a flow
+ res = await create_flow(
+ client,
+ FlowPanel(name="test_flow", desc="for client flow", owner="dbgpt"),
+ )
+ # 2. Update a flow
+ res = await update_flow(
+ client,
+ FlowPanel(name="test_flow", desc="for client flow333", owner="dbgpt"),
+ )
+ # 3. Delete a flow
+ res = await delete_flow(client, flow_id="bf1c7561-13fc-4fe0-bf5d-c22e724766a8")
+ # 4. Get a flow
+ res = await get_flow(client, flow_id="bf1c7561-13fc-4fe0-bf5d-c22e724766a8")
+ # 5. List all flows
+ res = await list_flow(client)
+
+"""
+import asyncio
+
+from dbgpt.client import Client
+from dbgpt.client.flow import list_flow
+
+
+async def main():
+ # initialize client
+ DBGPT_API_KEY = "dbgpt"
+ client = Client(api_key=DBGPT_API_KEY)
+ res = await list_flow(client)
+ print(res)
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/examples/client/knowledge_crud_example.py b/examples/client/knowledge_crud_example.py
new file mode 100644
index 000000000..879984255
--- /dev/null
+++ b/examples/client/knowledge_crud_example.py
@@ -0,0 +1,123 @@
+"""Client: Simple Knowledge CRUD example.
+
+This example demonstrates how to use the dbgpt client to create, get, update, and
+delete knowledge spaces and documents.
+
+Example:
+ .. code-block:: python
+
+ DBGPT_API_KEY = "dbgpt"
+ client = Client(api_key=DBGPT_API_KEY)
+ # 1. Create a space
+ res = await create_space(
+ client,
+ SpaceModel(
+ name="test_space",
+ vector_type="Chroma",
+ desc="for client space",
+ owner="dbgpt",
+ ),
+ )
+ # 2. Update a space
+ res = await update_space(
+ client,
+ SpaceModel(
+ name="test_space",
+ vector_type="Chroma",
+ desc="for client space333",
+ owner="dbgpt",
+ ),
+ )
+ # 3. Delete a space
+ res = await delete_space(client, space_id="37")
+ # 4. Get a space
+ res = await get_space(client, space_id="5")
+ # 5. List all spaces
+ res = await list_space(client)
+ # 6. Create a document
+ res = await create_document(
+ client,
+ DocumentModel(
+ space_id="5",
+ doc_name="test_doc",
+ doc_type="TEXT",
+ doc_content="test content",
+ doc_source="",
+ ),
+ )
+ # 7. Sync a document
+ res = await sync_document(
+ client,
+ sync_model=SyncModel(
+ doc_id="153",
+ space_id="40",
+ model_name="text2vec",
+ chunk_parameters=ChunkParameters(chunk_strategy="Automatic"),
+ ),
+ )
+ # 8. Get a document
+ res = await get_document(client, "52")
+ # 9. List all documents
+ res = await list_document(client)
+ # 10. Delete a document
+ res = await delete_document(client, "150")
+"""
+import asyncio
+
+from dbgpt.client import Client
+from dbgpt.client.knowledge import create_space
+from dbgpt.client.schema import SpaceModel
+
+
+async def main():
+ # initialize client
+ DBGPT_API_KEY = "dbgpt"
+ client = Client(api_key=DBGPT_API_KEY)
+
+ res = await create_space(
+ client,
+ SpaceModel(
+ name="test_space_1",
+ vector_type="Chroma",
+ desc="for client space desc",
+ owner="dbgpt",
+ ),
+ )
+ print(res)
+
+ # list all spaces
+ # res = await list_space(client)
+ # print(res)
+
+ # get space
+ # res = await get_space(client, space_id='5')
+
+ # create space
+ # res = await create_space(client, SpaceModel(name="test_space", vector_type="Chroma", desc="for client space", owner="dbgpt"))
+
+ # update space
+ # res = await update_space(client, SpaceModel(name="test_space", vector_type="Chroma", desc="for client space333", owner="dbgpt"))
+
+ # delete space
+ # res = await delete_space(client, space_id='31')
+ # print(res)
+
+ # list all documents
+ # res = await list_document(client)
+
+ # get document
+ # res = await get_document(client, "52")
+
+ # delete document
+ # res = await delete_document(client, "150")
+
+ # create document
+ # res = await create_document(client, DocumentModel(space_id="5", doc_name="test_doc", doc_type="test", doc_content="test content"
+ # , doc_file=('your_file_name', open('{your_file_path}', 'rb'))))
+
+ # sync document
+ # res = await sync_document(client, sync_model=SyncModel(doc_id="157", space_id="49", model_name="text2vec", chunk_parameters=ChunkParameters(chunk_strategy="Automatic")))
+
+
+if __name__ == "__main__":
+ asyncio.run(main())