diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8dd7e174f..d2d768bb8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,7 +129,8 @@ jobs: ${{ env.DOCKERHUB_REPO }}bisheng-frontend:${{ steps.get_version.outputs.VERSION }} - name: notify feishu - uses: sozo-design/curl@v1.0.2 + uses: fjogeleit/http-request-action@v1 with: - args: -X POST -d '{"msg_type":"text","content":{"text":"latest version 编译成功"}}' https://open.feishu.cn/open-apis/bot/v2/hook/2cfe0d8d-647c-4408-9f39-c59134035c4b - + url: ${{ secrets.FEISHU_WEBHOOK }} + method: 'POST' + data: '{"msg_type":"text","content":{"text":" ${{ steps.get_version.outputs.VERSION }}发布成功"}}' \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e683528fb..234795479 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -exclude: ^scripts|docs|docker|requirements|README.md +exclude: ^scripts|docs|docker|requirements|README.md|test repos: - repo: https://github.com/PyCQA/flake8.git rev: 3.8.3 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 95c6278da..68880e735 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -40,7 +40,7 @@ services: office: container_name: bisheng-office - image: onlyoffice/documentserver:7.1.1 + image: onlyoffice/documentserver:7.2.1 ports: - "8701:80" environment: @@ -144,28 +144,28 @@ services: timeout: 20s retries: 3 - milvus: - container_name: milvus-standalone - image: milvusdb/milvus:v2.3.3 - command: ["milvus", "run", "standalone"] - security_opt: - - seccomp:unconfined - environment: - ETCD_ENDPOINTS: etcd:2379 - MINIO_ADDRESS: minio:9000 - volumes: - - /etc/localtime:/etc/localtime:ro - - ${DOCKER_VOLUME_DIRECTORY:-.}/data/milvus:/var/lib/milvus - restart: on-failure - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"] - start_period: 90s - interval: 30s - timeout: 20s - retries: 3 - ports: - - "19530:19530" - - "9091:9091" - depends_on: - - etcd - - minio + # milvus: + # container_name: milvus-standalone + # image: milvusdb/milvus:v2.3.3 + # command: ["milvus", "run", "standalone"] + # security_opt: + # - seccomp:unconfined + # environment: + # ETCD_ENDPOINTS: etcd:2379 + # MINIO_ADDRESS: minio:9000 + # volumes: + # - /etc/localtime:/etc/localtime:ro + # - ${DOCKER_VOLUME_DIRECTORY:-.}/data/milvus:/var/lib/milvus + # restart: on-failure + # healthcheck: + # test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"] + # start_period: 90s + # interval: 30s + # timeout: 20s + # retries: 3 + # ports: + # - "19530:19530" + # - "9091:9091" + # depends_on: + # - etcd + # - minio diff --git a/src/backend/bisheng/api/services/chat_imp.py b/src/backend/bisheng/api/services/chat_imp.py index bdf47cd22..799a0749e 100644 --- a/src/backend/bisheng/api/services/chat_imp.py +++ b/src/backend/bisheng/api/services/chat_imp.py @@ -1,5 +1,5 @@ -from bisheng.api.v1.schemas import ChatMessage from bisheng.database.base import session_getter +from bisheng.database.models.message import ChatMessage def comment_answer(message_id: int, comment: str): diff --git a/src/backend/bisheng/api/services/knowledge_imp.py b/src/backend/bisheng/api/services/knowledge_imp.py new file mode 100644 index 000000000..197dd1f34 --- /dev/null +++ b/src/backend/bisheng/api/services/knowledge_imp.py @@ -0,0 +1,38 @@ +import re +import time +from uuid import uuid4 + +from bisheng.database.base import session_getter +from bisheng.database.models.knowledge import Knowledge, KnowledgeCreate +from bisheng.settings import settings +from fastapi import HTTPException +from sqlmodel import select + + +def create_knowledge(knowledge: KnowledgeCreate, user_id: int): + """ 创建知识库. """ + knowledge.is_partition = knowledge.is_partition or settings.get_knowledge().get( + 'vectorstores', {}).get('Milvus', {}).get('is_partition', True) + db_knowldge = Knowledge.model_validate(knowledge) + with session_getter() as session: + know = session.exec( + select(Knowledge).where(Knowledge.name == knowledge.name, + knowledge.user_id == user_id)).all() + if know: + raise HTTPException(status_code=500, detail='知识库名称重复') + if not db_knowldge.collection_name: + if knowledge.is_partition: + embedding = re.sub(r'[^\w]', '_', knowledge.model) + suffix_id = settings.get_knowledge().get('vectorstores').get('Milvus', {}).get( + 'partition_suffix', 1) + db_knowldge.collection_name = f'partition_{embedding}_knowledge_{suffix_id}' + else: + # 默认collectionName + db_knowldge.collection_name = f'col_{int(time.time())}_{str(uuid4())[:8]}' + db_knowldge.index_name = f'col_{int(time.time())}_{str(uuid4())[:8]}' + db_knowldge.user_id = user_id + with session_getter() as session: + session.add(db_knowldge) + session.commit() + session.refresh(db_knowldge) + return db_knowldge.copy() diff --git a/src/backend/bisheng/api/v1/endpoints.py b/src/backend/bisheng/api/v1/endpoints.py index 952376e78..94da3bb1a 100644 --- a/src/backend/bisheng/api/v1/endpoints.py +++ b/src/backend/bisheng/api/v1/endpoints.py @@ -10,7 +10,7 @@ from bisheng.cache.redis import redis_client from bisheng.cache.utils import save_uploaded_file from bisheng.chat.utils import judge_source, process_source_document -from bisheng.database.base import get_session +from bisheng.database.base import get_session, session_getter from bisheng.database.models.config import Config from bisheng.database.models.flow import Flow from bisheng.database.models.message import ChatMessage @@ -110,7 +110,6 @@ def save_config(data: dict, session: Session = Depends(get_session)): @router.post('/predict/{flow_id}', response_model=UnifiedResponseModel[ProcessResponse]) @router.post('/process/{flow_id}', response_model=UnifiedResponseModel[ProcessResponse]) async def process_flow( - session: Annotated[Session, Depends(get_session)], flow_id: str, inputs: Optional[dict] = None, tweaks: Optional[dict] = None, @@ -124,9 +123,10 @@ async def process_flow( """ if inputs and isinstance(inputs, dict) and 'id' in inputs: inputs.pop('id') - + logger.info(f'act=api_call sessionid={session_id} flow_id={flow_id}') try: - flow = session.get(Flow, flow_id) + with session_getter() as session: + flow = session.get(Flow, flow_id) if flow is None: raise ValueError(f'Flow {flow_id} not found') if flow.data is None: @@ -201,10 +201,16 @@ async def process_flow( session.commit() session.refresh(message) extra.update({'source': source, 'message_id': message.id}) - task_result.update(extra) - task_result.update({'result': result}) - if source != 0: + + if source == 1: await process_source_document(source_documents, session_id, message.id, answer) + elif source == 4: + # QA + extra_qa = json.loads(answer.metadata.get('extra')) + extra_qa.pop('answer', None) + extra.update({'doc': [extra_qa]}) + task_result.update(extra) + task_result.update({'answer': result}) except Exception as e: logger.error(e) diff --git a/src/backend/bisheng/api/v1/knowledge.py b/src/backend/bisheng/api/v1/knowledge.py index 42f16e029..d881a2262 100644 --- a/src/backend/bisheng/api/v1/knowledge.py +++ b/src/backend/bisheng/api/v1/knowledge.py @@ -35,6 +35,7 @@ from langchain.schema import Document from langchain.text_splitter import CharacterTextSplitter from langchain.vectorstores.base import VectorStore +from pymilvus import Collection from sqlalchemy import func, or_ from sqlmodel import Session, select @@ -167,19 +168,18 @@ async def process_knowledge(*, @router.post('/create', response_model=UnifiedResponseModel[KnowledgeRead], status_code=201) -def create_knowledge(*, - session: Session = Depends(get_session), - knowledge: KnowledgeCreate, - Authorize: AuthJWT = Depends()): +def create_knowledge(*, knowledge: KnowledgeCreate, Authorize: AuthJWT = Depends()): + """ 创建知识库. """ Authorize.jwt_required() payload = json.loads(Authorize.get_jwt_subject()) - """ 创建知识库. """ + user_id = payload.get('user_id') knowledge.is_partition = knowledge.is_partition or settings.get_knowledge().get( 'vectorstores', {}).get('Milvus', {}).get('is_partition', True) db_knowldge = Knowledge.model_validate(knowledge) - know = session.exec( - select(Knowledge).where(Knowledge.name == knowledge.name, - knowledge.user_id == payload.get('user_id'))).all() + with session_getter() as session: + know = session.exec( + select(Knowledge).where(Knowledge.name == knowledge.name, + knowledge.user_id == user_id)).all() if know: raise HTTPException(status_code=500, detail='知识库名称重复') if not db_knowldge.collection_name: @@ -192,11 +192,12 @@ def create_knowledge(*, # 默认collectionName db_knowldge.collection_name = f'col_{int(time.time())}_{str(uuid4())[:8]}' db_knowldge.index_name = f'col_{int(time.time())}_{str(uuid4())[:8]}' - db_knowldge.user_id = payload.get('user_id') - session.add(db_knowldge) - session.commit() - session.refresh(db_knowldge) - return resp_200(db_knowldge) + db_knowldge.user_id = user_id + with session_getter() as session: + session.add(db_knowldge) + session.commit() + session.refresh(db_knowldge) + return resp_200(db_knowldge.copy()) @router.get('/', status_code=200) @@ -304,7 +305,7 @@ def delete_knowledge(*, # 处理vector embeddings = FakeEmbedding() vectore_client = decide_vectorstores(knowledge.collection_name, 'Milvus', embeddings) - if vectore_client.col: + if isinstance(vectore_client.col, Collection): logger.info(f'drop_vectore col={knowledge.collection_name}') if knowledge.collection_name.startswith('col'): vectore_client.col.drop() @@ -486,6 +487,7 @@ def addEmbedding(collection_name, index_name, knowledge_id: int, model: str, chu logger.info('process_file_done file_name={} file_id={} time_cost={}', knowledge_file.file_name, knowledge_file.id, time.time() - ts1) + except Exception as e: logger.error('insert_metadata={} ', metadatas, e) session = next(get_session()) @@ -592,8 +594,8 @@ def file_knowledge( status=1, object_name=metadata_extra.get('url')) session.add(db_file) - result = db_file.model_dump() session.flush() + result = db_file.model_dump() try: metadata = [{ @@ -633,8 +635,8 @@ def text_knowledge( try: embeddings = decide_embeddings(db_knowledge.model) vectore_client = decide_vectorstores(db_knowledge.collection_name, 'Milvus', embeddings) - es_client = decide_vectorstores(db_knowledge.index_name, 'ElasticKeywordsSearch', - embeddings) + index_name = db_knowledge.index_name or db_knowledge.collection_name + es_client = decide_vectorstores(index_name, 'ElasticKeywordsSearch', embeddings) except Exception as e: logger.exception(e) diff --git a/src/backend/bisheng/api/v2/filelib.py b/src/backend/bisheng/api/v2/filelib.py index bb1a8b0ed..92238045e 100644 --- a/src/backend/bisheng/api/v2/filelib.py +++ b/src/backend/bisheng/api/v2/filelib.py @@ -1,7 +1,6 @@ -import time from typing import Optional -from uuid import uuid4 +from bisheng.api.services import knowledge_imp from bisheng.api.v1.knowledge import (addEmbedding, decide_embeddings, decide_vectorstores, file_knowledge, text_knowledge) from bisheng.api.v1.schemas import ChunkInput, UnifiedResponseModel, resp_200 @@ -26,26 +25,10 @@ @router.post('/', response_model=KnowledgeRead, status_code=201) -def create_knowledge( - *, - session: Session = Depends(get_session), - knowledge: KnowledgeCreate, -): +def creat(knowledge: KnowledgeCreate): """创建知识库.""" - db_knowldge = Knowledge.from_orm(knowledge) - know = session.exec( - select(Knowledge).where( - Knowledge.name == knowledge.name, - knowledge.user_id == settings.get_from_db('default_operator').get('user'))).all() - if know: - raise HTTPException(status_code=500, detail='知识库名称重复') - if not db_knowldge.collection_name: - # 默认collectionName - db_knowldge.collection_name = f'col_{int(time.time())}_{str(uuid4())[:8]}' - db_knowldge.user_id = settings.get_from_db('default_operator').get('user') - session.add(db_knowldge) - session.commit() - session.refresh(db_knowldge) + user_id = knowledge.user_id or settings.get_from_db('default_operator').get('user') + db_knowldge = knowledge_imp.create_knowledge(knowledge, user_id) return db_knowldge diff --git a/src/backend/bisheng/database/models/knowledge_file.py b/src/backend/bisheng/database/models/knowledge_file.py index fe8bd65b3..80d8291a7 100644 --- a/src/backend/bisheng/database/models/knowledge_file.py +++ b/src/backend/bisheng/database/models/knowledge_file.py @@ -13,7 +13,7 @@ class KnowledgeFileBase(SQLModelSerializable): md5: Optional[str] = Field(index=False) status: Optional[int] = Field(index=False) object_name: Optional[str] = Field(index=False) - remark: Optional[str] = Field(sa_column=String(length=512)) + remark: Optional[str] = Field(sa_column=Column(String(length=512))) create_time: Optional[datetime] = Field( sa_column=Column(DateTime, nullable=False, server_default=text('CURRENT_TIMESTAMP'))) update_time: Optional[datetime] = Field( diff --git a/src/backend/bisheng/default_node.yaml b/src/backend/bisheng/default_node.yaml index d32b77e7b..3851d4847 100644 --- a/src/backend/bisheng/default_node.yaml +++ b/src/backend/bisheng/default_node.yaml @@ -192,6 +192,8 @@ llms: documentation: "" CustomLLMChat: documentation: "" + SenseChat: + documentation: "" ### # There's a bug in this component deactivating until we get it sorted: _language_models.py", line 804, in send_message # is_blocked=safety_attributes.get("blocked", False), diff --git a/src/backend/bisheng/initdb_config.yaml b/src/backend/bisheng/initdb_config.yaml index ab0370e1c..a37bceb91 100644 --- a/src/backend/bisheng/initdb_config.yaml +++ b/src/backend/bisheng/initdb_config.yaml @@ -10,7 +10,7 @@ knowledges: # openai_api_version: "" # azure api_version embedding-host: # 知识库下拉框中显示的embedding模型的名称,可自定义 host_base_url: "" # 在模型管理页面中已上线的embedding服务的地址 - model: "" # 在模型管理页面中已上线的embedding模型的名称 + model: "" # 在模型管理页面中已上线的embedding模型的名称 vectorstores: # Milvus 最低要求cpu 4C 8G 推荐4C 16G Milvus: # 如果需要切换其他vectordb,确保其他服务已经启动,然后配置对应参数 diff --git a/src/backend/bisheng/settings.py b/src/backend/bisheng/settings.py index fad145ddb..c46eb5404 100644 --- a/src/backend/bisheng/settings.py +++ b/src/backend/bisheng/settings.py @@ -71,7 +71,7 @@ def set_redis_url(cls, values): if match: password = match.group(0) new_password = decrypt_token(password) - new_redis_url = re.sub(pattern, f':{new_password}@', values['redis_url']) + new_redis_url = re.sub(pattern, f'{new_password}', values['redis_url']) values['redis_url'] = new_redis_url return values diff --git a/src/backend/bisheng/template/frontend_node/autogenrole.py b/src/backend/bisheng/template/frontend_node/autogenrole.py index 9edf6fabf..9de42433e 100644 --- a/src/backend/bisheng/template/frontend_node/autogenrole.py +++ b/src/backend/bisheng/template/frontend_node/autogenrole.py @@ -15,53 +15,58 @@ def format_field(field: TemplateField, name: Optional[str] = None) -> None: field.show = True def add_extra_fields(self) -> None: - if self.name in {'AutoGenAssistant', - 'AutoGenGroupChatManager', - }: - self.template.add_field( - TemplateField( - field_type='float', - required=True, - show=True, - name='temperature', - advanced=False, - value=0 - )) - self.template.add_field( - TemplateField( - field_type='str', - required=True, - show=True, - name='model_name', - value='gpt-4-0613', - advanced=False, - )) - self.template.add_field( - TemplateField( - field_type='str', - required=False, - show=True, - name='openai_api_base', - value='', - advanced=False, - )) - self.template.add_field( - TemplateField( - field_type='str', - required=False, - show=True, - name='openai_api_key', - value='', - advanced=False, - )) - self.template.add_field( - TemplateField( - field_type='str', - required=False, - show=True, - name='openai_proxy', - advanced=False, - )) + if self.name in { + 'AutoGenAssistant', + 'AutoGenGroupChatManager', + }: + self.template.add_field( + TemplateField(field_type='float', + required=True, + show=True, + name='temperature', + advanced=False, + value=0)) + self.template.add_field( + TemplateField(field_type='str', + required=True, + show=True, + name='model_name', + value='gpt-4-0613', + advanced=False)) + self.template.add_field( + TemplateField(field_type='str', + required=False, + show=True, + name='openai_api_base', + value='', + advanced=False)) + self.template.add_field( + TemplateField(field_type='str', + required=False, + show=True, + name='openai_api_key', + value='', + advanced=False)) + self.template.add_field( + TemplateField(field_type='str', + required=False, + show=True, + name='openai_proxy', + advanced=False)) + self.template.add_field( + TemplateField(field_type='str', + required=False, + show=True, + name='api_type', + info='azure', + advanced=False)) + self.template.add_field( + TemplateField(field_type='str', + required=False, + show=True, + name='api_version', + info='azure', + advanced=False)) if self.name == 'AutoGenGroupChatManager': self.template.add_field( @@ -74,23 +79,19 @@ def add_extra_fields(self) -> None: advanced=False, )) self.template.add_field( - TemplateField( - field_type='int', - required=False, - show=True, - name='max_round', - advanced=False, - value=50 - )) + TemplateField(field_type='int', + required=False, + show=True, + name='max_round', + advanced=False, + value=50)) self.template.add_field( - TemplateField( - field_type='str', - required=False, - show=True, - name='system_message', - advanced=False, - value='Group chat manager.' - )) + TemplateField(field_type='str', + required=False, + show=True, + name='system_message', + advanced=False, + value='Group chat manager.')) self.template.add_field( TemplateField( field_type='str', @@ -121,23 +122,19 @@ def add_extra_fields(self) -> None: if self.name == 'AutoGenUser': self.template.add_field( - TemplateField( - field_type='int', - required=False, - show=True, - name='max_consecutive_auto_reply', - advanced=False, - value=10 - )) + TemplateField(field_type='int', + required=False, + show=True, + name='max_consecutive_auto_reply', + advanced=False, + value=10)) self.template.add_field( - TemplateField( - field_type='str', - required=True, - show=True, - name='human_input_mode', - advanced=False, - value='ALWAYS' - )) + TemplateField(field_type='str', + required=True, + show=True, + name='human_input_mode', + advanced=False, + value='ALWAYS')) if self.name == 'AutoGenCustomRole': self.template.add_field( TemplateField( diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index c01bb9669..8080a9d25 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -18,8 +18,8 @@ include = ["./bisheng/*", "bisheng/**/*"] bisheng = "bisheng.__main__:main" [tool.poetry.dependencies] -bisheng_langchain = "0.2.2.3" -bisheng_pyautogen = "0.1.19" +bisheng_langchain = "0.2.2.4" +bisheng_pyautogen = "0.2.0" minio = "7.2.0" loguru = "^0.7.1" fastapi_jwt_auth = "^0.5.0" diff --git a/src/backend/test/milvus_trans.py b/src/backend/test/milvus_trans.py index 8096013cf..91e3ef53a 100644 --- a/src/backend/test/milvus_trans.py +++ b/src/backend/test/milvus_trans.py @@ -3,12 +3,12 @@ import requests from bisheng_langchain.embeddings import HostEmbeddings from bisheng_langchain.vectorstores import Milvus -from pymilvus import Collection, MilvusException +from pymilvus import Collection, MilvusClient, MilvusException from sqlmodel import Session, create_engine params = {} params['connection_args'] = { - 'host': '192.168.106.109', + 'host': '192.168.106.116', 'port': '19530', 'user': '', 'password': '', @@ -23,6 +23,23 @@ def milvus_clean(): + milvus_cli = MilvusClient(uri='http://192.168.106.109:19530') + + collection = milvus_cli.list_collections() + for col in collection: + if col.startswith('tmp'): + print(col) + milvus_cli.drop_collection(col) + # if not col.startswith('rag'): + # if milvus_cli.num_entities(col) < 10: + # print(col) + # milvus_cli.drop_collection(col) + + +milvus_clean() + + +def milvus_trans(): params['collection_name'] = 'partition_textembeddingada002_knowledge_1' openai_target = Milvus.from_documents(embedding=embedding, **params) params['collection_name'] = 'partition_multilinguale5large_knowledge_1' @@ -145,19 +162,20 @@ def milvus_clean(): def elastic_clean(): - url = 'http://192.168.106.116:9200/_stats' - headers = {'Authorization': 'Basic ZWxhc3RpYzpvU0dMLXpWdlo1UDNUbTdxa0RMQw=='} + url = 'http://192.168.106.109:9200/_stats' + user_name = 'elastic' + auth = 'MBDsrs5O_zHCE+12na3f' del_url = 'http://192.168.106.116:9200/%s' - col = requests.get(url, headers=headers).json() + col = requests.get(url, auth=(user_name, auth)).json() for c in col.get('indices').keys(): if c.startswith('tmp'): print(c) - x = requests.delete(del_url % c, headers=headers) + # x = requests.delete(del_url % c, headers=headers) # url = f'http:// - elif col.get('indices').get(c).get('primaries').get('docs').get('count') == 0: - print(c) - x = requests.delete(del_url % c, headers=headers) - print(x) + # elif col.get('indices').get(c).get('primaries').get('docs').get('count') == 0: + # print(c) + # x = requests.delete(del_url % c, headers=headers) + # print(x) -elastic_clean() +# elastic_clean() diff --git a/src/backend/test/test.py b/src/backend/test/test.py index 4ed8ef2ad..726df9eac 100644 --- a/src/backend/test/test.py +++ b/src/backend/test/test.py @@ -1,46 +1,3 @@ -# from http.server import HTTPServer, BaseHTTPRequestHandler -# import json -# import time - -# class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): - -# def do_POST(self, **kwargs): -# response = { -# 'id': -# 'chatcmpl-1016', -# 'created': -# time.time(), -# 'model': -# 'Qwen-14B-Chat', -# 'choices': [{ -# 'index': 0, -# 'message': { -# 'role': 'assistant', -# 'content': 'ok' -# }, -# 'finish_reason': 'stop' -# }], -# 'usage': { -# 'prompt_tokens': 34, -# 'total_tokens': 59, -# 'completion_tokens': 25 -# } -# } -# time.sleep(2) # 设置sleep -# self.send_response(200) -# self.send_header('Content-type', 'text/html') -# self.end_headers() -# self.wfile.write(json.dumps(response).encode()) - -# def run(server_class=HTTPServer, handler_class=SimpleHTTPRequestHandler, port=8000): -# server_address = ('', port) -# httpd = server_class(server_address, handler_class) -# print(f'Starting httpd server on port {port}...') -# httpd.serve_forever() - -# if __name__ == '__main__': -# run() - import asyncio import json import time diff --git a/src/backend/test/test_api.py b/src/backend/test/test_api.py index 1d481e10a..f78092149 100644 --- a/src/backend/test/test_api.py +++ b/src/backend/test/test_api.py @@ -1,3 +1,10 @@ +import json +import os +from typing import Optional + +import requests + + def _test_python_code(): from bisheng import load_flow_from_json @@ -48,13 +55,80 @@ def _test_uns(): print(resp) -def test_input(): - from bisheng_langchain.input_output import InputNode - param = {'input': [1]} - a = InputNode(**param) - print(a) +# API URL for the flow +# 定义流程的 API URL +BASE_API_URL = 'https://bisheng.dataelem.com/api/v1/process' + + +def batch_api(FLOW_ID='30231781-2f58-4cea-bcfc-c20ec65c58b8'): + questions = { + 'questions': [ + '安装pageoffice时页面报错,提示“写入注册表键时出错”,如何处理?', '操作现金回收,系统提示“银行流水未完成指认”,怎么办?', + '处置方案变更中--拟处置资产和成本金额为0,如何修改?', '处置方案中“处置效果与收益测算”模块的“处置参考价格”、“预计处置净收益”字段是什么含义,是如何取值的?', + '处置尽调环节,将多笔债权的合同导入系统后,页面应该展示每笔合同对应的金额,但目前页面展示的是债权总额,为什么会这样?', '传统收购尽调的折现公式是什么?', + '传统收购尽调录入完成后还需要在系统进行哪些操作?', '对一笔临时的回款做回收资金操作,还款计划变更和资金回收确认应该先做哪个?', + '方案内容有错误需要修改,但此时方案已经提交到总部,如何操作?', '方案审批的时候,只能下载,不能预览', '合作备忘录出资比例应该如何填写?', + '合作立项审批协办机构为什么看不见审批流程?', '老系统的单据在新系统如何打印?', '批复逾期,项目需要重新发起方案,应该如何操作?', '如何参加股权决策小组成员?', + '如何查询债权资产的当前管理责人?', '如何申请结项项目的查询权限?', '如何通过债务人的名称查询项目信息?', '如何增加系统权限?', '是否可以更改项目名称?', + '收购成本变更对后续流程有什么影响?', '提前还本的线上操作流程', '提前还款如何操作?', '为什么不能下载附件?', '为什么没有可用的电子印章?', + '无法新增还款计划变更,怎么处理?', '误操作提交了合作备忘录,如何补救?', '项目立项时,项目类型字段选项是空的,应该怎么处理?', + '项目立项已经审批完成,如何修改项目名称?', '一笔资金流水如何才能关联多个项目?', 'SPV的优先、中间、劣后分层资产信息怎么录入?', + '处置简版审批方案是否还需要处长审批?', '当天的银行流水什么时候才能在核心系统查询到?', '导入excel报错,如何解决?', '合作备忘录的审批流程是怎么样的?', + '江苏分公司在进行处置方案审批表决时,如果已经有李明、张楠、方可、刘宇、芦苇进行了投票,而吴天文没有投票,请问可以进入下一步流程吗?', + '客户是国企并且属于能源类企业,请问在立项时归口管理部门应该选择哪个部门?', '客户为房地产行业的国企,请问在做“其他投放类”的立项时,归口管理部门应该选择哪个部门?', + '立项审批已完成,此时发现数据有误,是否可以在尽调的时候对立项数据进行修改?', '两个分公司合作的项目,应该由谁来上传方案的回复意见?', + '其他投放类收购尽调,要确认法院涉诉黑名单,应该在哪里操作?', '如何申请新核心系统的权限?', '使用核心系统对电脑的浏览器有什么要求?', + '提前还款时,资金的入账顺序是先入本金还是先入利息?', '未处置孳息数据的核算口径', '项目立项审批阶段,是否可以选择其他处室的负责人来审批?', + '需要修改合作备忘录中的出资规模比例,应该使用哪个功能?', '已经提交尽调审批,如何修改信息?', + '浙江分公司与江苏分公司合作的项目,由浙江分公司发起立项,请问需要到江苏分公司进行备案吗?', '资金申领审批之前是否必须对收款方做反洗钱排查?' + ] + } + + # You can tweak the flow by adding a tweaks dictionary + # 通过添加一个 tweaks 字典来调整流程 + # 例如:{"OpenAI-XXXXX": {"model_name": "gpt-4"}} + + def run_flow(inputs: dict, flow_id: str, session_id: Optional[str] = None) -> dict: + """ + Run a flow with a given message and optional tweaks. + 运行流程,使用给定的消息和可选的调整参数。 + :param message: 要发送到流程的消息 + :param flow_id: 要运行的流程的ID + :param tweaks: 可选的调整参数,用于自定义流程 + :return: 流程的 JSON 响应 + """ + api_url = f'{BASE_API_URL}/{flow_id}' + payload = {'inputs': inputs, 'session_id': session_id} + response = requests.post(api_url, json=payload, timeout=30) + print(f'response {response.status_code}') + return response.json() + + # Iterate over each question + # 遍历每个问题 + session_id = None + for ques in questions['questions']: + inputs = {'query': ques, 'id': 'SequentialChain-xgOSC'} + # Run the flow with the question as input + # 使用问题作为输入运行流程 + res = run_flow(inputs, flow_id=FLOW_ID, session_id=session_id) + session_id = res['data']['session_id'] + + # Add the question to the response + # 将问题添加到响应中 + res['question'] = ques + # Create the directory if it doesn't exist + # 如果目录不存在则创建目录 + os.makedirs('./GPT4', exist_ok=True) + # Save the response as a JSON file + # 将响应保存为 JSON 文件 + with open(f'./GPT4/GPT4_res_{ques}.json', 'w') as f: + json.dump(res, f, ensure_ascii=False, indent=2) + # Print the result + # 打印结果 + print(f'Response {ques}') # _test_python_code() # _test_uns() -test_input() +# test_input() +batch_api() diff --git a/src/backend/test/test_filelib.py b/src/backend/test/test_filelib.py index 34df9ad77..e1d6203d5 100644 --- a/src/backend/test/test_filelib.py +++ b/src/backend/test/test_filelib.py @@ -1,10 +1,18 @@ +import os +import sys + import requests +from bisheng.database.models.knowledge import KnowledgeCreate + +parent_dir = os.path.dirname(os.path.abspath(__file__)).replace('test', '') +sys.path.append(parent_dir) +os.environ['config'] = os.path.join(parent_dir, 'bisheng/config.dev.yaml') -url_host = 'http://{ip}:{port}/api/v1'.format(ip='127.0.0.1', port=7860) +url_host = 'http://{ip}:{port}/api'.format(ip='127.0.0.1', port=7860) def test_env(): - requests.get(url_host / 'env') + requests.get(url_host / 'v1/env') def test_upload(): @@ -15,12 +23,33 @@ def test_upload(): resp -def test_file(): - url = 'http://127.0.0.1:7860/api/v2/filelib/chunks' - data = {'knowledge_id': 349, 'metadata': "{\"url\":\"https://baidu.com\"}"} +def test_file(knowledge_id: int): + url = url_host + '/v2/filelib/chunks' + data = {'knowledge_id': knowledge_id, 'metadata': "{\"url\":\"https://baidu.com\"}"} file = {'file': open('/Users/huangly/Downloads/co2.pdf', 'rb')} resp = requests.post(url=url, data=data, files=file) + print(resp.text) + resp + + +def string_knowledge(knowledge_id: int): + url = url_host + '/v2/filelib/chunks_string' + json_data = { + 'knowledge_id': + knowledge_id, + 'documents': [{ + 'page_content': '达梦有多少专利和知识产权?', + 'metadata': { + 'source': '达梦有多少专利和知识产权?.txt', + 'url': 'http://baidu.com', + 'answer': '达梦共有177个已获授权专利情况,293个软件著作权情况', + 'page': 1 + } + }] + } + resp = requests.post(url=url, json=json_data) + print(resp.text) resp @@ -33,5 +62,19 @@ def test_upload2(): resp -test_file() +def test_create(): + url = url_host + '/v2/filelib/' + inp = KnowledgeCreate(name='es_index', + description='test', + model='multilingual-e5-large', + user_id=1, + is_partition=False) + resp = requests.post(url=url, json=inp.model_dump()) + print(resp.text) + return resp + + +# test_create() +test_file(479) +# string_knowledge(471) # test_upload() diff --git a/src/bisheng-langchain/bisheng_langchain/autogen_role/assistant.py b/src/bisheng-langchain/bisheng_langchain/autogen_role/assistant.py index a883a5491..8bef12cc0 100644 --- a/src/bisheng-langchain/bisheng_langchain/autogen_role/assistant.py +++ b/src/bisheng-langchain/bisheng_langchain/autogen_role/assistant.py @@ -1,9 +1,11 @@ """Chain that runs an arbitrary python function.""" import logging -from typing import Any, Awaitable, Callable, Dict, List, Optional +import os +from typing import Callable, Dict, Optional import openai from autogen import AssistantAgent +from langchain.base_language import BaseLanguageModel logger = logging.getLogger(__name__) @@ -33,22 +35,32 @@ def __init__( openai_api_base: Optional[str] = '', # when llm_flag=True, need to set openai_proxy: Optional[str] = '', # when llm_flag=True, need to set temperature: Optional[float] = 0, # when llm_flag=True, need to set - system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE, # agent system message, llm or group chat manage will use # noqa + api_type: Optional[str] = None, # when llm_flag=True, need to set + api_version: Optional[str] = None, # when llm_flag=True, need to set + llm: Optional[BaseLanguageModel] = None, + system_message: Optional[ + str] = DEFAULT_SYSTEM_MESSAGE, # agent system message, llm or group chat manage will use # noqa is_termination_msg: Optional[Callable[[Dict], bool]] = None, **kwargs, ): - is_termination_msg = ( - is_termination_msg if is_termination_msg is not None else (lambda x: x.get("content") == "TERMINATE") - ) + is_termination_msg = (is_termination_msg if is_termination_msg is not None else + (lambda x: x.get('content') == 'TERMINATE')) if openai_proxy: openai.proxy = {'https': openai_proxy, 'http': openai_proxy} + else: + openai.proxy = None if openai_api_base: openai.api_base = openai_api_base + else: + openai.api_base = os.environ.get('OPENAI_API_BASE', 'https://api.openai.com/v1') config_list = [ { 'model': model_name, 'api_key': openai_api_key, + 'api_base': openai_api_base, + 'api_type': api_type, + 'api_version': api_version, }, ] llm_config = { @@ -61,9 +73,10 @@ def __init__( super().__init__( name, llm_config=llm_config, + llm=llm, system_message=system_message, is_termination_msg=is_termination_msg, max_consecutive_auto_reply=None, - human_input_mode="NEVER", + human_input_mode='NEVER', code_execution_config=False, ) diff --git a/src/bisheng-langchain/bisheng_langchain/autogen_role/custom.py b/src/bisheng-langchain/bisheng_langchain/autogen_role/custom.py index ad672908d..baa96e90b 100644 --- a/src/bisheng-langchain/bisheng_langchain/autogen_role/custom.py +++ b/src/bisheng-langchain/bisheng_langchain/autogen_role/custom.py @@ -1,4 +1,3 @@ - from typing import Any, Awaitable, Callable, Dict, List, Optional, Union from autogen import Agent, ConversableAgent @@ -15,14 +14,13 @@ def __init__( coroutine: Optional[Callable[..., Awaitable[str]]] = None, **kwargs, ): - super().__init__( - name=name, - system_message=system_message, - human_input_mode='NEVER', - code_execution_config=False, - llm_config=False, - **kwargs, - ) + super().__init__(name=name, + system_message=system_message, + human_input_mode='NEVER', + code_execution_config=False, + llm_config=False, + llm=None, + **kwargs) self.func = func self.coroutine = coroutine self.register_reply(Agent, AutoGenCustomRole.generate_custom_reply) diff --git a/src/bisheng-langchain/bisheng_langchain/autogen_role/groupchat_manager.py b/src/bisheng-langchain/bisheng_langchain/autogen_role/groupchat_manager.py index 68ac5ab22..8819113ef 100644 --- a/src/bisheng-langchain/bisheng_langchain/autogen_role/groupchat_manager.py +++ b/src/bisheng-langchain/bisheng_langchain/autogen_role/groupchat_manager.py @@ -1,9 +1,11 @@ """Chain that runs an arbitrary python function.""" import logging +import os from typing import List, Optional import openai from autogen import Agent, GroupChat, GroupChatManager +from langchain.base_language import BaseLanguageModel from .user import AutoGenUser @@ -13,6 +15,7 @@ class AutoGenGroupChatManager(GroupChatManager): """A chat manager agent that can manage a group chat of multiple agents. """ + def __init__( self, agents: List[Agent], @@ -22,7 +25,10 @@ def __init__( openai_api_base: Optional[str] = '', openai_proxy: Optional[str] = '', temperature: Optional[float] = 0, + api_type: Optional[str] = None, # when llm_flag=True, need to set + api_version: Optional[str] = None, # when llm_flag=True, need to set name: Optional[str] = 'chat_manager', + llm: Optional[BaseLanguageModel] = None, system_message: Optional[str] = 'Group chat manager.', **kwargs, ): @@ -33,13 +39,20 @@ def __init__( if openai_proxy: openai.proxy = {'https': openai_proxy, 'http': openai_proxy} + else: + openai.proxy = None if openai_api_base: openai.api_base = openai_api_base + else: + openai.api_base = os.environ.get('OPENAI_API_BASE', 'https://api.openai.com/v1') config_list = [ { 'model': model_name, 'api_key': openai_api_key, + 'api_base': openai_api_base, + 'api_type': api_type, + 'api_version': api_version, }, ] llm_config = { @@ -52,6 +65,7 @@ def __init__( super().__init__( groupchat=groupchat, llm_config=llm_config, + llm=llm, name=name, system_message=system_message, ) diff --git a/src/bisheng-langchain/bisheng_langchain/autogen_role/user.py b/src/bisheng-langchain/bisheng_langchain/autogen_role/user.py index 31ed46e11..e76cd6a86 100644 --- a/src/bisheng-langchain/bisheng_langchain/autogen_role/user.py +++ b/src/bisheng-langchain/bisheng_langchain/autogen_role/user.py @@ -1,9 +1,11 @@ """Chain that runs an arbitrary python function.""" import logging +import os from typing import Callable, Dict, Optional import openai from autogen import UserProxyAgent +from langchain.base_language import BaseLanguageModel logger = logging.getLogger(__name__) @@ -25,6 +27,9 @@ def __init__( openai_api_base: Optional[str] = '', # when llm_flag=True, need to set openai_proxy: Optional[str] = '', # when llm_flag=True, need to set temperature: Optional[float] = 0, # when llm_flag=True, need to set + api_type: Optional[str] = None, # when llm_flag=True, need to set + api_version: Optional[str] = None, # when llm_flag=True, need to set + llm: Optional[BaseLanguageModel] = None, system_message: Optional[ str] = '', # agent system message, llm or group chat manage will use is_termination_msg: Optional[Callable[[Dict], bool]] = None, @@ -44,12 +49,19 @@ def __init__( if llm_flag: if openai_proxy: openai.proxy = {'https': openai_proxy, 'http': openai_proxy} + else: + openai.proxy = None if openai_api_base: openai.api_base = openai_api_base + else: + openai.api_base = os.environ.get('OPENAI_API_BASE', 'https://api.openai.com/v1') config_list = [ { 'model': model_name, 'api_key': openai_api_key, + 'api_base': openai_api_base, + 'api_type': api_type, + 'api_version': api_version, }, ] llm_config = { @@ -68,6 +80,7 @@ def __init__( function_map=function_map, code_execution_config=code_execution_config, llm_config=llm_config, + llm=llm, system_message=system_message) @@ -96,6 +109,7 @@ def __init__( human_input_mode=human_input_mode, code_execution_config=code_execution_config, llm_config=llm_config, + llm=None, system_message=system_message) @@ -126,4 +140,5 @@ def __init__( function_map=function_map, code_execution_config=code_execution_config, llm_config=llm_config, + llm=None, system_message=system_message) diff --git a/src/bisheng-langchain/bisheng_langchain/chat_models/__init__.py b/src/bisheng-langchain/bisheng_langchain/chat_models/__init__.py index cc96c8912..f71539035 100644 --- a/src/bisheng-langchain/bisheng_langchain/chat_models/__init__.py +++ b/src/bisheng-langchain/bisheng_langchain/chat_models/__init__.py @@ -5,8 +5,9 @@ from .wenxin import ChatWenxin from .xunfeiai import ChatXunfeiAI from .zhipuai import ChatZhipuAI +from .sensetime import SenseChat __all__ = [ 'ProxyChatLLM', 'ChatMinimaxAI', 'ChatWenxin', 'ChatZhipuAI', 'ChatXunfeiAI', 'HostChatGLM', - 'HostBaichuanChat', 'HostLlama2Chat', 'HostQwenChat', 'CustomLLMChat', 'ChatQWen' + 'HostBaichuanChat', 'HostLlama2Chat', 'HostQwenChat', 'CustomLLMChat', 'ChatQWen', 'SenseChat' ] diff --git a/src/bisheng-langchain/bisheng_langchain/chat_models/sensetime.py b/src/bisheng-langchain/bisheng_langchain/chat_models/sensetime.py new file mode 100644 index 000000000..1761957ab --- /dev/null +++ b/src/bisheng-langchain/bisheng_langchain/chat_models/sensetime.py @@ -0,0 +1,425 @@ +from __future__ import annotations + +import logging +import sys +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Mapping, Optional, Tuple, Union +from langchain.callbacks.manager import AsyncCallbackManagerForLLMRun, CallbackManagerForLLMRun +from langchain.chat_models.base import BaseChatModel +from langchain.schema import ChatGeneration, ChatResult +from langchain.schema.messages import (AIMessage, BaseMessage, ChatMessage, FunctionMessage, + HumanMessage, SystemMessage) +from langchain.utils import get_from_dict_or_env +from langchain_core.pydantic_v1 import Field, root_validator +from tenacity import (before_sleep_log, retry, retry_if_exception_type, stop_after_attempt, + wait_exponential) +from bisheng_langchain.utils.requests import Requests + +import time +import requests +import json + + +import jwt +# if TYPE_CHECKING: +# import jwt + +logger = logging.getLogger(__name__) + +def _import_pyjwt() -> Any: + try: + import jwt + except ImportError: + raise ValueError('Could not import jwt python package. ' + 'This is needed in order to calculate get_token_ids. ' + 'Please install it with `pip install PyJWT`.') + return jwt + +def encode_jwt_token(ak, sk): + headers = { + "alg": "HS256", + "typ": "JWT" + } + payload = { + "iss": ak, + "exp": int(time.time()) + 1800, # 填写您期望的有效时间,此处示例代表当前时间+30分钟 + "nbf": int(time.time()) - 5 # 填写您期望的生效时间,此处示例代表当前时间-5秒 + } + token = jwt.encode(payload, sk, headers=headers) + return token + +def _create_retry_decorator(llm): + + min_seconds = 1 + max_seconds = 20 + # Wait 2^x * 1 second between each retry starting with + # 4 seconds, then up to 10 seconds, then 10 seconds afterwards + return retry( + reraise=True, + stop=stop_after_attempt(llm.max_retries), + wait=wait_exponential(multiplier=1, min=min_seconds, max=max_seconds), + retry=(retry_if_exception_type(Exception)), + before_sleep=before_sleep_log(logger, logging.WARNING), + ) + + +def _convert_dict_to_message(_dict: Mapping[str, Any]) -> BaseMessage: + role = _dict['role'] + if role == 'user': + return HumanMessage(content=_dict['content']) + elif role == 'assistant': + content = _dict['message'] or '' # OpenAI returns None for tool invocations + if _dict.get('function_call'): + additional_kwargs = {'function_call': dict(_dict['function_call'])} + else: + additional_kwargs = {} + return AIMessage(content=content, additional_kwargs=additional_kwargs) + elif role == 'system': + return SystemMessage(content=_dict['content']) + elif role == 'function': + return FunctionMessage(content=_dict['content'], name=_dict['name']) + else: + return ChatMessage(content=_dict['content'], role=role) + + +def _convert_message_to_dict(message: BaseMessage) -> dict: + if isinstance(message, ChatMessage): + message_dict = {'role': message.role, 'content': message.content} + elif isinstance(message, HumanMessage): + message_dict = {'role': 'user', 'content': message.content} + elif isinstance(message, AIMessage): + message_dict = {'role': 'assistant', 'content': message.content} + elif isinstance(message, SystemMessage): + message_dict = {'role': 'system', 'content': message.content} + # raise ValueError(f"not support system role {message}") + + elif isinstance(message, FunctionMessage): + raise ValueError(f'not support funciton {message}') + else: + raise ValueError(f'Got unknown type {message}') + + # if "name" in message.additional_kwargs: + # message_dict["name"] = message.additional_kwargs["name"] + return message_dict + + +def _convert_message_to_dict2(message: BaseMessage) -> List[dict]: + if isinstance(message, ChatMessage): + message_dict = {'role': message.role, 'content': message.content} + elif isinstance(message, HumanMessage): + message_dict = {'role': 'user', 'content': message.content} + elif isinstance(message, AIMessage): + message_dict = {'role': 'assistant', 'content': message.content} + elif isinstance(message, SystemMessage): + raise ValueError(f'not support system role {message}') + + elif isinstance(message, FunctionMessage): + raise ValueError(f'not support funciton {message}') + else: + raise ValueError(f'Got unknown type {message}') + + return [message_dict] + +url = "https://api.sensenova.cn/v1/llm/chat-completions" + +class SenseChat(BaseChatModel): + + client: Optional[Any] #: :meta private: + model_name: str = Field(default='SenseChat', alias='model') + """Model name to use.""" + temperature: float = 0.8 + top_p: float = 0.7 + """What sampling temperature to use.""" + model_kwargs: Optional[Dict[str, Any]] = Field(default_factory=dict) + """Holds any model parameters valid for `create` call not explicitly specified.""" + access_key_id: Optional[str] = None + secret_access_key: Optional[str] = None + + repetition_penalty: float = 1.05 + request_timeout: Optional[Union[float, Tuple[float, float]]] = None + """Timeout for requests to OpenAI completion API. Default is 600 seconds.""" + max_retries: Optional[int] = 6 + """Maximum number of retries to make when generating.""" + streaming: Optional[bool] = False + """Whether to stream the results or not.""" + n: Optional[int] = 1 + """Number of chat completions to generate for each prompt.""" + max_tokens: Optional[int] = 1024 + """Maximum number of tokens to generate.""" + tiktoken_model_name: Optional[str] = None + """The model name to pass to tiktoken when using this class. + Tiktoken is used to count the number of tokens in documents to constrain + them to be under a certain limit. By default, when set to None, this will + be the same as the embedding model name. However, there are some cases + where you may want to use this Embedding class with a model name not + supported by tiktoken. This can include when using Azure embeddings or + when using one of the many model providers that expose an OpenAI-like + API but with different models. In those cases, in order to avoid erroring + when tiktoken is called, you can specify a model name to use here.""" + verbose: Optional[bool] = False + + class Config: + """Configuration for this pydantic object.""" + + allow_population_by_field_name = True + + @root_validator() + def validate_environment(cls, values: Dict) -> Dict: + + """Validate that api key and python package exists in environment.""" + + _import_pyjwt() + + values['access_key_id'] = get_from_dict_or_env(values, 'access_key_id', + 'ACCESS_KEY_ID') + values['secret_access_key'] = get_from_dict_or_env(values, 'secret_access_key', + 'SECRET_ACCESS_KEY') + token = encode_jwt_token(values['access_key_id'], values['secret_access_key']) + if isinstance(token, bytes): + token = token.decode('utf-8') + + try: + header = {'Authorization': 'Bearer {}'.format(token), + 'Content-Type': 'application/json'} + values['client'] = Requests(headers=header, ) + except AttributeError: + raise ValueError('Try upgrading it with `pip install --upgrade requests`.') + return values + + @property + def _default_params(self) -> Dict[str, Any]: + """Get the default parameters for calling ZhipuAI API.""" + return { + 'model': self.model_name, + 'temperature': self.temperature, + 'top_p': self.top_p, + 'max_tokens': self.max_tokens, + **self.model_kwargs, + } + + def completion_with_retry(self, **kwargs: Any) -> Any: + retry_decorator = _create_retry_decorator(self) + + @retry_decorator + def _completion_with_retry(**kwargs: Any) -> Any: + messages = kwargs.get('messages') + temperature = kwargs.get('temperature') + top_p = kwargs.get('top_p') + # messages + params = { + 'messages': messages, + 'model': self.model_name, + 'top_p': top_p, + 'temperature': temperature, + 'repetition_penalty': self.repetition_penalty, + 'n': self.n, + "max_new_tokens": self.max_tokens, + 'stream': False#self.streaming + } + + token = encode_jwt_token(self.access_key_id, self.secret_access_key) + if isinstance(token, bytes): + token = token.decode('utf-8') + self.client.headers.update({'Authorization': 'Bearer {}'.format(token)}) + + response = self.client.post(url=url, json=params).json() + return response + rsp_dict = _completion_with_retry(**kwargs) + if 'error' in rsp_dict: + logger.error(f'sensechat_error resp={rsp_dict}') + message = rsp_dict['error']['message'] + raise Exception(message) + else: + # return rsp_dict['data'], rsp_dict.get('usage', '') + return rsp_dict, rsp_dict.get('usage', '') + + + async def acompletion_with_retry(self, **kwargs: Any) -> Any: + """Use tenacity to retry the async completion call.""" + retry_decorator = _create_retry_decorator(self) + + token = encode_jwt_token(self.access_key_id, self.secret_access_key) + if isinstance(token, bytes): + token = token.decode('utf-8') + self.client.headers.update({'Authorization': 'Bearer {}'.format(token)}) + + if self.streaming: + self.client.headers.update({'Accept': 'text/event-stream'}) + else: + self.client.headers.pop('Accept', '') + + @retry_decorator + async def _acompletion_with_retry(**kwargs: Any) -> Any: + messages = kwargs.pop('messages', '') + input, params = SenseChat._build_input_parameters(self.model_name, + messages=messages, + **kwargs) + inp = {'input': input, 'parameters': params, 'model': self.model_name} + # Use OpenAI's async api https://github.com/openai/openai-python#async-api + async with self.client.apost(url=url, json=inp) as response: + async for line in response.content.iter_any(): + if b'\n' in line: + for txt_ in line.split(b'\n'): + yield txt_.decode('utf-8').strip() + else: + yield line.decode('utf-8').strip() + + async for response in _acompletion_with_retry(**kwargs): + is_error = False + if response: + if response.startswith('event:error'): + is_error = True + elif response.startswith('data:'): + yield (is_error, response[len('data:'):]) + if is_error: + break + elif response.startswith('{'): + yield (is_error, response) + else: + continue + + def _combine_llm_outputs(self, llm_outputs: List[Optional[dict]]) -> dict: + overall_token_usage: dict = {} + for output in llm_outputs: + if output is None: + # Happens in streaming + continue + token_usage = output['token_usage'] + for k, v in token_usage.items(): + if k in overall_token_usage: + overall_token_usage[k] += v + else: + overall_token_usage[k] = v + return {'token_usage': overall_token_usage, 'model_name': self.model_name} + + def _generate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[CallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> ChatResult: + + message_dicts, params = self._create_message_dicts(messages, stop) + params = {**params, **kwargs} + response, usage = self.completion_with_retry(messages=message_dicts, **params) + + return self._create_chat_result(response) + + async def _agenerate( + self, + messages: List[BaseMessage], + stop: Optional[List[str]] = None, + run_manager: Optional[AsyncCallbackManagerForLLMRun] = None, + **kwargs: Any, + ) -> ChatResult: + message_dicts, params = self._create_message_dicts(messages, stop) + params = {**params, **kwargs} + if self.streaming: + inner_completion = '' + role = 'assistant' + params['stream'] = True + function_call: Optional[dict] = None + async for is_error, stream_resp in self.acompletion_with_retry(messages=message_dicts, + **params): + output = None + msg = json.loads(stream_resp) + if is_error: + logger.error(stream_resp) + raise ValueError(stream_resp) + if 'data' in msg: + output = msg['data'] + choices = output.get('choices') + if choices: + for choice in choices: + role = choice['message'].get('role', role) + token = choice['message'].get('message', '') + inner_completion += token or '' + _function_call = choice['message'].get('function_call', '') + if run_manager: + await run_manager.on_llm_new_token(token) + if _function_call: + if function_call is None: + function_call = _function_call + else: + function_call['arguments'] += _function_call['arguments'] + message = _convert_dict_to_message({ + 'content': inner_completion, + 'role': role, + 'function_call': function_call, + }) + return ChatResult(generations=[ChatGeneration(message=message)]) + else: + return self._generate(messages, stop, run_manager, **kwargs) + + def _create_message_dicts( + self, messages: List[BaseMessage], + stop: Optional[List[str]]) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]: + params = dict(self._client_params) + if stop is not None: + if 'stop' in params: + raise ValueError('`stop` found in both the input and default params.') + params['stop'] = stop + + system_content = '' + message_dicts = [] + for m in messages: + if m.type == 'system': + system_content += m.content + continue + message_dicts.extend(_convert_message_to_dict2(m)) + + if system_content: + message_dicts[-1]['content'] = system_content + message_dicts[-1]['content'] + + return message_dicts, params + + def _create_chat_result(self, response: Mapping[str, Any]) -> ChatResult: + generations = [] + + # print('response', response) + def _norm_text(text): + if text[0] == '"' and text[-1] == '"': + out = eval(text) + else: + out = text + return out + for res in response['data']['choices']: + res['content'] = _norm_text(res['message']) + res["role"] = 'user' + message = _convert_dict_to_message(res) + gen = ChatGeneration(message=message) + generations.append(gen) + + llm_output = {'token_usage': response['data']['usage'], 'model_name': self.model_name} + return ChatResult(generations=generations, llm_output=llm_output) + + @property + def _identifying_params(self) -> Mapping[str, Any]: + """Get the identifying parameters.""" + return {**{'model_name': self.model_name}, **self._default_params} + + @property + def _client_params(self) -> Mapping[str, Any]: + """Get the parameters used for the openai client.""" + zhipu_creds: Dict[str, Any] = { + 'access_key_id': self.access_key_id, + 'secret_access_key': self.secret_access_key, + 'model': self.model_name, + } + return {**zhipu_creds, **self._default_params} + + def _get_invocation_params(self, + stop: Optional[List[str]] = None, + **kwargs: Any) -> Dict[str, Any]: + """Get the parameters used to invoke the model FOR THE CALLBACKS.""" + return { + **super()._get_invocation_params(stop=stop, **kwargs), + **self._default_params, + 'model': self.model_name, + 'function': kwargs.get('functions'), + } + + @property + def _llm_type(self) -> str: + """Return type of chat model.""" + return 'sense-chat' diff --git a/src/bisheng-langchain/version.txt b/src/bisheng-langchain/version.txt index 33fc434c1..c33ed94f4 100644 --- a/src/bisheng-langchain/version.txt +++ b/src/bisheng-langchain/version.txt @@ -1,2 +1,2 @@ -v0.2.2.3 +v0.2.2.4 diff --git a/src/frontend/package.json b/src/frontend/package.json index fb9125ebb..35d7a082f 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -1,6 +1,6 @@ { "name": "bisheng", - "version": "0.2.3", + "version": "0.2.2.4", "private": true, "dependencies": { "@emotion/react": "^11.11.1", diff --git a/src/frontend/public/locales/en/bs.json b/src/frontend/public/locales/en/bs.json index 9744123a4..f57d244fd 100644 --- a/src/frontend/public/locales/en/bs.json +++ b/src/frontend/public/locales/en/bs.json @@ -15,7 +15,7 @@ "pleaseEnterPassword": "Please enter your password", "accountTooShort": "Account is too short", "passwordTooShort": "Password is too short, at least 8 characters", - "passwordError": "The password must include upper and lower case letters, numbers, and special characters!", + "passwordError": "The password must include upper and lower case letters, numbers, and characters!", "passwordMismatch": "Passwords do not match", "registrationSuccess": "Registration successful. Please enter your password to log in", "pleaseEnterCaptcha": "Please enter captcha" diff --git a/src/frontend/public/locales/zh/bs.json b/src/frontend/public/locales/zh/bs.json index 347f5b757..c121e1508 100644 --- a/src/frontend/public/locales/zh/bs.json +++ b/src/frontend/public/locales/zh/bs.json @@ -15,7 +15,7 @@ "pleaseEnterPassword": "请填写密码", "accountTooShort": "账号过短", "passwordTooShort": "请填写密码,至少8位", - "passwordError": "密码必须包含大小写字母、数字和特殊字符!", + "passwordError": "密码必须包含大小写字母、数字和字符!", "passwordMismatch": "两次密码不一致", "registrationSuccess": "注册成功,请输入密码进行登录", "pleaseEnterCaptcha": "请输入验证码" diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 0f0a94445..681ec88c3 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -142,7 +142,7 @@ export default function GenericNode({ data, selected }: { */} -
e.stopPropagation()}> +
{data.node.description}
<> {Object.keys(data.node.template) diff --git a/src/frontend/src/contexts/tabsContext.tsx b/src/frontend/src/contexts/tabsContext.tsx index 590d8233d..632ef3081 100644 --- a/src/frontend/src/contexts/tabsContext.tsx +++ b/src/frontend/src/contexts/tabsContext.tsx @@ -47,7 +47,7 @@ export function TabsProvider({ children }: { children: ReactNode }) { async function saveFlow(flow: FlowType) { // save api const newFlow = await captureAndAlertRequestErrorHoc(updateFlowApi(flow)) - if (!newFlow) return {} + if (!newFlow) return null; console.log('action :>> ', 'save'); setFlow(newFlow) setTabsState((prev) => { diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 37fa39229..b87495d08 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -399,6 +399,8 @@ const useKeyBoard = (reactFlowWrapper) => { useEffect(() => { // this effect is used to attach the global event handlers const onKeyDown = (event: KeyboardEvent) => { + if (event.target.tagName === 'INPUT') return // 排除输入框内复制粘贴 + if ( (event.ctrlKey || event.metaKey) && event.key === "c" && diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 5cd3c475a..e4e90b178 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -119,7 +119,7 @@ export default function ExtraSidebar({ flow }: { flow: FlowType }) { diff --git a/src/frontend/src/pages/login.tsx b/src/frontend/src/pages/login.tsx index 77dd23a77..00bf619e6 100644 --- a/src/frontend/src/pages/login.tsx +++ b/src/frontend/src/pages/login.tsx @@ -64,7 +64,7 @@ export const LoginPage = () => { if (!mail) error.push(t('login.pleaseEnterAccount')) if (mail.length < 3) error.push(t('login.accountTooShort')) if (!/.{8,}/.test(pwd)) error.push(t('login.passwordTooShort')) - if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&#^])[A-Za-z\d@$!%*?&#=_()^]{8,}$/.test(pwd)) error.push(t('login.passwordError')) + if (!/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{8,}$/.test(pwd)) error.push(t('login.passwordError')) if (pwd !== apwd) error.push(t('login.passwordMismatch')) if (captchaData.user_capthca && !captchaRef.current.value) error.push(t('login.pleaseEnterCaptcha')) if (error.length) return setErrorData({