diff --git a/en/.doctrees/agentscope.agents.agent.doctree b/en/.doctrees/agentscope.agents.agent.doctree index a286facdc..6423cfe50 100644 Binary files a/en/.doctrees/agentscope.agents.agent.doctree and b/en/.doctrees/agentscope.agents.agent.doctree differ diff --git a/en/.doctrees/agentscope.agents.doctree b/en/.doctrees/agentscope.agents.doctree index 4c9629026..f87476899 100644 Binary files a/en/.doctrees/agentscope.agents.doctree and b/en/.doctrees/agentscope.agents.doctree differ diff --git a/en/.doctrees/agentscope.agents.user_agent.doctree b/en/.doctrees/agentscope.agents.user_agent.doctree index d5dfbf34f..ed0423504 100644 Binary files a/en/.doctrees/agentscope.agents.user_agent.doctree and b/en/.doctrees/agentscope.agents.user_agent.doctree differ diff --git a/en/.doctrees/agentscope.doctree b/en/.doctrees/agentscope.doctree index 246d74695..faf1caa0f 100644 Binary files a/en/.doctrees/agentscope.doctree and b/en/.doctrees/agentscope.doctree differ diff --git a/en/.doctrees/agentscope.exception.doctree b/en/.doctrees/agentscope.exception.doctree index 9248f2073..5d4eeb03f 100644 Binary files a/en/.doctrees/agentscope.exception.doctree and b/en/.doctrees/agentscope.exception.doctree differ diff --git a/en/.doctrees/agentscope.utils.logging_utils.doctree b/en/.doctrees/agentscope.logging.doctree similarity index 75% rename from en/.doctrees/agentscope.utils.logging_utils.doctree rename to en/.doctrees/agentscope.logging.doctree index 6616bff49..2949990c5 100644 Binary files a/en/.doctrees/agentscope.utils.logging_utils.doctree and b/en/.doctrees/agentscope.logging.doctree differ diff --git a/en/.doctrees/agentscope.message.doctree b/en/.doctrees/agentscope.message.doctree index 271b2b147..8c4786048 100644 Binary files a/en/.doctrees/agentscope.message.doctree and b/en/.doctrees/agentscope.message.doctree differ diff --git a/en/.doctrees/agentscope.server.doctree b/en/.doctrees/agentscope.server.doctree index 7fda2661d..5c3e08858 100644 Binary files a/en/.doctrees/agentscope.server.doctree and b/en/.doctrees/agentscope.server.doctree differ diff --git a/en/.doctrees/agentscope.server.launcher.doctree b/en/.doctrees/agentscope.server.launcher.doctree index 177bd97e6..c0e7c0f14 100644 Binary files a/en/.doctrees/agentscope.server.launcher.doctree and b/en/.doctrees/agentscope.server.launcher.doctree differ diff --git a/en/.doctrees/agentscope.server.servicer.doctree b/en/.doctrees/agentscope.server.servicer.doctree index 1ac656881..9aab36f90 100644 Binary files a/en/.doctrees/agentscope.server.servicer.doctree and b/en/.doctrees/agentscope.server.servicer.doctree differ diff --git a/en/.doctrees/agentscope.studio.doctree b/en/.doctrees/agentscope.studio.doctree new file mode 100644 index 000000000..4939ab0e3 Binary files /dev/null and b/en/.doctrees/agentscope.studio.doctree differ diff --git a/en/.doctrees/agentscope.utils.doctree b/en/.doctrees/agentscope.utils.doctree index 97abc2297..2352830f3 100644 Binary files a/en/.doctrees/agentscope.utils.doctree and b/en/.doctrees/agentscope.utils.doctree differ diff --git a/en/.doctrees/agentscope.utils.tools.doctree b/en/.doctrees/agentscope.utils.tools.doctree index aa85acbd8..f8a2f5a46 100644 Binary files a/en/.doctrees/agentscope.utils.tools.doctree and b/en/.doctrees/agentscope.utils.tools.doctree differ diff --git a/en/.doctrees/agentscope.web.doctree b/en/.doctrees/agentscope.web.doctree index 201805acc..6ca95efa9 100644 Binary files a/en/.doctrees/agentscope.web.doctree and b/en/.doctrees/agentscope.web.doctree differ diff --git a/en/.doctrees/agentscope.web.studio.constants.doctree b/en/.doctrees/agentscope.web.gradio.constants.doctree similarity index 80% rename from en/.doctrees/agentscope.web.studio.constants.doctree rename to en/.doctrees/agentscope.web.gradio.constants.doctree index a7fa9af0b..971ac61c8 100644 Binary files a/en/.doctrees/agentscope.web.studio.constants.doctree and b/en/.doctrees/agentscope.web.gradio.constants.doctree differ diff --git a/en/.doctrees/agentscope.web.studio.doctree b/en/.doctrees/agentscope.web.gradio.doctree similarity index 82% rename from en/.doctrees/agentscope.web.studio.doctree rename to en/.doctrees/agentscope.web.gradio.doctree index c34d21a04..f1012cfd2 100644 Binary files a/en/.doctrees/agentscope.web.studio.doctree and b/en/.doctrees/agentscope.web.gradio.doctree differ diff --git a/en/.doctrees/agentscope.web.studio.studio.doctree b/en/.doctrees/agentscope.web.gradio.studio.doctree similarity index 82% rename from en/.doctrees/agentscope.web.studio.studio.doctree rename to en/.doctrees/agentscope.web.gradio.studio.doctree index 659386382..3d5f5cec0 100644 Binary files a/en/.doctrees/agentscope.web.studio.studio.doctree and b/en/.doctrees/agentscope.web.gradio.studio.doctree differ diff --git a/en/.doctrees/agentscope.web.studio.utils.doctree b/en/.doctrees/agentscope.web.gradio.utils.doctree similarity index 85% rename from en/.doctrees/agentscope.web.studio.utils.doctree rename to en/.doctrees/agentscope.web.gradio.utils.doctree index 1eaac289b..936fd7d88 100644 Binary files a/en/.doctrees/agentscope.web.studio.utils.doctree and b/en/.doctrees/agentscope.web.gradio.utils.doctree differ diff --git a/en/.doctrees/agentscope.web.workstation.workflow_dag.doctree b/en/.doctrees/agentscope.web.workstation.workflow_dag.doctree index 9dd564cc7..6a32f4d5b 100644 Binary files a/en/.doctrees/agentscope.web.workstation.workflow_dag.doctree and b/en/.doctrees/agentscope.web.workstation.workflow_dag.doctree differ diff --git a/en/.doctrees/environment.pickle b/en/.doctrees/environment.pickle index b425b5aec..e3adc27e1 100644 Binary files a/en/.doctrees/environment.pickle and b/en/.doctrees/environment.pickle differ diff --git a/en/.doctrees/index.doctree b/en/.doctrees/index.doctree index bad7f1683..132def218 100644 Binary files a/en/.doctrees/index.doctree and b/en/.doctrees/index.doctree differ diff --git a/en/_modules/agentscope/_init.html b/en/_modules/agentscope/_init.html index d87ec8239..54b3ce2dc 100644 --- a/en/_modules/agentscope/_init.html +++ b/en/_modules/agentscope/_init.html @@ -108,11 +108,12 @@

Source code for agentscope._init

 from .agents import AgentBase
 from ._runtime import _runtime
 from .file_manager import file_manager
-from .utils.logging_utils import LOG_LEVEL, setup_logger
+from .logging import LOG_LEVEL, setup_logger
 from .utils.monitor import MonitorFactory
 from .models import read_model_configs
 from .constants import _DEFAULT_DIR
 from .constants import _DEFAULT_LOG_LEVEL
+from .studio._client import _studio_client
 
 # init setting
 _INIT_SETTINGS = {}
@@ -132,6 +133,7 @@ 

Source code for agentscope._init

     logger_level: LOG_LEVEL = _DEFAULT_LOG_LEVEL,
     runtime_id: Optional[str] = None,
     agent_configs: Optional[Union[str, list, dict]] = None,
