Based on the design features of agentUniverse domain components, creating a Planner definition involves two parts, similar to other components:
- planner_xx.yaml
- planner_xx.py
The
planner_xx.yaml
file contains important information about the Planner component, such as its name, description, input, output, etc. Theplanner_xx.py
file contains the specific behavior definitions of the Planner. With this understanding, let's see how to create these two parts in detail.
We will detail the various components in the configuration.
name
: The name of the Planner, which can be set to any unique identifier you prefer.description
: A description of the Plannerinput_key
: Defaults to "input," represents the key for actual inputoutput_key
: Defaults to "output," represents the key for actual output
metadata
- Component Metadata
type
: Component type, 'PLANNER'module
: The package path of the Planner entityclass
: The class name of the Planner entity
name: 'expressing_planner'
description: 'expressing planner'
metadata:
type: 'PLANNER'
module: 'agentuniverse.agent.plan.planner.expressing_planner.expressing_planner'
class: 'ExpressingPlanner'
The above is an actual sample of a Planner configuration.
Besides, the standard configuration items mentioned above, you can find more planner configuration YAML samples in the agentuniverse.agent.plan.planner
path in the project.
Additionally, agentUniverse does not restrict users from extending the Planner YAML configuration content. You can create any custom configuration keys according to your requirements, but please be mindful not to use the same names as the default configuration keywords mentioned above.
Create the corresponding Planner class object and inherit the base class Planner from the agentUniverse framework.
Write the actual logic fragments for the plan in the invoke
method.
def invoke(self, agent_model: AgentModel, planner_input: dict, input_object: InputObject) -> dict:
"""Invoke the planner.
Args:
agent_model (AgentModel): Agent model object.
planner_input (dict): Planner input object.
input_object (InputObject): The input parameters passed by the user.
Returns:
dict: The planner result.
"""
planner_config = agent_model.plan.get('planner')
sub_agents = self.generate_sub_agents(planner_config)
return self.agents_run(sub_agents, planner_config, planner_input, input_object)
The above example uses a peer planner to illustrate, where it selects the various sub-agents in PEER mode and runs them accordingly. We will elaborate further on the working principles of PEER in its respective section.
Similarly, the following non-mandatory methods will not be elaborated here. It is recommended to refer to an actual planner source code for a comprehensive understanding, such as agentuniverse.agent.plan.planner.planning_planner.planning_planner
.
The handle_prompt
method handles prompt processing within the planner. It is optional.
The handle_memory
method handles memory processing within the planner. It is optional.
The handle_all_actions
method handles all action processing within the planner. It is optional.
The handle_llm
method handles LLM processing within the planner. It is optional.
import asyncio
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from agentuniverse.agent.agent_model import AgentModel
from agentuniverse.agent.input_object import InputObject
from agentuniverse.agent.memory.chat_memory import ChatMemory
from agentuniverse.agent.plan.planner.planner import Planner
from agentuniverse.base.util.memory_util import generate_memories
from agentuniverse.base.util.prompt_util import process_llm_token
from agentuniverse.llm.llm import LLM
from agentuniverse.prompt.chat_prompt import ChatPrompt
from agentuniverse.prompt.prompt import Prompt
from agentuniverse.prompt.prompt_manager import PromptManager
from agentuniverse.prompt.prompt_model import AgentPromptModel
class RagPlanner(Planner):
"""Rag planner class."""
def invoke(self, agent_model: AgentModel, planner_input: dict,
input_object: InputObject) -> dict:
"""Invoke the planner.
Args:
agent_model (AgentModel): Agent model object.
planner_input (dict): Planner input object.
input_object (InputObject): The input parameters passed by the user.
Returns:
dict: The planner result.
"""
memory: ChatMemory = self.handle_memory(agent_model, planner_input)
self.run_all_actions(agent_model, planner_input, input_object)
llm: LLM = self.handle_llm(agent_model)
prompt: ChatPrompt = self.handle_prompt(agent_model, planner_input)
process_llm_token(llm, prompt.as_langchain(), agent_model.profile, planner_input)
chat_history = memory.as_langchain().chat_memory if memory else InMemoryChatMessageHistory()
chain_with_history = RunnableWithMessageHistory(
prompt.as_langchain() | llm.as_langchain(),
lambda session_id: chat_history,
history_messages_key="chat_history",
input_messages_key=self.input_key,
)
res = asyncio.run(
chain_with_history.ainvoke(input=planner_input, config={"configurable": {"session_id": "unused"}}))
return {**planner_input, self.output_key: res.content, 'chat_history': generate_memories(chat_history)}
def handle_prompt(self, agent_model: AgentModel, planner_input: dict) -> ChatPrompt:
"""Prompt module processing.
Args:
agent_model (AgentModel): Agent model object.
planner_input (dict): Planner input object.
Returns:
ChatPrompt: The chat prompt instance.
"""
profile: dict = agent_model.profile
profile_prompt_model: AgentPromptModel = AgentPromptModel(introduction=profile.get('introduction'),
target=profile.get('target'),
instruction=profile.get('instruction'))
# get the prompt by the prompt version
prompt_version: str = profile.get('prompt_version')
version_prompt: Prompt = PromptManager().get_instance_obj(prompt_version)
if version_prompt is None and not profile_prompt_model:
raise Exception("Either the `prompt_version` or `introduction & target & instruction`"
" in agent profile configuration should be provided.")
if version_prompt:
version_prompt_model: AgentPromptModel = AgentPromptModel(
introduction=getattr(version_prompt, 'introduction', ''),
target=getattr(version_prompt, 'target', ''),
instruction=getattr(version_prompt, 'instruction', ''))
profile_prompt_model = profile_prompt_model + version_prompt_model
chat_prompt = ChatPrompt().build_prompt(profile_prompt_model, self.prompt_assemble_order)
image_urls: list = planner_input.pop('image_urls', []) or []
if image_urls:
chat_prompt.generate_image_prompt(image_urls)
return chat_prompt
The above is an actual sample based on the Rag planner. Further details will be provided in the section introducing the Rag planner.
With the above Planner configuration and definition, you have learned all the steps for creating a Planner. Next, we will use these Planners. Before using them, ensure the created Planner is in the correct package scan path.
In the config.toml file of the agentUniverse project, you need to configure the package corresponding to the Planner configuration. Please confirm whether the package path of the created file is under the CORE_PACKAGE
's planner
path or its subpaths.
Here is an example configuration from the sample project:
[CORE_PACKAGE]
# Scan and register planner components for all paths under this list, with priority over the default.
planner = ['sample_standard_app.app.core.planner']
Following the Creating and Using Agents guide, you can set up any created Planner component in the agent's planner section. Refer to the example: demo_rag_agent
, with the specific file path sample_standard_app/app/core/agent/rag_agent_case/demo_rag_agent.yaml
.
You can get the Planner instance corresponding to its name using the .get_instance_obj(xx_planner_name)
method in the Planner Manager and call it using the invoke
method.
from agentuniverse.agent.plan.planner.planner_manager import PlannerManager
planner_base = PlannerManager().get_instance_obj('xx_planner_name')
planner_result = planner_base.invoke(self.agent_model, agent_input, input_object)
You can find more Planner components in the agentuniverse.agent.plan.planner
package path. You can further check the corresponding code or learn more about them in our extension component introduction section. For more integrated Planners, you can refer to the Planner list chapter.
Now you have mastered the definition and use of Planners. Go ahead and try defining and using your own Planners.