    """Map (certain) error message to frontend rendering form, otherwise show
    'internal backend  error'. Currently, only handle OpenAI error message.
    """
    if "openai" in error_message:
        if "Timeout" in error_message:
            return "OpenAI timeout error. Please try again."
        elif "RateLimitError" in error_message:
            return "OpenAI rate limit error. Please try again."
        elif "APIConnectionError" in error_message:
            return "OpenAI API connection error. Please try again."
        elif "InvalidRequestError" in error_message:
            return "OpenAI invalid request error. Please try again."
        elif "AuthenticationError" in error_message:
            return "OpenAI authentication error. Please try again."
        elif "ServiceUnavailableError" in error_message:
            return "OpenAI service unavailable error. Please try again."
    else:
        return "Internal backend error. Please try again."

def init_log(**sink_channel):
    """Initialize loguru log information"""

    # Just for sys.stdout log message
    format_stdout = (
        "<g>{time:YYYY-MM-DD HH:mm:ss}</g> | <lvl>{level}</lvl> - {extra[user_id]}++{extra[chat_id]}-><y>{extra[api]}</y> "
        "<LC>{extra[msg_head]}</LC>:{message}"
    )

    # Avoid unexpected KeyError
    # Do not unpack key-value pairs, but save all records.
    format_full_extra = (
        "<g>{time:YYYY-MM-DD HH:mm:ss}</g> | <lvl>{level}</lvl> - <c><u>{name}</u></c> | {message} - {extra}"
    )

    logger.remove()

    logger.configure(
        handlers=[
            dict(sink=sys.stdout, format=format_stdout, level="TRACE"),
            dict(
                sink=sink_channel.get("error"),
                format=format_full_extra,
                level="ERROR",
                diagnose=False,
                rotation="1 week",
            ),
            dict(
                sink=sink_channel.get("runtime"),
                format=format_full_extra,
                level="DEBUG",
                diagnose=False,
                rotation="20 MB",
                retention="20 days",
            ),
            dict(
                sink=sink_channel.get("serialize"),
                level="DEBUG",
                diagnose=False,
                serialize=True,
            ),
        ],
        extra={"user_id": "", "chat_id": "", "api": "", "msg_head": ""},
    )

    return logger

def create_personal_folder(user_id: str) -> str:
    # mkdir user folder
    from backend.main import app

    user_folder = os.path.join(app.config["UPLOAD_FOLDER"], user_id)
    os.makedirs(user_folder, exist_ok=True)
    # mkdir chat folder under user folder
    return user_folder


                sink=sink_channel.get("runtime"),
                format=format_full_extra,
                level="DEBUG",
                diagnose=False,
                rotation="20 MB",
                retention="20 days",
            ),
            dict(
                sink=sink_channel.get("serialize"),
                level="DEBUG",
                diagnose=False,
                serialize=True,
            ),
        ],
        extra={"user_id": "", "chat_id": "", "api": "", "msg_head": ""},
    )

    return logger

def create_personal_folder(user_id: str) -> str:
    # mkdir user folder
    from backend.main import app

    user_folder = os.path.join(app.config["UPLOAD_FOLDER"], user_id)
    os.makedirs(user_folder, exist_ok=True)
    # mkdir chat folder under user folder
    return user_folder


    DataSummaryExecutor,
    TableSummaryExecutor,
    ImageSummaryExecutor,
)
from real_agents.adapters.schema import SQLDatabase
from backend.utils.running_time_storage import get_running_time_storage
from backend.app import app
from backend.schemas import DEFAULT_USER_ID

TABLE_EXTENSIONS = {"csv", "xls", "xlsx", "tsv"}
DOCUMENT_EXTENSIONS = {"pdf", "doc", "docx", "txt"}
DATABASE_EXTENSIONS = {"sqlite", "db"}
IMAGE_EXTENSIONS = {"jpg", "png", "jpeg"}
ALLOW_EXTENSIONS = TABLE_EXTENSIONS | DOCUMENT_EXTENSIONS | DATABASE_EXTENSIONS | IMAGE_EXTENSIONS

LOCAL = "local"
REDIS = "redis"

class VariableRegister:
    def __init__(self, name=None, backend=LOCAL) -> None:
        self.backend = backend
        if self.backend == LOCAL:
            self.variables: Dict[int, Any] = {}
            self.counter = 1
        elif self.backend == REDIS:
            assert name is not None
            self.name = name
            self.counter_name = f"{self.name}:counter"
            self.variables_name = f"{self.name}:variables"
            with app.app_context():
                self.redis_client = get_running_time_storage()
            if not self.redis_client.exists(self.counter_name):
                self.redis_client.set(self.counter_name, 0)
            else:
                logger.bind(msg_head="VariableRegister").debug(
                    f"Reuse the {self.counter_name}({self.redis_client.get(self.counter_name)}) and {self.variables_name}."
                )
        else:
            raise ValueError("Unknown backend option: {}".format(self.backend))

    def add_variable(self, variable: Any) -> int:
        if self.backend == LOCAL:
            variable_id = self.counter
            self.variables[variable_id] = variable
            self.counter += 1
            return variable_id
        elif self.backend == REDIS:
            variable_id = self.redis_client.incrby(self.counter_name, 1)
            self.redis_client.hset(self.variables_name, variable_id, variable)
            return variable_id

    def get_variable(self, variable_id: int) -> Any:
        if self.backend == LOCAL:
            return self.variables.get(variable_id, None)
        elif self.backend == REDIS:
            return self.redis_client.hget(self.variables_name, variable_id)

    def get_variables(self) -> Dict[int, Any]:
        if self.backend == LOCAL:


        self.backend = backend
        if self.backend == LOCAL:
            self.variables: Dict[int, Any] = {}
            self.counter = 1
        elif self.backend == REDIS:
            assert name is not None
            self.name = name
            self.counter_name = f"{self.name}:counter"
            self.variables_name = f"{self.name}:variables"
            with app.app_context():
                self.redis_client = get_running_time_storage()
            if not self.redis_client.exists(self.counter_name):
                self.redis_client.set(self.counter_name, 0)
            else:
                logger.bind(msg_head="VariableRegister").debug(
                    f"Reuse the {self.counter_name}({self.redis_client.get(self.counter_name)}) and {self.variables_name}."
                )
        else:
            raise ValueError("Unknown backend option: {}".format(self.backend))

    def add_variable(self, variable: Any) -> int:
        if self.backend == LOCAL:
            variable_id = self.counter
            self.variables[variable_id] = variable
            self.counter += 1
            return variable_id
        elif self.backend == REDIS:
            variable_id = self.redis_client.incrby(self.counter_name, 1)
            self.redis_client.hset(self.variables_name, variable_id, variable)
            return variable_id

    def get_variable(self, variable_id: int) -> Any:
        if self.backend == LOCAL:
            return self.variables.get(variable_id, None)
        elif self.backend == REDIS:
            return self.redis_client.hget(self.variables_name, variable_id)

    def get_variables(self) -> Dict[int, Any]:
        if self.backend == LOCAL:
            return self.variables
        elif self.backend == REDIS:
            return self.redis_client.hgetall(self.variables_name)

def get_user_and_chat_id_from_request_json(request_json: Dict) -> Tuple[str, str]:
    user_id = request_json.pop("user_id", DEFAULT_USER_ID)
    chat_id = request_json["chat_id"]
    return user_id, chat_id


            if not self.redis_client.exists(self.counter_name):
                self.redis_client.set(self.counter_name, 0)
            else:
                logger.bind(msg_head="VariableRegister").debug(
                    f"Reuse the {self.counter_name}({self.redis_client.get(self.counter_name)}) and {self.variables_name}."
                )
        else:
            raise ValueError("Unknown backend option: {}".format(self.backend))

    def add_variable(self, variable: Any) -> int:
        if self.backend == LOCAL:
            variable_id = self.counter
            self.variables[variable_id] = variable
            self.counter += 1
            return variable_id
        elif self.backend == REDIS:
            variable_id = self.redis_client.incrby(self.counter_name, 1)
            self.redis_client.hset(self.variables_name, variable_id, variable)
            return variable_id

    def get_variable(self, variable_id: int) -> Any:
        if self.backend == LOCAL:
            return self.variables.get(variable_id, None)
        elif self.backend == REDIS:
            return self.redis_client.hget(self.variables_name, variable_id)

    def get_variables(self) -> Dict[int, Any]:
        if self.backend == LOCAL:
            return self.variables
        elif self.backend == REDIS:
            return self.redis_client.hgetall(self.variables_name)

def get_user_and_chat_id_from_request_json(request_json: Dict) -> Tuple[str, str]:
    user_id = request_json.pop("user_id", DEFAULT_USER_ID)
    chat_id = request_json["chat_id"]
    return user_id, chat_id

def get_user_and_chat_id_from_request(request: Request) -> Tuple[str, str]:
    user_id = request.form.get("user_id", DEFAULT_USER_ID)
    chat_id = request.form.get("chat_id")
    return user_id, chat_id


        else:
            raise ValueError("Unknown backend option: {}".format(self.backend))

    def add_variable(self, variable: Any) -> int:
        if self.backend == LOCAL:
            variable_id = self.counter
            self.variables[variable_id] = variable
            self.counter += 1
            return variable_id
        elif self.backend == REDIS:
            variable_id = self.redis_client.incrby(self.counter_name, 1)
            self.redis_client.hset(self.variables_name, variable_id, variable)
            return variable_id

    def get_variable(self, variable_id: int) -> Any:
        if self.backend == LOCAL:
            return self.variables.get(variable_id, None)
        elif self.backend == REDIS:
            return self.redis_client.hget(self.variables_name, variable_id)

    def get_variables(self) -> Dict[int, Any]:
        if self.backend == LOCAL:
            return self.variables
        elif self.backend == REDIS:
            return self.redis_client.hgetall(self.variables_name)