+    studio_url: Optional[str] = None,
 ) -> Sequence[AgentBase]:
     """A unified entry to initialize the package, including model configs,
     runtime names, saving directories and logging settings.
@@ -167,6 +169,8 @@ 

Source code for agentscope._init

             which can be loaded by json.loads(). One agent config should
             cover the required arguments to initialize a specific agent
             object, otherwise the default values will be used.
+        studio_url (`Optional[str]`, defaults to `None`):
+            The url of the agentscope studio.
     """
     init_process(
         model_configs=model_configs,
@@ -178,17 +182,19 @@ 

Source code for agentscope._init

         save_log=save_log,
         use_monitor=use_monitor,
         logger_level=logger_level,
+        studio_url=studio_url,
     )
 
     # save init settings for subprocess
     _INIT_SETTINGS["model_configs"] = model_configs
-    _INIT_SETTINGS["project"] = project
-    _INIT_SETTINGS["name"] = name
+    _INIT_SETTINGS["project"] = _runtime.project
+    _INIT_SETTINGS["name"] = _runtime.name
     _INIT_SETTINGS["runtime_id"] = _runtime.runtime_id
     _INIT_SETTINGS["save_dir"] = save_dir
     _INIT_SETTINGS["save_api_invoke"] = save_api_invoke
     _INIT_SETTINGS["save_log"] = save_log
     _INIT_SETTINGS["logger_level"] = logger_level
+    _INIT_SETTINGS["use_monitor"] = use_monitor
 
     # Save code if needed
     if save_code:
@@ -231,6 +237,7 @@ 

Source code for agentscope._init

     save_log: bool = False,
     use_monitor: bool = True,
     logger_level: LOG_LEVEL = _DEFAULT_LOG_LEVEL,
+    studio_url: Optional[str] = None,
 ) -> None:
     """An entry to initialize the package in a process.
 
@@ -256,12 +263,16 @@ 

Source code for agentscope._init

             Whether to activate the monitor.
         logger_level (`LOG_LEVEL`, defaults to `"INFO"`):
             The logging level of logger.
+        studio_url (`Optional[str]`, defaults to `None`):
+            The url of the agentscope studio.
     """
     # Init the runtime
     if project is not None:
         _runtime.project = project
+
     if name is not None:
         _runtime.name = name
+
     if runtime_id is not None:
         _runtime.runtime_id = runtime_id
 
@@ -281,6 +292,19 @@ 

Source code for agentscope._init

         db_path=file_manager.path_db,
         impl_type="sqlite" if use_monitor else "dummy",
     )
+
+    # Init studio client, which will push messages to web ui and fetch user
+    # inputs from web ui
+    if studio_url is not None:
+        _studio_client.initialize(_runtime.runtime_id, studio_url)
+        # Register in AgentScope Studio
+        _studio_client.register_running_instance(
+            project=_runtime.project,
+            name=_runtime.name,
+            timestamp=_runtime.timestamp,
+            run_dir=file_manager.dir_root,
+            pid=os.getpid(),
+        )
 
diff --git a/en/_modules/agentscope/agents/agent.html b/en/_modules/agentscope/agents/agent.html index dbbe104ab..f859ab6f1 100644 --- a/en/_modules/agentscope/agents/agent.html +++ b/en/_modules/agentscope/agents/agent.html @@ -112,6 +112,7 @@

Source code for agentscope.agents.agent

 from loguru import logger
 
 from agentscope.agents.operator import Operator
+from agentscope.message import Msg
 from agentscope.models import load_model_by_config_name
 from agentscope.memory import TemporaryMemory
 
@@ -435,10 +436,33 @@ 

Source code for agentscope.agents.agent

 [docs]
     def speak(
         self,
-        content: Union[str, dict],
+        content: Union[str, Msg],
     ) -> None:
-        """Speak out the content generated by the agent."""
-        logger.chat(content)
+ """ + Speak out the message generated by the agent. If a string is given, + a Msg object will be created with the string as the content. + + Args: + content (`Union[str, Msg]`): + The content of the message to be spoken out. If a string is + given, a Msg object will be created with the agent's name, role + as "assistant", and the given string as the content. + """ + if isinstance(content, str): + msg = Msg( + name=self.name, + content=content, + role="assistant", + ) + elif isinstance(content, Msg): + msg = content + else: + raise TypeError( + "From version 0.0.5, the speak method only accepts str or Msg " + f"object, got {type(content)} instead.", + ) + + logger.chat(msg)
diff --git a/en/_modules/agentscope/agents/rpc_agent.html b/en/_modules/agentscope/agents/rpc_agent.html index e76ff5a31..6ff3ad03d 100644 --- a/en/_modules/agentscope/agents/rpc_agent.html +++ b/en/_modules/agentscope/agents/rpc_agent.html @@ -109,6 +109,7 @@

Source code for agentscope.agents.rpc_agent

 )
 from agentscope.rpc import RpcAgentClient
 from agentscope.server.launcher import RpcAgentServerLauncher
+from agentscope.studio._client import _studio_client
 
 
 
@@ -174,6 +175,9 @@

Source code for agentscope.agents.rpc_agent

         launch_server = port is None
         if launch_server:
             self.host = "localhost"
+            studio_url = None
+            if _studio_client.active:
+                studio_url = _studio_client.studio_url
             self.server_launcher = RpcAgentServerLauncher(
                 host=self.host,
                 port=port,
@@ -181,6 +185,7 @@ 

Source code for agentscope.agents.rpc_agent

                 max_timeout_seconds=max_timeout_seconds,
                 local_mode=local_mode,
                 custom_agents=[agent_class],
+                studio_url=studio_url,
             )
             if not lazy_launch:
                 self._launch_server()
diff --git a/en/_modules/agentscope/agents/user_agent.html b/en/_modules/agentscope/agents/user_agent.html
index 3edd31484..aacd2d9f4 100644
--- a/en/_modules/agentscope/agents/user_agent.html
+++ b/en/_modules/agentscope/agents/user_agent.html
@@ -106,8 +106,9 @@ 

Source code for agentscope.agents.user_agent

from loguru import logger
 
 from agentscope.agents import AgentBase
+from agentscope.studio._client import _studio_client
 from agentscope.message import Msg
-from agentscope.web.studio.utils import user_input
+from agentscope.web.gradio.utils import user_input
 
 
 
@@ -172,25 +173,41 @@

Source code for agentscope.agents.user_agent

if self.memory:
             self.memory.add(x)
 
