|
@@ -1,9 +1,14 @@
|
|
|
+import json
|
|
|
import logging
|
|
|
+import time
|
|
|
+from collections.abc import Generator
|
|
|
from datetime import UTC, datetime
|
|
|
|
|
|
+import requests
|
|
|
+from flask import request
|
|
|
from flask_login import current_user # type: ignore
|
|
|
-from flask_restful import reqparse # type: ignore
|
|
|
-from werkzeug.exceptions import InternalServerError, NotFound
|
|
|
+from flask_restful import Resource, reqparse # type: ignore
|
|
|
+from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
|
|
|
|
|
|
import services
|
|
|
from controllers.console.app.error import (
|
|
@@ -27,10 +32,11 @@ from core.errors.error import (
|
|
|
from core.model_runtime.errors.invoke import InvokeError
|
|
|
from extensions.ext_database import db
|
|
|
from libs import helper
|
|
|
-from libs.helper import uuid_value
|
|
|
+from libs.helper import extract_remote_ip, uuid_value
|
|
|
from models.model import AppMode
|
|
|
from services.app_generate_service import AppGenerateService
|
|
|
from services.errors.llm import InvokeRateLimitError
|
|
|
+from services.robot_account_service import RobotAccountService
|
|
|
|
|
|
|
|
|
# define completion api for user
|
|
@@ -143,6 +149,146 @@ class ChatApi(InstalledAppResource):
|
|
|
logging.exception("internal server error.")
|
|
|
raise InternalServerError()
|
|
|
|
|
|
+class ChatApiForRobot(Resource):
|
|
|
+
|
|
|
+ def post(self, installed_app_id):
|
|
|
+ # 1) 解析机器人聊天接口参数
|
|
|
+ parser = reqparse.RequestParser()
|
|
|
+ parser.add_argument("id", type=str, required=True, location="json")
|
|
|
+ parser.add_argument("enterprise_id", type=str, required=False, location="json")
|
|
|
+ parser.add_argument("device_id", type=str, required=False, location="json")
|
|
|
+ parser.add_argument("messages", type=list, required=True, location="json")
|
|
|
+ parser.add_argument("max_tokens", type=int, required=True, location="json")
|
|
|
+ parser.add_argument("stream", type=bool, required=True, location="json")
|
|
|
+ args = parser.parse_args()
|
|
|
+
|
|
|
+ # 2) 将机器人聊天请求参数转换为dify聊天请求参数
|
|
|
+ messages = args["messages"]
|
|
|
+ if messages is None or len(messages) == 0:
|
|
|
+ raise BadRequest("messages is empty.")
|
|
|
+
|
|
|
+ id = args["id"]
|
|
|
+ query = messages[len(messages) - 1]["content"]
|
|
|
+ response_mode = "streaming" if args["stream"] else "blocking"
|
|
|
+ device_id = args["device_id"]
|
|
|
+ data = {
|
|
|
+ "inputs": {},
|
|
|
+ "query": query,
|
|
|
+ "response_mode": response_mode,
|
|
|
+ "conversation_id": "",
|
|
|
+ "user": device_id if device_id else "abc-123",
|
|
|
+ "files": []
|
|
|
+ }
|
|
|
+
|
|
|
+ # 3) 获取用户token,封装请求头
|
|
|
+ login_json = {
|
|
|
+ "email": "suhh@mail.com.cn",
|
|
|
+ "password": "tj123456",
|
|
|
+ }
|
|
|
+
|
|
|
+ # 3.1) 方案1:每次login
|
|
|
+ access_token = RobotAccountService.login(login_json["email"], login_json["password"])
|
|
|
+
|
|
|
+ # # 3.2) 方案2:login后将access_token写入redis,根据是否能够从redis取得内容判断
|
|
|
+ # access_token = RobotAccountService.get_account_access_token(login_json["email"])
|
|
|
+ # # access_token = None
|
|
|
+ # if access_token is None:
|
|
|
+ # # 3.2.1) 方案2-1:调用登录接口,使用返回的access_token
|
|
|
+ # login_response = requests.post(f"{request.host_url}console/api/login", json=login_json)
|
|
|
+ # access_token = login_response.json()["data"]["access_token"]
|
|
|
+ # access_token_key = RobotAccountService._get_access_token_key(login_json["email"])
|
|
|
+ # redis_client.setex(access_token_key, ACCESS_TOKEN_EXPIRY, access_token)
|
|
|
+ #
|
|
|
+ # # 3.2.2) 方案2-2:调用service层封装的login方法,使用返回的access_token
|
|
|
+ # access_token = RobotAccountService.login(login_json["email"], login_json["password"])
|
|
|
+
|
|
|
+ headers = {
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ "User-Agent": "Robot",
|
|
|
+ "Accept": "*/*",
|
|
|
+ "Authorization": f'Bearer {access_token}',
|
|
|
+ "Connection": "keep-alive",
|
|
|
+ }
|
|
|
+
|
|
|
+ logging.info(f"Robot request: {extract_remote_ip(request)}")
|
|
|
+
|
|
|
+ # 4) 封装请求url并发送请求
|
|
|
+ chat_message_url = f'{request.host_url}console/api/installed-apps/{installed_app_id}/chat-messages'
|
|
|
+ logging.info("Sending request to %s", chat_message_url)
|
|
|
+
|
|
|
+ # 5) 按照输出要求处理返回的流式数据
|
|
|
+ if args["stream"]:
|
|
|
+ def after_response_generator():
|
|
|
+ timestamp = int(time.time())
|
|
|
+ new_content = {
|
|
|
+ "id": id,
|
|
|
+ "model": "advanced-chat",
|
|
|
+ "created": timestamp,
|
|
|
+ "choices": [],
|
|
|
+ }
|
|
|
+
|
|
|
+ i = 0
|
|
|
+ choice = {
|
|
|
+ "index": 0,
|
|
|
+ "delta": {
|
|
|
+ "role": "assistant",
|
|
|
+ "content": ""
|
|
|
+ },
|
|
|
+ "finish_reason": None
|
|
|
+ }
|
|
|
+ new_content["choices"].append(choice)
|
|
|
+ yield f"id: {i}\ndata: {json.dumps(new_content)}\n\n"
|
|
|
+ new_content["choices"].pop()
|
|
|
+
|
|
|
+ i = i + 1
|
|
|
+ choice["delta"] = {
|
|
|
+ "role": None,
|
|
|
+ "content": "您的问题正在检索中,请稍等......\n\n"
|
|
|
+ }
|
|
|
+ new_content["choices"].append(choice)
|
|
|
+ yield f"id: {i}\ndata: {json.dumps(new_content)}\n\n"
|
|
|
+ new_content["choices"].pop()
|
|
|
+
|
|
|
+ i = i + 1
|
|
|
+ response = requests.post(chat_message_url, data=json.dumps(data), headers=headers)
|
|
|
+ for line in response.iter_lines():
|
|
|
+ line_str = line.decode("utf-8")
|
|
|
+ if not line_str.startswith('data:'):
|
|
|
+ continue
|
|
|
+
|
|
|
+ content = json.loads(line_str[6:])
|
|
|
+ event = content["event"]
|
|
|
+ if event not in ["message", "message_end"]:
|
|
|
+ continue
|
|
|
+
|
|
|
+ new_content["created"] = content["created_at"]
|
|
|
+
|
|
|
+ i = i + 1
|
|
|
+ if content["event"] == "message":
|
|
|
+ choice["delta"]["content"] = content["answer"]
|
|
|
+ else:
|
|
|
+ choice["delta"]["content"] = ""
|
|
|
+ choice["finish_reason"] = "stop"
|
|
|
+ new_content["choices"] = [choice]
|
|
|
+
|
|
|
+ yield f"id: {i}\ndata: {json.dumps(new_content)}\n\n"
|
|
|
+
|
|
|
+ new_response = after_response_generator()
|
|
|
+
|
|
|
+ def generate() -> Generator:
|
|
|
+ yield from new_response
|
|
|
+
|
|
|
+ return helper.compact_generate_response(generate())
|
|
|
+ else:
|
|
|
+ response = requests.post(chat_message_url, data=json.dumps(data), headers=headers)
|
|
|
+ content = json.loads(response.text)
|
|
|
+ new_response = {
|
|
|
+ "id": id,
|
|
|
+ "model": "advanced-chat",
|
|
|
+ "created": content["created_at"],
|
|
|
+ "answer": content["answer"],
|
|
|
+ }
|
|
|
+ return new_response
|
|
|
|
|
|
class ChatStopApi(InstalledAppResource):
|
|
|
def post(self, installed_app, task_id):
|