From 0ed30aa44ae043b20b791055e418eae5e1aa6d0a Mon Sep 17 00:00:00 2001 From: Fangyin Cheng Date: Mon, 18 Mar 2024 18:24:08 +0800 Subject: [PATCH 01/12] feat: Add dbgpt client and add api v2 --- .env.template | 8 +- dbgpt/_private/config.py | 2 + dbgpt/app/dbgpt_server.py | 4 + .../initialization/db_model_initialization.py | 2 +- .../initialization/serve_initialization.py | 10 + dbgpt/app/knowledge/api.py | 26 +- dbgpt/app/knowledge/document_db.py | 71 ++- dbgpt/app/knowledge/request/request.py | 29 +- dbgpt/app/knowledge/service.py | 21 +- dbgpt/app/knowledge/space_db.py | 186 +++---- dbgpt/app/openapi/api_v2.py | 345 ++++++++++++ dbgpt/client/__init__.py | 0 dbgpt/client/app.py | 18 + dbgpt/client/client.py | 349 ++++++++++++ dbgpt/client/flow.py | 49 ++ dbgpt/client/knowledge.py | 93 ++++ dbgpt/client/schemas.py | 206 +++++++ dbgpt/client/tests/__init__.py | 0 dbgpt/serve/agent/app/endpoints.py | 65 +++ dbgpt/serve/conversation/api/endpoints.py | 6 +- dbgpt/serve/core/config.py | 11 +- dbgpt/serve/core/schemas.py | 6 +- dbgpt/serve/core/serve.py | 2 +- dbgpt/serve/flow/api/endpoints.py | 9 +- dbgpt/serve/flow/serve.py | 11 +- dbgpt/serve/prompt/api/endpoints.py | 6 +- dbgpt/serve/rag/api/endpoints.py | 300 ++++++++++ dbgpt/serve/rag/api/schemas.py | 93 ++++ dbgpt/serve/rag/config.py | 28 + dbgpt/serve/rag/dependencies.py | 1 + dbgpt/serve/rag/serve.py | 62 +++ dbgpt/serve/rag/service/__init__.py | 0 dbgpt/serve/rag/service/service.py | 522 ++++++++++++++++++ dbgpt/serve/rag/tests/__init__.py | 0 examples/client/__init__.py | 0 examples/client/app_crud_example.py | 35 ++ examples/client/client_chat_example.py | 73 +++ examples/client/flow_crud_example.py | 48 ++ examples/client/knowledge_crud_example.py | 109 ++++ 39 files changed, 2663 insertions(+), 143 deletions(-) create mode 100644 dbgpt/app/openapi/api_v2.py create mode 100644 dbgpt/client/__init__.py create mode 100644 dbgpt/client/app.py create mode 100644 dbgpt/client/client.py create mode 100644 dbgpt/client/flow.py create mode 100644 dbgpt/client/knowledge.py create mode 100644 dbgpt/client/schemas.py create mode 100644 dbgpt/client/tests/__init__.py create mode 100644 dbgpt/serve/agent/app/endpoints.py create mode 100644 dbgpt/serve/rag/api/endpoints.py create mode 100644 dbgpt/serve/rag/api/schemas.py create mode 100644 dbgpt/serve/rag/config.py create mode 100644 dbgpt/serve/rag/dependencies.py create mode 100644 dbgpt/serve/rag/serve.py create mode 100644 dbgpt/serve/rag/service/__init__.py create mode 100644 dbgpt/serve/rag/service/service.py create mode 100644 dbgpt/serve/rag/tests/__init__.py create mode 100644 examples/client/__init__.py create mode 100644 examples/client/app_crud_example.py create mode 100644 examples/client/client_chat_example.py create mode 100644 examples/client/flow_crud_example.py create mode 100644 examples/client/knowledge_crud_example.py diff --git a/.env.template b/.env.template index 0b660b575..1f653f231 100644 --- a/.env.template +++ b/.env.template @@ -235,4 +235,10 @@ 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=dbgpt \ No newline at end of file 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..f15d3e7f2 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,18 +79,18 @@ 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 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): 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/knowledge/space_db.py b/dbgpt/app/knowledge/space_db.py index bc283db1d..933a2b57b 100644 --- a/dbgpt/app/knowledge/space_db.py +++ b/dbgpt/app/knowledge/space_db.py @@ -1,93 +1,93 @@ -from datetime import datetime - -from sqlalchemy import Column, DateTime, Integer, String, Text - -from dbgpt._private.config import Config -from dbgpt.app.knowledge.request.request import KnowledgeSpaceRequest -from dbgpt.storage.metadata import BaseDao, Model - -CFG = Config() - - -class KnowledgeSpaceEntity(Model): - __tablename__ = "knowledge_space" - id = Column(Integer, primary_key=True) - name = Column(String(100)) - vector_type = Column(String(100)) - desc = Column(String(100)) - owner = Column(String(100)) - context = Column(Text) - gmt_created = Column(DateTime) - gmt_modified = Column(DateTime) - - def __repr__(self): - return f"KnowledgeSpaceEntity(id={self.id}, name='{self.name}', vector_type='{self.vector_type}', desc='{self.desc}', owner='{self.owner}' context='{self.context}', gmt_created='{self.gmt_created}', gmt_modified='{self.gmt_modified}')" - - -class KnowledgeSpaceDao(BaseDao): - def create_knowledge_space(self, space: KnowledgeSpaceRequest): - session = self.get_raw_session() - knowledge_space = KnowledgeSpaceEntity( - name=space.name, - vector_type=CFG.VECTOR_STORE_TYPE, - desc=space.desc, - owner=space.owner, - gmt_created=datetime.now(), - gmt_modified=datetime.now(), - ) - session.add(knowledge_space) - session.commit() - session.close() - - def get_knowledge_space(self, query: KnowledgeSpaceEntity): - session = self.get_raw_session() - knowledge_spaces = session.query(KnowledgeSpaceEntity) - if query.id is not None: - knowledge_spaces = knowledge_spaces.filter( - KnowledgeSpaceEntity.id == query.id - ) - if query.name is not None: - knowledge_spaces = knowledge_spaces.filter( - KnowledgeSpaceEntity.name == query.name - ) - if query.vector_type is not None: - knowledge_spaces = knowledge_spaces.filter( - KnowledgeSpaceEntity.vector_type == query.vector_type - ) - if query.desc is not None: - knowledge_spaces = knowledge_spaces.filter( - KnowledgeSpaceEntity.desc == query.desc - ) - if query.owner is not None: - knowledge_spaces = knowledge_spaces.filter( - KnowledgeSpaceEntity.owner == query.owner - ) - if query.gmt_created is not None: - knowledge_spaces = knowledge_spaces.filter( - KnowledgeSpaceEntity.gmt_created == query.gmt_created - ) - if query.gmt_modified is not None: - knowledge_spaces = knowledge_spaces.filter( - KnowledgeSpaceEntity.gmt_modified == query.gmt_modified - ) - - knowledge_spaces = knowledge_spaces.order_by( - KnowledgeSpaceEntity.gmt_created.desc() - ) - result = knowledge_spaces.all() - session.close() - return result - - def update_knowledge_space(self, space: KnowledgeSpaceEntity): - session = self.get_raw_session() - session.merge(space) - session.commit() - session.close() - return True - - def delete_knowledge_space(self, space: KnowledgeSpaceEntity): - session = self.get_raw_session() - if space: - session.delete(space) - session.commit() - session.close() +# from datetime import datetime +# +# from sqlalchemy import Column, DateTime, Integer, String, Text +# +# from dbgpt._private.config import Config +# from dbgpt.app.knowledge.request.request import KnowledgeSpaceRequest +# from dbgpt.storage.metadata import BaseDao, Model +# +# CFG = Config() +# +# +# class KnowledgeSpaceEntity(Model): +# __tablename__ = "knowledge_space" +# id = Column(Integer, primary_key=True) +# name = Column(String(100)) +# vector_type = Column(String(100)) +# desc = Column(String(100)) +# owner = Column(String(100)) +# context = Column(Text) +# gmt_created = Column(DateTime) +# gmt_modified = Column(DateTime) +# +# def __repr__(self): +# return f"KnowledgeSpaceEntity(id={self.id}, name='{self.name}', vector_type='{self.vector_type}', desc='{self.desc}', owner='{self.owner}' context='{self.context}', gmt_created='{self.gmt_created}', gmt_modified='{self.gmt_modified}')" +# +# +# class KnowledgeSpaceDao(BaseDao): +# def create_knowledge_space(self, space: KnowledgeSpaceRequest): +# session = self.get_raw_session() +# knowledge_space = KnowledgeSpaceEntity( +# name=space.name, +# vector_type=CFG.VECTOR_STORE_TYPE, +# desc=space.desc, +# owner=space.owner, +# gmt_created=datetime.now(), +# gmt_modified=datetime.now(), +# ) +# session.add(knowledge_space) +# session.commit() +# session.close() +# +# def get_knowledge_space(self, query: KnowledgeSpaceEntity): +# session = self.get_raw_session() +# knowledge_spaces = session.query(KnowledgeSpaceEntity) +# if query.id is not None: +# knowledge_spaces = knowledge_spaces.filter( +# KnowledgeSpaceEntity.id == query.id +# ) +# if query.name is not None: +# knowledge_spaces = knowledge_spaces.filter( +# KnowledgeSpaceEntity.name == query.name +# ) +# if query.vector_type is not None: +# knowledge_spaces = knowledge_spaces.filter( +# KnowledgeSpaceEntity.vector_type == query.vector_type +# ) +# if query.desc is not None: +# knowledge_spaces = knowledge_spaces.filter( +# KnowledgeSpaceEntity.desc == query.desc +# ) +# if query.owner is not None: +# knowledge_spaces = knowledge_spaces.filter( +# KnowledgeSpaceEntity.owner == query.owner +# ) +# if query.gmt_created is not None: +# knowledge_spaces = knowledge_spaces.filter( +# KnowledgeSpaceEntity.gmt_created == query.gmt_created +# ) +# if query.gmt_modified is not None: +# knowledge_spaces = knowledge_spaces.filter( +# KnowledgeSpaceEntity.gmt_modified == query.gmt_modified +# ) +# +# knowledge_spaces = knowledge_spaces.order_by( +# KnowledgeSpaceEntity.gmt_created.desc() +# ) +# result = knowledge_spaces.all() +# session.close() +# return result +# +# def update_knowledge_space(self, space: KnowledgeSpaceEntity): +# session = self.get_raw_session() +# session.merge(space) +# session.commit() +# session.close() +# return True +# +# def delete_knowledge_space(self, space: KnowledgeSpaceEntity): +# session = self.get_raw_session() +# if space: +# session.delete(space) +# session.commit() +# session.close() diff --git a/dbgpt/app/openapi/api_v2.py b/dbgpt/app/openapi/api_v2.py new file mode 100644 index 000000000..794f2c62e --- /dev/null +++ b/dbgpt/app/openapi/api_v2.py @@ -0,0 +1,345 @@ +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 fastchat.protocol.api_protocol import ( + ChatCompletionResponse, + ChatCompletionResponseChoice, + ChatCompletionResponseStreamChoice, + ChatCompletionStreamResponse, + ChatMessage, + DeltaMessage, + UsageInfo, +) +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.schemas import ChatCompletionRequestBody +from dbgpt.component import logger +from dbgpt.core.awel import CommonLLMHttpRequestBody, CommonLLMHTTPRequestContext +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(), + flow_service: FlowService = Depends(get_chat_flow), +): + """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 == "chat_app": + 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 == ChatScene.ChatFlow.value(): + # 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, + # ) + return StreamingResponse( + chat_flow_stream_wrapper(request), + headers=headers, + media_type="text/event-stream", + ) + elif ( + request.chat_mode is None + or request.chat_mode == ChatScene.ChatKnowledge.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/client/__init__.py b/dbgpt/client/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/dbgpt/client/app.py b/dbgpt/client/app.py new file mode 100644 index 000000000..8dca987b3 --- /dev/null +++ b/dbgpt/client/app.py @@ -0,0 +1,18 @@ +from dbgpt.client.client import Client + + +async def get_app(client: Client, app_id: str): + """Get an app. + Args: + client (Client): The dbgpt client. + app_id (str): The app id. + """ + return await client.get("/apps/" + app_id) + + +async def list_app(client: Client): + """List apps. + Args: + client (Client): The dbgpt client. + """ + return await client.get("/apps") diff --git a/dbgpt/client/client.py b/dbgpt/client/client.py new file mode 100644 index 000000000..f8c0bbfab --- /dev/null +++ b/dbgpt/client/client.py @@ -0,0 +1,349 @@ +import json +from typing import Any, AsyncGenerator, List, Optional, Union +from urllib.parse import urlparse + +import httpx +from fastchat.protocol.api_protocol import ChatCompletionResponse + +from dbgpt.app.openapi.api_view_model import ChatCompletionStreamResponse +from dbgpt.client.schemas 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): + """ + 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. + """ + reason = json.loads(reason) + if http_resp: + self.status = http_resp.status_code + self.reason = http_resp.content + self.body = http_resp.content + self.headers = None + else: + self.status = status + self.reason = reason + self.body = None + self.headers = None + + def __str__(self): + """Custom error messages for exception""" + 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 + + +class Client(object): + def __init__( + self, + api_base: Optional[str] = "http://localhost:5000", + api_key: Optional[str] = None, + version: Optional[str] = "v2", + timeout: Optional[httpx._types.TimeoutTypes] = 120, + ): + """ + Args: + api_base: Optional[str], a full URL for the DB-GPT API. Defaults to the http://localhost:5000. + 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.client import Client + + DBGPT_API_BASE = "http://localhost:5000" + DBGPT_API_KEY = "dbgpt" + client = Client(api_base=DBGPT_API_BASE, api_key=DBGPT_API_KEY) + client.chat(model="chatgpt_proxyllm", messages="Hello?") + """ + if is_valid_url(api_base): + self._api_url = api_base.rstrip("/") + else: + raise ValueError(f"api url {api_base} does not exist or is not accessible.") + self._api_key = api_key + self._version = version + self._api_url = api_base + CLIENT_API_PATH + "/" + 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.client import Client + + DBGPT_API_BASE = "http://localhost:5000" + 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.client import Client + + DBGPT_API_BASE = "http://localhost:5000" + 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 == "data: [DONE]\n": + 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: + yield f"data:[SERVER_ERROR]{str(e)}\n\n" + + else: + try: + error = await response.aread() + yield json.loads(error) + except Exception as e: + yield f"data:[SERVER_ERROR]{str(e)}\n\n" + + 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( + 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( + 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( + 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(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( + 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( + 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..7f0cd34d3 --- /dev/null +++ b/dbgpt/client/flow.py @@ -0,0 +1,49 @@ +from dbgpt.client.client import Client +from dbgpt.core.awel.flow.flow_factory import FlowPanel + + +async def create_flow(client: Client, flow: FlowPanel): + """Create a new flow. + Args: + client (Client): The dbgpt client. + flow (FlowPanel): The flow panel. + """ + return await client.get("/awel/flows", flow.dict()) + + +async def update_flow(client: Client, flow: FlowPanel): + """Update a flow. + Args: + client (Client): The dbgpt client. + flow (FlowPanel): The flow panel. + """ + return await client.put("/awel/flows", flow.dict()) + + +async def delete_flow(client: Client, flow_id: str): + """ + Delete a flow. + Args: + client (Client): The dbgpt client. + flow_id (str): The flow id. + """ + return await client.get("/awel/flows/" + flow_id) + + +async def get_flow(client: Client, flow_id: str): + """ + Get a flow. + Args: + client (Client): The dbgpt client. + flow_id (str): The flow id. + """ + return await client.get("/awel/flows/" + flow_id) + + +async def list_flow(client: Client): + """ + List flows. + Args: + client (Client): The dbgpt client. + """ + return await client.get("/awel/flows") diff --git a/dbgpt/client/knowledge.py b/dbgpt/client/knowledge.py new file mode 100644 index 000000000..7adf40192 --- /dev/null +++ b/dbgpt/client/knowledge.py @@ -0,0 +1,93 @@ +import json + +from dbgpt.client.client import Client +from dbgpt.client.schemas import DocumentModel, SpaceModel, SyncModel + + +async def create_space(client: Client, app_model: SpaceModel): + """Create a new space. + Args: + client (Client): The dbgpt client. + app_model (SpaceModel): The app model. + """ + return await client.post("/knowledge/spaces", app_model.dict()) + + +async def update_space(client: Client, app_model: SpaceModel): + """Update a document. + Args: + client (Client): The dbgpt client. + app_model (SpaceModel): The app model. + """ + return await client.put("/knowledge/spaces", app_model.dict()) + + +async def delete_space(client: Client, space_id: str): + """Delete a space. + Args: + client (Client): The dbgpt client. + app_id (str): The app id. + """ + return await client.delete("/knowledge/spaces/" + space_id) + + +async def get_space(client: Client, space_id: str): + """Get a document. + Args: + client (Client): The dbgpt client. + app_id (str): The app id. + """ + return await client.get("/knowledge/spaces/" + space_id) + + +async def list_space(client: Client): + """List apps. + Args: + client (Client): The dbgpt client. + """ + return await client.get("/knowledge/spaces") + + +async def create_document(client: Client, doc_model: DocumentModel): + """Create a new space. + Args: + client (Client): The dbgpt client. + doc_model (SpaceModel): The document model. + """ + return await client.post_param("/knowledge/documents", doc_model.dict()) + + +async def delete_document(client: Client, document_id: str): + """Delete a document. + Args: + client (Client): The dbgpt client. + app_id (str): The app id. + """ + return await client.delete("/knowledge/documents/" + document_id) + + +async def get_document(client: Client, document_id: str): + """Get a document. + Args: + client (Client): The dbgpt client. + app_id (str): The app id. + """ + return await client.get("/knowledge/documents/" + document_id) + + +async def list_document(client: Client): + """List documents. + Args: + client (Client): The dbgpt client. + """ + return await client.get("/knowledge/documents") + + +async def sync_document(client: Client, sync_model: SyncModel): + """sync document. + Args: + client (Client): The dbgpt client. + """ + return await client.post( + "/knowledge/documents/sync", [json.loads(sync_model.json())] + ) diff --git a/dbgpt/client/schemas.py b/dbgpt/client/schemas.py new file mode 100644 index 000000000..1b97c5d1f --- /dev/null +++ b/dbgpt/client/schemas.py @@ -0,0 +1,206 @@ +from datetime import datetime +from typing import Dict, List, Optional, Union + +from fastapi import File, UploadFile +from pydantic import BaseModel, Field + +from dbgpt.agent.resource.resource_api import AgentResource +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=False, 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 SpaceModel(BaseModel): + """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") + """owner: owner""" + owner: str = Field(None, description="The owner") + + +class AppDetailModel(BaseModel): + app_code: Optional[str] = Field(None, title="app code") + app_name: Optional[str] = Field(None, title="app name") + agent_name: Optional[str] = Field(None, title="agent name") + node_id: Optional[str] = Field(None, title="node id") + resources: Optional[list[AgentResource]] = Field(None, title="resources") + prompt_template: Optional[str] = Field(None, title="prompt template") + llm_strategy: Optional[str] = Field(None, title="llm strategy") + llm_strategy_value: Optional[str] = Field(None, title="llm strategy value") + created_at: datetime = datetime.now() + updated_at: datetime = datetime.now() + + +class AwelTeamModel(BaseModel): + 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 AppModel(BaseModel): + 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): + 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", + ) + + +class DocumentModel(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 = 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/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..5add79f30 100644 --- a/dbgpt/serve/core/schemas.py +++ b/dbgpt/serve/core/schemas.py @@ -69,11 +69,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..2b2d8b723 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: 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/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..0ffb986d6 --- /dev/null +++ b/dbgpt/serve/rag/api/schemas.py @@ -0,0 +1,93 @@ +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(None, description="The vector type") + """desc: description""" + desc: str = Field(None, description="The description") + """owner: owner""" + owner: str = Field(None, description="The owner") + + +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/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..9959a0e83 --- /dev/null +++ b/dbgpt/serve/rag/service/service.py @@ -0,0 +1,522 @@ +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.awel.dag.dag_manager import DAGManager +from dbgpt.rag.chunk import Chunk +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}", + ) + space = spaces[0] + query_request = {"id": space.id} + update_obj = self._dao.update(query_request, update_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/examples/client/__init__.py b/examples/client/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/client/app_crud_example.py b/examples/client/app_crud_example.py new file mode 100644 index 000000000..8687c8ee0 --- /dev/null +++ b/examples/client/app_crud_example.py @@ -0,0 +1,35 @@ +import asyncio + +from dbgpt.client.app import list_app +from dbgpt.client.client import Client + +""" +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" + ) + +""" + + +async def main(): + # initialize client + + DBGPT_API_KEY = "dbgpt" + client = Client(api_key=DBGPT_API_KEY) + res = await list_app(client) + print(res.json()) + + +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..5ae7009f8 --- /dev/null +++ b/examples/client/client_chat_example.py @@ -0,0 +1,73 @@ +import asyncio + +from dbgpt.client.client import Client + +""" +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()) +""" + + +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..8fee72196 --- /dev/null +++ b/examples/client/flow_crud_example.py @@ -0,0 +1,48 @@ +import asyncio + +from dbgpt.client.app import list_app +from dbgpt.client.client import Client +from dbgpt.client.flow import list_flow + +""" +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) + +""" + + +async def main(): + # initialize client + + DBGPT_API_KEY = "dbgpt" + client = Client(api_key=DBGPT_API_KEY) + res = await list_flow(client) + print(res.json()) + + +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..e9ff7ea9f --- /dev/null +++ b/examples/client/knowledge_crud_example.py @@ -0,0 +1,109 @@ +import asyncio + +from dbgpt.client.client import Client +from dbgpt.client.knowledge import list_space + +"""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") +""" + + +async def main(): + + DBGPT_API_KEY = "dbgpt" + client = Client(api_key=DBGPT_API_KEY) + + # 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='37') + + # 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="153", space_id="40", model_name="text2vec", chunk_parameters=ChunkParameters(chunk_strategy="Automatic"))) + + +if __name__ == "__main__": + asyncio.run(main()) From 4413ff682f757871d9234251a05f756d54c3b5a3 Mon Sep 17 00:00:00 2001 From: aries_ckt <916701291@qq.com> Date: Mon, 18 Mar 2024 19:37:06 +0800 Subject: [PATCH 02/12] fix:rag serve error --- .env.template | 1 + dbgpt/app/openapi/api_v2.py | 21 +--- dbgpt/client/schemas.py | 10 ++ dbgpt/serve/rag/models/__init__.py | 0 dbgpt/serve/rag/models/models.py | 147 ++++++++++++++++++++++ dbgpt/serve/rag/service/service.py | 2 +- examples/client/app_crud_example.py | 2 +- examples/client/client_chat_example.py | 1 + examples/client/flow_crud_example.py | 2 +- examples/client/knowledge_crud_example.py | 3 +- 10 files changed, 168 insertions(+), 21 deletions(-) create mode 100644 dbgpt/serve/rag/models/__init__.py create mode 100644 dbgpt/serve/rag/models/models.py diff --git a/.env.template b/.env.template index 1f653f231..fc5911ce4 100644 --- a/.env.template +++ b/.env.template @@ -241,4 +241,5 @@ DBGPT_LOG_LEVEL=INFO #*******************************************************************# #** 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/dbgpt/app/openapi/api_v2.py b/dbgpt/app/openapi/api_v2.py index 794f2c62e..8141aba2b 100644 --- a/dbgpt/app/openapi/api_v2.py +++ b/dbgpt/app/openapi/api_v2.py @@ -26,7 +26,7 @@ from dbgpt.app.openapi.api_v1.api_v1 import ( stream_generator, ) from dbgpt.app.scene import BaseChat, ChatScene -from dbgpt.client.schemas import ChatCompletionRequestBody +from dbgpt.client.schemas import ChatCompletionRequestBody, ChatMode from dbgpt.component import logger from dbgpt.core.awel import CommonLLMHttpRequestBody, CommonLLMHTTPRequestContext from dbgpt.model.cluster.apiserver.api import APISettings @@ -94,7 +94,7 @@ async def chat_completions( check_chat_request(request) if request.conv_uid is None: request.conv_uid = str(uuid.uuid4()) - if request.chat_mode == "chat_app": + if request.chat_mode == ChatMode.CHAT_APP.value: if request.stream is False: raise HTTPException( status_code=400, @@ -114,27 +114,14 @@ async def chat_completions( headers=headers, media_type="text/event-stream", ) - elif request.chat_mode == ChatScene.ChatFlow.value(): - # 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, - # ) + 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 == ChatScene.ChatKnowledge.value() + request.chat_mode is None or request.chat_mode == ChatMode.CHAT_KNOWLEDGE.value ): with root_tracer.start_span( "get_chat_instance", span_type=SpanType.CHAT, metadata=request.dict() diff --git a/dbgpt/client/schemas.py b/dbgpt/client/schemas.py index 1b97c5d1f..82765a495 100644 --- a/dbgpt/client/schemas.py +++ b/dbgpt/client/schemas.py @@ -1,4 +1,5 @@ from datetime import datetime +from enum import Enum from typing import Dict, List, Optional, Union from fastapi import File, UploadFile @@ -60,6 +61,15 @@ class ChatCompletionRequestBody(BaseModel): ) +class ChatMode(Enum): + """Chat mode""" + + CHAT_NORMAL = "chat_normal" + CHAT_APP = "chat_app" + CHAT_AWEL_FLOW = "chat_flow" + CHAT_KNOWLEDGE = "chat_knowledge" + + class SpaceModel(BaseModel): """name: knowledge space name""" 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/serve/rag/models/models.py b/dbgpt/serve/rag/models/models.py new file mode 100644 index 000000000..809bc3eea --- /dev/null +++ b/dbgpt/serve/rag/models/models.py @@ -0,0 +1,147 @@ +from datetime import datetime +from typing import Any, Dict, List, Union + +from sqlalchemy import Column, DateTime, Integer, String, Text + +from dbgpt.serve.rag.api.schemas import SpaceServeRequest, SpaceServeResponse +from dbgpt.storage.metadata import BaseDao, Model + + +class KnowledgeSpaceEntity(Model): + __tablename__ = "knowledge_space" + id = Column(Integer, primary_key=True) + name = Column(String(100)) + vector_type = Column(String(100)) + desc = Column(String(100)) + owner = Column(String(100)) + context = Column(Text) + gmt_created = Column(DateTime) + gmt_modified = Column(DateTime) + + def __repr__(self): + return f"KnowledgeSpaceEntity(id={self.id}, name='{self.name}', vector_type='{self.vector_type}', desc='{self.desc}', owner='{self.owner}' context='{self.context}', gmt_created='{self.gmt_created}', gmt_modified='{self.gmt_modified}')" + + +class KnowledgeSpaceDao(BaseDao): + def create_knowledge_space(self, space: SpaceServeRequest): + """Create knowledge space""" + session = self.get_raw_session() + knowledge_space = KnowledgeSpaceEntity( + name=space.name, + vector_type=space.vector_type, + desc=space.desc, + owner=space.owner, + gmt_created=datetime.now(), + gmt_modified=datetime.now(), + ) + session.add(knowledge_space) + session.commit() + space_id = knowledge_space.id + session.close() + return space_id + + 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: + knowledge_spaces = knowledge_spaces.filter( + KnowledgeSpaceEntity.id == query.id + ) + if query.name is not None: + knowledge_spaces = knowledge_spaces.filter( + KnowledgeSpaceEntity.name == query.name + ) + if query.vector_type is not None: + knowledge_spaces = knowledge_spaces.filter( + KnowledgeSpaceEntity.vector_type == query.vector_type + ) + if query.desc is not None: + knowledge_spaces = knowledge_spaces.filter( + KnowledgeSpaceEntity.desc == query.desc + ) + if query.owner is not None: + knowledge_spaces = knowledge_spaces.filter( + KnowledgeSpaceEntity.owner == query.owner + ) + if query.gmt_created is not None: + knowledge_spaces = knowledge_spaces.filter( + KnowledgeSpaceEntity.gmt_created == query.gmt_created + ) + if query.gmt_modified is not None: + knowledge_spaces = knowledge_spaces.filter( + KnowledgeSpaceEntity.gmt_modified == query.gmt_modified + ) + knowledge_spaces = knowledge_spaces.order_by( + KnowledgeSpaceEntity.gmt_created.desc() + ) + result = knowledge_spaces.all() + session.close() + return result + + def update_knowledge_space(self, space: KnowledgeSpaceEntity): + """Update knowledge space""" + session = self.get_raw_session() + session.merge(space) + session.commit() + session.close() + return True + + 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, + ) + + 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/service/service.py b/dbgpt/serve/rag/service/service.py index 9959a0e83..3910fcc34 100644 --- a/dbgpt/serve/rag/service/service.py +++ b/dbgpt/serve/rag/service/service.py @@ -21,8 +21,8 @@ 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 import Chunk from dbgpt.rag.chunk_manager import ChunkParameters from dbgpt.rag.embedding import EmbeddingFactory from dbgpt.rag.knowledge import ChunkStrategy, KnowledgeFactory, KnowledgeType diff --git a/examples/client/app_crud_example.py b/examples/client/app_crud_example.py index 8687c8ee0..516f483ae 100644 --- a/examples/client/app_crud_example.py +++ b/examples/client/app_crud_example.py @@ -23,8 +23,8 @@ Client: Simple App CRUD example async def main(): - # initialize client + # initialize client DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) res = await list_app(client) diff --git a/examples/client/client_chat_example.py b/examples/client/client_chat_example.py index 5ae7009f8..425915166 100644 --- a/examples/client/client_chat_example.py +++ b/examples/client/client_chat_example.py @@ -55,6 +55,7 @@ Client: Simple Chat example async def main(): + # initialize client DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) diff --git a/examples/client/flow_crud_example.py b/examples/client/flow_crud_example.py index 8fee72196..f59970b75 100644 --- a/examples/client/flow_crud_example.py +++ b/examples/client/flow_crud_example.py @@ -36,8 +36,8 @@ Client: Simple Flow CRUD example async def main(): - # initialize client + # initialize client DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) res = await list_flow(client) diff --git a/examples/client/knowledge_crud_example.py b/examples/client/knowledge_crud_example.py index e9ff7ea9f..d77c4a36b 100644 --- a/examples/client/knowledge_crud_example.py +++ b/examples/client/knowledge_crud_example.py @@ -69,12 +69,13 @@ from dbgpt.client.knowledge import list_space async def main(): + # initialize client DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) # list all spaces res = await list_space(client) - print(res) + print(res.json()) # get space # res = await get_space(client, space_id='5') From 7bc5c59a89740108da9fe66d69e420c078866f06 Mon Sep 17 00:00:00 2001 From: aries_ckt <916701291@qq.com> Date: Mon, 18 Mar 2024 22:12:25 +0800 Subject: [PATCH 03/12] fix:client mypy error --- .mypy.ini | 6 ++ Makefile | 8 +- dbgpt/client/__init__.py | 1 + dbgpt/client/app.py | 3 + dbgpt/client/client.py | 89 +++++++++++++++-------- dbgpt/client/flow.py | 6 ++ dbgpt/client/knowledge.py | 13 +++- dbgpt/client/schemas.py | 60 ++++++++------- examples/client/app_crud_example.py | 1 - examples/client/client_chat_example.py | 1 - examples/client/flow_crud_example.py | 1 - examples/client/knowledge_crud_example.py | 1 - 12 files changed, 124 insertions(+), 66 deletions(-) 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/dbgpt/client/__init__.py b/dbgpt/client/__init__.py index e69de29bb..be187a027 100644 --- a/dbgpt/client/__init__.py +++ b/dbgpt/client/__init__.py @@ -0,0 +1 @@ +"""This module is the client of the dbgpt package.""" diff --git a/dbgpt/client/app.py b/dbgpt/client/app.py index 8dca987b3..f25589d26 100644 --- a/dbgpt/client/app.py +++ b/dbgpt/client/app.py @@ -1,8 +1,10 @@ +"""App Client API.""" from dbgpt.client.client import Client async def get_app(client: Client, app_id: str): """Get an app. + Args: client (Client): The dbgpt client. app_id (str): The app id. @@ -12,6 +14,7 @@ async def get_app(client: Client, app_id: str): async def list_app(client: Client): """List apps. + Args: client (Client): The dbgpt client. """ diff --git a/dbgpt/client/client.py b/dbgpt/client/client.py index f8c0bbfab..84a20b238 100644 --- a/dbgpt/client/client.py +++ b/dbgpt/client/client.py @@ -1,3 +1,4 @@ +"""This module contains the client for the DB-GPT API.""" import json from typing import Any, AsyncGenerator, List, Optional, Union from urllib.parse import urlparse @@ -16,7 +17,8 @@ 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. @@ -35,7 +37,7 @@ class ClientException(Exception): self.headers = None def __str__(self): - """Custom error messages for exception""" + """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) @@ -46,19 +48,28 @@ class ClientException(Exception): return error_message -class Client(object): +"""Client API.""" + + +class Client: + """The client for the DB-GPT API.""" + def __init__( self, - api_base: Optional[str] = "http://localhost:5000", + api_base: str = "http://localhost:5000", api_key: Optional[str] = None, - version: Optional[str] = "v2", + 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_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. + api_base: Optional[str], a full URL for the DB-GPT API. + Defaults to the `http://localhost:5000`. + 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 @@ -75,7 +86,7 @@ class Client(object): client = Client(api_base=DBGPT_API_BASE, api_key=DBGPT_API_KEY) client.chat(model="chatgpt_proxyllm", messages="Hello?") """ - if is_valid_url(api_base): + if api_base and is_valid_url(api_base): self._api_url = api_base.rstrip("/") else: raise ValueError(f"api url {api_base} does not exist or is not accessible.") @@ -105,18 +116,24 @@ class Client(object): ) -> 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. + 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. + 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. @@ -173,18 +190,24 @@ class Client(object): ) -> 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. + 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. + 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. @@ -240,11 +263,12 @@ class Client(object): error = await response.aread() yield json.loads(error) except Exception as e: + yield f"data:[SERVER_ERROR]{str(e)}\n\n" async def get(self, path: str, *args): - """ - Get method. + """Get method. + Args: path: str, The path to get. args: Any, The arguments to pass to the get method. @@ -256,11 +280,12 @@ class Client(object): ) return response finally: + await self._http_client.aclose() async def post(self, path: str, args): - """ - Post method. + """Post method. + Args: path: str, The path to post. args: Any, The arguments to pass to the post @@ -274,8 +299,8 @@ class Client(object): await self._http_client.aclose() async def post_param(self, path: str, args): - """ - Post method. + """Post method. + Args: path: str, The path to post. args: Any, The arguments to pass to the post @@ -289,8 +314,8 @@ class Client(object): await self._http_client.aclose() async def patch(self, path: str, *args): - """ - Patch method. + """Patch method. + Args: path: str, The path to patch. args: Any, The arguments to pass to the patch. @@ -298,8 +323,8 @@ class Client(object): return self._http_client.patch(self._api_url + CLIENT_SERVE_PATH + path, *args) async def put(self, path: str, args): - """ - Put method. + """Put method. + Args: path: str, The path to put. args: Any, The arguments to pass to the put. @@ -312,8 +337,8 @@ class Client(object): await self._http_client.aclose() async def delete(self, path: str, *args): - """ - Delete method. + """Delete method. + Args: path: str, The path to delete. args: Any, The arguments to pass to the delete. @@ -326,8 +351,8 @@ class Client(object): await self._http_client.aclose() async def head(self, path: str, *args): - """ - Head method. + """Head method. + Args: path: str, The path to head. args: Any, The arguments to pass to the head @@ -336,8 +361,8 @@ class Client(object): def is_valid_url(api_url: Any) -> bool: - """ - Check if the given URL is valid. + """Check if the given URL is valid. + Args: api_url: Any, The URL to check. Returns: diff --git a/dbgpt/client/flow.py b/dbgpt/client/flow.py index 7f0cd34d3..cb58e325d 100644 --- a/dbgpt/client/flow.py +++ b/dbgpt/client/flow.py @@ -1,9 +1,11 @@ +"""this module contains the flow client functions.""" from dbgpt.client.client import Client from dbgpt.core.awel.flow.flow_factory import FlowPanel async def create_flow(client: Client, flow: FlowPanel): """Create a new flow. + Args: client (Client): The dbgpt client. flow (FlowPanel): The flow panel. @@ -13,6 +15,7 @@ async def create_flow(client: Client, flow: FlowPanel): async def update_flow(client: Client, flow: FlowPanel): """Update a flow. + Args: client (Client): The dbgpt client. flow (FlowPanel): The flow panel. @@ -23,6 +26,7 @@ async def update_flow(client: Client, flow: FlowPanel): async def delete_flow(client: Client, flow_id: str): """ Delete a flow. + Args: client (Client): The dbgpt client. flow_id (str): The flow id. @@ -33,6 +37,7 @@ async def delete_flow(client: Client, flow_id: str): async def get_flow(client: Client, flow_id: str): """ Get a flow. + Args: client (Client): The dbgpt client. flow_id (str): The flow id. @@ -43,6 +48,7 @@ async def get_flow(client: Client, flow_id: str): async def list_flow(client: Client): """ List flows. + Args: client (Client): The dbgpt client. """ diff --git a/dbgpt/client/knowledge.py b/dbgpt/client/knowledge.py index 7adf40192..ed5950ceb 100644 --- a/dbgpt/client/knowledge.py +++ b/dbgpt/client/knowledge.py @@ -1,3 +1,4 @@ +"""Knowledge API client.""" import json from dbgpt.client.client import Client @@ -6,6 +7,7 @@ from dbgpt.client.schemas import DocumentModel, SpaceModel, SyncModel async def create_space(client: Client, app_model: SpaceModel): """Create a new space. + Args: client (Client): The dbgpt client. app_model (SpaceModel): The app model. @@ -15,6 +17,7 @@ async def create_space(client: Client, app_model: SpaceModel): async def update_space(client: Client, app_model: SpaceModel): """Update a document. + Args: client (Client): The dbgpt client. app_model (SpaceModel): The app model. @@ -24,6 +27,7 @@ async def update_space(client: Client, app_model: SpaceModel): async def delete_space(client: Client, space_id: str): """Delete a space. + Args: client (Client): The dbgpt client. app_id (str): The app id. @@ -33,6 +37,7 @@ async def delete_space(client: Client, space_id: str): async def get_space(client: Client, space_id: str): """Get a document. + Args: client (Client): The dbgpt client. app_id (str): The app id. @@ -42,6 +47,7 @@ async def get_space(client: Client, space_id: str): async def list_space(client: Client): """List apps. + Args: client (Client): The dbgpt client. """ @@ -50,6 +56,7 @@ async def list_space(client: Client): async def create_document(client: Client, doc_model: DocumentModel): """Create a new space. + Args: client (Client): The dbgpt client. doc_model (SpaceModel): The document model. @@ -59,6 +66,7 @@ async def create_document(client: Client, doc_model: DocumentModel): async def delete_document(client: Client, document_id: str): """Delete a document. + Args: client (Client): The dbgpt client. app_id (str): The app id. @@ -68,6 +76,7 @@ async def delete_document(client: Client, document_id: str): async def get_document(client: Client, document_id: str): """Get a document. + Args: client (Client): The dbgpt client. app_id (str): The app id. @@ -77,6 +86,7 @@ async def get_document(client: Client, document_id: str): async def list_document(client: Client): """List documents. + Args: client (Client): The dbgpt client. """ @@ -84,7 +94,8 @@ async def list_document(client: Client): async def sync_document(client: Client, sync_model: SyncModel): - """sync document. + """Sync document. + Args: client (Client): The dbgpt client. """ diff --git a/dbgpt/client/schemas.py b/dbgpt/client/schemas.py index 82765a495..c2bb15524 100644 --- a/dbgpt/client/schemas.py +++ b/dbgpt/client/schemas.py @@ -1,6 +1,7 @@ +"""this module contains the schemas for the dbgpt client.""" from datetime import datetime from enum import Enum -from typing import Dict, List, Optional, Union +from typing import List, Optional, Union from fastapi import File, UploadFile from pydantic import BaseModel, Field @@ -8,6 +9,8 @@ from pydantic import BaseModel, Field from dbgpt.agent.resource.resource_api import AgentResource from dbgpt.rag.chunk_manager import ChunkParameters +"""Chat completion request body""" + class ChatCompletionRequestBody(BaseModel): """ChatCompletion LLM http request body.""" @@ -54,15 +57,20 @@ class ChatCompletionRequestBody(BaseModel): ) 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.", + 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" ) +"""Chat completion response""" + + class ChatMode(Enum): - """Chat mode""" + """Chat mode.""" CHAT_NORMAL = "chat_normal" CHAT_APP = "chat_app" @@ -70,34 +78,30 @@ class ChatMode(Enum): CHAT_KNOWLEDGE = "chat_knowledge" -class SpaceModel(BaseModel): - """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") - """owner: owner""" - owner: str = Field(None, description="The owner") +"""Agent model""" class AppDetailModel(BaseModel): - app_code: Optional[str] = Field(None, title="app code") - app_name: Optional[str] = Field(None, title="app name") - agent_name: Optional[str] = Field(None, title="agent name") - node_id: Optional[str] = Field(None, title="node id") - resources: Optional[list[AgentResource]] = Field(None, title="resources") - prompt_template: Optional[str] = Field(None, title="prompt template") - llm_strategy: Optional[str] = Field(None, title="llm strategy") - llm_strategy_value: Optional[str] = Field(None, title="llm strategy value") + """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[AgentResource]] = 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() +"""Awel team model""" + + class AwelTeamModel(BaseModel): + """Awel team model.""" + dag_id: str = Field( ..., description="The unique id of dag", @@ -148,6 +152,8 @@ class AwelTeamModel(BaseModel): 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") @@ -166,6 +172,8 @@ class AppModel(BaseModel): class SpaceModel(BaseModel): + """Space model.""" + name: str = Field( default=None, description="knowledge space name", @@ -185,6 +193,8 @@ class SpaceModel(BaseModel): 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""" @@ -200,7 +210,7 @@ class DocumentModel(BaseModel): class SyncModel(BaseModel): - """Sync model""" + """Sync model.""" """doc_id: doc id""" doc_id: str = Field(None, description="The doc id") @@ -211,6 +221,6 @@ class SyncModel(BaseModel): """model_name: model name""" model_name: Optional[str] = Field(None, description="model name") - """chunk_parameters: chunk parameters + """chunk_parameters: chunk parameters """ chunk_parameters: ChunkParameters = Field(None, description="chunk parameters") diff --git a/examples/client/app_crud_example.py b/examples/client/app_crud_example.py index 516f483ae..f634959a5 100644 --- a/examples/client/app_crud_example.py +++ b/examples/client/app_crud_example.py @@ -23,7 +23,6 @@ Client: Simple App CRUD example async def main(): - # initialize client DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) diff --git a/examples/client/client_chat_example.py b/examples/client/client_chat_example.py index 425915166..5ae7009f8 100644 --- a/examples/client/client_chat_example.py +++ b/examples/client/client_chat_example.py @@ -55,7 +55,6 @@ Client: Simple Chat example async def main(): - # initialize client DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) diff --git a/examples/client/flow_crud_example.py b/examples/client/flow_crud_example.py index f59970b75..90834b7e0 100644 --- a/examples/client/flow_crud_example.py +++ b/examples/client/flow_crud_example.py @@ -36,7 +36,6 @@ Client: Simple Flow CRUD example async def main(): - # initialize client DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) diff --git a/examples/client/knowledge_crud_example.py b/examples/client/knowledge_crud_example.py index d77c4a36b..17a2fed01 100644 --- a/examples/client/knowledge_crud_example.py +++ b/examples/client/knowledge_crud_example.py @@ -68,7 +68,6 @@ from dbgpt.client.knowledge import list_space async def main(): - # initialize client DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) From f43abf3155e7ca6b37d68029ff00e75865a75986 Mon Sep 17 00:00:00 2001 From: aries_ckt <916701291@qq.com> Date: Wed, 20 Mar 2024 16:22:38 +0800 Subject: [PATCH 04/12] fix:client mypy error --- dbgpt/app/openapi/api_v2.py | 5 +- dbgpt/client/app.py | 38 +- dbgpt/client/client.py | 16 +- dbgpt/client/flow.py | 81 ++- dbgpt/client/knowledge.py | 178 +++++- dbgpt/client/schemas.py | 10 +- dbgpt/serve/flow/api/endpoints.py | 4 +- dbgpt/serve/rag/api/schemas.py | 12 +- dbgpt/serve/rag/models/models.py | 17 +- dbgpt/serve/rag/service/service.py | 4 +- docs/docs/api/app.md | 188 +++++++ docs/docs/api/chat.md | 280 +++++++++ docs/docs/api/flow.md | 306 ++++++++++ docs/docs/api/introduction.md | 37 ++ docs/docs/api/knowledge.md | 657 ++++++++++++++++++++++ docs/sidebars.js | 33 ++ examples/client/app_crud_example.py | 2 +- examples/client/flow_crud_example.py | 3 +- examples/client/knowledge_crud_example.py | 23 +- 19 files changed, 1814 insertions(+), 80 deletions(-) create mode 100644 docs/docs/api/app.md create mode 100644 docs/docs/api/chat.md create mode 100644 docs/docs/api/flow.md create mode 100644 docs/docs/api/introduction.md create mode 100644 docs/docs/api/knowledge.md diff --git a/dbgpt/app/openapi/api_v2.py b/dbgpt/app/openapi/api_v2.py index 8141aba2b..92857eadd 100644 --- a/dbgpt/app/openapi/api_v2.py +++ b/dbgpt/app/openapi/api_v2.py @@ -72,7 +72,6 @@ async def check_api_key( @router.post("/v2/chat/completions", dependencies=[Depends(check_api_key)]) async def chat_completions( request: ChatCompletionRequestBody = Body(), - flow_service: FlowService = Depends(get_chat_flow), ): """Chat V2 completions Args: @@ -121,7 +120,9 @@ async def chat_completions( media_type="text/event-stream", ) elif ( - request.chat_mode is None or request.chat_mode == ChatMode.CHAT_KNOWLEDGE.value + 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() diff --git a/dbgpt/client/app.py b/dbgpt/client/app.py index f25589d26..594576cb0 100644 --- a/dbgpt/client/app.py +++ b/dbgpt/client/app.py @@ -1,21 +1,49 @@ """App Client API.""" -from dbgpt.client.client import Client +from typing import List + +from dbgpt.client.client import Client, ClientException +from dbgpt.client.schemas import AppModel +from dbgpt.serve.core import Result -async def get_app(client: Client, app_id: str): +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. """ - return await client.get("/apps/" + app_id) + 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): +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. """ - return await client.get("/apps") + 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 index 84a20b238..b2c7f40ad 100644 --- a/dbgpt/client/client.py +++ b/dbgpt/client/client.py @@ -24,17 +24,11 @@ class ClientException(Exception): reason: Optional[str], the reason for the exception. http_resp: Optional[httpx.Response], the HTTP response object. """ - reason = json.loads(reason) - if http_resp: - self.status = http_resp.status_code - self.reason = http_resp.content - self.body = http_resp.content - self.headers = None - else: - self.status = status - self.reason = reason - self.body = None - self.headers = None + 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.""" diff --git a/dbgpt/client/flow.py b/dbgpt/client/flow.py index cb58e325d..489b99ae2 100644 --- a/dbgpt/client/flow.py +++ b/dbgpt/client/flow.py @@ -1,55 +1,114 @@ """this module contains the flow client functions.""" -from dbgpt.client.client import Client +from typing import List + +from dbgpt.client.client import Client, ClientException from dbgpt.core.awel.flow.flow_factory import FlowPanel +from dbgpt.serve.core import Result -async def create_flow(client: Client, flow: FlowPanel): +async def create_flow(client: Client, flow: FlowPanel) -> FlowPanel: """Create a new flow. Args: client (Client): The dbgpt client. flow (FlowPanel): The flow panel. """ - return await client.get("/awel/flows", flow.dict()) + 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): +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. """ - return await client.put("/awel/flows", flow.dict()) + 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): +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. """ - return await client.get("/awel/flows/" + flow_id) + 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): +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. """ - return await client.get("/awel/flows/" + flow_id) + 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): +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. """ - return await client.get("/awel/flows") + 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 index ed5950ceb..5ed1cdd71 100644 --- a/dbgpt/client/knowledge.py +++ b/dbgpt/client/knowledge.py @@ -1,104 +1,220 @@ """Knowledge API client.""" import json +from typing import List -from dbgpt.client.client import Client +from dbgpt.client.client import Client, ClientException from dbgpt.client.schemas import DocumentModel, SpaceModel, SyncModel +from dbgpt.serve.core import Result -async def create_space(client: Client, app_model: SpaceModel): +async def create_space(client: Client, space_model: SpaceModel) -> SpaceModel: """Create a new space. Args: client (Client): The dbgpt client. - app_model (SpaceModel): The app model. + space_model (SpaceModel): The space model. + Returns: + SpaceModel: The space model. + Raises: + ClientException: If the request failed. """ - return await client.post("/knowledge/spaces", app_model.dict()) + 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, app_model: SpaceModel): +async def update_space(client: Client, space_model: SpaceModel) -> SpaceModel: """Update a document. Args: client (Client): The dbgpt client. - app_model (SpaceModel): The app model. + space_model (SpaceModel): The space model. + Returns: + SpaceModel: The space model. + Raises: + ClientException: If the request failed. """ - return await client.put("/knowledge/spaces", app_model.dict()) + 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): +async def delete_space(client: Client, space_id: str) -> SpaceModel: """Delete a space. Args: client (Client): The dbgpt client. - app_id (str): The app id. + space_id (str): The space id. + Returns: + SpaceModel: The space model. + Raises: + ClientException: If the request failed. """ - return await client.delete("/knowledge/spaces/" + space_id) + 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): +async def get_space(client: Client, space_id: str) -> SpaceModel: """Get a document. Args: client (Client): The dbgpt client. - app_id (str): The app id. + space_id (str): The space id. + Returns: + SpaceModel: The space model. + Raises: + ClientException: If the request failed. """ - return await client.get("/knowledge/spaces/" + space_id) + 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 apps. +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. """ - return await client.get("/knowledge/spaces") + 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): - """Create a new space. +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. + """ - return await client.post_param("/knowledge/documents", doc_model.dict()) + 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): +async def delete_document(client: Client, document_id: str) -> DocumentModel: """Delete a document. Args: client (Client): The dbgpt client. - app_id (str): The app id. + document_id (str): The document id. + Returns: + DocumentModel: The document model. + Raises: + ClientException: If the request failed. """ - return await client.delete("/knowledge/documents/" + document_id) + 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): +async def get_document(client: Client, document_id: str) -> DocumentModel: """Get a document. Args: client (Client): The dbgpt client. - app_id (str): The app id. + document_id (str): The document id. + Returns: + DocumentModel: The document model. + Raises: + ClientException: If the request failed. """ - return await client.get("/knowledge/documents/" + document_id) + 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): +async def list_document(client: Client) -> List[DocumentModel]: """List documents. Args: client (Client): The dbgpt client. """ - return await client.get("/knowledge/documents") + 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): +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. """ - return await client.post( - "/knowledge/documents/sync", [json.loads(sync_model.json())] - ) + 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/schemas.py b/dbgpt/client/schemas.py index c2bb15524..aab0f0211 100644 --- a/dbgpt/client/schemas.py +++ b/dbgpt/client/schemas.py @@ -21,7 +21,7 @@ class ChatCompletionRequestBody(BaseModel): messages: Union[str, List[str]] = Field( ..., description="User input messages", examples=["Hello", "How are you?"] ) - stream: bool = Field(default=False, description="Whether return stream") + stream: bool = Field(default=True, description="Whether return stream") temperature: Optional[float] = Field( default=None, @@ -174,6 +174,10 @@ class AppModel(BaseModel): class SpaceModel(BaseModel): """Space model.""" + id: str = Field( + default=None, + description="space id", + ) name: str = Field( default=None, description="knowledge space name", @@ -190,6 +194,10 @@ class SpaceModel(BaseModel): default=None, description="space owner", ) + context: Optional[str] = Field( + default=None, + description="space argument context", + ) class DocumentModel(BaseModel): diff --git a/dbgpt/serve/flow/api/endpoints.py b/dbgpt/serve/flow/api/endpoints.py index 2b2d8b723..c3939ceb8 100644 --- a/dbgpt/serve/flow/api/endpoints.py +++ b/dbgpt/serve/flow/api/endpoints.py @@ -147,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/rag/api/schemas.py b/dbgpt/serve/rag/api/schemas.py index 0ffb986d6..1b9a91357 100644 --- a/dbgpt/serve/rag/api/schemas.py +++ b/dbgpt/serve/rag/api/schemas.py @@ -15,11 +15,17 @@ class SpaceServeRequest(BaseModel): 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(None, description="The vector type") + vector_type: str = Field("Chroma", description="The vector type") """desc: description""" - desc: str = Field(None, description="The description") + desc: Optional[str] = Field(None, description="The description") """owner: owner""" - owner: str = Field(None, description="The 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): diff --git a/dbgpt/serve/rag/models/models.py b/dbgpt/serve/rag/models/models.py index 809bc3eea..f8fd2986c 100644 --- a/dbgpt/serve/rag/models/models.py +++ b/dbgpt/serve/rag/models/models.py @@ -38,7 +38,7 @@ class KnowledgeSpaceDao(BaseDao): session.commit() space_id = knowledge_space.id session.close() - return space_id + return self.to_response(knowledge_space) def get_knowledge_space(self, query: KnowledgeSpaceEntity): """Get knowledge space by query""" @@ -81,11 +81,21 @@ class KnowledgeSpaceDao(BaseDao): 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""" @@ -127,6 +137,7 @@ class KnowledgeSpaceDao(BaseDao): vector_type=entity.vector_type, desc=entity.desc, owner=entity.owner, + context=entity.context, ) def to_response(self, entity: KnowledgeSpaceEntity) -> SpaceServeResponse: diff --git a/dbgpt/serve/rag/service/service.py b/dbgpt/serve/rag/service/service.py index 3910fcc34..4cb100c50 100644 --- a/dbgpt/serve/rag/service/service.py +++ b/dbgpt/serve/rag/service/service.py @@ -145,9 +145,7 @@ class Service(BaseService[KnowledgeSpaceEntity, SpaceServeRequest, SpaceServeRes status_code=400, detail=f"no space name named {request.name}", ) - space = spaces[0] - query_request = {"id": space.id} - update_obj = self._dao.update(query_request, update_request=request) + update_obj = self._dao.update_knowledge_space(self._dao.from_request(request)) return update_obj async def create_document( diff --git a/docs/docs/api/app.md b/docs/docs/api/app.md new file mode 100644 index 000000000..1972719cd --- /dev/null +++ b/docs/docs/api/app.md @@ -0,0 +1,188 @@ +# 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.client import Client + +DBGPT_API_KEY = "dbgpt" +APP_ID="{YOUR_APP_ID}" + +client = Client(api_key=DBGPT_API_KEY) +response = client.chat_stream(messages="Introduce AWEL", model="chatgpt_proxyllm", chat_mode="chat_app", chat_param=APP_ID) +``` + + + +### 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} +``` + +#### Query Parameters +________ +app_id string Required + +app id +________ + +#### Response body +Return App Object + +### List App + +```python +GET /api/v2/serve/apps +``` + +#### 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..5ea2d8ef6 --- /dev/null +++ b/docs/docs/api/chat.md @@ -0,0 +1,280 @@ +# 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.client import Client + +DBGPT_API_KEY = "dbgpt" +client = Client(api_key=DBGPT_API_KEY) +response = client.chat_stream(messages="Hello", model="chatgpt_proxyllm") +``` + + + +### 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.client import Client + +DBGPT_API_KEY = "dbgpt" +client = Client(api_key=DBGPT_API_KEY) +response = client.chat(messages="Hello", model="chatgpt_proxyllm") +``` + + + +### 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..e91438c20 --- /dev/null +++ b/docs/docs/api/flow.md @@ -0,0 +1,306 @@ +# 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.client import Client + +DBGPT_API_KEY = "dbgpt" +FLOW_ID="{YOUR_FLOW_ID}" + +client = Client(api_key=DBGPT_API_KEY) +response = client.chat_stream(messages="Hello", model="chatgpt_proxyllm", chat_mode="chat_flow", chat_param=FLOW_ID) +``` + + + +#### 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/knowledge/spaces/$FLOW_ID" \ + -H "Authorization: Bearer $DBGPT_API_KEY" \ + +``` + + + + + +```python +from dbgpt.client.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 --location --request GET 'http://localhost:5000/api/v2/serve/awel/flows/$FLOW_ID' \ + --header 'Authorization: Bearer $DBGPT_API_KEY' +``` + + + + + +```python +from dbgpt.client.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.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..d195b7c56 --- /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.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..f2bdf2b32 --- /dev/null +++ b/docs/docs/api/knowledge.md @@ -0,0 +1,657 @@ +# 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 --location --request POST 'http://127.0.0.1:5000/api/v2/chat/completions' \ +--header 'Authorization: Bearer $DBGPT_API_KEY' \ +--header 'Content-Type: application/json' \ +--data-raw '{ + + "model": "chatgpt_proxyllm", + "messages": "introduce awel", + "chat_mode":"chat_knowledge", + "chat_param":$SPACE_NAME +}' + +``` + + + + +```python +from dbgpt.client.client import Client + +DBGPT_API_KEY = "dbgpt" +SPACE_NAME="{YOUR_SPACE_NAME}" + +client = Client(api_key=DBGPT_API_KEY) +response = client.chat_stream(messages="Hello", model="chatgpt_proxyllm", chat_mode="chat_knowledge", chat_param=SPACE_NAME) +``` + + + +#### 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.client import Client +from dbgpt.client.knowledge import create_space +from dbgpt.client.schemas 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.client import Client +from dbgpt.client.knowledge import update_space +from dbgpt.client.schemas 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.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 --location --request GET 'http://localhost:5000/api/v2/serve/knowledge/spaces/$SPACE_ID' \ + --header 'Authorization: Bearer $DBGPT_API_KEY' +``` + + + + + +```python +from dbgpt.client.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 --location --request GET 'http://localhost:5000/api/v2/serve/knowledge/spaces' \ +--header 'Authorization: Bearer dbgpt' +``` + + + + + +```python +from dbgpt.client.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 index f634959a5..2f7e9bae5 100644 --- a/examples/client/app_crud_example.py +++ b/examples/client/app_crud_example.py @@ -27,7 +27,7 @@ async def main(): DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) res = await list_app(client) - print(res.json()) + print(res) if __name__ == "__main__": diff --git a/examples/client/flow_crud_example.py b/examples/client/flow_crud_example.py index 90834b7e0..29dc95f87 100644 --- a/examples/client/flow_crud_example.py +++ b/examples/client/flow_crud_example.py @@ -1,6 +1,5 @@ import asyncio -from dbgpt.client.app import list_app from dbgpt.client.client import Client from dbgpt.client.flow import list_flow @@ -40,7 +39,7 @@ async def main(): DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) res = await list_flow(client) - print(res.json()) + print(res) if __name__ == "__main__": diff --git a/examples/client/knowledge_crud_example.py b/examples/client/knowledge_crud_example.py index 17a2fed01..b00d01e53 100644 --- a/examples/client/knowledge_crud_example.py +++ b/examples/client/knowledge_crud_example.py @@ -1,7 +1,8 @@ import asyncio from dbgpt.client.client import Client -from dbgpt.client.knowledge import list_space +from dbgpt.client.knowledge import create_space +from dbgpt.client.schemas import SpaceModel """Client: Simple Knowledge CRUD example @@ -72,9 +73,20 @@ async def main(): 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.json()) + # res = await list_space(client) + # print(res) # get space # res = await get_space(client, space_id='5') @@ -86,7 +98,8 @@ async def main(): # 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='37') + # res = await delete_space(client, space_id='31') + # print(res) # list all documents # res = await list_document(client) @@ -102,7 +115,7 @@ async def main(): # , doc_file=('your_file_name', open('{your_file_path}', 'rb')))) # sync document - # res = await sync_document(client, sync_model=SyncModel(doc_id="153", space_id="40", model_name="text2vec", chunk_parameters=ChunkParameters(chunk_strategy="Automatic"))) + # 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__": From 75f086a41d7b32a4e758e207d1ac2167106a6a5b Mon Sep 17 00:00:00 2001 From: aries_ckt <916701291@qq.com> Date: Wed, 20 Mar 2024 18:40:59 +0800 Subject: [PATCH 05/12] doc:update api docs --- dbgpt/app/knowledge/request/request.py | 14 ---- dbgpt/app/knowledge/space_db.py | 93 -------------------------- docs/docs/api/app.md | 78 ++++++++++++++++++++- docs/docs/api/flow.md | 58 ++++++++-------- docs/docs/api/knowledge.md | 37 ++++------ 5 files changed, 118 insertions(+), 162 deletions(-) delete mode 100644 dbgpt/app/knowledge/space_db.py diff --git a/dbgpt/app/knowledge/request/request.py b/dbgpt/app/knowledge/request/request.py index f15d3e7f2..14e12ce90 100644 --- a/dbgpt/app/knowledge/request/request.py +++ b/dbgpt/app/knowledge/request/request.py @@ -79,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/space_db.py b/dbgpt/app/knowledge/space_db.py deleted file mode 100644 index 933a2b57b..000000000 --- a/dbgpt/app/knowledge/space_db.py +++ /dev/null @@ -1,93 +0,0 @@ -# from datetime import datetime -# -# from sqlalchemy import Column, DateTime, Integer, String, Text -# -# from dbgpt._private.config import Config -# from dbgpt.app.knowledge.request.request import KnowledgeSpaceRequest -# from dbgpt.storage.metadata import BaseDao, Model -# -# CFG = Config() -# -# -# class KnowledgeSpaceEntity(Model): -# __tablename__ = "knowledge_space" -# id = Column(Integer, primary_key=True) -# name = Column(String(100)) -# vector_type = Column(String(100)) -# desc = Column(String(100)) -# owner = Column(String(100)) -# context = Column(Text) -# gmt_created = Column(DateTime) -# gmt_modified = Column(DateTime) -# -# def __repr__(self): -# return f"KnowledgeSpaceEntity(id={self.id}, name='{self.name}', vector_type='{self.vector_type}', desc='{self.desc}', owner='{self.owner}' context='{self.context}', gmt_created='{self.gmt_created}', gmt_modified='{self.gmt_modified}')" -# -# -# class KnowledgeSpaceDao(BaseDao): -# def create_knowledge_space(self, space: KnowledgeSpaceRequest): -# session = self.get_raw_session() -# knowledge_space = KnowledgeSpaceEntity( -# name=space.name, -# vector_type=CFG.VECTOR_STORE_TYPE, -# desc=space.desc, -# owner=space.owner, -# gmt_created=datetime.now(), -# gmt_modified=datetime.now(), -# ) -# session.add(knowledge_space) -# session.commit() -# session.close() -# -# def get_knowledge_space(self, query: KnowledgeSpaceEntity): -# session = self.get_raw_session() -# knowledge_spaces = session.query(KnowledgeSpaceEntity) -# if query.id is not None: -# knowledge_spaces = knowledge_spaces.filter( -# KnowledgeSpaceEntity.id == query.id -# ) -# if query.name is not None: -# knowledge_spaces = knowledge_spaces.filter( -# KnowledgeSpaceEntity.name == query.name -# ) -# if query.vector_type is not None: -# knowledge_spaces = knowledge_spaces.filter( -# KnowledgeSpaceEntity.vector_type == query.vector_type -# ) -# if query.desc is not None: -# knowledge_spaces = knowledge_spaces.filter( -# KnowledgeSpaceEntity.desc == query.desc -# ) -# if query.owner is not None: -# knowledge_spaces = knowledge_spaces.filter( -# KnowledgeSpaceEntity.owner == query.owner -# ) -# if query.gmt_created is not None: -# knowledge_spaces = knowledge_spaces.filter( -# KnowledgeSpaceEntity.gmt_created == query.gmt_created -# ) -# if query.gmt_modified is not None: -# knowledge_spaces = knowledge_spaces.filter( -# KnowledgeSpaceEntity.gmt_modified == query.gmt_modified -# ) -# -# knowledge_spaces = knowledge_spaces.order_by( -# KnowledgeSpaceEntity.gmt_created.desc() -# ) -# result = knowledge_spaces.all() -# session.close() -# return result -# -# def update_knowledge_space(self, space: KnowledgeSpaceEntity): -# session = self.get_raw_session() -# session.merge(space) -# session.commit() -# session.close() -# return True -# -# def delete_knowledge_space(self, space: KnowledgeSpaceEntity): -# session = self.get_raw_session() -# if space: -# session.delete(space) -# session.commit() -# session.close() diff --git a/docs/docs/api/app.md b/docs/docs/api/app.md index 1972719cd..3a0105543 100644 --- a/docs/docs/api/app.md +++ b/docs/docs/api/app.md @@ -27,14 +27,14 @@ import TabItem from '@theme/TabItem'; ```shell - DBGPT_API_KEY="dbgpt" - APP_ID="{YOUR_APP_ID}" + 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"}" + -d "{\"messages\":\"Hello\",\"model\":\"chatgpt_proxyllm\", \"chat_mode\": \"chat_app\", \"chat_param\": \"$APP_ID\"}" ``` @@ -65,6 +65,43 @@ data: [DONE] 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.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 @@ -80,6 +117,41 @@ Return App Object ```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.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 diff --git a/docs/docs/api/flow.md b/docs/docs/api/flow.md index e91438c20..8e4173139 100644 --- a/docs/docs/api/flow.md +++ b/docs/docs/api/flow.md @@ -27,14 +27,14 @@ import TabItem from '@theme/TabItem'; ```shell - DBGPT_API_KEY="dbgpt" - FLOW_ID="{YOUR_FLOW_ID}" +DBGPT_API_KEY=dbgpt +FLOW_ID={YOUR_FLOW_ID} - curl -X POST "http://localhost:5000/api/v2/chat/completions" \ +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"}" + -d "{\"messages\":\"Hello\",\"model\":\"chatgpt_proxyllm\", \"chat_mode\": \"chat_flow\", \"chat_param\": \"$FLOW_ID\"}" ``` @@ -86,27 +86,27 @@ DELETE /api/v2/serve/awel/flows ``` - + ```shell - DBGPT_API_KEY="dbgpt" - FLOW_ID="{YOUR_FLOW_ID}" +DBGPT_API_KEY=dbgpt +FLOW_ID={YOUR_FLOW_ID} - curl -X DELETE "http://localhost:5000/api/v2/serve/knowledge/spaces/$FLOW_ID" \ + curl -X DELETE "http://localhost:5000/api/v2/serve/awel/flows/$FLOW_ID" \ -H "Authorization: Bearer $DBGPT_API_KEY" \ ``` - + ```python @@ -140,26 +140,26 @@ Return Flow Object GET /api/v2/serve/awel/flows/{flow_id} ``` - + ```shell - DBGPT_API_KEY="dbgpt" - FLOW_ID="{YOUR_FLOW_ID}" +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" - curl --location --request GET 'http://localhost:5000/api/v2/serve/awel/flows/$FLOW_ID' \ - --header 'Authorization: Bearer $DBGPT_API_KEY' ``` - + ```python @@ -195,25 +195,25 @@ GET /api/v2/serve/awel/flows - + ```shell - DBGPT_API_KEY="dbgpt" +DBGPT_API_KEY=dbgpt + +curl -X GET "http://localhost:5000/api/v2/serve/awel/flows" -H "Authorization: Bearer $DBGPT_API_KEY" - curl -X GET "http://localhost:5000/api/v2/serve/awel/flows" \ - -H "Authorization: Bearer $DBGPT_API_KEY" \ ``` - + ```python diff --git a/docs/docs/api/knowledge.md b/docs/docs/api/knowledge.md index f2bdf2b32..ef3818bb0 100644 --- a/docs/docs/api/knowledge.md +++ b/docs/docs/api/knowledge.md @@ -27,20 +27,14 @@ import TabItem from '@theme/TabItem'; ```shell - DBGPT_API_KEY="dbgpt" - SPACE_NAME="{YOUR_SPACE_NAME}" - - curl --location --request POST 'http://127.0.0.1:5000/api/v2/chat/completions' \ ---header 'Authorization: Bearer $DBGPT_API_KEY' \ ---header 'Content-Type: application/json' \ ---data-raw '{ - - "model": "chatgpt_proxyllm", - "messages": "introduce awel", - "chat_mode":"chat_knowledge", - "chat_param":$SPACE_NAME -}' +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\"}" ``` @@ -490,8 +484,8 @@ DELETE /api/v2/serve/knowledge/spaces ```shell - DBGPT_API_KEY="dbgpt" - SPACE_ID="{YOUR_SPACE_ID}" + 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" \ @@ -546,11 +540,9 @@ GET /api/v2/serve/knowledge/spaces/{space_id} ```shell - DBGPT_API_KEY="dbgpt" - SPACE_ID="{YOUR_SPACE_ID}" - - curl --location --request GET 'http://localhost:5000/api/v2/serve/knowledge/spaces/$SPACE_ID' \ - --header 'Authorization: Bearer $DBGPT_API_KEY' +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" ``` @@ -600,10 +592,9 @@ GET /api/v2/serve/knowledge/spaces ```shell - DBGPT_API_KEY="dbgpt" + DBGPT_API_KEY=dbgpt -curl --location --request GET 'http://localhost:5000/api/v2/serve/knowledge/spaces' \ ---header 'Authorization: Bearer dbgpt' +curl -X GET 'http://localhost:5000/api/v2/serve/knowledge/spaces' -H "Authorization: Bearer $DBGPT_API_KEY" ``` From 01ea5f8064d54d96fe14b00df7663983cb97f3ff Mon Sep 17 00:00:00 2001 From: aries_ckt <916701291@qq.com> Date: Wed, 20 Mar 2024 21:37:52 +0800 Subject: [PATCH 06/12] doc:update api docs --- docs/docs/api/app.md | 9 ++++++++- docs/docs/api/chat.md | 10 +++++++--- docs/docs/api/flow.md | 7 ++++++- docs/docs/api/knowledge.md | 8 +++++++- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/docs/docs/api/app.md b/docs/docs/api/app.md index 3a0105543..3d7a9953b 100644 --- a/docs/docs/api/app.md +++ b/docs/docs/api/app.md @@ -48,7 +48,14 @@ DBGPT_API_KEY = "dbgpt" APP_ID="{YOUR_APP_ID}" client = Client(api_key=DBGPT_API_KEY) -response = client.chat_stream(messages="Introduce AWEL", model="chatgpt_proxyllm", chat_mode="chat_app", chat_param=APP_ID) + +async for data in client.chat_stream( + messages="Introduce AWEL", + model="chatgpt_proxyllm", + chat_mode="chat_app", + chat_param=APP_ID): + print(data) + ``` diff --git a/docs/docs/api/chat.md b/docs/docs/api/chat.md index 5ea2d8ef6..55284a28e 100644 --- a/docs/docs/api/chat.md +++ b/docs/docs/api/chat.md @@ -45,8 +45,12 @@ import TabItem from '@theme/TabItem'; from dbgpt.client.client import Client DBGPT_API_KEY = "dbgpt" -client = Client(api_key=DBGPT_API_KEY) -response = client.chat_stream(messages="Hello", model="chatgpt_proxyllm") + +async for data in client.chat_stream( + model="chatgpt_proxyllm", + messages="hello", +): + print(data) ``` @@ -104,7 +108,7 @@ from dbgpt.client.client import Client DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) -response = client.chat(messages="Hello", model="chatgpt_proxyllm") +response = await client.chat(model="chatgpt_proxyllm" ,messages="hello") ``` diff --git a/docs/docs/api/flow.md b/docs/docs/api/flow.md index 8e4173139..ae649839b 100644 --- a/docs/docs/api/flow.md +++ b/docs/docs/api/flow.md @@ -48,7 +48,12 @@ DBGPT_API_KEY = "dbgpt" FLOW_ID="{YOUR_FLOW_ID}" client = Client(api_key=DBGPT_API_KEY) -response = client.chat_stream(messages="Hello", model="chatgpt_proxyllm", chat_mode="chat_flow", chat_param=FLOW_ID) +async for data in client.chat_stream( + messages="Introduce AWEL", + model="chatgpt_proxyllm", + chat_mode="chat_flow", + chat_param=FLOW_ID): + print(data) ``` diff --git a/docs/docs/api/knowledge.md b/docs/docs/api/knowledge.md index ef3818bb0..688add674 100644 --- a/docs/docs/api/knowledge.md +++ b/docs/docs/api/knowledge.md @@ -47,7 +47,13 @@ DBGPT_API_KEY = "dbgpt" SPACE_NAME="{YOUR_SPACE_NAME}" client = Client(api_key=DBGPT_API_KEY) -response = client.chat_stream(messages="Hello", model="chatgpt_proxyllm", chat_mode="chat_knowledge", chat_param=SPACE_NAME) + +async for data in client.chat_stream( + messages="Introduce AWEL", + model="chatgpt_proxyllm", + chat_mode="chat_knowledge", + chat_param=SPACE_NAME): + print(data) ``` From ab3e8e54a173ea608e996dff655ebdd18af7757b Mon Sep 17 00:00:00 2001 From: Fangyin Cheng Date: Thu, 21 Mar 2024 09:58:32 +0800 Subject: [PATCH 07/12] feat(client): Modify api address --- dbgpt/client/__init__.py | 4 ++ dbgpt/client/app.py | 2 +- dbgpt/client/client.py | 28 ++++++----- dbgpt/client/flow.py | 2 +- dbgpt/client/knowledge.py | 2 +- docs/docs/api/app.md | 6 +-- docs/docs/api/chat.md | 4 +- docs/docs/api/flow.md | 8 +-- docs/docs/api/introduction.md | 2 +- docs/docs/api/knowledge.md | 12 ++--- examples/client/__init__.py | 0 examples/client/app_crud_example.py | 33 ++++++------- examples/client/client_chat_example.py | 15 +++--- examples/client/flow_crud_example.py | 60 +++++++++++------------ examples/client/knowledge_crud_example.py | 17 ++++--- 15 files changed, 98 insertions(+), 97 deletions(-) delete mode 100644 examples/client/__init__.py diff --git a/dbgpt/client/__init__.py b/dbgpt/client/__init__.py index be187a027..7ddf7e9eb 100644 --- a/dbgpt/client/__init__.py +++ b/dbgpt/client/__init__.py @@ -1 +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 index 594576cb0..f90a9906b 100644 --- a/dbgpt/client/app.py +++ b/dbgpt/client/app.py @@ -1,7 +1,7 @@ """App Client API.""" from typing import List -from dbgpt.client.client import Client, ClientException +from dbgpt.client import Client, ClientException from dbgpt.client.schemas import AppModel from dbgpt.serve.core import Result diff --git a/dbgpt/client/client.py b/dbgpt/client/client.py index b2c7f40ad..60d46ba9c 100644 --- a/dbgpt/client/client.py +++ b/dbgpt/client/client.py @@ -1,5 +1,6 @@ """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 @@ -50,7 +51,7 @@ class Client: def __init__( self, - api_base: str = "http://localhost:5000", + api_base: Optional[str] = None, api_key: Optional[str] = None, version: str = "v2", timeout: Optional[httpx._types.TimeoutTypes] = 120, @@ -59,7 +60,7 @@ class Client: Args: api_base: Optional[str], a full URL for the DB-GPT API. - Defaults to the `http://localhost:5000`. + 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. @@ -73,20 +74,25 @@ class Client: -------- .. code-block:: python - from dbgpt.client.client import Client + from dbgpt.client import Client - DBGPT_API_BASE = "http://localhost:5000" + 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.rstrip("/") + 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._api_url = api_base + CLIENT_API_PATH + "/" + version self._timeout = timeout headers = {"Authorization": f"Bearer {self._api_key}"} if self._api_key else {} self._http_client = httpx.AsyncClient( @@ -135,9 +141,9 @@ class Client: -------- .. code-block:: python - from dbgpt.client.client import Client + from dbgpt.client import Client - DBGPT_API_BASE = "http://localhost:5000" + 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?") @@ -210,9 +216,9 @@ class Client: -------- .. code-block:: python - from dbgpt.client.client import Client + from dbgpt.client import Client - DBGPT_API_BASE = "http://localhost:5000" + 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?") @@ -257,7 +263,6 @@ class Client: error = await response.aread() yield json.loads(error) except Exception as e: - yield f"data:[SERVER_ERROR]{str(e)}\n\n" async def get(self, path: str, *args): @@ -274,7 +279,6 @@ class Client: ) return response finally: - await self._http_client.aclose() async def post(self, path: str, args): diff --git a/dbgpt/client/flow.py b/dbgpt/client/flow.py index 489b99ae2..eda995da6 100644 --- a/dbgpt/client/flow.py +++ b/dbgpt/client/flow.py @@ -1,7 +1,7 @@ """this module contains the flow client functions.""" from typing import List -from dbgpt.client.client import Client, ClientException +from dbgpt.client import Client, ClientException from dbgpt.core.awel.flow.flow_factory import FlowPanel from dbgpt.serve.core import Result diff --git a/dbgpt/client/knowledge.py b/dbgpt/client/knowledge.py index 5ed1cdd71..323b51fcb 100644 --- a/dbgpt/client/knowledge.py +++ b/dbgpt/client/knowledge.py @@ -2,7 +2,7 @@ import json from typing import List -from dbgpt.client.client import Client, ClientException +from dbgpt.client import Client, ClientException from dbgpt.client.schemas import DocumentModel, SpaceModel, SyncModel from dbgpt.serve.core import Result diff --git a/docs/docs/api/app.md b/docs/docs/api/app.md index 3d7a9953b..013b4e297 100644 --- a/docs/docs/api/app.md +++ b/docs/docs/api/app.md @@ -42,7 +42,7 @@ import TabItem from '@theme/TabItem'; ```python -from dbgpt.client.client import Client +from dbgpt.client import Client DBGPT_API_KEY = "dbgpt" APP_ID="{YOUR_APP_ID}" @@ -94,7 +94,7 @@ curl -X GET "http://localhost:5000/api/v2/serve/apps/$APP_ID" -H "Authorization: ```python -from dbgpt.client.client import Client +from dbgpt.client import Client from dbgpt.client.app import get_app DBGPT_API_KEY = "dbgpt" @@ -146,7 +146,7 @@ curl -X GET 'http://localhost:5000/api/v2/serve/apps' -H "Authorization: Bearer ```python -from dbgpt.client.client import Client +from dbgpt.client import Client from dbgpt.client.app import list_app DBGPT_API_KEY = "dbgpt" diff --git a/docs/docs/api/chat.md b/docs/docs/api/chat.md index 55284a28e..6cf3d2621 100644 --- a/docs/docs/api/chat.md +++ b/docs/docs/api/chat.md @@ -42,7 +42,7 @@ import TabItem from '@theme/TabItem'; ```python -from dbgpt.client.client import Client +from dbgpt.client import Client DBGPT_API_KEY = "dbgpt" @@ -104,7 +104,7 @@ data: [DONE] ```python -from dbgpt.client.client import Client +from dbgpt.client import Client DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) diff --git a/docs/docs/api/flow.md b/docs/docs/api/flow.md index ae649839b..7e63e93cf 100644 --- a/docs/docs/api/flow.md +++ b/docs/docs/api/flow.md @@ -42,7 +42,7 @@ curl -X POST "http://localhost:5000/api/v2/chat/completions" \ ```python -from dbgpt.client.client import Client +from dbgpt.client import Client DBGPT_API_KEY = "dbgpt" FLOW_ID="{YOUR_FLOW_ID}" @@ -115,7 +115,7 @@ FLOW_ID={YOUR_FLOW_ID} ```python -from dbgpt.client.client import Client +from dbgpt.client import Client from dbgpt.client.flow import delete_flow DBGPT_API_KEY = "dbgpt" @@ -168,7 +168,7 @@ curl -X GET "http://localhost:5000/api/v2/serve/awel/flows/$FLOW_ID" -H "Authori ```python -from dbgpt.client.client import Client +from dbgpt.client import Client from dbgpt.client.knowledge import get_flow DBGPT_API_KEY = "dbgpt" @@ -222,7 +222,7 @@ curl -X GET "http://localhost:5000/api/v2/serve/awel/flows" -H "Authorization: B ```python -from dbgpt.client.client import Client +from dbgpt.client import Client from dbgpt.client.flow import list_flow DBGPT_API_KEY = "dbgpt" diff --git a/docs/docs/api/introduction.md b/docs/docs/api/introduction.md index d195b7c56..6354982ae 100644 --- a/docs/docs/api/introduction.md +++ b/docs/docs/api/introduction.md @@ -21,7 +21,7 @@ Example with the DB-GPT API curl command: Example with the DB-GPT Client Python package: ```python - from dbgpt.client.client import Client + from dbgpt.client import Client DBGPT_API_KEY = "dbgpt" client = Client(api_key=DBGPT_API_KEY) diff --git a/docs/docs/api/knowledge.md b/docs/docs/api/knowledge.md index 688add674..b19be1478 100644 --- a/docs/docs/api/knowledge.md +++ b/docs/docs/api/knowledge.md @@ -41,7 +41,7 @@ curl -X POST "http://localhost:5000/api/v2/chat/completions" \ ```python -from dbgpt.client.client import Client +from dbgpt.client import Client DBGPT_API_KEY = "dbgpt" SPACE_NAME="{YOUR_SPACE_NAME}" @@ -345,7 +345,7 @@ POST /api/v2/serve/knowledge/spaces ```python -from dbgpt.client.client import Client +from dbgpt.client import Client from dbgpt.client.knowledge import create_space from dbgpt.client.schemas import SpaceModel @@ -422,7 +422,7 @@ PUT /api/v2/serve/knowledge/spaces ```python -from dbgpt.client.client import Client +from dbgpt.client import Client from dbgpt.client.knowledge import update_space from dbgpt.client.schemas import SpaceModel @@ -504,7 +504,7 @@ DELETE /api/v2/serve/knowledge/spaces ```python -from dbgpt.client.client import Client +from dbgpt.client import Client from dbgpt.client.knowledge import delete_space DBGPT_API_KEY = "dbgpt" @@ -556,7 +556,7 @@ curl -X GET "http://localhost:5000/api/v2/serve/knowledge/spaces/$SPACE_ID" -H " ```python -from dbgpt.client.client import Client +from dbgpt.client import Client from dbgpt.client.knowledge import get_space DBGPT_API_KEY = "dbgpt" @@ -608,7 +608,7 @@ curl -X GET 'http://localhost:5000/api/v2/serve/knowledge/spaces' -H "Authorizat ```python -from dbgpt.client.client import Client +from dbgpt.client import Client from dbgpt.client.knowledge import list_space DBGPT_API_KEY = "dbgpt" diff --git a/examples/client/__init__.py b/examples/client/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/examples/client/app_crud_example.py b/examples/client/app_crud_example.py index 2f7e9bae5..000607841 100644 --- a/examples/client/app_crud_example.py +++ b/examples/client/app_crud_example.py @@ -1,25 +1,20 @@ +"""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 -from dbgpt.client.client import Client - -""" -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" - ) - -""" async def main(): diff --git a/examples/client/client_chat_example.py b/examples/client/client_chat_example.py index 5ae7009f8..266ef1bc7 100644 --- a/examples/client/client_chat_example.py +++ b/examples/client/client_chat_example.py @@ -1,13 +1,8 @@ -import asyncio +"""Client: Simple Chat example. -from dbgpt.client.client import Client +This example demonstrates how to use the dbgpt client to chat with the chatgpt model. -""" -Client: Simple Chat example - - This example demonstrates how to use the dbgpt client to chat with the chatgpt model. - - Example: +Example: .. code-block:: python DBGPT_API_KEY = "dbgpt" @@ -53,6 +48,10 @@ Client: Simple Chat example print(data.dict()) """ +import asyncio + +from dbgpt.client import Client + async def main(): # initialize client diff --git a/examples/client/flow_crud_example.py b/examples/client/flow_crud_example.py index 29dc95f87..7b83a00a0 100644 --- a/examples/client/flow_crud_example.py +++ b/examples/client/flow_crud_example.py @@ -1,38 +1,36 @@ +"""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.client import Client +from dbgpt.client import Client from dbgpt.client.flow import list_flow -""" -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) - -""" - async def main(): # initialize client diff --git a/examples/client/knowledge_crud_example.py b/examples/client/knowledge_crud_example.py index b00d01e53..f3533bea2 100644 --- a/examples/client/knowledge_crud_example.py +++ b/examples/client/knowledge_crud_example.py @@ -1,13 +1,9 @@ -import asyncio +"""Client: Simple Knowledge CRUD example. -from dbgpt.client.client import Client -from dbgpt.client.knowledge import create_space -from dbgpt.client.schemas import SpaceModel +This example demonstrates how to use the dbgpt client to create, get, update, and +delete knowledge spaces and documents. -"""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: +Example: .. code-block:: python DBGPT_API_KEY = "dbgpt" @@ -66,6 +62,11 @@ from dbgpt.client.schemas import SpaceModel # 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.schemas import SpaceModel async def main(): From b4b810d68fe1ea3a581ab9283c3fc5feef01223e Mon Sep 17 00:00:00 2001 From: Fangyin Cheng Date: Thu, 21 Mar 2024 11:23:24 +0800 Subject: [PATCH 08/12] feat(core): Add common schemas --- dbgpt/app/openapi/api_v1/api_v1.py | 8 +- dbgpt/app/openapi/api_v2.py | 20 ++-- dbgpt/app/openapi/api_view_model.py | 18 ---- dbgpt/client/app.py | 7 +- dbgpt/client/client.py | 10 +- dbgpt/client/flow.py | 5 +- dbgpt/client/knowledge.py | 7 +- dbgpt/client/{schemas.py => schema.py} | 2 +- dbgpt/core/schema/__init__.py | 1 + dbgpt/core/schema/api.py | 116 ++++++++++++++++++++++ dbgpt/model/utils/chatgpt_utils.py | 12 +-- dbgpt/serve/core/schemas.py | 38 +------ dbgpt/serve/flow/service/service.py | 4 +- docs/docs/api/app.md | 11 +- docs/docs/api/chat.md | 1 + docs/docs/api/flow.md | 11 +- docs/docs/api/knowledge.md | 38 +++---- examples/client/knowledge_crud_example.py | 2 +- 18 files changed, 188 insertions(+), 123 deletions(-) rename dbgpt/client/{schemas.py => schema.py} (99%) create mode 100644 dbgpt/core/schema/__init__.py create mode 100644 dbgpt/core/schema/api.py diff --git a/dbgpt/app/openapi/api_v1/api_v1.py b/dbgpt/app/openapi/api_v1/api_v1.py index 3441ab8e0..6c3dd0d86 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 diff --git a/dbgpt/app/openapi/api_v2.py b/dbgpt/app/openapi/api_v2.py index 92857eadd..4ed8335d7 100644 --- a/dbgpt/app/openapi/api_v2.py +++ b/dbgpt/app/openapi/api_v2.py @@ -6,15 +6,6 @@ from typing import Optional from fastapi import APIRouter, Body, Depends, HTTPException from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer -from fastchat.protocol.api_protocol import ( - ChatCompletionResponse, - ChatCompletionResponseChoice, - ChatCompletionResponseStreamChoice, - ChatCompletionStreamResponse, - ChatMessage, - DeltaMessage, - UsageInfo, -) from starlette.responses import StreamingResponse from dbgpt.app.openapi.api_v1.api_v1 import ( @@ -26,9 +17,18 @@ from dbgpt.app.openapi.api_v1.api_v1 import ( stream_generator, ) from dbgpt.app.scene import BaseChat, ChatScene -from dbgpt.client.schemas import ChatCompletionRequestBody, ChatMode +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 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/client/app.py b/dbgpt/client/app.py index f90a9906b..23f2f90ea 100644 --- a/dbgpt/client/app.py +++ b/dbgpt/client/app.py @@ -1,9 +1,10 @@ """App Client API.""" from typing import List -from dbgpt.client import Client, ClientException -from dbgpt.client.schemas import AppModel -from dbgpt.serve.core import Result +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: diff --git a/dbgpt/client/client.py b/dbgpt/client/client.py index 60d46ba9c..5f41529f0 100644 --- a/dbgpt/client/client.py +++ b/dbgpt/client/client.py @@ -5,10 +5,10 @@ from typing import Any, AsyncGenerator, List, Optional, Union from urllib.parse import urlparse import httpx -from fastchat.protocol.api_protocol import ChatCompletionResponse -from dbgpt.app.openapi.api_view_model import ChatCompletionStreamResponse -from dbgpt.client.schemas import ChatCompletionRequestBody +from dbgpt.core.schema.api import ChatCompletionResponse, ChatCompletionStreamResponse + +from .schema import ChatCompletionRequestBody CLIENT_API_PATH = "/api" CLIENT_SERVE_PATH = "/serve" @@ -256,14 +256,14 @@ class Client: ) yield chat_completion_response except Exception as e: - yield f"data:[SERVER_ERROR]{str(e)}\n\n" + raise e else: try: error = await response.aread() yield json.loads(error) except Exception as e: - yield f"data:[SERVER_ERROR]{str(e)}\n\n" + raise e async def get(self, path: str, *args): """Get method. diff --git a/dbgpt/client/flow.py b/dbgpt/client/flow.py index eda995da6..f5206b2d1 100644 --- a/dbgpt/client/flow.py +++ b/dbgpt/client/flow.py @@ -1,9 +1,10 @@ """this module contains the flow client functions.""" from typing import List -from dbgpt.client import Client, ClientException from dbgpt.core.awel.flow.flow_factory import FlowPanel -from dbgpt.serve.core import Result +from dbgpt.core.schema.api import Result + +from .client import Client, ClientException async def create_flow(client: Client, flow: FlowPanel) -> FlowPanel: diff --git a/dbgpt/client/knowledge.py b/dbgpt/client/knowledge.py index 323b51fcb..f8b3404ec 100644 --- a/dbgpt/client/knowledge.py +++ b/dbgpt/client/knowledge.py @@ -2,9 +2,10 @@ import json from typing import List -from dbgpt.client import Client, ClientException -from dbgpt.client.schemas import DocumentModel, SpaceModel, SyncModel -from dbgpt.serve.core import Result +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: diff --git a/dbgpt/client/schemas.py b/dbgpt/client/schema.py similarity index 99% rename from dbgpt/client/schemas.py rename to dbgpt/client/schema.py index aab0f0211..de33e5351 100644 --- a/dbgpt/client/schemas.py +++ b/dbgpt/client/schema.py @@ -4,8 +4,8 @@ from enum import Enum from typing import List, Optional, Union from fastapi import File, UploadFile -from pydantic import BaseModel, Field +from dbgpt._private.pydantic import BaseModel, Field from dbgpt.agent.resource.resource_api import AgentResource from dbgpt.rag.chunk_manager import ChunkParameters 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/core/schemas.py b/dbgpt/serve/core/schemas.py index 5add79f30..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( 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/docs/docs/api/app.md b/docs/docs/api/app.md index 013b4e297..f2cbec390 100644 --- a/docs/docs/api/app.md +++ b/docs/docs/api/app.md @@ -50,11 +50,12 @@ 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) + messages="Introduce AWEL", + model="chatgpt_proxyllm", + chat_mode="chat_app", + chat_param=APP_ID +): + print(data) ``` diff --git a/docs/docs/api/chat.md b/docs/docs/api/chat.md index 6cf3d2621..da64c99a8 100644 --- a/docs/docs/api/chat.md +++ b/docs/docs/api/chat.md @@ -45,6 +45,7 @@ import TabItem from '@theme/TabItem'; 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", diff --git a/docs/docs/api/flow.md b/docs/docs/api/flow.md index 7e63e93cf..88be326d2 100644 --- a/docs/docs/api/flow.md +++ b/docs/docs/api/flow.md @@ -49,11 +49,12 @@ 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) + messages="Introduce AWEL", + model="chatgpt_proxyllm", + chat_mode="chat_flow", + chat_param=FLOW_ID +): + print(data) ``` diff --git a/docs/docs/api/knowledge.md b/docs/docs/api/knowledge.md index b19be1478..8810f05fb 100644 --- a/docs/docs/api/knowledge.md +++ b/docs/docs/api/knowledge.md @@ -49,11 +49,12 @@ 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) + messages="Introduce AWEL", + model="chatgpt_proxyllm", + chat_mode="chat_knowledge", + chat_param=SPACE_NAME +): + print(data) ``` @@ -343,21 +344,20 @@ POST /api/v2/serve/knowledge/spaces - ```python from dbgpt.client import Client from dbgpt.client.knowledge import create_space -from dbgpt.client.schemas import SpaceModel +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")) - +res = await create_space(client, SpaceModel( + name="test_space", + vector_type="Chroma", + desc="for client space", + owner="dbgpt" +)) ``` @@ -420,20 +420,20 @@ PUT /api/v2/serve/knowledge/spaces - ```python from dbgpt.client import Client from dbgpt.client.knowledge import update_space -from dbgpt.client.schemas import SpaceModel +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")) + name="test_space", + vector_type="Chroma", + desc="for client space update", + owner="dbgpt" +)) ``` diff --git a/examples/client/knowledge_crud_example.py b/examples/client/knowledge_crud_example.py index f3533bea2..879984255 100644 --- a/examples/client/knowledge_crud_example.py +++ b/examples/client/knowledge_crud_example.py @@ -66,7 +66,7 @@ import asyncio from dbgpt.client import Client from dbgpt.client.knowledge import create_space -from dbgpt.client.schemas import SpaceModel +from dbgpt.client.schema import SpaceModel async def main(): From a1369c02c4086b970128f0c7ef526f1769d2b9e7 Mon Sep 17 00:00:00 2001 From: aries_ckt <916701291@qq.com> Date: Thu, 21 Mar 2024 14:13:59 +0800 Subject: [PATCH 09/12] fix:client path error and update chat_knowledge prompt --- dbgpt/app/scene/chat_knowledge/v1/prompt.py | 14 +++++++++++--- dbgpt/client/client.py | 18 ++++++++++-------- 2 files changed, 21 insertions(+), 11 deletions(-) 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标签格式的信息,确保在答案中包含原文这些图片、链接、表格和代码标签,不要丢弃不要修改,如:图片格式:![image.png](xxx), 链接格式:[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 ![image.png](xxx), 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/client.py b/dbgpt/client/client.py index 5f41529f0..bb064f227 100644 --- a/dbgpt/client/client.py +++ b/dbgpt/client/client.py @@ -10,8 +10,8 @@ from dbgpt.core.schema.api import ChatCompletionResponse, ChatCompletionStreamRe from .schema import ChatCompletionRequestBody -CLIENT_API_PATH = "/api" -CLIENT_SERVE_PATH = "/serve" +CLIENT_API_PATH = "api" +CLIENT_SERVE_PATH = "serve" class ClientException(Exception): @@ -274,7 +274,7 @@ class Client: """ try: response = await self._http_client.get( - self._api_url + CLIENT_SERVE_PATH + path, + f"{self._api_url}/{CLIENT_SERVE_PATH}{path}", *args, ) return response @@ -290,7 +290,7 @@ class Client: """ try: return await self._http_client.post( - self._api_url + CLIENT_SERVE_PATH + path, + f"{self._api_url}/{CLIENT_SERVE_PATH}{path}", json=args, ) finally: @@ -305,7 +305,7 @@ class Client: """ try: return await self._http_client.post( - self._api_url + CLIENT_SERVE_PATH + path, + f"{self._api_url}/{CLIENT_SERVE_PATH}{path}", params=args, ) finally: @@ -318,7 +318,9 @@ class Client: path: str, The path to patch. args: Any, The arguments to pass to the patch. """ - return self._http_client.patch(self._api_url + CLIENT_SERVE_PATH + path, *args) + return self._http_client.patch( + f"{self._api_url}/{CLIENT_SERVE_PATH}{path}", *args + ) async def put(self, path: str, args): """Put method. @@ -329,7 +331,7 @@ class Client: """ try: return await self._http_client.put( - self._api_url + CLIENT_SERVE_PATH + path, json=args + f"{self._api_url}/{CLIENT_SERVE_PATH}{path}", json=args ) finally: await self._http_client.aclose() @@ -343,7 +345,7 @@ class Client: """ try: return await self._http_client.delete( - self._api_url + CLIENT_SERVE_PATH + path, *args + f"{self._api_url}/{CLIENT_SERVE_PATH}{path}", *args ) finally: await self._http_client.aclose() From 0cf08f37b0024c411c68ee1da867c32b3be2ace2 Mon Sep 17 00:00:00 2001 From: aries_ckt <916701291@qq.com> Date: Thu, 21 Mar 2024 15:30:57 +0800 Subject: [PATCH 10/12] fix:update app schemas. --- dbgpt/client/schema.py | 104 +++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 29 deletions(-) diff --git a/dbgpt/client/schema.py b/dbgpt/client/schema.py index de33e5351..b4e8ad397 100644 --- a/dbgpt/client/schema.py +++ b/dbgpt/client/schema.py @@ -1,16 +1,14 @@ """this module contains the schemas for the dbgpt client.""" +import json from datetime import datetime from enum import Enum -from typing import List, Optional, Union +from typing import Any, Dict, List, Optional, Union from fastapi import File, UploadFile from dbgpt._private.pydantic import BaseModel, Field -from dbgpt.agent.resource.resource_api import AgentResource from dbgpt.rag.chunk_manager import ChunkParameters -"""Chat completion request body""" - class ChatCompletionRequestBody(BaseModel): """ChatCompletion LLM http request body.""" @@ -26,7 +24,8 @@ class ChatCompletionRequestBody(BaseModel): 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 " + "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( @@ -66,9 +65,6 @@ class ChatCompletionRequestBody(BaseModel): ) -"""Chat completion response""" - - class ChatMode(Enum): """Chat mode.""" @@ -78,27 +74,6 @@ class ChatMode(Enum): CHAT_KNOWLEDGE = "chat_knowledge" -"""Agent model""" - - -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[AgentResource]] = 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() - - -"""Awel team model""" - - class AwelTeamModel(BaseModel): """Awel team model.""" @@ -151,6 +126,77 @@ class AwelTeamModel(BaseModel): ) +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.""" From ab9d8a370e83cf6a8776ce91290d1ed1eb5d9a3c Mon Sep 17 00:00:00 2001 From: aries_ckt <916701291@qq.com> Date: Thu, 21 Mar 2024 15:59:46 +0800 Subject: [PATCH 11/12] fix:return conv_uid and update wechat --- assets/wechat.jpg | Bin 194545 -> 120607 bytes dbgpt/app/openapi/api_v1/api_v1.py | 4 +--- dbgpt/client/client.py | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/assets/wechat.jpg b/assets/wechat.jpg index cfc35352709c3ed1ac4d33dd7bbae1c91e28ff01..3dde06d24e824b070db596e0d1c324156c0725bf 100644 GIT binary patch literal 120607 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⁣cR$OC2C}a85{~1J9<}kIX^-auZxK(rW1_N^4I_q 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