def get_user_and_chat_id_from_request_json(request_json: Dict) -> Tuple[str, str]:
    user_id = request_json.pop("user_id", DEFAULT_USER_ID)
    chat_id = request_json["chat_id"]
    return user_id, chat_id

def get_user_and_chat_id_from_request(request: Request) -> Tuple[str, str]:
    user_id = request.form.get("user_id", DEFAULT_USER_ID)
    chat_id = request.form.get("chat_id")
    return user_id, chat_id

def load_grounding_source(file_path: str) -> Any:
    # TODO: Maybe convert to DataModel here
    suffix = Path(file_path).suffix
    if Path(file_path).is_dir():
        # Assume it is a collection of csv files, usually downloaded from kaggle.
        grounding_source = {}


backend.utils.threading

# Python program using
# traces to kill threads
from typing import Dict, Tuple, Optional
from multiprocess import Process

class ThreadManager:
    """Manager class of all user chat threads."""

    def __init__(self) -> None:
        self.thread_pool: Dict[str, Process] = {}
        self.stop_pool: Dict[str, bool] = {}
        self.timeout_pool: Dict[str, bool] = {}
        self.run_error_pool: Dict[str, Optional[str]] = {}

    def register_thread(self, chat_id, thread: Process) -> None:
        self.thread_pool[chat_id] = thread
        self.stop_pool[chat_id] = False
        self.timeout_pool[chat_id] = False
        self.run_error_pool[chat_id] = None

    def flush_thread(self, chat_id) -> Tuple[bool, bool, str]:
        # self.thread_pool[chat_id] = None
        stop_flag = self.stop_pool[chat_id]
        timeout_flag = self.timeout_pool[chat_id]
        run_error = self.run_error_pool[chat_id]
        _ = self.thread_pool.pop(chat_id)
        _.terminate()
        del _
        self.stop_pool.pop(chat_id)
        self.timeout_pool.pop(chat_id)
        self.run_error_pool.pop(chat_id)
        return stop_flag, timeout_flag, run_error

    def kill_thread(self, chat_id) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.stop_pool[chat_id] = True
                while self.thread_pool[chat_id].is_alive():
                    self.thread_pool[chat_id].terminate()
            except Exception as e:
                if not self.thread_pool[chat_id].is_alive():
                    self.stop_pool[chat_id] = True
                pass

    def timeout_thread(self, chat_id) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.timeout_pool[chat_id] = True
                while self.thread_pool[chat_id].is_alive():
                    self.thread_pool[chat_id].terminate()
            except Exception as e:
                if not self.thread_pool[chat_id].is_alive():
                    self.timeout_pool[chat_id] = True
                pass

    def error_thread(self, chat_id, e_msg: str) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.run_error_pool[chat_id] = e_msg
            except:
                pass


# Python program using
# traces to kill threads
from typing import Dict, Tuple, Optional
from multiprocess import Process

class ThreadManager:
    """Manager class of all user chat threads."""

    def __init__(self) -> None:
        self.thread_pool: Dict[str, Process] = {}
        self.stop_pool: Dict[str, bool] = {}
        self.timeout_pool: Dict[str, bool] = {}
        self.run_error_pool: Dict[str, Optional[str]] = {}

    def register_thread(self, chat_id, thread: Process) -> None:
        self.thread_pool[chat_id] = thread
        self.stop_pool[chat_id] = False
        self.timeout_pool[chat_id] = False
        self.run_error_pool[chat_id] = None

    def flush_thread(self, chat_id) -> Tuple[bool, bool, str]:
        # self.thread_pool[chat_id] = None
        stop_flag = self.stop_pool[chat_id]
        timeout_flag = self.timeout_pool[chat_id]
        run_error = self.run_error_pool[chat_id]
        _ = self.thread_pool.pop(chat_id)
        _.terminate()
        del _
        self.stop_pool.pop(chat_id)
        self.timeout_pool.pop(chat_id)
        self.run_error_pool.pop(chat_id)
        return stop_flag, timeout_flag, run_error


# Python program using
# traces to kill threads
from typing import Dict, Tuple, Optional
from multiprocess import Process

class ThreadManager:
    """Manager class of all user chat threads."""

    def __init__(self) -> None:
        self.thread_pool: Dict[str, Process] = {}
        self.stop_pool: Dict[str, bool] = {}
        self.timeout_pool: Dict[str, bool] = {}
        self.run_error_pool: Dict[str, Optional[str]] = {}

    def register_thread(self, chat_id, thread: Process) -> None:
        self.thread_pool[chat_id] = thread
        self.stop_pool[chat_id] = False
        self.timeout_pool[chat_id] = False
        self.run_error_pool[chat_id] = None

    def flush_thread(self, chat_id) -> Tuple[bool, bool, str]:
        # self.thread_pool[chat_id] = None
        stop_flag = self.stop_pool[chat_id]
        timeout_flag = self.timeout_pool[chat_id]
        run_error = self.run_error_pool[chat_id]
        _ = self.thread_pool.pop(chat_id)
        _.terminate()
        del _
        self.stop_pool.pop(chat_id)
        self.timeout_pool.pop(chat_id)
        self.run_error_pool.pop(chat_id)
        return stop_flag, timeout_flag, run_error

    def kill_thread(self, chat_id) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.stop_pool[chat_id] = True
                while self.thread_pool[chat_id].is_alive():
                    self.thread_pool[chat_id].terminate()


# traces to kill threads
from typing import Dict, Tuple, Optional
from multiprocess import Process

class ThreadManager:
    """Manager class of all user chat threads."""

    def __init__(self) -> None:
        self.thread_pool: Dict[str, Process] = {}
        self.stop_pool: Dict[str, bool] = {}
        self.timeout_pool: Dict[str, bool] = {}
        self.run_error_pool: Dict[str, Optional[str]] = {}

    def register_thread(self, chat_id, thread: Process) -> None:
        self.thread_pool[chat_id] = thread
        self.stop_pool[chat_id] = False
        self.timeout_pool[chat_id] = False
        self.run_error_pool[chat_id] = None

    def flush_thread(self, chat_id) -> Tuple[bool, bool, str]:
        # self.thread_pool[chat_id] = None
        stop_flag = self.stop_pool[chat_id]
        timeout_flag = self.timeout_pool[chat_id]
        run_error = self.run_error_pool[chat_id]
        _ = self.thread_pool.pop(chat_id)
        _.terminate()
        del _
        self.stop_pool.pop(chat_id)
        self.timeout_pool.pop(chat_id)
        self.run_error_pool.pop(chat_id)
        return stop_flag, timeout_flag, run_error

    def kill_thread(self, chat_id) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.stop_pool[chat_id] = True
                while self.thread_pool[chat_id].is_alive():
                    self.thread_pool[chat_id].terminate()
            except Exception as e:
                if not self.thread_pool[chat_id].is_alive():
                    self.stop_pool[chat_id] = True
                pass

    def timeout_thread(self, chat_id) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.timeout_pool[chat_id] = True
                while self.thread_pool[chat_id].is_alive():
                    self.thread_pool[chat_id].terminate()
            except Exception as e:
                if not self.thread_pool[chat_id].is_alive():


    def register_thread(self, chat_id, thread: Process) -> None:
        self.thread_pool[chat_id] = thread
        self.stop_pool[chat_id] = False
        self.timeout_pool[chat_id] = False
        self.run_error_pool[chat_id] = None

    def flush_thread(self, chat_id) -> Tuple[bool, bool, str]:
        # self.thread_pool[chat_id] = None
        stop_flag = self.stop_pool[chat_id]
        timeout_flag = self.timeout_pool[chat_id]
        run_error = self.run_error_pool[chat_id]
        _ = self.thread_pool.pop(chat_id)
        _.terminate()
        del _
        self.stop_pool.pop(chat_id)
        self.timeout_pool.pop(chat_id)
        self.run_error_pool.pop(chat_id)
        return stop_flag, timeout_flag, run_error

    def kill_thread(self, chat_id) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.stop_pool[chat_id] = True
                while self.thread_pool[chat_id].is_alive():
                    self.thread_pool[chat_id].terminate()
            except Exception as e:
                if not self.thread_pool[chat_id].is_alive():
                    self.stop_pool[chat_id] = True
                pass

    def timeout_thread(self, chat_id) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.timeout_pool[chat_id] = True
                while self.thread_pool[chat_id].is_alive():
                    self.thread_pool[chat_id].terminate()
            except Exception as e:
                if not self.thread_pool[chat_id].is_alive():
                    self.timeout_pool[chat_id] = True
                pass

    def error_thread(self, chat_id, e_msg: str) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.run_error_pool[chat_id] = e_msg
            except:
                pass


        run_error = self.run_error_pool[chat_id]
        _ = self.thread_pool.pop(chat_id)
        _.terminate()
        del _
        self.stop_pool.pop(chat_id)
        self.timeout_pool.pop(chat_id)
        self.run_error_pool.pop(chat_id)
        return stop_flag, timeout_flag, run_error

    def kill_thread(self, chat_id) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.stop_pool[chat_id] = True
                while self.thread_pool[chat_id].is_alive():
                    self.thread_pool[chat_id].terminate()
            except Exception as e:
                if not self.thread_pool[chat_id].is_alive():
                    self.stop_pool[chat_id] = True
                pass

    def timeout_thread(self, chat_id) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.timeout_pool[chat_id] = True
                while self.thread_pool[chat_id].is_alive():
                    self.thread_pool[chat_id].terminate()
            except Exception as e:
                if not self.thread_pool[chat_id].is_alive():
                    self.timeout_pool[chat_id] = True
                pass

    def error_thread(self, chat_id, e_msg: str) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.run_error_pool[chat_id] = e_msg
            except:
                pass


            try:
                self.stop_pool[chat_id] = True
                while self.thread_pool[chat_id].is_alive():
                    self.thread_pool[chat_id].terminate()
            except Exception as e:
                if not self.thread_pool[chat_id].is_alive():
                    self.stop_pool[chat_id] = True
                pass

    def timeout_thread(self, chat_id) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.timeout_pool[chat_id] = True
                while self.thread_pool[chat_id].is_alive():
                    self.thread_pool[chat_id].terminate()
            except Exception as e:
                if not self.thread_pool[chat_id].is_alive():
                    self.timeout_pool[chat_id] = True
                pass

    def error_thread(self, chat_id, e_msg: str) -> None:
        if chat_id in self.thread_pool and self.thread_pool[chat_id] is not None:
            try:
                self.run_error_pool[chat_id] = e_msg
            except:
                pass


