mirror of
https://github.com/microsoft/autogen.git
synced 2025-12-12 07:21:18 +00:00
Update agent and agent runtime doc with routed agent (#4935)
* add back removed note Signed-off-by: Mohammad Mazraeh <mazraeh.mohammad@gmail.com> * fix formatting issues Signed-off-by: Mohammad Mazraeh <mazraeh.mohammad@gmail.com> --------- Signed-off-by: Mohammad Mazraeh <mazraeh.mohammad@gmail.com>
This commit is contained in:
parent
d610d481cd
commit
ad123641da
@ -1,285 +1,284 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Agent and Agent Runtime\n",
|
||||
"\n",
|
||||
"In this and the following section, we focus on the core concepts of AutoGen:\n",
|
||||
"agents, agent runtime, messages, and communication.\n",
|
||||
"You will not find any AI models or tools here, just the foundational\n",
|
||||
"building blocks for building multi-agent applications.\n",
|
||||
"\n",
|
||||
"```{note}\n",
|
||||
"The Core API is designed to be unopinionated and flexible. So at times, you\n",
|
||||
"may find it challenging. Continue if you are building\n",
|
||||
"an interactive, scalable and distributed multi-agent system and want full control\n",
|
||||
"of all workflows.\n",
|
||||
"If you just want to get something running\n",
|
||||
"quickly, you may take a look at the [AgentChat API](../../agentchat-user-guide/index.md).\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"An agent in AutoGen is an entity defined by the base class {py:class}`autogen_core.Agent`.\n",
|
||||
"It has a unique identifier of the type {py:class}`autogen_core.AgentId`,\n",
|
||||
"a metadata dictionary of the type {py:class}`autogen_core.AgentMetadata`,\n",
|
||||
"and method for handling messages {py:meth}`autogen_core.BaseAgent.on_message_impl`.\n",
|
||||
"\n",
|
||||
"An agent runtime is the execution environment for agents in AutoGen.\n",
|
||||
"Similar to the runtime environment of a programming language,\n",
|
||||
"an agent runtime provides the necessary infrastructure to facilitate communication\n",
|
||||
"between agents, manage agent lifecycles, enforce security boundaries, and support monitoring and\n",
|
||||
"debugging.\n",
|
||||
"For local development, developers can use {py:class}`~autogen_core.SingleThreadedAgentRuntime`,\n",
|
||||
"which can be embedded in a Python application.\n",
|
||||
"\n",
|
||||
"```{note}\n",
|
||||
"Agents are not directly instantiated and managed by application code.\n",
|
||||
"Instead, they are created by the runtime when needed and managed by the runtime.\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Implementing an Agent\n",
|
||||
"\n",
|
||||
"To implement an agent, the developer must subclass the {py:class}`~autogen_core.BaseAgent` class\n",
|
||||
"and implement the {py:meth}`~autogen_core.BaseAgent.on_message_impl` method.\n",
|
||||
"This method is invoked when the agent receives a message. For example,\n",
|
||||
"the following agent handles a simple message type and prints the message it receives:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from dataclasses import dataclass\n",
|
||||
"\n",
|
||||
"from autogen_core import AgentId, BaseAgent, MessageContext\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class MyMessageType:\n",
|
||||
" content: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class MyAgent(BaseAgent):\n",
|
||||
" def __init__(self) -> None:\n",
|
||||
" super().__init__(\"MyAgent\")\n",
|
||||
"\n",
|
||||
" async def on_message_impl(self, message: MyMessageType, ctx: MessageContext) -> None:\n",
|
||||
" print(f\"Received message: {message.content}\") # type: ignore"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This agent only handles `MyMessageType` messages. \n",
|
||||
"To handle multiple message types, developers can subclass the {py:class}`~autogen_core.RoutedAgent` class\n",
|
||||
"which provides an easy-to use API to implement different message handlers for different message types.\n",
|
||||
"See the next section on [message and communication](./message-and-communication.ipynb)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Registering Agent Type\n",
|
||||
"\n",
|
||||
"To make agents available to the runtime, developers can use the\n",
|
||||
"{py:meth}`~autogen_core.BaseAgent.register` class method of the\n",
|
||||
"{py:class}`~autogen_core.BaseAgent` class.\n",
|
||||
"The process of registration associates an agent type, which is uniquely identified by a string, \n",
|
||||
"and a factory function\n",
|
||||
"that creates an instance of the agent type of the given class.\n",
|
||||
"The factory function is used to allow automatic creation of agent instances \n",
|
||||
"when they are needed.\n",
|
||||
"\n",
|
||||
"Agent type ({py:class}`~autogen_core.AgentType`) is not the same as the agent class. In this example,\n",
|
||||
"the agent type is `AgentType(\"my_agent\")` and the agent class is the Python class `MyAgent`.\n",
|
||||
"The factory function is expected to return an instance of the agent class \n",
|
||||
"on which the {py:meth}`~autogen_core.BaseAgent.register` class method is invoked.\n",
|
||||
"Read [Agent Identity and Lifecycles](../core-concepts/agent-identity-and-lifecycle.md)\n",
|
||||
"to learn more about agent type and identity.\n",
|
||||
"\n",
|
||||
"```{note}\n",
|
||||
"Different agent types can be registered with factory functions that return \n",
|
||||
"the same agent class. For example, in the factory functions, \n",
|
||||
"variations of the constructor parameters\n",
|
||||
"can be used to create different instances of the same agent class.\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"To register an agent type with the \n",
|
||||
"{py:class}`~autogen_core.SingleThreadedAgentRuntime`,\n",
|
||||
"the following code can be used:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AgentType(type='my_agent')"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from autogen_core import SingleThreadedAgentRuntime\n",
|
||||
"\n",
|
||||
"runtime = SingleThreadedAgentRuntime()\n",
|
||||
"await MyAgent.register(runtime, \"my_agent\", lambda: MyAgent())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Once an agent type is registered, we can send a direct message to an agent instance\n",
|
||||
"using an {py:class}`~autogen_core.AgentId`.\n",
|
||||
"The runtime will create the instance the first time it delivers a\n",
|
||||
"message to this instance."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Received message: Hello, World!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"agent_id = AgentId(\"my_agent\", \"default\")\n",
|
||||
"runtime.start() # Start processing messages in the background.\n",
|
||||
"await runtime.send_message(MyMessageType(\"Hello, World!\"), agent_id)\n",
|
||||
"await runtime.stop() # Stop processing messages in the background."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"```{note}\n",
|
||||
"Because the runtime manages the lifecycle of agents, an {py:class}`~autogen_core.AgentId`\n",
|
||||
"is only used to communicate with the agent or retrieve its metadata (e.g., description).\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Running the Single-Threaded Agent Runtime\n",
|
||||
"\n",
|
||||
"The above code snippet uses `runtime.start()` to start a background task\n",
|
||||
"to process and deliver messages to recepients' message handlers.\n",
|
||||
"This is a feature of the\n",
|
||||
"local embedded runtime {py:class}`~autogen_core.SingleThreadedAgentRuntime`.\n",
|
||||
"\n",
|
||||
"To stop the background task immediately, use the `stop()` method:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"runtime.start()\n",
|
||||
"# ... Send messages, publish messages, etc.\n",
|
||||
"await runtime.stop() # This will return immediately but will not cancel\n",
|
||||
"# any in-progress message handling."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"You can resume the background task by calling `start()` again.\n",
|
||||
"\n",
|
||||
"For batch scenarios such as running benchmarks for evaluating agents,\n",
|
||||
"you may want to wait for the background task to stop automatically when\n",
|
||||
"there are no unprocessed messages and no agent is handling messages --\n",
|
||||
"the batch may considered complete.\n",
|
||||
"You can achieve this by using the `stop_when_idle()` method:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"runtime.start()\n",
|
||||
"# ... Send messages, publish messages, etc.\n",
|
||||
"await runtime.stop_when_idle() # This will block until the runtime is idle."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"You can also directly process messages one-by-one without a background task using:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"await runtime.process_next()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Other runtime implementations will have their own ways of running the runtime."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Agent and Agent Runtime\n",
|
||||
"\n",
|
||||
"In this and the following section, we focus on the core concepts of AutoGen:\n",
|
||||
"agents, agent runtime, messages, and communication.\n",
|
||||
"You will not find any AI models or tools here, just the foundational\n",
|
||||
"building blocks for building multi-agent applications.\n",
|
||||
"\n",
|
||||
"```{note}\n",
|
||||
"The Core API is designed to be unopinionated and flexible. So at times, you\n",
|
||||
"may find it challenging. Continue if you are building\n",
|
||||
"an interactive, scalable and distributed multi-agent system and want full control\n",
|
||||
"of all workflows.\n",
|
||||
"If you just want to get something running\n",
|
||||
"quickly, you may take a look at the [AgentChat API](../../agentchat-user-guide/index.md).\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"An agent in AutoGen is an entity defined by the base class {py:class}`autogen_core.Agent`.\n",
|
||||
"It has a unique identifier of the type {py:class}`autogen_core.AgentId`,\n",
|
||||
"a metadata dictionary of the type {py:class}`autogen_core.AgentMetadata`,\n",
|
||||
"\n",
|
||||
"and method for handling messages {py:meth}`autogen_core.BaseAgent.on_message_impl`. In most cases, you can subclass your agents from higher level class {py:class}`autogen_core.RoutedAgent` which enables you to route messages to corresponding message handler specified with {py:meth}`autogen_core.message_handler` decorator and proper type hint for the `message` variable.\n",
|
||||
"An agent runtime is the execution environment for agents in AutoGen.\n",
|
||||
"Similar to the runtime environment of a programming language,\n",
|
||||
"an agent runtime provides the necessary infrastructure to facilitate communication\n",
|
||||
"between agents, manage agent lifecycles, enforce security boundaries, and support monitoring and\n",
|
||||
"debugging.\n",
|
||||
"For local development, developers can use {py:class}`~autogen_core.SingleThreadedAgentRuntime`,\n",
|
||||
"which can be embedded in a Python application.\n",
|
||||
"\n",
|
||||
"```{note}\n",
|
||||
"Agents are not directly instantiated and managed by application code.\n",
|
||||
"Instead, they are created by the runtime when needed and managed by the runtime.\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Implementing an Agent\n",
|
||||
"\n",
|
||||
"To implement an agent, the developer must subclass the {py:class}`~autogen_core.RoutedAgent` class\n",
|
||||
"and implement the {py:meth}`~autogen_core.RoutedAgent.on_message_impl` method.\n",
|
||||
"This method is invoked when the agent receives a message. For example,\n",
|
||||
"the following agent handles a simple message type `MyMessageType` and prints the message it receives:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from dataclasses import dataclass\n",
|
||||
"\n",
|
||||
"from autogen_core import AgentId, MessageContext, RoutedAgent, message_handler\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"@dataclass\n",
|
||||
"class MyMessageType:\n",
|
||||
" content: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class MyAgent(RoutedAgent):\n",
|
||||
" def __init__(self) -> None:\n",
|
||||
" super().__init__(\"MyAgent\")\n",
|
||||
"\n",
|
||||
" @message_handler\n",
|
||||
" async def handle_my_message_type(self, message: MyMessageType, ctx: MessageContext) -> None:\n",
|
||||
" print(f\"Received message: {message.content}\") # type: ignore"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"This agent only handles `MyMessageType` and messages will be delivered to `handle_my_message_type` method. Developers can have multiple message handlers for different message types by using `@message_handler` decorator and setting the type hint for the `message` variable in the handler function. You can also leverage [python typing union](https://docs.python.org/3/library/typing.html#typing.Union) for the `message` variable in one message handler function if it better suits agent's logic.\n",
|
||||
"See the next section on [message and communication](./message-and-communication.ipynb)."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Registering Agent Type\n",
|
||||
"\n",
|
||||
"To make agents available to the runtime, developers can use the\n",
|
||||
"{py:meth}`~autogen_core.BaseAgent.register` class method of the\n",
|
||||
"{py:class}`~autogen_core.BaseAgent` class.\n",
|
||||
"The process of registration associates an agent type, which is uniquely identified by a string, \n",
|
||||
"and a factory function\n",
|
||||
"that creates an instance of the agent type of the given class.\n",
|
||||
"The factory function is used to allow automatic creation of agent instances \n",
|
||||
"when they are needed.\n",
|
||||
"\n",
|
||||
"Agent type ({py:class}`~autogen_core.AgentType`) is not the same as the agent class. In this example,\n",
|
||||
"the agent type is `AgentType(\"my_agent\")` and the agent class is the Python class `MyAgent`.\n",
|
||||
"The factory function is expected to return an instance of the agent class \n",
|
||||
"on which the {py:meth}`~autogen_core.BaseAgent.register` class method is invoked.\n",
|
||||
"Read [Agent Identity and Lifecycles](../core-concepts/agent-identity-and-lifecycle.md)\n",
|
||||
"to learn more about agent type and identity.\n",
|
||||
"\n",
|
||||
"```{note}\n",
|
||||
"Different agent types can be registered with factory functions that return \n",
|
||||
"the same agent class. For example, in the factory functions, \n",
|
||||
"variations of the constructor parameters\n",
|
||||
"can be used to create different instances of the same agent class.\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"To register an agent type with the \n",
|
||||
"{py:class}`~autogen_core.SingleThreadedAgentRuntime`,\n",
|
||||
"the following code can be used:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"AgentType(type='my_agent')"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from autogen_core import SingleThreadedAgentRuntime\n",
|
||||
"\n",
|
||||
"runtime = SingleThreadedAgentRuntime()\n",
|
||||
"await MyAgent.register(runtime, \"my_agent\", lambda: MyAgent())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Once an agent type is registered, we can send a direct message to an agent instance\n",
|
||||
"using an {py:class}`~autogen_core.AgentId`.\n",
|
||||
"The runtime will create the instance the first time it delivers a\n",
|
||||
"message to this instance."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Received message: Hello, World!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"agent_id = AgentId(\"my_agent\", \"default\")\n",
|
||||
"runtime.start() # Start processing messages in the background.\n",
|
||||
"await runtime.send_message(MyMessageType(\"Hello, World!\"), agent_id)\n",
|
||||
"await runtime.stop() # Stop processing messages in the background."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"```{note}\n",
|
||||
"Because the runtime manages the lifecycle of agents, an {py:class}`~autogen_core.AgentId`\n",
|
||||
"is only used to communicate with the agent or retrieve its metadata (e.g., description).\n",
|
||||
"```"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Running the Single-Threaded Agent Runtime\n",
|
||||
"\n",
|
||||
"The above code snippet uses `runtime.start()` to start a background task\n",
|
||||
"to process and deliver messages to recepients' message handlers.\n",
|
||||
"This is a feature of the\n",
|
||||
"local embedded runtime {py:class}`~autogen_core.SingleThreadedAgentRuntime`.\n",
|
||||
"\n",
|
||||
"To stop the background task immediately, use the `stop()` method:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"runtime.start()\n",
|
||||
"# ... Send messages, publish messages, etc.\n",
|
||||
"await runtime.stop() # This will return immediately but will not cancel\n",
|
||||
"# any in-progress message handling."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"You can resume the background task by calling `start()` again.\n",
|
||||
"\n",
|
||||
"For batch scenarios such as running benchmarks for evaluating agents,\n",
|
||||
"you may want to wait for the background task to stop automatically when\n",
|
||||
"there are no unprocessed messages and no agent is handling messages --\n",
|
||||
"the batch may considered complete.\n",
|
||||
"You can achieve this by using the `stop_when_idle()` method:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"runtime.start()\n",
|
||||
"# ... Send messages, publish messages, etc.\n",
|
||||
"await runtime.stop_when_idle() # This will block until the runtime is idle."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"You can also directly process messages one-by-one without a background task using:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"await runtime.process_next()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Other runtime implementations will have their own ways of running the runtime."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.11"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user