J8YrzteI=qe@C-ErfVPg#2*VR}p}$f9 z)w%5`5!PmzD2Zx{!q_OE-YgGSnrHhDo4R9*rac?0n|-}29J`m5r|t;h+2X3bIX}6K zGTfY*a^<{b-4q9g+yJ-3G^XK|T-j13{=zX$LR9oF?}qDia1Q zdMr0tIK)1D@P>NF?NhmyW4jjhS-bTnB19tBd9$bXC(Iqq@m2z`yEit(Nf5BmaPNh(XfEBg6kbwK$CFr ze|j<%*F5U?&7wW#JHel{=*C*9w+bIBd*`}+V$Oyl-(_50_2XwlP(vzSldRH~BXzzV zgz0}yjmr5 zv2}V`!M8bB!ETD4=yrYz&I2t74)!Kxd)ASTpW^7hIi94fSQZ-6lphDG(h?W-?7Xd? zo~~7TVRLns9ilruDpW4wH|DjQYdRs3W{-AxHE5WAHu|Wt=n}s5{49o_0UYIgYIV`| zTAw)uS?UHZ8j@+<60fSI@h-&pna#8f-=*YP`l>Boq-sy{+T{KEQelqY^TT&y9_Wl) zIRcq|71|+ipd1!GB%jOL%;uMNj!YZ5Y(C79S{c06%FuC@oX+}Xmi>>{?L) zGVq`dI+gz7^&GV^qB8M$>70$+_DWh_O}UrT?qc5UN@Gg|J8=4dPjjb7ZODaEPbd9` zG5g2yM%{8kFxC+cV!%4%l90ALOSZ+y3-_Z8o_t^YwPU==O3t2Pjj!CzrzUqOpT4Oq zd5ToH#I^UL+sx2QhyaPgk_sn!=QD#%euZT#2YO7d07T3O|u z)w-{|nrN%m>BprNga9OwYMX&ZC>%dxAo#=$802t(kPI8x0mfvNE(8CutsRbL7R+0r z_aS0AE5I#TJO^?4UdFT?+~PWlK3c)U_Ih8_Dd zuM%D66A)x~@jn50i!c>qW@s!^nZ4w zlXX6J?V7D_a38b*>B5C|Vn@@ZH6=LCIC z(u$UP>02#f3vk`9y-(W>rzZBcLE91Gb9MC(&0%5$Prq$UKc}c`RIz=NI$Dk7c44yI zqpq%Qhtk&9`EN*d4*AED+$*D%@kVPWl_j_lWhL@XJclaa{gFH)IF{!#*;*#=e=OKG zb=}j;VNpz?PVD>2qw`49rN6ll;`^Gv(Ju}rl#PZW3O2LQy@c5m!)8^xrf+XU#IwbIv=vzIE)9{m~%u1zOh)qbdWQ0e#c&OpHRWOvO zx(yo&!=7{v&2Lzeui&pkit*9iZh!Ay-rQ-oaY$ZZ{YBT10Sh?Ypj&upLs1R$h3Uv+ zo>jFaJ-*nt_m2s!vmW=lnO>T8Pe0^dS3TdFxaYLAt>Ghdqd=YdktiYDPdUrYEBARg zyn|$%@X8}IDn4P=?%_grui$0H;IQ(62_DZvVoGNo=mSaw)_efG8X7lUjkkHFVA8&bTkpW1nP(^|qw3_n`D z2=7CfT1SpT_-je{X*XlU3HevUXgKCk1I92jaF^a<3hm@7WI zOz_En#X2>=Gb>304QOx?ClgX?ibNsiFM?Y?F5hCri`UL4Hm#w}6Yg&rF&df3OwY?F zSq)@&Jc+fgo|`k4Mm|ei2AoqS_a_upc2QRb_eG*;w}~DzU}%3ct+k-yox9V zWTkT_5$GC~k*;{n3J=2Ru4?xjz+68>xe^y{C zA4QjvIdm?Le-J&!Zjcwcx0=gaiHHY01g?2GNu2GoI+x|TuaiELst^({*n~hK{wOn& zCRrTVOPD(}UHrPRxc6_?Zh)fao&P2z34#rkiWIev^b0Jh z9~*`1(3EE<_r7i>S%wY9weL*&j$hhW_c<_K-D;=9NsQ<_8pZ|QG@@dyo&lrBK1H_X zYn@yY%5QyBy(HG_sN@-yLqC^U&u=c>@Z0hAlooEN;jaQ4+*1ULK>`lqzX4GuUcp8q zXf+FaWexl;6PwR_JYc!+`u@%+ZD+N_PW!1x$B!f(4qjRcpfL0$WGDU@M);Z#o!^I~ z#=jkp(iwnp3A~RjavY{Cs`AOLVhzfg)(NYuGs7+|*=cdZJCi$CYvp%+UFy&~&r)$* z0>#;>OiO@B4aoBErMPfSB6zCh8umBW#;`TljgB0>>l&)AA#1-U*J4FTEC%u^-0j%Q z5dYwXe(e+k-Vu8*vQr*+y#2-77AG0^wxC^W2U668&+TPv%mrn?A#BDf#xYu$HmpVH ziJ$tUYsYCbKdpoT~8L z?c~l1ws+J75-C=08js2+s4%;SFPyqD^ukWBW5}$aa_&HPn%dXhd&aPHN?idb9U;fz zbAQ}PJIP!)Vswf#G5EbGcWq$Pm!unCT)S7s|G1=fFg0J|ryszOGM4 z+PiVn8ts$M0NuXRt`V0!faqa1#s@k^F#p3Ji9i2=F!Ar4gujczidd7v_)-ms@~a}m z@!P~dNvIhL&P;DKoH4QrV#~Bdwx6;bz7U{Tb8x1> z>^}A@q$Uz(3T=Jy)8C%Vs4zDDZ-bW;|ED-&$Q>5T!*l4jvm!(h<~Y&PZNfKbmB2^* zY1*KwL35y!P(@JMzwdKU{N94XP{aOx>~PIjyVp2(xY}Ya$O&L1Vb1d*Mj_!E#fLT8 znK}d=zupmR>YTnTZLf^we}10reJAaPV`AyBCFarOtV{U07cGSBFc!PJi@vYXzfTLw? zaaCa|qI9UG{OhLc?#u2@#lr za;XLqU2Mp$t9X5Y{&?r@y>5kV+iq^O_Fgeik+yt*G^p`U^n8cNHBeoY9+~91RPGLX z!?w7z20w4utZET91UZ@ev8i%p^gh1xTlzKpDZ(Y^o)*@Vkg9hu@f@h^++QIvQQeZ|^TQzoYLV{za2Ie$-h zbvUOw0}K012nRfL6H@ziAM&{beCmKK@za+e2gq}DMDQu7D_fsOu87B+plY@4R@d?9 zl6MV1qDh!|!e31`6c8ZEuP(ZdAGi_Z0WviU;?YC24oPv8K^TK8u}l(u0ZQ6Xi29cg z4dttFIoOd{A6C}>p)M9atS?zp^NwD?3tQK=wL0;q?N3Oe^$#E4o1JXud`bNhF4oRg zyG#IXtM&p42mi=%$X`Bw(LTBU@5g5RuE<+UL{}gjL*7r80Y}f96(62z)h4uf#0Vep zOKu>%K5=Mutr3fMLaeJiD>;e~ziicHk0>QBr9pkKoiZ>Y2N z7sRE9%7Ow~;*5H`%A|!*>E>H)c@(C`pdMu)5V?l51KIMXur>Lj=0N2O$EN*`1MUQA z62{>reK1|D=FPW-IB6@3EW~WX)WQq=596>Iy7-f{POHA6w~S8B8lsFCi}ty5$C4c%tWB_~GI;nyzpu6Z3nPDY#9v?>TLvXc8fCBr9G zv(@kU56(#9lF*ddq)DG!B*rYkAPbe< z__Tlj5cjXY019=7ej7XeZwJnBszLMw=553?XN&PclPp3xO`Akr$VI{m-ej$|YdTl= zB=lqX$2pa!E7Vx?r7BfDj1IljlHX^0;DVhAp6i0CZ>@mGl%hn)t6 z(?zc66u4_RA_?*=Z&A28Q8a%xbsq)Bnd48I%1}Ut^J?)bo>k?AL>erxjMATjH+x>8&YKFd$mb%v7jes6m=P(O? zJDxqNMiS#&3O3kd=lK*Co*#aq?>E5R*1QTab9^hMy>bWjEQE_Kk7!(XdPnv2+T56m z1BTesbGL0@_;R+`nKmAO0X@(!miZ>wf`)$gy3kUYy3os0$e=0HzW-G*uU}$f5S`L#0Zh`}2i%0X*$} z0b1rlGbvuh!X%C5TdKcyUH{OM{pCTp#7Dz!JZqem7budqF@mu6B8Yp@Z~rAy{%_m_ zZpI~iI)a86jDQW^)Bqh>+F(j=kbX3DoaH~}T5t~a);m>;XT4}WCoo>^Y%m<9wMadI)4sKSd+@11GFcz;O^%mhu8g zD(a*de4MN~R4|Bn%qvtS^dY-*bPsppQKM)5@ehqwTm6mhKM2^XN4orOQF{|yB$V2W z&o{V=9LI;BrR?Svq)hPdC~yj5Hm7G;e|LJg>AL4g+`K$Cn> zpgRx+I|uOn&2mOwtH<%z2l1~>yKI$tsBBrAzH2iF!oYAV09<@;swe;G2=s)`Ee%@Gi@A1nfO!W z2x6{rd<0#rr}5MDh@Nb?&OY!J&hAuYq1JJ z&yK#yPn9yBUD(*Xco$*XVSDmVWCmu(5asa00(h*Z^PhxcjZ!yW1bfxkA9i4$v|M&y zag-GpHBzj^GcBi@wf5cc*dg!HGGTc2s-&Ks&*>X4Z5=hEpH(}m(+7Tvaa#ozNMjy! z$4D1I!aZ4h7~~&{5THR4%4h`^)pg`3?K!!O7cCE(jL)^jIB(f{?ZsBZoLLtB`B8J$ z*Hi2u4s;tR8sWjix1db|N1<_x*eJ-tqtPh8#Qx$_-Egsc^XazS{GI#sO*Gj0U}A8|X6u>TB^w$d(bW5c6v)1;nre@=fN$TDyO=edjD~)xE4Ed-86# z(xQ(Ydg&^{sP@5h;R3vvfZqa_HG!{t1Y&g0qs0`MT{?;`QLTvwGon>|7Ao~UI@~XX zciFxt`<)A-he%;5x_5FPl)rti7Fedsxc?qfUtduRF1eD(-A0`6DGWqM&ywf^7etvs zem%^JQ!QRgT0a!Y{V0;3HFfCVLC3>q98|`Oe)ATfneK!C+Eu(7B7>v2X(xZ;2Rlpv z!0-S8K?R+xEM5M>A+BrRr_l+S-L{g?16_(U)MnZDs(DLiodE!^(fQxQ0D3QdbE_z*^P9`cloiUS@6T4^JIgHmG)N-G+_X ze8yZ*`LvleL&JLxxX2qWmg}i)_Rkx}}G)m3U8g@ZMrPpewBA)#- zlLuP&I?pxN&Z>=1NI51ES+i?4FqYsOAGVtVpCo^8CW{%Z5!QZf&C_D@z_VI$C98+d zKeHsvdLE-v=Xm+;VM&SWvcO1+Mp)Z~ivUt7RRGl(&S;z$dCOh7<=})MZzJ2XT2{ort`VJ(M6Vdg6D-6OBF2NgC^Xcy| z*zJk4t+q=D_ek&fc{jz>+1%#b%CGYuxrfTQgklqkRsE6FRYI?#c4!6{`m-I`abv9c zN_*e9y1J`jFt;%hZ)Y)H8UAsN-~3i_Ze%TOM}>-T{3L_Bg|2D%uGJ4~jJ%ct^H(FFavsr33wD;%W_BB@cD_BX>N@Ev16P1f-e&*eUtWXoM-g(Xt%A-^;O9j@p)1<_qC{1l!OObf_eCPc_jJkEN8wV>~C#Ycpj~NlF5P-4)%N~IX5&$hZ z9*jEd2Xq2MwL!`UQOH_tW=1i%{=x9yPQcq~;l+z17)nU*3?(|QeFXPUE_D?)#&XI= zdeB||<`#3}f|G8^1;eD|T=V+zG0zXK$6(5o`d!Hv4Jv~SuJeW?dmE7>w8-Sbu#0EY zi5{JuYT3R^f|g#p`%c18SLN6{eGv5RcZR zn-_KLIb3yGzea!jz9QZeP2ee2@}-@4rqKae`)lubb`ejis;<0EeBGs=a9b~@My0CB z4x{onxR9Y{_`~$G6Th&DaTFT~r0v)Oy5g6hIAd=I@drm^zug^aWSjLxN#{=6E2-5$ z#k)EajYJ>tV%VIb%7%qqU1(pWzum$&&-DGDWS!55E;7SpNZtQ0=~L(54DT2z9@V4A zmM(2DsEh$Zo$EDi!}INW1vj@RJ6twb;wQ%bv0YMmfv-MBR{g$$DXJ&Tg-PDhm6=HN zkDfI#JMx-SYppl!%qXlkx-IcRcesB3Z8=Gm>eH(Mg)x3JBxoYbO9RMqw{B}M=H;ic zcT;xAdy^O5!sjU@H0SB9z9ZzY3HcaI^;+8gabrWMviI(A+mqMa!a7gaJm3imvl+IV z?(8%1|N8S zr;t=K-j#pFe9(2BYul?tZ0;t1x2Lz9mi|faSN)Cm%AT$A3B{J@1r0<0A$Lbfd8ZS7 zDCxrCokL!SNwqiTecLD%W_&~9JHrRhs_B8&_=nFRNzt2uh&R~coDg7PYC##zZKNdR zSrDy4V{Oj)(&dauPvRhd((LL!-*;YPX=@v|zWunKo{sk;hvDV(i5;5kq|7uFZ~LO8 zuyJXSpKpHMzT;69=^x?}!d-qSP*hm7P8@^~p*wvz0yDJ6p2k+07>TH?aBW%YWvLP; zl-aU$dD32^@a`L3F9H?UWll4M%H#@sVb2|Zfx&={9D`f1bhYtDiAzcfZjV|98+;khf(&%jN6p?oc}BOr_t^-l~b2o2PV{ z=z{#}eKL8O4zPC7s5f7$1?mS!>yfg+o)xV&>IQt{>OAj@DvwKspZ%m%hC6;~JiPs2 zjx|uPa*0Ca%yMQyVAWpKBMsHt#TAtg23pcOe~=%(+)-GsaqDx!v%27vvz?c0`hRzC zaN2&uFWVt9!Qy-AYZu~WZPbJ0T~HUYYUrrxMAsiC+1A!EkDdG%bS>PI$l0=eK=b-@ zN9klC5XOEr%@c?qmkSK@26OmP>r9~7Q%MA0zEBOo;8sa?>?;R8e?{S;O@5A7l&k9{l33mC0)%)B^7q} zdTaT+Cx1-tT$>Z}$SP`o^>@c-Y<|o)o0Eh9q|^(qgVJfg*>~DQS3K(1?=2f zpGBcV%}z8c&f!gRBj>t`>iXAzJR$j&5Z+kj&r>RIo0Q3|GAT$;OS|;`ef#`S8S8lt zQW|0={&$-Z%p#z=jJRSeb}X)PfsoLAe21C1jrY*YB*4V@o4Z`-@!ph|^~lC?i3apy zIixESiwjegoAN~qWtkt6jBVNs_XNJxymQ!f_KQ_FKN?notSocNoCy@$6(delFx^5IbO(DhoFC|O2p@IsVu_j6vw82X7koG-nHwrU`{QYu`&ibdHyf~Qd?kMF zJ_XZ?1Iya-@9J=L3UgXbj34wcg9{yWvv;mvS4;0`K>a7<83P&MR?!szg5%x3n{vDa z7DKe$qN+PK%0ts%N*VszG}6*vm42lbylI`=;$C4Q{ z!P!*(?R>p&z%!_-Ma!%U>e=+%OrlJSbcKEpoz0D&>}#mT&K2sm6sYCjr7iff;dx-5 zeT##vW2)DO=shpWkAzyb4>zA)Bsx!N!L^zQ5;1wlhm`IvA}S!uk@!Mc}sg-4e4AFtk)n|!gQvBs2UeRHnf>iL(@ul%RTNu*P6yXZD` zUJu3NQ@+o{@I|XSr`5-N&AQ%I|JZT)%Ig;r7ZT!5NDjOgTQPiz(SKr;^O4RPcJ}Q@ zXbmwpuaIXxe%hmJ=(h9b1%}+GQOjJNKVxQ7AAm|Y1qy6i=#Z8!dyW$xXUw&+5pFL) z>v(C^L=~e`M_Y`A&Y9gOojdAuk{lPFe`gu$-Y6@nUmgG{W7ForM~d+sr|aSzHz3AD_te9O``dVLIT}+F0G^ z^R6$djNUhEr^FzUp@JUzE%A*(Hfa2R8hZW%jam9z*xbhTVG|>WTxkv2NT8VW=Dj_| zd*sV$MZ?MROiy?BFC2mA{2fI&R!zevu@UGG;`)!f^lg6LNv}`St%*-FpW$)vo=*!2m%zD7^|O(yMd`h%_Tj zq<0YMAU)Jjq(~JIRHO<>2bB(?cj;XTE%Z(zE!5|%XP@Dx8zz^qH(r(hXq;hl4G?U{Nx~CWVx82A7I32z$&sN;R}1qqwdL=q_*} zcycfX3w(Ob*WXW!D_k}+#PS?gy*m|PZT(kA+Eq^WFKg?xf@d-cNNPs~{8;=KTY z+QGR1$buNUTrVM6-^zr0!N0$rS$O)73G+(U#U8ZMhL7aTbGGdX;E@R)1MAif09Mrj zRkaAe5464?tX1ZzhUoq|jUE{}%qdFKO0*|k*Z^ak4v1z}7lqE~&JB-%;~hcuWG%^3 zER-x`WX`d3i!Lu-w>mUrN@khyCf=!{Cn4cCnVT zW=DfxFwGEn1Fp_xkPycJH_dhTjORAvQ(^BP^#*$Ho~RRXHmuY*fX{t_uJ3WoNZZ^u zv=t~UI4Zj0$bc^}yR0hsj52^?&z(<6$4}r;7ii=iZCfv9#}#^*Sm#czOGrUH@lAix zWX)yKkzhsMCv3umYPrXD3Yn+?A1q2Gokuuy>E23{q1f$LdZsz5yhpt+T| zK2g(H;P|RKT+KH9)mshj`Q(fsM&BH#8`s)$;Z~6*sc9_=m(Yi|*8gdz&ejP@I2Le4 zI#wkYEl~QvI*G!7b*TE%@L=hRim~@}5QZxi5?GH2?oFy6R(*zc3wTL7dyqU{6Rsg4 z(VeX!T!hpA94ITRx%4tz$}jj(;FdjW!>v&M@qdD%4F6OEz2rA>BHM<#wc!lY*hspe z8V0x{4}AKm&p@#^C8h5#d~ix)rxPKKKtCk`_8#d;Wu|{)z)2ot|J;z8 zHTYPK)GCFJbML6PSMAvps}yd(Q~%NBi0_6h9jVeT{7{UA@+)#-${g-UlzXALFFWIht1I_VY zvR(gA{b_Vs?|G_bqb95RmllU#ibp&S)7TCx>AuZl0E7NG_K4y({Ljk4%4%1(K>a0Q zvo%A?6YyF?0^ze{-|Yd=YQrkKE+WB#@THyT;zRcMIA8q0&00{vKFyI|SgVlZoNL;8 z(pz`I!)JcanP6YvX03-cxg8L37P+j}1{_Ao1)~2pBhz%wuNlIyCtv-Fw{?_S&PI+w z9929+c(Iic%Tsl!W+_kTRJudV?EdI6jK~C|&HS!}C;-0eXiwiY&Ixof$JgJ6hQlE@ zA%f7uE`!l%0G$Zyo+~g>NE09R686knv7=Yv_gCl)m8Xv7825tFL z3#8SU8j{J_21fNBbgxQv5rd8|_y$|=!;@Z2pa5VLQ?fIn%#zE-ycBiM3}VAf(`y`% z7&;ZghkcOu1>V+`suIAa)|y;?kZ1mefAn3V5!r$fYXhD(z#*xiA@!p@V)_0wQE`MO zOzMivKPe%|c2$T})5pg?x5uYo?u#@sFj6bmer@D{eziy(Z|A*XD$cK`jj}fG^KExI zF_yZ|e&6TT%u4v~By`ldEsQ0KWV}6LOStz-tIlKlcjAPc+@@(nlUH-hQ~qD z#KmMLtDrX{Va*`_?HvISngBuZn! zoS>dAR6)36t_ZOo5Br+cNiJCvpTefWM0vp5H3Iy8}Haq01dsXvV!8Nm6dh8S7W?L zfB#+5Qn@|Bl&=|fH0a_vJg2vn1@ngILA^Jpzt?(aD6%RMt)_JFT7C;$`UP37hU;5) zwb|!S-+*aw$?7bqt>@oCz2?uq!B+{$0^K!~2Y64bk1>n+M=`^SdBiPs?rz)a=70uDdH13>Y| z*&f{&_b7+vx09&zJ5-m4b-+xaYXtuJUHe1XwYBVHyscwh3(Kl23ygjAp`~_;ag`g# z{gB|6GUUeW8K19l| zxZdfJuyh|gNMB+1ZRjvmJ+@<~3>KTM1-a|0z-MY#*;t_oRY)E*OkQT$7}fJybLPag zvLyRSEXB~g$3&=ndMu#c$d&-W+DXQthmBC&iOuef5UvHC#cOV_Kk02(_a@gfO4U6p zJQ07fKnKz?{^IyDy7^mO(>u_SGIKm#E`M&O&uYes+YK+Au=l{3ujPfBJTmF-+choU zChBrgPWI(QK*l*w*HUhajZVB-=+4eez{n4G@3%1-oqO#&wkL1C^fI;Lp^`qx<62e` z@!4$Vghq1ri~Z*kI;c2f3Y2cuFQy7cypy6I7=SSAK#;l?u)-h%%%iWLU0sWD(fc2C zyUWBk=IoA`$+c+NL8DqSJ{!%u6`uDom+F*TwEj3)tp1&H;$bf*u|IpL(nZo!(8crEKSDSQRy-ij5|?y| znW9Lz!Q*Cg0JuACWQwfKDqa>3u@)cDBt$1HW>0O>t#6(4wA81)Vqjpl)`TY(VWaxt z_0Oh`2jgZZg6d;0#?f}Y!1YvXkgYwnkXd5)4 zw)CUuWg)t{q7rz@rr|UP?b~9cgoq}`gZ_MhpPTqm1i@;+-&ZyMLZDVt^i-R7KZD1|iukFa(p+P=#w{Ex;1RtZJDy#+D*2i(CC>HC7HGWEn0}Pb zTmMi<>xHNG0toIP`9h5{GhoS(vq?yM_F8V@CoGecY}o@?0WV^)9|5n2H4U!p`{M%O zVi#rFKcZq+KJz%U9oqB?=gQr>i=L^JbPdi#si!62HR5Op{+&$lFvzFIkZJ|Npowno zuizYiB>zl18f>B@!>xgchT3ii{H!IeZ*-wzcgrroCCfVRpFXYx{Z!`-=^XXc7Ap1 zM!mDL@@{8w08bTHfw6|&EP;Ahd`9;!T&2){&4B(1gUh%XEH#P8IPE{6^zP)KpOONq>B}4h)^W>Tl3?8*ojqg z_gDxwVy3$(mA*JJ7Jnl7@FS+bBqCk}V5aUD=aMsZWIvolzN$AVmkInm|66@V%Z=tq zSH9?)Ct*Oz0beM#KAt~FuNcuuRJWjs!p_v0?%4`4j$KijjEWWq?gZfE00oiGAo z$`h@3wj~t=v24FLXWhE^DHX#w4XwL%N<$%+E)a0o&8#10Nd*ui5L`9DGEcM?m1~}s zeDRgN2RX=n{JMz6V>0rFN|E)=c>ZcYRA2G1PeKoDl<|uf{_uukD&!YORm}ER4+2e(X3Ys+BtS zruX3BNyd>*n!-rXH`p*Q3ksXIoEcPB*P1+Yk|o=9@$#az>|yNUf**B2>hBkRujKG{ zE#+{AeNBqfJ`{HsmAp!KFTRVQei=WRsIC3N%w_kDP9W6OEdU&!@(}!C+PxfUzj_Ur zB)MiYC2v&MrmyJHr!eWu7Kl<)jNXHI$YsmKj$`YiLKqy6ww%$IafYx2)rVC<%}cVb zjZhI==Ph zHG=Gp;-1q(9y3I~{l-EL`a%a%hZF0vr11W{eHg^Tf5(g-S@KL0;ecWic2hm=eToS# z%fz9|OgeIZJBW?kW2{(%i7{W*VTJ8ILEOYB=Ms$GmAoSmsi282B|p_FjGw-4-yc9_ zEuL4gcxGj>pII2ifpw%NHN;yo?yq_|o6v;9@I*%0Dlz>)F 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 TzyCk?kbg(R{=X#Y=l6dDc8RTc literal 194545 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%0Ae9q`y%#9^X$Oa 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 z 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 bSYXhkK2%>JjqtPE^B2?S|MaH%)A&CD=WQt) diff --git a/dbgpt/app/openapi/api_v1/api_v1.py b/dbgpt/app/openapi/api_v1/api_v1.py index 6c3dd0d86..87b69634e 100644 --- a/dbgpt/app/openapi/api_v1/api_v1.py +++ b/dbgpt/app/openapi/api_v1/api_v1.py @@ -439,9 +439,7 @@ async def stream_generator(chat, incremental: bool, model_name: str): _type_: streaming responses """ 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: @@ -453,7 +451,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/client/client.py b/dbgpt/client/client.py index bb064f227..eccbd26ae 100644 --- a/dbgpt/client/client.py +++ b/dbgpt/client/client.py @@ -247,7 +247,7 @@ class Client: if response.status_code == 200: async for line in response.aiter_lines(): try: - if line == "data: [DONE]\n": + if line.strip() == "data: [DONE]": break if line.startswith("data:"): json_data = json.loads(line[len("data: ") :]) From f117f4d29774d0afb6ab8bfb4fcb5c7e5bb17482 Mon Sep 17 00:00:00 2001 From: aries_ckt <916701291@qq.com> Date: Thu, 21 Mar 2024 16:06:09 +0800 Subject: [PATCH 12/12] fix:api_v1 msg error --- dbgpt/app/openapi/api_v1/api_v1.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dbgpt/app/openapi/api_v1/api_v1.py b/dbgpt/app/openapi/api_v1/api_v1.py index 87b69634e..300f5f8ae 100644 --- a/dbgpt/app/openapi/api_v1/api_v1.py +++ b/dbgpt/app/openapi/api_v1/api_v1.py @@ -439,6 +439,7 @@ async def stream_generator(chat, incremental: bool, model_name: str): _type_: streaming responses """ span = root_tracer.start_span("stream_generator") + msg = "[LLM_ERROR]: llm server has no output, maybe your prompt template is wrong." previous_response = "" async for chunk in chat.stream_call():