backend.utils.charts

import json

def polish_echarts(echarts_str):
    """Polishes the echarts output into prettier format."""
    try:
        option = json.loads(echarts_str)

        # turn numeric axis into str
        category_flag = False
        for idx, series_data in enumerate(option["series"]):
            if series_data["type"] in ["bar", "line"]:
                category_flag = True
                break
        if category_flag:
            option["xAxis"][0]["data"] = [str(_) for _ in option["xAxis"][0]["data"]]
            for idx, series_data in enumerate(option["series"]):
                try:
                    option["series"][idx]["data"] = [[str(_[0]), _[1]] for _ in series_data["data"]]
                except:
                    continue
        for idx, series_data in enumerate(option["series"]):
            option["series"][idx]["label"]["show"] = False
        # set title position
        option["title"][0]["bottom"] = "bottom"
        option["title"][0]["left"] = "center"
        option["tooltip"]["alwaysShowContent"] = False

        return json.dumps(option)
    except Exception as e:
        print(e)
        return echarts_str


from time import sleep
import copy
import redis
import json
import pickle
import traceback
from flask import Response, request, stream_with_context
from typing import Dict, Union
import os

from langchain.schema import HumanMessage, SystemMessage

from backend.api.language_model import get_llm
from backend.main import app, message_id_register, message_pool, logger
from backend.utils.streaming import single_round_chat_with_agent_streaming
from backend.schemas import OVERLOAD, NEED_CONTINUE_MODEL
from backend.schemas import DEFAULT_USER_ID
from real_agents.adapters.llm import BaseLanguageModel
from real_agents.adapters.agent_helpers import AgentExecutor, Tool
from real_agents.adapters.callbacks.agent_streaming import \
    AgentStreamingStdOutCallbackHandler
from real_agents.adapters.models import ChatOpenAI
from real_agents.adapters.memory import ConversationReActBufferMemory
from real_agents.adapters.data_model import DataModel, JsonDataModel
from real_agents.adapters.interactive_executor import initialize_webot_agent
from real_agents.web_agent import WebBrowsingExecutor, WebotExecutor

r = redis.Redis(host=os.getenv("REDIS_SERVER"), port=6379, db=0)  # adjust host/port/db as needed

# here webot and webot_status are stored in redis since the two global variable can not be modified and accessed normally in multiprocess
# fixme:now webot is stored without message_id or chat_id info, so it can only be used for one chat at a time
# fixme:now webot_status is stored with chat_id info, if the status is not reset after a message ended abnormally e.g. the message is interrupted, it will be reused wrongly for the next chat
def get_webot_from_redis(user_id: str, chat_id: str, ) -> WebBrowsingExecutor:
    data = r.get(f'webot_{user_id}_{chat_id}')
    if data is not None:
        webot = pickle.loads(data)
    else:
        # initialize a webot with None instrucition if webot does not exist
        webot = WebBrowsingExecutor(None)
        save_webot_to_redis(user_id, chat_id, webot)
    return webot

def save_webot_to_redis(user_id: str, chat_id: str, webot: WebBrowsingExecutor, ):
    r.set(f'webot_{user_id}_{chat_id}', pickle.dumps(webot))

def get_webot_status_from_redis(user_id: str, chat_id: str):
    webot_status_json = r.get(f'webot_status_{user_id}_{chat_id}')
    if webot_status_json is not None:
        webot_status = json.loads(webot_status_json)
        return webot_status
    else:
        return {}

def save_webot_status_to_redis(user_id: str, chat_id: str, webot_status: Dict):
    r.set(f'webot_status_{user_id}_{chat_id}', json.dumps(webot_status))

def reset_webot(user_id: str, chat_id: str):
    webot = WebBrowsingExecutor(None)
    save_webot_to_redis(user_id, chat_id, webot)

def reset_webot_status(user_id: str, chat_id: str):
    webot_status = {"webot_status": "idle", "url": None}
    save_webot_status_to_redis(user_id, chat_id, webot_status)

# this function has been deprecated
def get_plan(instruction: str, start_url: str, chat_llm: ChatOpenAI):
    # fixme: Move this into a separate chain or executors to decompose the LLMs
    system_message = f"""
You are a planner to assist another browser automation assistant.

Here is the instruction for the other assistant:
```
You MUST take one of the following actions. NEVER EVER EVER make up actions that do not exist:

1. click(element): Clicks on an element
2. setValue(element, value: string): Focuses on and sets the value of an input element
3. finish(): Indicates the task is finished
4. fail(): Indicates that you are unable to complete the task
You will be be given a task to perform and the current state of the DOM. You will also be given previous actions that you have taken. You may retry a failed action up to one time.

This is an example of an action:

<Thought>I should click the add to cart button</Thought>
<Action>click(223)</Action>

You MUST always include the <Thought> and <Action> open/close tags or else your response will be marked as invalid.

Rules you MUST follow:
1. You must only take one step at a time. You cannot take multiple actions in a single response.
2. You should not consider the action to present the result to the user. You only need to do available actions. If info in current page is enough for the user to solve the problem, you should finish.
```
Now your responsibility is to give a step-by-step plan according to user's instruction. This plan will be given to the assistant as a reference when it is performing tasks.
""".strip()

    human_message = f"""
The user requests the following task:

{instruction}

Now you are at {start_url}

Provide a plan to do this (you can use pseudo description as below to describe the item).

Here is an example case:

request: Go to google calendar to schedule a meeting

current url: "https://google.com"

example plan:

1. setValue(searchBar, "google calendar")
2. click(search)
3. click(the item with title of google calendar)
4.1 if user has loginned
    do nothing
4.2 if user hasn't loginned
    do login
5. click(create event button)
6. setValue(event title input bar, "meeting")
7. click(save event button)
8. finish()
""".strip()

    messages = [SystemMessage(content=system_message),
                HumanMessage(content=human_message)]
    response = chat_llm(messages).content
    return response

def create_webot_interaction_executor(
        llm: BaseLanguageModel,
        llm_name: str,
        user_id: str,
        chat_id: str
) -> AgentExecutor:
    """Creates an agent executor for interaction.

    Args:
        llm: A llm model.
        llm_name: A string llm name.
        user_id: A string of user id.
        chat_id: A string chat id.

    Returns:
        An agent executor.

    """
    # Initialize memory
    memory = ConversationReActBufferMemory(memory_key="chat_history",
                                           return_messages=True, max_token_limit=10000)

    class RunWebot:
        def __init__(self, webot: WebotExecutor, llm: BaseLanguageModel, user_id: str,
                     chat_id: str):
            self.llm = llm
            self.webot = webot
            self.user_id = user_id
            self.chat_id = chat_id

        def run(self, term: str) -> Union[str, Dict, DataModel]:
            try:
                user_id = self.user_id
                chat_id = self.chat_id
                reset_webot(user_id=user_id, chat_id=chat_id)
                reset_webot_status(user_id=user_id, chat_id=chat_id)
                raw_observation = self.webot.run(user_intent=term, llm=self.llm)
                instruction, start_url = raw_observation["instruction"], \
                    raw_observation["start_url"]
                webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                webot.instruction = instruction
                # webot.plan = get_plan(instruction, start_url)
                webot.plan = ""
                save_webot_to_redis(user_id=user_id, chat_id=chat_id, webot=webot)
                webot_status = {
                    "webot_status": "running",
                    "url": start_url
                }
                save_webot_status_to_redis(user_id=user_id, chat_id=chat_id,
                                           webot_status=webot_status)
                while True:
                    webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                    if webot.finish or webot.interrupt or webot.error or webot.fail:
                        break
                    else:
                        sleep(0.5)
                save_webot_status_to_redis(user_id=user_id, chat_id=chat_id,
                                           webot_status={"webot_status": "idle",
                                                         "url": None})
                webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                webot.instruction = None
                save_webot_to_redis(user_id=user_id, chat_id=chat_id, webot=webot)

                if webot.finish:
                    webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                    action_history = webot.action_history
                    last_page = webot.pages_viewed[-1]
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": True,
                            "result": json.dumps({"action_history": action_history,
                                                  "last_page": last_page}, indent=4),
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.fail:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": True,
                            "result": "The webot failed to execute the instruction.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.interrupt:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": False,
                            "result": "The web browsing is interrupted by user.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.error:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": False,
                            "result": "Error occurs during web browsing.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

            except Exception as e:
                print(traceback.format_exc())
                observation = JsonDataModel.from_raw_data(
                    {
                        "success": False,
                        "result": f"Failed in web browsing with the input: {term}, please try again later.",
                        "intermediate_steps": json.dumps({"error": str(e)})
                    }
                )
                return observation

    webot = WebotExecutor.from_webot()
    llm = copy.deepcopy(llm)
    run_webot = RunWebot(webot, llm, chat_id=chat_id, user_id=user_id)
    tools = [Tool(name=webot.name, func=run_webot.run, description=webot.description)]

    continue_model = llm_name if llm_name in NEED_CONTINUE_MODEL else None
    interaction_executor = initialize_webot_agent(
        tools, llm, continue_model, memory=memory, verbose=True
    )
    return interaction_executor