-        # TODO: To avoid order confusion, because `input` print much quicker
-        #  than logger.chat
-        time.sleep(0.5)
-        content = user_input(timeout=timeout)
-
-        kwargs = {}
-        if required_keys is not None:
-            if isinstance(required_keys, str):
-                required_keys = [required_keys]
-
-            for key in required_keys:
-                kwargs[key] = input(f"{key}: ")
-
-        # Input url of file, image, video, audio or website
-        url = None
-        if self.require_url:
-            url = input("URL (or Enter to skip): ")
-            if url == "":
-                url = None
+        if _studio_client.active:
+            logger.info(
+                f"Waiting for input from:\n\n"
+                f"    * {_studio_client.get_run_detail_page_url()}\n",
+            )
+            raw_input = _studio_client.get_user_input(
+                agent_id=self.agent_id,
+                name=self.name,
+                require_url=self.require_url,
+                required_keys=required_keys,
+            )
+
+            print("Python: receive ", raw_input)
+            content = raw_input["content"]
+            url = raw_input["url"]
+            kwargs = {}
+        else:
+            # TODO: To avoid order confusion, because `input` print much
+            #  quicker than logger.chat
+            time.sleep(0.5)
+            content = user_input(timeout=timeout)
+            kwargs = {}
+            if required_keys is not None:
+                if isinstance(required_keys, str):
+                    required_keys = [required_keys]
+
+                for key in required_keys:
+                    kwargs[key] = input(f"{key}: ")
+
+            # Input url of file, image, video, audio or website
+            url = None
+            if self.require_url:
+                url = input("URL (or Enter to skip): ")
+                if url == "":
+                    url = None
 
         # Add additional keys
         msg = Msg(
@@ -214,10 +231,35 @@ 

Source code for agentscope.agents.user_agent

[docs]
     def speak(
         self,
-        content: Union[str, dict],
+        content: Union[str, Msg],
     ) -> None:
-        """Speak the content to the audience."""
-        logger.chat(content, disable_studio=True)
+ """ + Speak out the message generated by the agent. If a string is given, + a Msg object will be created with the string as the content. + + Args: + content (`Union[str, Msg]`): + The content of the message to be spoken out. If a string is + given, a Msg object will be created with the agent's name, role + as "user", and the given string as the content. + + """ + if isinstance(content, str): + msg = Msg( + name=self.name, + content=content, + role="assistant", + ) + _studio_client.push_message(msg) + elif isinstance(content, Msg): + msg = content + else: + raise TypeError( + "From version 0.0.5, the speak method only accepts str or Msg " + f"object, got {type(content)} instead.", + ) + + logger.chat(msg)
diff --git a/en/_modules/agentscope/exception.html b/en/_modules/agentscope/exception.html index a3ae4e6a1..7dc0897bd 100644 --- a/en/_modules/agentscope/exception.html +++ b/en/_modules/agentscope/exception.html @@ -238,6 +238,30 @@

Source code for agentscope.exception

 class ArgumentTypeError(FunctionCallError):
     """The exception class for argument type error."""
+ + +
+[docs] +class StudioError(Exception): + """The base class for exception raising during interaction with agentscope + studio.""" + +
+[docs] + def __init__(self, message: str) -> None: + self.message = message
+ + + def __str__(self) -> str: + return f"{self.__class__.__name__}: {self.message}"
+ + + +
+[docs] +class StudioRegisterError(StudioError): + """The exception class for error when registering to agentscope studio."""
+
diff --git a/en/_modules/agentscope/utils/logging_utils.html b/en/_modules/agentscope/logging.html similarity index 90% rename from en/_modules/agentscope/utils/logging_utils.html rename to en/_modules/agentscope/logging.html index d5d43fe8d..57a438171 100644 --- a/en/_modules/agentscope/utils/logging_utils.html +++ b/en/_modules/agentscope/logging.html @@ -1,29 +1,29 @@ - + - agentscope.utils.logging_utils — AgentScope documentation - - - + agentscope.logging — AgentScope documentation + + + - - - - - + + + + + - - - + + + @@ -34,43 +34,43 @@ - + AgentScope
-
+
@@ -79,16 +79,16 @@
@@ -97,17 +97,18 @@
-

Source code for agentscope.utils.logging_utils

+  

Source code for agentscope.logging

 # -*- coding: utf-8 -*-
 """Logging utilities."""
 import json
 import os
 import sys
-from typing import Optional, Literal, Union, Any
+from typing import Optional, Literal, Any
 
 from loguru import logger
 
-from agentscope.web.studio.utils import (
+from agentscope.studio._client import _studio_client
+from agentscope.web.gradio.utils import (
     generate_image_from_name,
     send_msg,
     get_reset_msg,
@@ -170,20 +171,25 @@ 

Source code for agentscope.utils.logging_utils

# add chat function for logger def _chat( - message: Union[str, dict], + message: dict, *args: Any, disable_studio: bool = False, **kwargs: Any, ) -> None: - """Log a chat message with the format of"<speaker>: <content>". + """ + Log a chat message with the format of"<speaker>: <content>". If the + running instance is registered in the studio, the message will be sent + and display in the studio. Args: - message (`Union[str, dict]`): - The message to be logged. If it is a string, it will be logged - directly. If it's a dict, it should have "name"(or "role") and - "content" keys, and the message will be logged as "<name/role>: - <content>". + message (`dict`): + The message to be logged as "<name/role>: <content>", which must + be an object of Msg class. """ + # Push message to studio if it is active + if _studio_client.active: + _studio_client.push_message(message) + # Save message into chat file, add default to ignore not serializable # objects logger.log( @@ -245,7 +251,7 @@

Source code for agentscope.utils.logging_utils

-[docs] +[docs] def log_studio(message: dict, uid: str, **kwargs: Any) -> None: """Send chat message to studio. @@ -318,7 +324,7 @@

Source code for agentscope.utils.logging_utils

-[docs] +[docs] def setup_logger( path_log: Optional[str] = None, level: LOG_LEVEL = "INFO", diff --git a/en/_modules/agentscope/message.html b/en/_modules/agentscope/message.html index d6ff18efa..442e2dac1 100644 --- a/en/_modules/agentscope/message.html +++ b/en/_modules/agentscope/message.html @@ -161,10 +161,7 @@

Source code for agentscope.message

         self.content = content
         self.role = role
 
-        if url:
-            self.url = url
-        else:
-            self.url = None
+        self.url = url
 
         self.update(kwargs)
@@ -381,7 +378,7 @@

Source code for agentscope.message

 
 
[docs] -class PlaceholderMessage(MessageBase): +class PlaceholderMessage(Msg): """A placeholder for the return message of RpcAgent.""" PLACEHOLDER_ATTRS = { diff --git a/en/_modules/agentscope/server/launcher.html b/en/_modules/agentscope/server/launcher.html index 51fd9d673..52501dea2 100644 --- a/en/_modules/agentscope/server/launcher.html +++ b/en/_modules/agentscope/server/launcher.html @@ -101,11 +101,12 @@

Source code for agentscope.server.launcher

 # -*- coding: utf-8 -*-
 """ Server of distributed agent"""
 import os
-from multiprocessing import Process, Event, Pipe
-from multiprocessing.synchronize import Event as EventClass
 import asyncio
 import signal
 import argparse
+import time
+from multiprocessing import Process, Event, Pipe
+from multiprocessing.synchronize import Event as EventClass
 from typing import Type
 from concurrent import futures
 from loguru import logger
@@ -123,14 +124,10 @@ 

Source code for agentscope.server.launcher

         import_error,
         "distribute",
     )
-
 import agentscope
 from agentscope.server.servicer import AgentServerServicer
 from agentscope.agents.agent import AgentBase
-from agentscope.utils.tools import (
-    _get_timestamp,
-    check_port,
-)
+from agentscope.utils.tools import check_port, generate_id_from_seed
 
 
 def _setup_agent_server(
@@ -144,6 +141,7 @@ 

Source code for agentscope.server.launcher

     local_mode: bool = True,
     max_pool_size: int = 8192,
     max_timeout_seconds: int = 1800,
+    studio_url: str = None,
     custom_agents: list = None,
 ) -> None:
     """Setup agent server.
@@ -156,7 +154,7 @@ 

Source code for agentscope.server.launcher

         server_id (`str`):
             The id of the server.
         init_settings (`dict`, defaults to `None`):
-            Init settings for agentscope.init.
+            Init settings for _init_server.
         start_event (`EventClass`, defaults to `None`):
             An Event instance used to determine whether the child process
             has been started.
@@ -171,6 +169,8 @@ 

Source code for agentscope.server.launcher

             Max number of agent replies that the server can accommodate.
         max_timeout_seconds (`int`, defaults to `1800`):
             Timeout for agent replies.
+        studio_url (`str`, defaults to `None`):
+            URL of the AgentScope Studio.
         custom_agents (`list`, defaults to `None`):
             A list of custom agent classes that are not in `agentscope.agents`.
     """
@@ -186,6 +186,7 @@ 

Source code for agentscope.server.launcher

             local_mode=local_mode,
             max_pool_size=max_pool_size,
             max_timeout_seconds=max_timeout_seconds,
+            studio_url=studio_url,
             custom_agents=custom_agents,
         ),
     )
@@ -202,6 +203,7 @@ 

Source code for agentscope.server.launcher

     local_mode: bool = True,
     max_pool_size: int = 8192,
     max_timeout_seconds: int = 1800,
+    studio_url: str = None,
     custom_agents: list = None,
 ) -> None:
     """Setup agent server in an async way.
@@ -214,7 +216,7 @@ 

Source code for agentscope.server.launcher

         server_id (`str`):
             The id of the server.
         init_settings (`dict`, defaults to `None`):
-            Init settings for agentscope.init.
+            Init settings for _init_server.
         start_event (`EventClass`, defaults to `None`):
             An Event instance used to determine whether the child process
             has been started.
@@ -233,6 +235,8 @@ 

Source code for agentscope.server.launcher

         max_timeout_seconds (`int`, defaults to `1800`):
             Maximum time for reply messages to be cached in the server.
             Note that expired messages will be deleted.
+        studio_url (`str`, defaults to `None`):
+            URL of the AgentScope Studio.
         custom_agents (`list`, defaults to `None`):
             A list of custom agent classes that are not in `agentscope.agents`.
     """
@@ -243,6 +247,8 @@ 

Source code for agentscope.server.launcher

     servicer = AgentServerServicer(
         host=host,
         port=port,
+        server_id=server_id,
+        studio_url=studio_url,
         max_pool_size=max_pool_size,
         max_timeout_seconds=max_timeout_seconds,
     )
@@ -320,6 +326,7 @@ 

Source code for agentscope.server.launcher

         local_mode: bool = False,
         custom_agents: list = None,
         server_id: str = None,
+        studio_url: str = None,
         agent_class: Type[AgentBase] = None,
         agent_args: tuple = (),
         agent_kwargs: dict = None,
@@ -347,6 +354,8 @@ 

Source code for agentscope.server.launcher

             server_id (`str`, defaults to `None`):
                 The id of the agent server. If not specified, a random id
                 will be generated.
+            studio_url (`Optional[str]`, defaults to `None`):
+                The url of the agentscope studio.
             agent_class (`Type[AgentBase]`, deprecated):
                 The AgentBase subclass encapsulated by this wrapper.
             agent_args (`tuple`, deprecated): The args tuple used to
@@ -364,8 +373,11 @@ 

Source code for agentscope.server.launcher

         self.parent_con = None
         self.custom_agents = custom_agents
         self.server_id = (
-            self.generate_server_id() if server_id is None else server_id
+            RpcAgentServerLauncher.generate_server_id(self.host, self.port)
+            if server_id is None
+            else server_id
         )
+        self.studio_url = studio_url
         if (
             agent_class is not None
             or len(agent_args) > 0
@@ -379,9 +391,10 @@ 

Source code for agentscope.server.launcher

 
 
[docs] - def generate_server_id(self) -> str: + @classmethod + def generate_server_id(cls, host: str, port: int) -> str: """Generate server id""" - return f"{self.host}:{self.port}-{_get_timestamp('%y%m%d-%H:%M:%S')}"
+ return generate_id_from_seed(f"{host}:{port}:{time.time()}", length=8)
def _launch_in_main(self) -> None: @@ -398,6 +411,7 @@

Source code for agentscope.server.launcher

                 max_timeout_seconds=self.max_timeout_seconds,
                 local_mode=self.local_mode,
                 custom_agents=self.custom_agents,
+                studio_url=self.studio_url,
             ),
         )
 
@@ -421,6 +435,7 @@ 

Source code for agentscope.server.launcher

                 "max_pool_size": self.max_pool_size,
                 "max_timeout_seconds": self.max_timeout_seconds,
                 "local_mode": self.local_mode,
+                "studio_url": self.studio_url,
                 "custom_agents": self.custom_agents,
             },
         )
@@ -540,7 +555,7 @@ 

Source code for agentscope.server.launcher

         type=bool,
         default=False,
         help=(
-            "If `True`, only listen to requests from 'localhost', otherwise, "
+            "if `True`, only listen to requests from 'localhost', otherwise, "
             "listen to requests from all hosts."
         ),
     )
@@ -549,21 +564,51 @@ 

Source code for agentscope.server.launcher

         type=str,
         help="path to the model config json file",
     )
+    parser.add_argument(
+        "--server-id",
+        type=str,
+        default=None,
+        help="id of the server, used to register to the studio, generated"
+        " randomly if not specified.",
+    )
+    parser.add_argument(
+        "--studio-url",
+        type=str,
+        default=None,
+        help="the url of agentscope studio",
+    )
+    parser.add_argument(
+        "--no-log",
+        action="store_true",
+        help="whether to disable log",
+    )
+    parser.add_argument(
+        "--save-api-invoke",
+        action="store_true",
+        help="whether to save api invoke",
+    )
+    parser.add_argument(
+        "--use-monitor",
+        action="store_true",
+        help="whether to use monitor",
+    )
     args = parser.parse_args()
     agentscope.init(
         project="agent_server",
         name=f"server_{args.host}:{args.port}",
-        runtime_id=_get_timestamp(
-            "server_{}_{}_%y%m%d-%H%M%S",
-        ).format(args.host, args.port),
+        save_log=not args.no_log,
+        save_api_invoke=args.save_api_invoke,
         model_configs=args.model_config_path,
+        use_monitor=args.use_monitor,
     )
     launcher = RpcAgentServerLauncher(
         host=args.host,
         port=args.port,
+        server_id=args.server_id,
         max_pool_size=args.max_pool_size,
         max_timeout_seconds=args.max_timeout_seconds,
         local_mode=args.local_mode,
+        studio_url=args.studio_url,
     )
     launcher.launch(in_subprocess=False)
     launcher.wait_until_terminate()
diff --git a/en/_modules/agentscope/server/servicer.html b/en/_modules/agentscope/server/servicer.html index a3f237b7b..78131eb5d 100644 --- a/en/_modules/agentscope/server/servicer.html +++ b/en/_modules/agentscope/server/servicer.html @@ -106,6 +106,7 @@

Source code for agentscope.server.servicer

 import traceback
 from concurrent import futures
 from loguru import logger
+import requests
 
 try:
     import dill
@@ -125,7 +126,10 @@ 

Source code for agentscope.server.servicer

         "distribute",
     )
 
+from .._runtime import _runtime
+from ..studio._client import _studio_client
 from ..agents.agent import AgentBase
+from ..exception import StudioRegisterError
 from ..rpc.rpc_agent_pb2_grpc import RpcAgentServicer
 from ..message import (
     Msg,
@@ -134,6 +138,24 @@ 

Source code for agentscope.server.servicer

 )
 
 
+def _register_to_studio(
+    studio_url: str,
+    server_id: str,
+    host: str,
+    port: int,
+) -> None:
+    """Register a server to studio."""
+    url = f"{studio_url}/api/servers/register"
+    resp = requests.post(
+        url,
+        json={"server_id": server_id, "host": host, "port": port},
+        timeout=10,  # todo: configurable timeout
+    )
+    if resp.status_code != 200:
+        logger.error(f"Failed to register server: {resp.text}")
+        raise StudioRegisterError(f"Failed to register server: {resp.text}")
+
+
 
[docs] class AgentServerServicer(RpcAgentServicer): @@ -145,6 +167,8 @@

Source code for agentscope.server.servicer

         self,
         host: str = "localhost",
         port: int = None,
+        server_id: str = None,
+        studio_url: str = None,
         max_pool_size: int = 8192,
         max_timeout_seconds: int = 1800,
     ):
@@ -155,6 +179,10 @@ 

Source code for agentscope.server.servicer

                 Hostname of the rpc agent server.
             port (`int`, defaults to `None`):
                 Port of the rpc agent server.
+            server_id (`str`, defaults to `None`):
+                Server id of the rpc agent server.
+            studio_url (`str`, defaults to `None`):
+                URL of the AgentScope Studio.
             max_pool_size (`int`, defaults to `8192`):
                 The max number of agent reply messages that the server can
                 accommodate. Note that the oldest message will be deleted
@@ -165,6 +193,17 @@ 

Source code for agentscope.server.servicer

         """
         self.host = host
         self.port = port
+        self.server_id = server_id
+        self.studio_url = studio_url
+        if studio_url is not None:
+            _register_to_studio(
+                studio_url=studio_url,
+                server_id=server_id,
+                host=host,
+                port=port,
+            )
+            _studio_client.initialize(_runtime.runtime_id, studio_url)
+
         self.result_pool = ExpiringDict(
             max_len=max_pool_size,
             max_age_seconds=max_timeout_seconds,
diff --git a/en/_modules/agentscope/studio/_app.html b/en/_modules/agentscope/studio/_app.html
new file mode 100644
index 000000000..050637734
--- /dev/null
+++ b/en/_modules/agentscope/studio/_app.html
@@ -0,0 +1,833 @@
+
+
+
+
+  
+  
+  agentscope.studio._app — AgentScope  documentation
+      
+      
+      
+
+  
+  
+  
+        
+        
+        
+        
+        
+        
+        
+    
+    
+     
+
+
+ 
+  
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for agentscope.studio._app

+# -*- coding: utf-8 -*-
+"""The Web Server of the AgentScope Studio."""
+import json
+import os
+import re
+import subprocess
+import tempfile
+import threading
+import traceback
+from datetime import datetime
+from typing import Tuple, Union, Any, Optional
+from pathlib import Path
+
+from flask import (
+    Flask,
+    request,
+    jsonify,
+    render_template,
+    Response,
+    abort,
+    send_file,
+)
+from flask_cors import CORS
+from flask_sqlalchemy import SQLAlchemy
+from flask_socketio import SocketIO, join_room, leave_room
+
+from agentscope._runtime import _runtime
+from agentscope.constants import _DEFAULT_SUBDIR_CODE, _DEFAULT_SUBDIR_INVOKE
+from agentscope.utils.tools import _is_process_alive
+
+_app = Flask(__name__)
+
+# Set the cache directory
+_cache_dir = str(Path.home() / ".cache" / "agentscope-studio")
+os.makedirs(_cache_dir, exist_ok=True)
+_app.config[
+    "SQLALCHEMY_DATABASE_URI"
+] = f"sqlite:////{_cache_dir}/agentscope.db"
+_db = SQLAlchemy(_app)
+
+_socketio = SocketIO(_app)
+
+# This will enable CORS for all routes
+CORS(_app)
+
+_RUNS_DIRS = []
+
+
+class _UserInputRequestQueue:
+    """A queue to store the user input requests."""
+
+    _requests = {}
+    """The user input requests in the queue."""
+
+    @classmethod
+    def add_request(cls, run_id: str, agent_id: str, data: dict) -> None:
+        """Add a new user input request into queue.
+
+        Args:
+            run_id (`str`):
+                The id of the runtime instance.
+            agent_id (`str`):
+                The id of the agent that requires user input.
+            data (`dict`):
+                The data of the user input request.
+        """
+        if run_id not in cls._requests:
+            cls._requests[run_id] = {agent_id: data}
+        else:
+            # We ensure that the agent_id is unique here
+            cls._requests[run_id][agent_id] = data
+
+    @classmethod
+    def fetch_a_request(cls, run_id: str) -> Optional[dict]:
+        """Fetch a user input request from the queue.
+
+        Args:
+            run_id (`str`):
+                The id of the runtime instance.
+        """
+        if run_id in cls._requests and len(cls._requests[run_id]) > 0:
+            # Fetch the oldest request
+            agent_id = list(cls._requests[run_id].keys())[0]
+            return cls._requests[run_id][agent_id]
+        else:
+            return None
+
+    @classmethod
+    def close_a_request(cls, run_id: str, agent_id: str) -> None:
+        """Close a user input request in the queue.
+
+        Args:
+            run_id (`str`):
+                The id of the runtime instance.
+            agent_id (`str`):
+                The id of the agent that requires user input.
+        """
+        if run_id in cls._requests:
+            cls._requests[run_id].pop(agent_id)
+
+
+class _RunTable(_db.Model):  # type: ignore[name-defined]
+    """Runtime object."""
+
+    run_id = _db.Column(_db.String, primary_key=True)
+    project = _db.Column(_db.String)
+    name = _db.Column(_db.String)
+    timestamp = _db.Column(_db.String)
+    run_dir = _db.Column(_db.String)
+    pid = _db.Column(_db.Integer)
+    status = _db.Column(_db.String, default="finished")
+
+
+class _ServerTable(_db.Model):  # type: ignore[name-defined]
+    """Server object."""
+
+    id = _db.Column(_db.String, primary_key=True)
+    host = _db.Column(_db.String)
+    port = _db.Column(_db.Integer)
+    create_time = _db.Column(_db.DateTime, default=datetime.now)
+
+
+class _MessageTable(_db.Model):  # type: ignore[name-defined]
+    """Message object."""
+
+    id = _db.Column(_db.Integer, primary_key=True)
+    run_id = _db.Column(
+        _db.String,
+        _db.ForeignKey("run_table.run_id"),
+        nullable=False,
+    )
+    name = _db.Column(_db.String)
+    role = _db.Column(_db.String)
+    content = _db.Column(_db.String)
+    url = _db.Column(_db.String)
+    meta = _db.Column(_db.String)
+    timestamp = _db.Column(_db.String)
+
+
+def _get_all_runs_from_dir() -> dict:
+    """Get all runs from the directory."""
+    global _RUNS_DIRS
+    runtime_configs_from_dir = {}
+    if _RUNS_DIRS is not None:
+        for runs_dir in set(_RUNS_DIRS):
+            for runtime_dir in os.listdir(runs_dir):
+                path_runtime = os.path.join(runs_dir, runtime_dir)
+                path_config = os.path.join(path_runtime, ".config")
+                if os.path.exists(path_config):
+                    with open(path_config, "r", encoding="utf-8") as file:
+                        runtime_config = json.load(file)
+
+                        # Default status is finished
+                        # Note: this is only for local runtime instances
+                        if "pid" in runtime_config and _is_process_alive(
+                            runtime_config["pid"],
+                            runtime_config["timestamp"],
+                        ):
+                            runtime_config["status"] = "running"
+                        else:
+                            runtime_config["status"] = "finished"
+
+                        if "run_dir" not in runtime_config:
+                            runtime_config["run_dir"] = path_runtime
+
+                        if "id" in runtime_config:
+                            runtime_config["run_id"] = runtime_config["id"]
+                            del runtime_config["id"]
+
+                        runtime_id = runtime_config.get("run_id")
+                        runtime_configs_from_dir[runtime_id] = runtime_config
+
+    return runtime_configs_from_dir
+
+
+def _remove_file_paths(error_trace: str) -> str:
+    """
+    Remove the real traceback when exception happens.
+    """
+    path_regex = re.compile(r'File "(.*?)(?=agentscope|app\.py)')
+    cleaned_trace = re.sub(path_regex, 'File "[hidden]/', error_trace)
+
+    return cleaned_trace
+
+
+def _convert_to_py(  # type: ignore[no-untyped-def]
+    content: str,
+    **kwargs,
+) -> Tuple:
+    """
+    Convert json config to python code.
+    """
+    from agentscope.web.workstation.workflow_dag import build_dag
+
+    try:
+        cfg = json.loads(content)
+        return "True", build_dag(cfg).compile(**kwargs)
+    except Exception as e:
+        return "False", _remove_file_paths(
+            f"Error: {e}\n\n" f"Traceback:\n" f"{traceback.format_exc()}",
+        )
+
+
+@_app.route("/workstation")
+def _workstation() -> str:
+    """Render the workstation page."""
+    return render_template("workstation.html")
+
+
+@_app.route("/api/runs/register", methods=["POST"])
+def _register_run() -> Response:
+    """Registers a running instance of an agentscope application."""
+
+    # Extract the input data from the request
+    data = request.json
+    run_id = data.get("run_id")
+
+    # check if the run_id is already in the database
+    if _RunTable.query.filter_by(run_id=run_id).first():
+        abort(400, f"RUN_ID {run_id} already exists")
+
+    # Add into the database
+    _db.session.add(
+        _RunTable(
+            run_id=run_id,
+            project=data.get("project"),
+            name=data.get("name"),
+            timestamp=data.get("timestamp"),
+            run_dir=data.get("run_dir"),
+            pid=data.get("pid"),
+            status="running",
+        ),
+    )
+    _db.session.commit()
+
+    return jsonify(status="ok")
+
+
+@_app.route("/api/servers/register", methods=["POST"])
+def _register_server() -> Response:
+    """
+    Registers an agent server.
+    """
+    data = request.json
+    server_id = data.get("server_id")
+    host = data.get("host")
+    port = data.get("port")
+
+    if _ServerTable.query.filter_by(id=server_id).first():
+        _app.logger.error(f"Server id {server_id} already exists.")
+        abort(400, f"run_id [{server_id}] already exists")
+
+    _db.session.add(
+        _ServerTable(
+            id=server_id,
+            host=host,
+            port=port,
+        ),
+    )
+    _db.session.commit()
+
+    _app.logger.info(f"Register server id {server_id}")
+    return jsonify(status="ok")
+
+
+@_app.route("/api/messages/push", methods=["POST"])
+def _push_message() -> Response:
+    """Receive a message from the agentscope application, and display it on
+    the web UI."""
+    _app.logger.debug("Flask: receive push_message")
+    data = request.json
+
+    run_id = data["run_id"]
+    name = data["name"]
+    role = data["role"]
+    content = data["content"]
+    metadata = data["metadata"]
+    timestamp = data["timestamp"]
+    url = data["url"]
+
+    try:
+        new_message = _MessageTable(
+            run_id=run_id,
+            name=name,
+            role=role,
+            content=content,
+            # Before storing into the database, we need to convert the url into
+            # a string
+            meta=json.dumps(metadata),
+            url=json.dumps(url),
+            timestamp=timestamp,
+        )
+        _db.session.add(new_message)
+        _db.session.commit()
+    except Exception as e:
+        abort(400, "Fail to put message with error: " + str(e))
+
+    data = {
+        "run_id": run_id,
+        "name": name,
+        "role": role,
+        "content": content,
+        "url": url,
+        "metadata": metadata,
+        "timestamp": timestamp,
+    }
+
+    _socketio.emit(
+        "display_message",
+        data,
+        room=run_id,
+    )
+    _app.logger.debug("Flask: send display_message")
+    return jsonify(status="ok")
+
+
+@_app.route("/api/messages/run/<run_id>", methods=["GET"])
+def _get_messages(run_id: str) -> Response:
+    """Get the history messages of specific run_id."""
+    # From registered runtime instances
+    if len(_RunTable.query.filter_by(run_id=run_id).all()) > 0:
+        messages = _MessageTable.query.filter_by(run_id=run_id).all()
+        msgs = [
+            {
+                "name": message.name,
+                "role": message.role,
+                "content": message.content,
+                "url": json.loads(message.url),
+                "metadata": json.loads(message.meta),
+                "timestamp": message.timestamp,
+            }
+            for message in messages
+        ]
+        return jsonify(msgs)
+
+    # From the local file
+    run_dir = request.args.get("run_dir", default=None, type=str)
+
+    # Search the run_dir from the registered runtime instances if not provided
+    if run_dir is None:
+        runtime_configs_from_dir = _get_all_runs_from_dir()
+        if run_id in runtime_configs_from_dir:
+            run_dir = runtime_configs_from_dir[run_id]["run_dir"]
+
+    # Load the messages from the local file
+    path_messages = os.path.join(run_dir, "logging.chat")
+    if run_dir is None or not os.path.exists(path_messages):
+        return jsonify([])
+    else:
+        with open(path_messages, "r", encoding="utf-8") as file:
+            msgs = [json.loads(_) for _ in file.readlines()]
+            return jsonify(msgs)
+
+
+@_app.route("/api/runs/get/<run_id>", methods=["GET"])
+def _get_run(run_id: str) -> Response:
+    """Get a specific run's detail."""
+    run = _RunTable.query.filter_by(run_id=run_id).first()
+    if not run:
+        abort(400, f"run_id [{run_id}] not exists")
+    return jsonify(
+        {
+            "run_id": run.run_id,
+            "project": run.project,
+            "name": run.name,
+            "timestamp": run.timestamp,
+            "run_dir": run.run_dir,
+            "pid": run.pid,
+            "status": run.status,
+        },
+    )
+
+
+@_app.route("/api/runs/all", methods=["GET"])
+def _get_all_runs() -> Response:
+    """Get all runs."""
+    # Update the status of the registered runtimes
+    # Note: this is only for the applications running on the local machine
+    for run in _RunTable.query.filter(
+        _RunTable.status.in_(["running", "waiting"]),
+    ).all():
+        if not _is_process_alive(run.pid, run.timestamp):
+            _RunTable.query.filter_by(run_id=run.run_id).update(
+                {"status": "finished"},
+            )
+            _db.session.commit()
+
+    # From web connection
+    runtime_configs_from_register = {
+        _.run_id: {
+            "run_id": _.run_id,
+            "project": _.project,
+            "name": _.name,
+            "timestamp": _.timestamp,
+            "run_dir": _.run_dir,
+            "pid": _.pid,
+            "status": _.status,
+        }
+        for _ in _RunTable.query.all()
+    }
+
+    # From directory
+    runtime_configs_from_dir = _get_all_runs_from_dir()
+
+    # Remove duplicates between two sources
+    clean_runtimes = {
+        **runtime_configs_from_dir,
+        **runtime_configs_from_register,
+    }
+
+    runs = list(clean_runtimes.values())
+
+    return jsonify(runs)
+
+
+@_app.route("/api/invocation", methods=["GET"])
+def _get_invocations() -> Response:
+    """Get all API invocations in a run instance."""
+    run_dir = request.args.get("run_dir")
+    path_invocations = os.path.join(run_dir, _DEFAULT_SUBDIR_INVOKE)
+
+    invocations = []
+    if os.path.exists(path_invocations):
+        for filename in os.listdir(path_invocations):
+            with open(
+                os.path.join(path_invocations, filename),
+                "r",
+                encoding="utf-8",
+            ) as file:
+                invocations.append(json.load(file))
+    return jsonify(invocations)
+
+
+@_app.route("/api/code", methods=["GET"])
+def _get_code() -> Response:
+    """Get the python code from the run directory."""
+    run_dir = request.args.get("run_dir")
+
+    dir_code = os.path.join(run_dir, _DEFAULT_SUBDIR_CODE)
+
+    codes = {}
+    if os.path.exists(dir_code):
+        for filename in os.listdir(dir_code):
+            with open(
+                os.path.join(dir_code, filename),
+                "r",
+                encoding="utf-8",
+            ) as file:
+                codes[filename] = "".join(file.readlines())
+    return jsonify(codes)
+
+
+@_app.route("/api/file", methods=["GET"])
+def _get_file() -> Any:
+    """Get the local file via the url."""
+    file_path = request.args.get("path", None)
+
+    if file_path is not None:
+        try:
+            file = send_file(file_path)
+            return file
+        except FileNotFoundError:
+            return jsonify({"error": "File not found."})
+    return jsonify({"error": "File not found."})
+
+
+@_app.route("/convert-to-py", methods=["POST"])
+def _convert_config_to_py() -> Response:
+    """
+    Convert json config to python code and send back.
+    """
+    content = request.json.get("data")
+    status, py_code = _convert_to_py(content)
+    return jsonify(py_code=py_code, is_success=status)
+
+
+def _cleanup_process(proc: subprocess.Popen) -> None:
+    """Clean up the process for running application started by workstation."""
+    proc.wait()
+    _app.logger.debug(f"The process with pid {proc.pid} is closed")
+
+
+@_app.route("/convert-to-py-and-run", methods=["POST"])
+def _convert_config_to_py_and_run() -> Response:
+    """
+    Convert json config to python code and run.
+    """
+    content = request.json.get("data")
+    studio_url = request.url_root.rstrip("/")
+    run_id = _runtime.generate_new_runtime_id()
+    status, py_code = _convert_to_py(
+        content,
+        runtime_id=run_id,
+        studio_url=studio_url,
+    )
+
+    if status == "True":
+        try:
+            with tempfile.NamedTemporaryFile(
+                delete=False,
+                suffix=".py",
+                mode="w+t",
+            ) as tmp:
+                tmp.write(py_code)
+                tmp.flush()
+                proc = subprocess.Popen(  # pylint: disable=R1732
+                    ["python", tmp.name],
+                )
+                threading.Thread(target=_cleanup_process, args=(proc,)).start()
+        except Exception as e:
+            status, py_code = "False", _remove_file_paths(
+                f"Error: {e}\n\n" f"Traceback:\n" f"{traceback.format_exc()}",
+            )
+    return jsonify(py_code=py_code, is_success=status, run_id=run_id)
+
+
+@_app.route("/read-examples", methods=["POST"])
+def _read_examples() -> Response:
+    """
+    Read tutorial examples from local file.
+    """
+    lang = request.json.get("lang")
+    file_index = request.json.get("data")
+
+    if not os.path.exists(
+        os.path.join(
+            _app.root_path,
+            "static",
+            "workstation_templates",
+            f"{lang}{file_index}.json",
+        ),
+    ):
+        lang = "en"
+
+    with open(
+        os.path.join(
+            _app.root_path,
+            "static",
+            "workstation_templates",
+            f"{lang}{file_index}.json",
+        ),
+        "r",
+        encoding="utf-8",
+    ) as jf:
+        data = json.load(jf)
+    return jsonify(json=data)
+
+
+@_app.route("/")
+def _home() -> str:
+    """Render the home page."""
+    return render_template("index.html")
+
+
+@_socketio.on("request_user_input")
+def _request_user_input(data: dict) -> None:
+    """Request user input"""
+    _app.logger.debug("Flask: receive request_user_input")
+
+    run_id = data["run_id"]
+    agent_id = data["agent_id"]
+
+    # Change the status into waiting
+    _db.session.query(_RunTable).filter_by(run_id=run_id).update(
+        {"status": "waiting"},
+    )
+    _db.session.commit()
+
+    # Record into the queue
+    _UserInputRequestQueue.add_request(run_id, agent_id, data)
+
+    # Ask for user input from the web ui
+    _socketio.emit(
+        "enable_user_input",
+        data,
+        room=run_id,
+    )
+
+    _app.logger.debug("Flask: send enable_user_input")
+
+
+@_socketio.on("user_input_ready")
+def _user_input_ready(data: dict) -> None:
+    """Get user input and send to the agent"""
+    _app.logger.debug(f"Flask: receive user_input_ready: {data}")
+
+    run_id = data["run_id"]
+    agent_id = data["agent_id"]
+    content = data["content"]
+    url = data["url"]
+
+    _db.session.query(_RunTable).filter_by(run_id=run_id).update(
+        {"status": "running"},
+    )
+    _db.session.commit()
+
+    # Return to AgentScope application
+    _socketio.emit(
+        "fetch_user_input",
+        {
+            "agent_id": agent_id,
+            "name": data["name"],
+            "run_id": run_id,
+            "content": content,
+            "url": None if url in ["", []] else url,
+        },
+        room=run_id,
+    )
+
+    # Close the request in the queue
+    _UserInputRequestQueue.close_a_request(run_id, agent_id)
+
+    # Fetch a new user input request for this run_id if exists
+    new_request = _UserInputRequestQueue.fetch_a_request(run_id)
+    if new_request is not None:
+        _socketio.emit(
+            "enable_user_input",
+            new_request,
+            room=run_id,
+        )
+
+    _app.logger.debug("Flask: send fetch_user_input")
+
+
+@_socketio.on("connect")
+def _on_connect() -> None:
+    """Execute when a client is connected."""
+    _app.logger.info("New client connected")
+
+
+@_socketio.on("disconnect")
+def _on_disconnect() -> None:
+    """Execute when a client is disconnected."""
+    _app.logger.info("Client disconnected")
+
+
+@_socketio.on("join")
+def _on_join(data: dict) -> None:
+    """Join a websocket room"""
+    run_id = data["run_id"]
+    join_room(run_id)
+
+    new_request = _UserInputRequestQueue.fetch_a_request(run_id)
+    if new_request is not None:
+        _socketio.emit(
+            "enable_user_input",
+            new_request,
+            room=run_id,
+        )
+
+
+@_socketio.on("leave")
+def _on_leave(data: dict) -> None:
+    """Leave a websocket room"""
+    run_id = data["run_id"]
+    leave_room(run_id)
+
+
+
+[docs] +def init( + host: str = "127.0.0.1", + port: int = 5000, + run_dirs: Optional[Union[str, list[str]]] = None, + debug: bool = False, +) -> None: + """Start the AgentScope Studio web UI with the given configurations. + + Args: + host (str, optional): + The host of the web UI. Defaults to "127.0.0.1" + port (int, optional): + The port of the web UI. Defaults to 5000. + run_dirs (`Optional[Union[str, list[str]]]`, defaults to `None`): + The directories to search for the history of runtime instances. + debug (`bool`, optional): + Whether to enable the debug mode. Defaults to False. + """ + + # Set the history directories + if isinstance(run_dirs, str): + run_dirs = [run_dirs] + + global _RUNS_DIRS + _RUNS_DIRS = run_dirs + + # Create the cache directory + with _app.app_context(): + _db.create_all() + + if debug: + _app.logger.setLevel("DEBUG") + else: + _app.logger.setLevel("INFO") + + _socketio.run( + _app, + host=host, + port=port, + debug=debug, + allow_unsafe_werkzeug=True, + )
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2024, Alibaba Tongyi Lab.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/en/_modules/agentscope/utils/monitor.html b/en/_modules/agentscope/utils/monitor.html index 7e4663f38..54adedc52 100644 --- a/en/_modules/agentscope/utils/monitor.html +++ b/en/_modules/agentscope/utils/monitor.html @@ -566,7 +566,7 @@

Source code for agentscope.utils.monitor

         self.db_path = db_path
         self.table_name = table_name
         self._create_monitor_table(drop_exists)
-        logger.info(
+        logger.debug(
             f"SqliteMonitor initialization completed at [{self.db_path}]",
         )
@@ -597,8 +597,8 @@

Source code for agentscope.utils.monitor

                 END;
                 """,
             )
-        logger.info(f"Init [{self.table_name}] as the monitor table")
-        logger.info(
+        logger.debug(f"Init [{self.table_name}] as the monitor table")
+        logger.debug(
             f"Init [{self.table_name}_quota_exceeded] as the monitor trigger",
         )
 
diff --git a/en/_modules/agentscope/utils/tools.html b/en/_modules/agentscope/utils/tools.html
index 5596dd960..e7ea61023 100644
--- a/en/_modules/agentscope/utils/tools.html
+++ b/en/_modules/agentscope/utils/tools.html
@@ -107,12 +107,13 @@ 

Source code for agentscope.utils.tools

 import secrets
 import string
 import socket
+import hashlib
+import random
 from typing import Any, Literal, List, Optional
 
 from urllib.parse import urlparse
-
+import psutil
 import requests
-from loguru import logger
 
 
 def _get_timestamp(
@@ -143,9 +144,7 @@ 

Source code for agentscope.utils.tools

     if "content" in item:
         clean_dict["content"] = _convert_to_str(item["content"])
     else:
-        logger.warning(
-            f"Message {item} doesn't have `content` field for " f"OpenAI API.",
-        )
+        raise ValueError("The content of the message is missing.")
 
     return clean_dict
@@ -194,17 +193,10 @@

Source code for agentscope.utils.tools

     """
     if port is None:
         new_port = find_available_port()
-        logger.warning(
-            "agent server port is not provided, automatically select "
-            f"[{new_port}] as the port number.",
-        )
         return new_port
     with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
         if s.connect_ex(("localhost", port)) == 0:
             new_port = find_available_port()
-            logger.warning(
-                f"Port [{port}] is occupied, use [{new_port}] instead",
-            )
             return new_port
     return port
@@ -329,7 +321,7 @@

Source code for agentscope.utils.tools

                     file.write(chunk)
             return True
         else:
-            logger.warning(
+            raise RuntimeError(
                 f"Failed to download file from {url} (status code: "
                 f"{response.status_code}). Retry {n_retry}/{max_retries}.",
             )
@@ -353,6 +345,28 @@ 

Source code for agentscope.utils.tools

     return "".join(secrets.choice(characters) for i in range(length))
 
 
+
+[docs] +def generate_id_from_seed(seed: str, length: int = 8) -> str: + """Generate random id from seed str. + + Args: + seed (`str`): seed string. + length (`int`): generated id length. + """ + hasher = hashlib.sha256() + hasher.update(seed.encode("utf-8")) + hash_digest = hasher.hexdigest() + + random.seed(hash_digest) + id_chars = [ + random.choice(string.ascii_letters + string.digits) + for _ in range(length) + ] + return "".join(id_chars)
+ + + def _is_json_serializable(obj: Any) -> bool: """Check if the given object is json serializable.""" try: @@ -490,6 +504,70 @@

Source code for agentscope.utils.tools

             )
         raise ImportError(err_msg)
+ + +def _get_process_creation_time() -> datetime.datetime: + """Get the creation time of the process.""" + pid = os.getpid() + # Find the process by pid + current_process = psutil.Process(pid) + # Obtain the process creation time + create_time = current_process.create_time() + # Change the timestamp to a readable format + return datetime.datetime.fromtimestamp(create_time) + + +def _is_process_alive( + pid: int, + create_time_str: str, + create_time_format: str = "%Y-%m-%d %H:%M:%S", + tolerance_seconds: int = 10, +) -> bool: + """Check if the process is alive by comparing the actual creation time of + the process with the given creation time. + + Args: + pid (`int`): + The process id. + create_time_str (`str`): + The given creation time string. + create_time_format (`str`, defaults to `"%Y-%m-%d %H:%M:%S"`): + The format of the given creation time string. + tolerance_seconds (`int`, defaults to `10`): + The tolerance seconds for comparing the actual creation time with + the given creation time. + + Returns: + `bool`: True if the process is alive, False otherwise. + """ + try: + # Try to create a process object by pid + proc = psutil.Process(pid) + # Obtain the actual creation time of the process + actual_create_time_timestamp = proc.create_time() + + # Convert the given creation time string to a datetime object + given_create_time_datetime = datetime.datetime.strptime( + create_time_str, + create_time_format, + ) + + # Calculate the time difference between the actual creation time and + time_difference = abs( + actual_create_time_timestamp + - given_create_time_datetime.timestamp(), + ) + + # Compare the actual creation time with the given creation time + if time_difference <= tolerance_seconds: + return True + + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + # If the process is not found, access is denied, or the process is a + # zombie process, return False + return False + + return False
diff --git a/en/_modules/agentscope/web/_app.html b/en/_modules/agentscope/web/_app.html deleted file mode 100644 index ad1a4df30..000000000 --- a/en/_modules/agentscope/web/_app.html +++ /dev/null @@ -1,255 +0,0 @@ - - - - - - - agentscope.web._app — AgentScope documentation - - - - - - - - - - - - - - - - - - - - -
- - -
- -
-
-
- -
-
-
-
- -

Source code for agentscope.web._app

-# -*- coding: utf-8 -*-
-"""The main entry point of the web UI."""
-import json
-import os
-
-from flask import Flask, jsonify, render_template, Response
-from flask_cors import CORS
-from flask_socketio import SocketIO
-
-app = Flask(__name__)
-socketio = SocketIO(app)
-CORS(app)  # This will enable CORS for all routes
-
-
-PATH_SAVE = ""
-
-
-@app.route("/getProjects", methods=["GET"])
-def get_projects() -> Response:
-    """Get all the projects in the runs directory."""
-    cfgs = []
-    for run_dir in os.listdir(PATH_SAVE):
-        print(run_dir)
-        path_cfg = os.path.join(PATH_SAVE, run_dir, ".config")
-        if os.path.exists(path_cfg):
-            with open(path_cfg, "r", encoding="utf-8") as file:
-                cfg = json.load(file)
-                cfg["dir"] = run_dir
-                cfgs.append(cfg)
-
-    # Filter the same projects
-    project_names = list({_["project"] for _ in cfgs})
-
-    return jsonify(
-        {
-            "names": project_names,
-            "runs": cfgs,
-        },
-    )
-
-
-@app.route("/")
-def home() -> str:
-    """Render the home page."""
-    return render_template("home.html")
-
-
-@app.route("/run/<run_dir>")
-def run_detail(run_dir: str) -> str:
-    """Render the run detail page."""
-    path_run = os.path.join(PATH_SAVE, run_dir)
-
-    # Find the logging and chat file by suffix
-    path_log = os.path.join(path_run, "logging.log")
-    path_dialog = os.path.join(path_run, "logging.chat")
-
-    if os.path.exists(path_log):
-        with open(path_log, "r", encoding="utf-8") as file:
-            logging_content = ["".join(file.readlines())]
-    else:
-        logging_content = None
-
-    if os.path.exists(path_dialog):
-        with open(path_dialog, "r", encoding="utf-8") as file:
-            dialog_content = file.readlines()
-        dialog_content = [json.loads(_) for _ in dialog_content]
-    else:
-        dialog_content = []
-
-    path_cfg = os.path.join(PATH_SAVE, run_dir, ".config")
-    if os.path.exists(path_cfg):
-        with open(path_cfg, "r", encoding="utf-8") as file:
-            cfg = json.load(file)
-    else:
-        cfg = {
-            "project": "-",
-            "name": "-",
-            "id": "-",
-            "timestamp": "-",
-        }
-
-    logging_and_dialog = {
-        "config": cfg,
-        "logging": logging_content,
-        "dialog": dialog_content,
-    }
-
-    return render_template("run.html", runInfo=logging_and_dialog)
-
-
-@socketio.on("connect")
-def on_connect() -> None:
-    """Execute when a client is connected."""
-    print("Client connected")
-
-
-@socketio.on("disconnect")
-def on_disconnect() -> None:
-    """Execute when a client is disconnected."""
-    print("Client disconnected")
-
-
-
-[docs] -def init( - path_save: str, - host: str = "127.0.0.1", - port: int = 5000, - debug: bool = False, -) -> None: - """Start the web UI.""" - global PATH_SAVE - - if not os.path.exists(path_save): - raise FileNotFoundError(f"The path {path_save} does not exist.") - - PATH_SAVE = path_save - socketio.run( - app, - host=host, - port=port, - debug=debug, - allow_unsafe_werkzeug=True, - )
- -
- -
-
-
- -
- -
-

© Copyright 2024, Alibaba Tongyi Lab.

-
- - Built with Sphinx using a - theme - provided by Read the Docs. - - -
-
-
-
-
- - - - \ No newline at end of file diff --git a/en/_modules/agentscope/web/studio/studio.html b/en/_modules/agentscope/web/gradio/studio.html similarity index 96% rename from en/_modules/agentscope/web/studio/studio.html rename to en/_modules/agentscope/web/gradio/studio.html index 1debdfde5..5d86fe955 100644 --- a/en/_modules/agentscope/web/studio/studio.html +++ b/en/_modules/agentscope/web/gradio/studio.html @@ -4,7 +4,7 @@ - agentscope.web.studio.studio — AgentScope documentation + agentscope.web.gradio.studio — AgentScope documentation @@ -45,8 +45,8 @@
diff --git a/en/agentscope.html b/en/agentscope.html index 477049f3c..ad00fa068 100644 --- a/en/agentscope.html +++ b/en/agentscope.html @@ -107,7 +107,7 @@

Import all modules in the package.

-agentscope.init(model_configs: dict | str | list | None = None, project: str | None = None, name: str | None = None, save_dir: str = './runs', save_log: bool = True, save_code: bool = True, save_api_invoke: bool = False, use_monitor: bool = True, logger_level: Literal['TRACE', 'DEBUG', 'INFO', 'SUCCESS', 'WARNING', 'ERROR', 'CRITICAL'] = 'INFO', runtime_id: str | None = None, agent_configs: dict | str | list | None = None) Sequence[AgentBase][source]
+agentscope.init(model_configs: dict | str | list | None = None, project: str | None = None, name: str | None = None, save_dir: str = './runs', save_log: bool = True, save_code: bool = True, save_api_invoke: bool = False, use_monitor: bool = True, logger_level: Literal['TRACE', 'DEBUG', 'INFO', 'SUCCESS', 'WARNING', 'ERROR', 'CRITICAL'] = 'INFO', runtime_id: str | None = None, agent_configs: dict | str | list | None = None, studio_url: str | None = None) Sequence[AgentBase][source]

A unified entry to initialize the package, including model configs, runtime names, saving directories and logging settings.

@@ -132,6 +132,7 @@ which can be loaded by json.loads(). One agent config should cover the required arguments to initialize a specific agent object, otherwise the default values will be used.

+
  • studio_url (Optional[str], defaults to None) – The url of the agentscope studio.

  • diff --git a/en/agentscope.utils.logging_utils.html b/en/agentscope.logging.html similarity index 60% rename from en/agentscope.utils.logging_utils.html rename to en/agentscope.logging.html index 2f8aedeb4..dfa96bc03 100644 --- a/en/agentscope.utils.logging_utils.html +++ b/en/agentscope.logging.html @@ -5,7 +5,7 @@ - agentscope.utils.logging_utils — AgentScope documentation + agentscope.logging — AgentScope documentation @@ -44,8 +44,8 @@