@app.route("/api/chat_xlang_webot", methods=["POST"])
def chat_xlang_webot() -> Dict:
    """Returns the chat response of web agent."""
    try:
        # Get request parameters
        request_json = request.get_json()
        user_id = request_json.pop("user_id", DEFAULT_USER_ID)
        chat_id = request_json["chat_id"]
        user_intent = request_json["user_intent"]
        parent_message_id = request_json["parent_message_id"]
        llm_name = request_json["llm_name"]
        temperature = request_json.get("temperature", 0.4)
        stop_words = ["[RESPONSE_BEGIN]", "TOOL RESPONSE"]
        kwargs = {
            "temperature": temperature,
            "stop": stop_words,
        }

        # Get language model
        llm = get_llm(llm_name, **kwargs)

        logger.bind(user_id=user_id, chat_id=chat_id, api="/chat",
                    msg_head="Request json").debug(request_json)

        human_message_id = message_id_register.add_variable(user_intent)
        ai_message_id = message_id_register.add_variable("")

        stream_handler = AgentStreamingStdOutCallbackHandler()
        # Build executor and run chat

        # reset webot and status
        reset_webot(user_id=user_id, chat_id=chat_id)
        reset_webot_status(user_id=user_id, chat_id=chat_id)

        interaction_executor = create_webot_interaction_executor(
            llm=llm,
            llm_name=llm_name,
            chat_id=chat_id,
            user_id=user_id
        )

        activated_message_list = message_pool.get_activated_message_list(user_id,
                                                                         chat_id,
                                                                         list(),
                                                                         parent_message_id)
        message_pool.load_agent_memory_from_list(interaction_executor.memory,
                                                 activated_message_list)
        return stream_with_context(
            Response(
                single_round_chat_with_agent_streaming(
                    interaction_executor=interaction_executor,
                    user_intent=user_intent,
                    human_message_id=human_message_id,
                    ai_message_id=ai_message_id,
                    user_id=user_id,
                    chat_id=chat_id,
                    message_list=activated_message_list,
                    parent_message_id=parent_message_id,
                    stream_handler=stream_handler,
                    llm_name=llm_name,
                    app_type="webot",
                ),
                content_type="application/json",
            )
        )

    except Exception as e:
        import traceback

        traceback.print_exc()
        return Response(response=None,
                        status=f"{OVERLOAD} backend is currently overloaded")


from backend.main import app, message_id_register, message_pool, logger
from backend.utils.streaming import single_round_chat_with_agent_streaming
from backend.schemas import OVERLOAD, NEED_CONTINUE_MODEL
from backend.schemas import DEFAULT_USER_ID
from real_agents.adapters.llm import BaseLanguageModel
from real_agents.adapters.agent_helpers import AgentExecutor, Tool
from real_agents.adapters.callbacks.agent_streaming import \
    AgentStreamingStdOutCallbackHandler
from real_agents.adapters.models import ChatOpenAI
from real_agents.adapters.memory import ConversationReActBufferMemory
from real_agents.adapters.data_model import DataModel, JsonDataModel
from real_agents.adapters.interactive_executor import initialize_webot_agent
from real_agents.web_agent import WebBrowsingExecutor, WebotExecutor

r = redis.Redis(host=os.getenv("REDIS_SERVER"), port=6379, db=0)  # adjust host/port/db as needed

# here webot and webot_status are stored in redis since the two global variable can not be modified and accessed normally in multiprocess
# fixme:now webot is stored without message_id or chat_id info, so it can only be used for one chat at a time
# fixme:now webot_status is stored with chat_id info, if the status is not reset after a message ended abnormally e.g. the message is interrupted, it will be reused wrongly for the next chat
def get_webot_from_redis(user_id: str, chat_id: str, ) -> WebBrowsingExecutor:
    data = r.get(f'webot_{user_id}_{chat_id}')
    if data is not None:
        webot = pickle.loads(data)
    else:
        # initialize a webot with None instrucition if webot does not exist
        webot = WebBrowsingExecutor(None)
        save_webot_to_redis(user_id, chat_id, webot)
    return webot

def save_webot_to_redis(user_id: str, chat_id: str, webot: WebBrowsingExecutor, ):
    r.set(f'webot_{user_id}_{chat_id}', pickle.dumps(webot))

def get_webot_status_from_redis(user_id: str, chat_id: str):
    webot_status_json = r.get(f'webot_status_{user_id}_{chat_id}')
    if webot_status_json is not None:
        webot_status = json.loads(webot_status_json)
        return webot_status
    else:
        return {}

def save_webot_status_to_redis(user_id: str, chat_id: str, webot_status: Dict):
    r.set(f'webot_status_{user_id}_{chat_id}', json.dumps(webot_status))

def reset_webot(user_id: str, chat_id: str):


from real_agents.adapters.interactive_executor import initialize_webot_agent
from real_agents.web_agent import WebBrowsingExecutor, WebotExecutor

r = redis.Redis(host=os.getenv("REDIS_SERVER"), port=6379, db=0)  # adjust host/port/db as needed

# here webot and webot_status are stored in redis since the two global variable can not be modified and accessed normally in multiprocess
# fixme:now webot is stored without message_id or chat_id info, so it can only be used for one chat at a time
# fixme:now webot_status is stored with chat_id info, if the status is not reset after a message ended abnormally e.g. the message is interrupted, it will be reused wrongly for the next chat
def get_webot_from_redis(user_id: str, chat_id: str, ) -> WebBrowsingExecutor:
    data = r.get(f'webot_{user_id}_{chat_id}')
    if data is not None:
        webot = pickle.loads(data)
    else:
        # initialize a webot with None instrucition if webot does not exist
        webot = WebBrowsingExecutor(None)
        save_webot_to_redis(user_id, chat_id, webot)
    return webot

def save_webot_to_redis(user_id: str, chat_id: str, webot: WebBrowsingExecutor, ):
    r.set(f'webot_{user_id}_{chat_id}', pickle.dumps(webot))

def get_webot_status_from_redis(user_id: str, chat_id: str):
    webot_status_json = r.get(f'webot_status_{user_id}_{chat_id}')
    if webot_status_json is not None:
        webot_status = json.loads(webot_status_json)
        return webot_status
    else:
        return {}

def save_webot_status_to_redis(user_id: str, chat_id: str, webot_status: Dict):
    r.set(f'webot_status_{user_id}_{chat_id}', json.dumps(webot_status))

def reset_webot(user_id: str, chat_id: str):
    webot = WebBrowsingExecutor(None)
    save_webot_to_redis(user_id, chat_id, webot)


# here webot and webot_status are stored in redis since the two global variable can not be modified and accessed normally in multiprocess
# fixme:now webot is stored without message_id or chat_id info, so it can only be used for one chat at a time
# fixme:now webot_status is stored with chat_id info, if the status is not reset after a message ended abnormally e.g. the message is interrupted, it will be reused wrongly for the next chat
def get_webot_from_redis(user_id: str, chat_id: str, ) -> WebBrowsingExecutor:
    data = r.get(f'webot_{user_id}_{chat_id}')
    if data is not None:
        webot = pickle.loads(data)
    else:
        # initialize a webot with None instrucition if webot does not exist
        webot = WebBrowsingExecutor(None)
        save_webot_to_redis(user_id, chat_id, webot)
    return webot

def save_webot_to_redis(user_id: str, chat_id: str, webot: WebBrowsingExecutor, ):
    r.set(f'webot_{user_id}_{chat_id}', pickle.dumps(webot))

def get_webot_status_from_redis(user_id: str, chat_id: str):
    webot_status_json = r.get(f'webot_status_{user_id}_{chat_id}')
    if webot_status_json is not None:
        webot_status = json.loads(webot_status_json)
        return webot_status
    else:
        return {}

def save_webot_status_to_redis(user_id: str, chat_id: str, webot_status: Dict):
    r.set(f'webot_status_{user_id}_{chat_id}', json.dumps(webot_status))

def reset_webot(user_id: str, chat_id: str):
    webot = WebBrowsingExecutor(None)
    save_webot_to_redis(user_id, chat_id, webot)

def reset_webot_status(user_id: str, chat_id: str):
    webot_status = {"webot_status": "idle", "url": None}
    save_webot_status_to_redis(user_id, chat_id, webot_status)

# this function has been deprecated
def get_plan(instruction: str, start_url: str, chat_llm: ChatOpenAI):
    # fixme: Move this into a separate chain or executors to decompose the LLMs
    system_message = f"""


    else:
        # initialize a webot with None instrucition if webot does not exist
        webot = WebBrowsingExecutor(None)
        save_webot_to_redis(user_id, chat_id, webot)
    return webot

def save_webot_to_redis(user_id: str, chat_id: str, webot: WebBrowsingExecutor, ):
    r.set(f'webot_{user_id}_{chat_id}', pickle.dumps(webot))

def get_webot_status_from_redis(user_id: str, chat_id: str):
    webot_status_json = r.get(f'webot_status_{user_id}_{chat_id}')
    if webot_status_json is not None:
        webot_status = json.loads(webot_status_json)
        return webot_status
    else:
        return {}

def save_webot_status_to_redis(user_id: str, chat_id: str, webot_status: Dict):
    r.set(f'webot_status_{user_id}_{chat_id}', json.dumps(webot_status))

def reset_webot(user_id: str, chat_id: str):
    webot = WebBrowsingExecutor(None)
    save_webot_to_redis(user_id, chat_id, webot)

def reset_webot_status(user_id: str, chat_id: str):
    webot_status = {"webot_status": "idle", "url": None}
    save_webot_status_to_redis(user_id, chat_id, webot_status)

# this function has been deprecated
def get_plan(instruction: str, start_url: str, chat_llm: ChatOpenAI):
    # fixme: Move this into a separate chain or executors to decompose the LLMs
    system_message = f"""
You are a planner to assist another browser automation assistant.

Here is the instruction for the other assistant:
```


    return webot

def save_webot_to_redis(user_id: str, chat_id: str, webot: WebBrowsingExecutor, ):
    r.set(f'webot_{user_id}_{chat_id}', pickle.dumps(webot))

def get_webot_status_from_redis(user_id: str, chat_id: str):
    webot_status_json = r.get(f'webot_status_{user_id}_{chat_id}')
    if webot_status_json is not None:
        webot_status = json.loads(webot_status_json)
        return webot_status
    else:
        return {}

def save_webot_status_to_redis(user_id: str, chat_id: str, webot_status: Dict):
    r.set(f'webot_status_{user_id}_{chat_id}', json.dumps(webot_status))

def reset_webot(user_id: str, chat_id: str):
    webot = WebBrowsingExecutor(None)
    save_webot_to_redis(user_id, chat_id, webot)

def reset_webot_status(user_id: str, chat_id: str):
    webot_status = {"webot_status": "idle", "url": None}
    save_webot_status_to_redis(user_id, chat_id, webot_status)

# this function has been deprecated
def get_plan(instruction: str, start_url: str, chat_llm: ChatOpenAI):
    # fixme: Move this into a separate chain or executors to decompose the LLMs
    system_message = f"""
You are a planner to assist another browser automation assistant.

Here is the instruction for the other assistant:
```
You MUST take one of the following actions. NEVER EVER EVER make up actions that do not exist:

1. click(element): Clicks on an element
2. setValue(element, value: string): Focuses on and sets the value of an input element
3. finish(): Indicates the task is finished


def get_webot_status_from_redis(user_id: str, chat_id: str):
    webot_status_json = r.get(f'webot_status_{user_id}_{chat_id}')
    if webot_status_json is not None:
        webot_status = json.loads(webot_status_json)
        return webot_status
    else:
        return {}

def save_webot_status_to_redis(user_id: str, chat_id: str, webot_status: Dict):
    r.set(f'webot_status_{user_id}_{chat_id}', json.dumps(webot_status))

def reset_webot(user_id: str, chat_id: str):
    webot = WebBrowsingExecutor(None)
    save_webot_to_redis(user_id, chat_id, webot)

def reset_webot_status(user_id: str, chat_id: str):
    webot_status = {"webot_status": "idle", "url": None}
    save_webot_status_to_redis(user_id, chat_id, webot_status)

# this function has been deprecated
def get_plan(instruction: str, start_url: str, chat_llm: ChatOpenAI):
    # fixme: Move this into a separate chain or executors to decompose the LLMs
    system_message = f"""
You are a planner to assist another browser automation assistant.

Here is the instruction for the other assistant:
```
You MUST take one of the following actions. NEVER EVER EVER make up actions that do not exist:

1. click(element): Clicks on an element
2. setValue(element, value: string): Focuses on and sets the value of an input element
3. finish(): Indicates the task is finished
4. fail(): Indicates that you are unable to complete the task
You will be be given a task to perform and the current state of the DOM. You will also be given previous actions that you have taken. You may retry a failed action up to one time.

This is an example of an action:


        return webot_status
    else:
        return {}

def save_webot_status_to_redis(user_id: str, chat_id: str, webot_status: Dict):
    r.set(f'webot_status_{user_id}_{chat_id}', json.dumps(webot_status))

def reset_webot(user_id: str, chat_id: str):
    webot = WebBrowsingExecutor(None)
    save_webot_to_redis(user_id, chat_id, webot)

def reset_webot_status(user_id: str, chat_id: str):
    webot_status = {"webot_status": "idle", "url": None}
    save_webot_status_to_redis(user_id, chat_id, webot_status)

# this function has been deprecated
def get_plan(instruction: str, start_url: str, chat_llm: ChatOpenAI):
    # fixme: Move this into a separate chain or executors to decompose the LLMs
    system_message = f"""
You are a planner to assist another browser automation assistant.

Here is the instruction for the other assistant:
```
You MUST take one of the following actions. NEVER EVER EVER make up actions that do not exist:

1. click(element): Clicks on an element
2. setValue(element, value: string): Focuses on and sets the value of an input element
3. finish(): Indicates the task is finished
4. fail(): Indicates that you are unable to complete the task
You will be be given a task to perform and the current state of the DOM. You will also be given previous actions that you have taken. You may retry a failed action up to one time.

This is an example of an action:

<Thought>I should click the add to cart button</Thought>
<Action>click(223)</Action>

You MUST always include the <Thought> and <Action> open/close tags or else your response will be marked as invalid.

Rules you MUST follow:
1. You must only take one step at a time. You cannot take multiple actions in a single response.
2. You should not consider the action to present the result to the user. You only need to do available actions. If info in current page is enough for the user to solve the problem, you should finish.
```
Now your responsibility is to give a step-by-step plan according to user's instruction. This plan will be given to the assistant as a reference when it is performing tasks.
""".strip()

    human_message = f"""
The user requests the following task:

{instruction}

Now you are at {start_url}

Provide a plan to do this (you can use pseudo description as below to describe the item).

Here is an example case:

request: Go to google calendar to schedule a meeting

current url: "https://google.com"

example plan:

1. setValue(searchBar, "google calendar")
2. click(search)
3. click(the item with title of google calendar)
4.1 if user has loginned
    do nothing
4.2 if user hasn't loginned
    do login
5. click(create event button)
6. setValue(event title input bar, "meeting")
7. click(save event button)
8. finish()
""".strip()

    messages = [SystemMessage(content=system_message),
                HumanMessage(content=human_message)]
    response = chat_llm(messages).content
    return response

def create_webot_interaction_executor(
        llm: BaseLanguageModel,
        llm_name: str,
        user_id: str,
        chat_id: str
) -> AgentExecutor:
    """Creates an agent executor for interaction.

    Args:
        llm: A llm model.
        llm_name: A string llm name.
        user_id: A string of user id.
        chat_id: A string chat id.

    Returns:
        An agent executor.

    """


1. setValue(searchBar, "google calendar")
2. click(search)
3. click(the item with title of google calendar)
4.1 if user has loginned
    do nothing
4.2 if user hasn't loginned
    do login
5. click(create event button)
6. setValue(event title input bar, "meeting")
7. click(save event button)
8. finish()
""".strip()

    messages = [SystemMessage(content=system_message),
                HumanMessage(content=human_message)]
    response = chat_llm(messages).content
    return response

def create_webot_interaction_executor(
        llm: BaseLanguageModel,
        llm_name: str,
        user_id: str,
        chat_id: str
) -> AgentExecutor:
    """Creates an agent executor for interaction.

    Args:
        llm: A llm model.
        llm_name: A string llm name.
        user_id: A string of user id.
        chat_id: A string chat id.

    Returns:
        An agent executor.

    """
    # Initialize memory
    memory = ConversationReActBufferMemory(memory_key="chat_history",
                                           return_messages=True, max_token_limit=10000)

    class RunWebot:
        def __init__(self, webot: WebotExecutor, llm: BaseLanguageModel, user_id: str,
                     chat_id: str):
            self.llm = llm
            self.webot = webot
            self.user_id = user_id
            self.chat_id = chat_id

        def run(self, term: str) -> Union[str, Dict, DataModel]:
            try:
                user_id = self.user_id
                chat_id = self.chat_id
                reset_webot(user_id=user_id, chat_id=chat_id)
                reset_webot_status(user_id=user_id, chat_id=chat_id)
                raw_observation = self.webot.run(user_intent=term, llm=self.llm)
                instruction, start_url = raw_observation["instruction"], \
                    raw_observation["start_url"]
                webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                webot.instruction = instruction
                # webot.plan = get_plan(instruction, start_url)
                webot.plan = ""
                save_webot_to_redis(user_id=user_id, chat_id=chat_id, webot=webot)
                webot_status = {
                    "webot_status": "running",
                    "url": start_url
                }
                save_webot_status_to_redis(user_id=user_id, chat_id=chat_id,
                                           webot_status=webot_status)
                while True:
                    webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                    if webot.finish or webot.interrupt or webot.error or webot.fail:
                        break
                    else:
                        sleep(0.5)
                save_webot_status_to_redis(user_id=user_id, chat_id=chat_id,
                                           webot_status={"webot_status": "idle",
                                                         "url": None})
                webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                webot.instruction = None
                save_webot_to_redis(user_id=user_id, chat_id=chat_id, webot=webot)

                if webot.finish:
                    webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                    action_history = webot.action_history
                    last_page = webot.pages_viewed[-1]
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": True,
                            "result": json.dumps({"action_history": action_history,
                                                  "last_page": last_page}, indent=4),
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.fail:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": True,
                            "result": "The webot failed to execute the instruction.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.interrupt:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": False,
                            "result": "The web browsing is interrupted by user.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.error:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": False,
                            "result": "Error occurs during web browsing.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

            except Exception as e:
                print(traceback.format_exc())
                observation = JsonDataModel.from_raw_data(
                    {
                        "success": False,
                        "result": f"Failed in web browsing with the input: {term}, please try again later.",
                        "intermediate_steps": json.dumps({"error": str(e)})
                    }
                )
                return observation

    webot = WebotExecutor.from_webot()
    llm = copy.deepcopy(llm)
    run_webot = RunWebot(webot, llm, chat_id=chat_id, user_id=user_id)
    tools = [Tool(name=webot.name, func=run_webot.run, description=webot.description)]

    continue_model = llm_name if llm_name in NEED_CONTINUE_MODEL else None
    interaction_executor = initialize_webot_agent(
        tools, llm, continue_model, memory=memory, verbose=True
    )
    return interaction_executor

@app.route("/api/chat_xlang_webot", methods=["POST"])
def chat_xlang_webot() -> Dict:
    """Returns the chat response of web agent."""
    try:
        # Get request parameters
        request_json = request.get_json()
        user_id = request_json.pop("user_id", DEFAULT_USER_ID)
        chat_id = request_json["chat_id"]
        user_intent = request_json["user_intent"]
        parent_message_id = request_json["parent_message_id"]
        llm_name = request_json["llm_name"]
        temperature = request_json.get("temperature", 0.4)
        stop_words = ["[RESPONSE_BEGIN]", "TOOL RESPONSE"]
        kwargs = {
            "temperature": temperature,
            "stop": stop_words,
        }


                        "success": False,
                        "result": f"Failed in web browsing with the input: {term}, please try again later.",
                        "intermediate_steps": json.dumps({"error": str(e)})
                    }
                )
                return observation

    webot = WebotExecutor.from_webot()
    llm = copy.deepcopy(llm)
    run_webot = RunWebot(webot, llm, chat_id=chat_id, user_id=user_id)
    tools = [Tool(name=webot.name, func=run_webot.run, description=webot.description)]

    continue_model = llm_name if llm_name in NEED_CONTINUE_MODEL else None
    interaction_executor = initialize_webot_agent(
        tools, llm, continue_model, memory=memory, verbose=True
    )
    return interaction_executor

@app.route("/api/chat_xlang_webot", methods=["POST"])
def chat_xlang_webot() -> Dict:
    """Returns the chat response of web agent."""
    try:
        # Get request parameters
        request_json = request.get_json()
        user_id = request_json.pop("user_id", DEFAULT_USER_ID)
        chat_id = request_json["chat_id"]
        user_intent = request_json["user_intent"]
        parent_message_id = request_json["parent_message_id"]
        llm_name = request_json["llm_name"]
        temperature = request_json.get("temperature", 0.4)
        stop_words = ["[RESPONSE_BEGIN]", "TOOL RESPONSE"]
        kwargs = {
            "temperature": temperature,
            "stop": stop_words,
        }

        # Get language model
        llm = get_llm(llm_name, **kwargs)

        logger.bind(user_id=user_id, chat_id=chat_id, api="/chat",
                    msg_head="Request json").debug(request_json)

        human_message_id = message_id_register.add_variable(user_intent)
        ai_message_id = message_id_register.add_variable("")

        stream_handler = AgentStreamingStdOutCallbackHandler()
        # Build executor and run chat

        # reset webot and status
        reset_webot(user_id=user_id, chat_id=chat_id)
        reset_webot_status(user_id=user_id, chat_id=chat_id)

        interaction_executor = create_webot_interaction_executor(
            llm=llm,
            llm_name=llm_name,
            chat_id=chat_id,
            user_id=user_id
        )

        activated_message_list = message_pool.get_activated_message_list(user_id,
                                                                         chat_id,
                                                                         list(),
                                                                         parent_message_id)
        message_pool.load_agent_memory_from_list(interaction_executor.memory,
                                                 activated_message_list)
        return stream_with_context(
            Response(
                single_round_chat_with_agent_streaming(
                    interaction_executor=interaction_executor,
                    user_intent=user_intent,
                    human_message_id=human_message_id,
                    ai_message_id=ai_message_id,
                    user_id=user_id,
                    chat_id=chat_id,
                    message_list=activated_message_list,
                    parent_message_id=parent_message_id,
                    stream_handler=stream_handler,
                    llm_name=llm_name,
                    app_type="webot",
                ),
                content_type="application/json",
            )
        )

    except Exception as e:
        import traceback

        traceback.print_exc()
        return Response(response=None,
                        status=f"{OVERLOAD} backend is currently overloaded")


RunWebot

        llm_name: str,
        user_id: str,
        chat_id: str
) -> AgentExecutor:
    """Creates an agent executor for interaction.

    Args:
        llm: A llm model.
        llm_name: A string llm name.
        user_id: A string of user id.
        chat_id: A string chat id.

    Returns:
        An agent executor.

    """
    # Initialize memory
    memory = ConversationReActBufferMemory(memory_key="chat_history",
                                           return_messages=True, max_token_limit=10000)

    class RunWebot:
        def __init__(self, webot: WebotExecutor, llm: BaseLanguageModel, user_id: str,
                     chat_id: str):
            self.llm = llm
            self.webot = webot
            self.user_id = user_id
            self.chat_id = chat_id

        def run(self, term: str) -> Union[str, Dict, DataModel]:
            try:
                user_id = self.user_id
                chat_id = self.chat_id
                reset_webot(user_id=user_id, chat_id=chat_id)
                reset_webot_status(user_id=user_id, chat_id=chat_id)
                raw_observation = self.webot.run(user_intent=term, llm=self.llm)
                instruction, start_url = raw_observation["instruction"], \
                    raw_observation["start_url"]
                webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                webot.instruction = instruction
                # webot.plan = get_plan(instruction, start_url)
                webot.plan = ""
                save_webot_to_redis(user_id=user_id, chat_id=chat_id, webot=webot)
                webot_status = {
                    "webot_status": "running",
                    "url": start_url
                }
                save_webot_status_to_redis(user_id=user_id, chat_id=chat_id,
                                           webot_status=webot_status)
                while True:
                    webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                    if webot.finish or webot.interrupt or webot.error or webot.fail:
                        break
                    else:
                        sleep(0.5)
                save_webot_status_to_redis(user_id=user_id, chat_id=chat_id,
                                           webot_status={"webot_status": "idle",
                                                         "url": None})
                webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                webot.instruction = None
                save_webot_to_redis(user_id=user_id, chat_id=chat_id, webot=webot)

                if webot.finish:
                    webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                    action_history = webot.action_history
                    last_page = webot.pages_viewed[-1]
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": True,
                            "result": json.dumps({"action_history": action_history,
                                                  "last_page": last_page}, indent=4),
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.fail:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": True,
                            "result": "The webot failed to execute the instruction.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.interrupt:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": False,
                            "result": "The web browsing is interrupted by user.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.error:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": False,
                            "result": "Error occurs during web browsing.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

            except Exception as e:
                print(traceback.format_exc())
                observation = JsonDataModel.from_raw_data(
                    {
                        "success": False,
                        "result": f"Failed in web browsing with the input: {term}, please try again later.",
                        "intermediate_steps": json.dumps({"error": str(e)})
                    }
                )
                return observation

    webot = WebotExecutor.from_webot()
    llm = copy.deepcopy(llm)
    run_webot = RunWebot(webot, llm, chat_id=chat_id, user_id=user_id)
    tools = [Tool(name=webot.name, func=run_webot.run, description=webot.description)]

    continue_model = llm_name if llm_name in NEED_CONTINUE_MODEL else None
    interaction_executor = initialize_webot_agent(
        tools, llm, continue_model, memory=memory, verbose=True
    )
    return interaction_executor

@app.route("/api/chat_xlang_webot", methods=["POST"])
def chat_xlang_webot() -> Dict:
    """Returns the chat response of web agent."""
    try:
        # Get request parameters
        request_json = request.get_json()
        user_id = request_json.pop("user_id", DEFAULT_USER_ID)


        user_id: str,
        chat_id: str
) -> AgentExecutor:
    """Creates an agent executor for interaction.

    Args:
        llm: A llm model.
        llm_name: A string llm name.
        user_id: A string of user id.
        chat_id: A string chat id.

    Returns:
        An agent executor.

    """
    # Initialize memory
    memory = ConversationReActBufferMemory(memory_key="chat_history",
                                           return_messages=True, max_token_limit=10000)

    class RunWebot:
        def __init__(self, webot: WebotExecutor, llm: BaseLanguageModel, user_id: str,
                     chat_id: str):
            self.llm = llm
            self.webot = webot
            self.user_id = user_id
            self.chat_id = chat_id

        def run(self, term: str) -> Union[str, Dict, DataModel]:
            try:
                user_id = self.user_id
                chat_id = self.chat_id
                reset_webot(user_id=user_id, chat_id=chat_id)
                reset_webot_status(user_id=user_id, chat_id=chat_id)
                raw_observation = self.webot.run(user_intent=term, llm=self.llm)
                instruction, start_url = raw_observation["instruction"], \
                    raw_observation["start_url"]
                webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                webot.instruction = instruction
                # webot.plan = get_plan(instruction, start_url)
                webot.plan = ""
                save_webot_to_redis(user_id=user_id, chat_id=chat_id, webot=webot)
                webot_status = {
                    "webot_status": "running",
                    "url": start_url
                }
                save_webot_status_to_redis(user_id=user_id, chat_id=chat_id,


run

        llm_name: A string llm name.
        user_id: A string of user id.
        chat_id: A string chat id.

    Returns:
        An agent executor.

    """
    # Initialize memory
    memory = ConversationReActBufferMemory(memory_key="chat_history",
                                           return_messages=True, max_token_limit=10000)

    class RunWebot:
        def __init__(self, webot: WebotExecutor, llm: BaseLanguageModel, user_id: str,
                     chat_id: str):
            self.llm = llm
            self.webot = webot
            self.user_id = user_id
            self.chat_id = chat_id

        def run(self, term: str) -> Union[str, Dict, DataModel]:
            try:
                user_id = self.user_id
                chat_id = self.chat_id
                reset_webot(user_id=user_id, chat_id=chat_id)
                reset_webot_status(user_id=user_id, chat_id=chat_id)
                raw_observation = self.webot.run(user_intent=term, llm=self.llm)
                instruction, start_url = raw_observation["instruction"], \
                    raw_observation["start_url"]
                webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                webot.instruction = instruction
                # webot.plan = get_plan(instruction, start_url)
                webot.plan = ""
                save_webot_to_redis(user_id=user_id, chat_id=chat_id, webot=webot)
                webot_status = {
                    "webot_status": "running",
                    "url": start_url
                }
                save_webot_status_to_redis(user_id=user_id, chat_id=chat_id,
                                           webot_status=webot_status)
                while True:
                    webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                    if webot.finish or webot.interrupt or webot.error or webot.fail:
                        break
                    else:
                        sleep(0.5)
                save_webot_status_to_redis(user_id=user_id, chat_id=chat_id,
                                           webot_status={"webot_status": "idle",
                                                         "url": None})
                webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                webot.instruction = None
                save_webot_to_redis(user_id=user_id, chat_id=chat_id, webot=webot)

                if webot.finish:
                    webot = get_webot_from_redis(user_id=user_id, chat_id=chat_id)
                    action_history = webot.action_history
                    last_page = webot.pages_viewed[-1]
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": True,
                            "result": json.dumps({"action_history": action_history,
                                                  "last_page": last_page}, indent=4),
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.fail:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": True,
                            "result": "The webot failed to execute the instruction.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.interrupt:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": False,
                            "result": "The web browsing is interrupted by user.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

                if webot.error:
                    observation = JsonDataModel.from_raw_data(
                        {
                            "success": False,
                            "result": "Error occurs during web browsing.",
                            "intermediate_steps": json.dumps(
                                {"instruction": instruction, "start_url": start_url},
                                indent=4)
                        }
                    )
                    return observation

            except Exception as e:
                print(traceback.format_exc())
                observation = JsonDataModel.from_raw_data(
                    {
                        "success": False,
                        "result": f"Failed in web browsing with the input: {term}, please try again later.",
                        "intermediate_steps": json.dumps({"error": str(e)})
                    }
                )
                return observation

    webot = WebotExecutor.from_webot()
    llm = copy.deepcopy(llm)
    run_webot = RunWebot(webot, llm, chat_id=chat_id, user_id=user_id)
    tools = [Tool(name=webot.name, func=run_webot.run, description=webot.description)]

    continue_model = llm_name if llm_name in NEED_CONTINUE_MODEL else None
    interaction_executor = initialize_webot_agent(
        tools, llm, continue_model, memory=memory, verbose=True
    )
    return interaction_executor

@app.route("/api/chat_xlang_webot", methods=["POST"])
def chat_xlang_webot() -> Dict:
    """Returns the chat response of web agent."""
    try:
        # Get request parameters
        request_json = request.get_json()
        user_id = request_json.pop("user_id", DEFAULT_USER_ID)


import base64
import copy
import json
import os
import random
import traceback
from typing import Dict, List, Union

import requests
from flask import Response, request, stream_with_context
from retrying import retry

from backend.api.language_model import get_llm
from backend.app import app
from backend.main import message_id_register, message_pool, logger
from backend.utils.streaming import single_round_chat_with_agent_streaming
from backend.schemas import OVERLOAD, NEED_CONTINUE_MODEL, DEFAULT_USER_ID
from backend.main import api_key_pool
from real_agents.adapters.llm import BaseLanguageModel
from real_agents.adapters.agent_helpers import AgentExecutor, Tool
from real_agents.adapters.callbacks.agent_streaming import \
    AgentStreamingStdOutCallbackHandler
from real_agents.adapters.data_model import DataModel, JsonDataModel
from real_agents.adapters.interactive_executor import initialize_plugin_agent
from real_agents.adapters.memory import ConversationReActBufferMemory
from real_agents.plugins_agent.plugins.utils import load_all_plugins_elements
from real_agents.plugins_agent.plugins.tool_selector import ToolSelector
from real_agents.plugins_agent import PluginExecutor

# The plugins list
global plugins
plugins = []

# Set up the tool selector for automatically selecting plugins
try:
    tool_selector = ToolSelector(tools_list=plugins, mode="embedding", api_key_pool=api_key_pool)
except Exception as e:
    print(e, "The auto selection feature of plugins agent will return random elements.")
    tool_selector = None

# Load plugin info and icon image
for plugin_type, plugin_info in load_all_plugins_elements().items():
    @retry(stop_max_attempt_number=10,
           wait_fixed=2000)  # Retry 3 times with a 2-second delay between retries
    def make_request(_image_url) -> Response:
        response = requests.get(_image_url)  # Replace with your actual request code
        response.raise_for_status()  # Raise an exception for unsuccessful response status codes
        return response

    # Load icon image
    image_url = plugin_info["meta_info"]["manifest"]["logo_url"]

    # If image is base64 encoded
    if image_url.startswith("data:image"):
        plugins.append(
            {
                "id": plugin_type,
                "name": plugin_type,
                "name_for_human": plugin_info["meta_info"]["manifest"][
                    "name_for_human"],
                "description": plugin_info["description"],
                "icon": image_url,
                "require_api_key": plugin_info["need_auth"],
            }
        )
        continue

    image_format = image_url.split(".")[-1]

    try:
        # Check if in cache
        os.makedirs("backend/static/images", exist_ok=True)
        if os.path.exists(f"backend/static/images/{plugin_type}.cache"):
            with open(f"backend/static/images/{plugin_type}.cache", "rb") as f:
                image_content = f.read()
        else:
            response = make_request(image_url)
            image_content = response.content
            # Make a .cache file for the image
            with open(f"backend/static/images/{plugin_type}.cache", "wb") as f:
                f.write(image_content)
    except requests.exceptions.RequestException as e:
        print(f"Error: Failed to make the request {plugin_type}:", e)
        continue

    if image_format == "svg":
        encoded_image = "data:image/svg+xml;base64,  ".format(
            image_format) + base64.b64encode(image_content).decode(
            "utf-8"
        )
    else:
        encoded_image = "data:image/{};base64,  ".format(
            image_format) + base64.b64encode(image_content).decode(
            "utf-8"
        )

    plugins.append(
        {
            "id": plugin_type,
            "name": plugin_type,
            "name_for_human": plugin_info["meta_info"]["manifest"]["name_for_human"],
            "description": plugin_info["description"],
            "icon": encoded_image,
            "require_api_key": plugin_info["need_auth"],
        }
    )

def create_plugins_interaction_executor(
        selected_plugins: List[str],
        api_key_info: List[Dict],
        llm: BaseLanguageModel,
        llm_name: str,
) -> AgentExecutor:
    """Creates an agent executor for interaction.

    Args:
        selected_plugins: A list of selected plugins.
        api_key_info: A list of plugin api keys.
        llm: A llm model.
        llm_name: A string llm name.

    Returns:
        An agent executor.

    """
    # Initialize memory
    memory = ConversationReActBufferMemory(memory_key="chat_history",
                                           return_messages=True, style="plugin",
                                           max_token_limit=10000)

    class RunPlugin:
        def __init__(self, plugin: PluginExecutor, llm: BaseLanguageModel):
            self.plugin = plugin
            self.llm = llm

        def run(self, term: str) -> Union[str, Dict, DataModel]:
            try:
                raw_observation = self.plugin.run(user_intent=term, llm=self.llm)
                input_json, output = raw_observation["input_json"], raw_observation[
                    "api_output"]
                observation = JsonDataModel.from_raw_data(
                    {
                        "success": True,
                        "result": json.dumps(output, indent=4) if isinstance(output,
                                                                             dict) else output,
                        "intermediate_steps": json.dumps(input_json, indent=4),
                    }
                )
                return observation

            except Exception as e:
                observation = JsonDataModel.from_raw_data(
                    {
                        "success": False,
                        "result": str(e),
                    }
                )
                print(traceback.format_exc())
                return observation

    # Load plugins from selected names
    _plugins = []
    for selected_plugin in selected_plugins:
        plugin = PluginExecutor.from_plugin_name(selected_plugin)
        llm = copy.deepcopy(llm)

        if len([i for i in api_key_info if i["tool_name"] == plugin.name]) != 0:
            plugin.api_key = \
            [i for i in api_key_info if i["tool_name"] == plugin.name][0]["api_key"]
            # For some plugins, we need to reload the plugin to update personal data
            plugin.load_personnel_info()  # warning: this will change the plugin object every time we make a new query

        run_plugin = RunPlugin(plugin, llm)

        _plugins.append(Tool(name=plugin.name, func=run_plugin.run,
                             description=plugin.full_description))

    continue_model = llm_name if llm_name in NEED_CONTINUE_MODEL else None
    interaction_executor = initialize_plugin_agent(
        _plugins, llm, continue_model, memory=memory, verbose=True
    )

    return interaction_executor

@app.route("/api/chat_xlang_plugin", methods=["POST"])
def chat_xlang_plugin() -> Dict:
    """Returns the chat response of plugins agent."""
    try:
        # Get request parameters
        request_json = request.get_json()
        user_id = request_json.pop("user_id", DEFAULT_USER_ID)
        chat_id = request_json["chat_id"]
        user_intent = request_json["user_intent"]
        parent_message_id = request_json["parent_message_id"]
        selected_plugins = request_json["selected_plugins"]
        llm_name = request_json["llm_name"]
        temperature = request_json.get("temperature", 0.4)
        stop_words = ["[RESPONSE_BEGIN]", "TOOL RESPONSE"]
        kwargs = {
            "temperature": temperature,
            "stop": stop_words,
        }

        # pass user id and chat id to tool selector
        if tool_selector:
            tool_selector.user_id = user_id
            tool_selector.chat_id = chat_id

        # Get language model
        llm = get_llm(llm_name, **kwargs)

        logger.bind(user_id=user_id, chat_id=chat_id, api="/chat",
                    msg_head="Request json").debug(request_json)

        # Get API key for plugins
        api_key_info = api_key_pool.get_pool_info_with_id(user_id,
                                                          default_value=[])  # fixme: mock user_id: 1

        activated_message_list = message_pool.get_activated_message_list(user_id,
                                                                         chat_id,
                                                                         list(),
                                                                         parent_message_id)

        # Flag for auto retrieving plugins
        if len(selected_plugins) == 1 and selected_plugins[0].lower() == "auto":

            if tool_selector:
                # this will return a list of plugin names sorted by relevance (lower case and the same as their dir name)
                query = tool_selector.load_query_from_message_list(activated_message_list,
                                                                   user_intent)
                selected_plugins = tool_selector.select_tools(query=query, top_k=5)
            else:
                selected_plugins = random.sample(plugins, 5)

        # Build executor and run chat
        stream_handler = AgentStreamingStdOutCallbackHandler()
        interaction_executor = create_plugins_interaction_executor(
            selected_plugins=selected_plugins,
            api_key_info=api_key_info,
            llm=llm,
            llm_name=llm_name,
        )

        message_pool.load_agent_memory_from_list(interaction_executor.memory,
                                                 activated_message_list)

        human_message_id = message_id_register.add_variable(user_intent)
        ai_message_id = message_id_register.add_variable("")

        return stream_with_context(
            Response(
                single_round_chat_with_agent_streaming(
                    interaction_executor=interaction_executor,
                    user_intent=user_intent,
                    human_message_id=human_message_id,
                    ai_message_id=ai_message_id,
                    user_id=user_id,
                    chat_id=chat_id,
                    message_list=activated_message_list,
                    parent_message_id=parent_message_id,
                    stream_handler=stream_handler,
                    llm_name=llm_name,
                    app_type="plugins",
                ),
                content_type="application/json",
            )
        )

    except Exception as e:
        import traceback

        traceback.print_exc()
        return Response(response=None,
                        status=f"{OVERLOAD} backend is currently overloaded")


            "utf-8"
        )
    else:
        encoded_image = "data:image/{};base64,  ".format(
            image_format) + base64.b64encode(image_content).decode(
            "utf-8"
        )

    plugins.append(
        {
            "id": plugin_type,
            "name": plugin_type,
            "name_for_human": plugin_info["meta_info"]["manifest"]["name_for_human"],
            "description": plugin_info["description"],
            "icon": encoded_image,
            "require_api_key": plugin_info["need_auth"],
        }
    )

def create_plugins_interaction_executor(
        selected_plugins: List[str],
        api_key_info: List[Dict],
        llm: BaseLanguageModel,
        llm_name: str,
) -> AgentExecutor:
    """Creates an agent executor for interaction.

    Args:
        selected_plugins: A list of selected plugins.
        api_key_info: A list of plugin api keys.
        llm: A llm model.
        llm_name: A string llm name.

    Returns:
        An agent executor.

    """
    # Initialize memory
    memory = ConversationReActBufferMemory(memory_key="chat_history",
                                           return_messages=True, style="plugin",
                                           max_token_limit=10000)

    class RunPlugin:
        def __init__(self, plugin: PluginExecutor, llm: BaseLanguageModel):
            self.plugin = plugin
            self.llm = llm

        def run(self, term: str) -> Union[str, Dict, DataModel]:
            try:
                raw_observation = self.plugin.run(user_intent=term, llm=self.llm)
                input_json, output = raw_observation["input_json"], raw_observation[
                    "api_output"]
                observation = JsonDataModel.from_raw_data(
                    {
                        "success": True,
                        "result": json.dumps(output, indent=4) if isinstance(output,
                                                                             dict) else output,
                        "intermediate_steps": json.dumps(input_json, indent=4),
                    }
                )
                return observation

            except Exception as e:
                observation = JsonDataModel.from_raw_data(
                    {
                        "success": False,
                        "result": str(e),
                    }
                )
                print(traceback.format_exc())
                return observation

    # Load plugins from selected names
    _plugins = []
    for selected_plugin in selected_plugins:
        plugin = PluginExecutor.from_plugin_name(selected_plugin)
        llm = copy.deepcopy(llm)

        if len([i for i in api_key_info if i["tool_name"] == plugin.name]) != 0:
            plugin.api_key = \
            [i for i in api_key_info if i["tool_name"] == plugin.name][0]["api_key"]
            # For some plugins, we need to reload the plugin to update personal data
            plugin.load_personnel_info()  # warning: this will change the plugin object every time we make a new query

        run_plugin = RunPlugin(plugin, llm)

        _plugins.append(Tool(name=plugin.name, func=run_plugin.run,
                             description=plugin.full_description))

    continue_model = llm_name if llm_name in NEED_CONTINUE_MODEL else None
    interaction_executor = initialize_plugin_agent(
        _plugins, llm, continue_model, memory=memory, verbose=True
    )

    return interaction_executor

@app.route("/api/chat_xlang_plugin", methods=["POST"])
def chat_xlang_plugin() -> Dict:
    """Returns the chat response of plugins agent."""
    try:
        # Get request parameters
        request_json = request.get_json()
        user_id = request_json.pop("user_id", DEFAULT_USER_ID)
        chat_id = request_json["chat_id"]
        user_intent = request_json["user_intent"]
        parent_message_id = request_json["parent_message_id"]
        selected_plugins = request_json["selected_plugins"]
        llm_name = request_json["llm_name"]
        temperature = request_json.get("temperature", 0.4)
        stop_words = ["[RESPONSE_BEGIN]", "TOOL RESPONSE"]
        kwargs = {
            "temperature": temperature,
            "stop": stop_words,
        }


        if len([i for i in api_key_info if i["tool_name"] == plugin.name]) != 0:
            plugin.api_key = \
            [i for i in api_key_info if i["tool_name"] == plugin.name][0]["api_key"]
            # For some plugins, we need to reload the plugin to update personal data
            plugin.load_personnel_info()  # warning: this will change the plugin object every time we make a new query

        run_plugin = RunPlugin(plugin, llm)

        _plugins.append(Tool(name=plugin.name, func=run_plugin.run,
                             description=plugin.full_description))

    continue_model = llm_name if llm_name in NEED_CONTINUE_MODEL else None
    interaction_executor = initialize_plugin_agent(
        _plugins, llm, continue_model, memory=memory, verbose=True
    )

    return interaction_executor

@app.route("/api/chat_xlang_plugin", methods=["POST"])
def chat_xlang_plugin() -> Dict:
    """Returns the chat response of plugins agent."""
    try:
        # Get request parameters
        request_json = request.get_json()
        user_id = request_json.pop("user_id", DEFAULT_USER_ID)
        chat_id = request_json["chat_id"]
        user_intent = request_json["user_intent"]
        parent_message_id = request_json["parent_message_id"]
        selected_plugins = request_json["selected_plugins"]
        llm_name = request_json["llm_name"]
        temperature = request_json.get("temperature", 0.4)
        stop_words = ["[RESPONSE_BEGIN]", "TOOL RESPONSE"]
        kwargs = {
            "temperature": temperature,
            "stop": stop_words,
        }

        # pass user id and chat id to tool selector
        if tool_selector:
            tool_selector.user_id = user_id
            tool_selector.chat_id = chat_id

        # Get language model
        llm = get_llm(llm_name, **kwargs)

        logger.bind(user_id=user_id, chat_id=chat_id, api="/chat",
                    msg_head="Request json").debug(request_json)

        # Get API key for plugins
        api_key_info = api_key_pool.get_pool_info_with_id(user_id,
                                                          default_value=[])  # fixme: mock user_id: 1

        activated_message_list = message_pool.get_activated_message_list(user_id,
                                                                         chat_id,
                                                                         list(),
                                                                         parent_message_id)

        # Flag for auto retrieving plugins
        if len(selected_plugins) == 1 and selected_plugins[0].lower() == "auto":

            if tool_selector:
                # this will return a list of plugin names sorted by relevance (lower case and the same as their dir name)
                query = tool_selector.load_query_from_message_list(activated_message_list,
                                                                   user_intent)
                selected_plugins = tool_selector.select_tools(query=query, top_k=5)
            else:
                selected_plugins = random.sample(plugins, 5)

        # Build executor and run chat
        stream_handler = AgentStreamingStdOutCallbackHandler()
        interaction_executor = create_plugins_interaction_executor(
            selected_plugins=selected_plugins,
            api_key_info=api_key_info,
            llm=llm,
            llm_name=llm_name,
        )

        message_pool.load_agent_memory_from_list(interaction_executor.memory,
                                                 activated_message_list)

        human_message_id = message_id_register.add_variable(user_intent)
        ai_message_id = message_id_register.add_variable("")

        return stream_with_context(
            Response(
                single_round_chat_with_agent_streaming(
                    interaction_executor=interaction_executor,
                    user_intent=user_intent,
                    human_message_id=human_message_id,
                    ai_message_id=ai_message_id,
                    user_id=user_id,
                    chat_id=chat_id,
                    message_list=activated_message_list,
                    parent_message_id=parent_message_id,
                    stream_handler=stream_handler,
                    llm_name=llm_name,
                    app_type="plugins",
                ),
                content_type="application/json",
            )
        )

    except Exception as e:
        import traceback

        traceback.print_exc()
        return Response(response=None,
                        status=f"{OVERLOAD} backend is currently overloaded")


from real_agents.adapters.memory import ConversationReActBufferMemory
from real_agents.plugins_agent.plugins.utils import load_all_plugins_elements
from real_agents.plugins_agent.plugins.tool_selector import ToolSelector
from real_agents.plugins_agent import PluginExecutor

# The plugins list
global plugins
plugins = []

# Set up the tool selector for automatically selecting plugins
try:
    tool_selector = ToolSelector(tools_list=plugins, mode="embedding", api_key_pool=api_key_pool)
except Exception as e:
    print(e, "The auto selection feature of plugins agent will return random elements.")
    tool_selector = None

# Load plugin info and icon image
for plugin_type, plugin_info in load_all_plugins_elements().items():
    @retry(stop_max_attempt_number=10,
           wait_fixed=2000)  # Retry 3 times with a 2-second delay between retries
    def make_request(_image_url) -> Response:
        response = requests.get(_image_url)  # Replace with your actual request code
        response.raise_for_status()  # Raise an exception for unsuccessful response status codes
        return response

    # Load icon image
    image_url = plugin_info["meta_info"]["manifest"]["logo_url"]

    # If image is base64 encoded
    if image_url.startswith("data:image"):
        plugins.append(
            {
                "id": plugin_type,
                "name": plugin_type,
                "name_for_human": plugin_info["meta_info"]["manifest"][
                    "name_for_human"],
                "description": plugin_info["description"],
                "icon": image_url,
                "require_api_key": plugin_info["need_auth"],
            }
        )
        continue