# 如何创建智能体
您或许已经在[开始使用](../../../开始使用)中了解如何快速创建一个智能体，亦或是在[智能体原理](智能体.md)掌握了智能体的重要组成部分，在本节我们会进一步详细说明如何创建智能体。

根据agentUniverse领域组件的设计特性，创建一个智能体在创建过程中由2部分组成:
* agent_xx.yaml
* agent_xx.py

其中`agent_xx.yaml`必须创建, 其包含了agent的Introduction、Target、Instruction、LLM等重要属性信息；`agent_xx.py`为按需创建，其包含了agent的特定行为并且支持了用户对于agent进行领域行为自定义注入。理解这一原理后，让我们具体看看该如何创建这两部分内容。

## 创建智能体配置 - agent_xx.yaml
我们将详细介绍配置中的各组成部分。

### 设置智能体的基本信息
**`info` - 智能体基本信息**
* `name`:  智能体名称
* `description`:  智能体描述

### 设置智能体的全局设定
**`profile` - 智能体全局设定**
* `introduction`:  智能体角色介绍
* `target`:  智能体目标
* `instruction`: 智能体指令
* `llm_model`: 智能体使用的LLM
  * `name`: LLM 名称
  * `model_name`: LLM 模型名  

  您可以选择已有或接入任意的LLM，我们在本部分不展开说明，您可以关注[模型LLM章节](../模型/模型.md)。

### 设置智能体动作
**`action` - 智能体动作**
* `Tool` : 供智能体使用的工具
  * tool_name_list，工具名称列表, 如:  
    \- tool_name_a  
    \- tool_name_b  
    \- tool_name_c  

  您可以选择已有或接入任意的Tool，我们在本部分不展开说明，您可以关注[Tool章节](../工具/工具.md)。

* `Knowledge` : 供智能体使用的知识
  * knowledge_name_list，知识名称列表, 如:  
    \- knowledge_name_a  
    \- knowledge_name_b  
    \- knowledge_name_c
  
  您可以选择已有或接入任意的Knowledge，我们在本部分不展开说明，您可以关注Knowledge章节。

### 设置智能体记忆
**`memory` - 智能体记忆**

### 设置组件元信息
**`metadata` - 组件元信息**
* `type` : 组件类型，'AGENT'
* `module`: Agent实体包路径
* `class`: Agent实体类名

所有已经提供的Agent组件都将提供对应的`module`、`class`复制到该部分即可。该部分会将智能体的所有配置与其行为组成一个整体，若您扩展了智能体的行为，则需要按照实际路径填写这部分，我们将在后文中的[从已有的智能体对象创建](#从已有的智能体对象创建)小节进一步说明。

### 一个智能体配置的实际样例
```yaml
info:
  name: 'demo_agent'
  description: 'demo agent'
profile:
  introduction: 你是一位精通信息分析的ai助手。
  target: 你的目标是判断问题对应的答案是否提供了有价值的信息，并对问题的答案做出建议和评价。
  instruction: |
    你需要遵守的规则是:
    1. 必须使用中文结合查询的背景信息结合你所拥有的知识回答用户提出的问题。
    2. 结构化答案生成，必要时通过空行提升阅读体验。
    3. 不采用背景信息中的错误信息。
    4. 要考虑答案和问题的相关性，不做对问题没有帮助的回答。
    5. 详尽回答问题，重点突出，不过多花哨词藻。
    6. 不说模糊的推测。
    7. 尽量多的使用数值类信息。

    背景信息是:
    {background}

    开始!

    需要回答的问题是: {input}
  llm_model:
    name: 'demo_llm'
    model_name: 'gpt-4o'
memory:
  name: 'demo_memory'
action:
  tool:
    - 'google_search_tool'
  knowledge:
    - 'knowledge_a'
metadata:
  type: 'AGENT'
  module: 'sample_standard_app.intelligence.agentic.agent.agent_instance.demo_agent'
  class: 'DemoAgent'
```
上述是一个实际的智能体配置的样例。除了上述介绍的标准配置项，细心的你可能发现了prompt中包含了类似`{background}`、`{input}`等变量，这是一个非常实用的prompt替换功能，我们在智能体领域行为定义中将解释这部分能力。

除此之外agentuniverse不限制用户对agent yaml配置内容进行扩展，您可以根据自己的要求创建任何自定义配置key,但请注意不要与上述默认的配置关键字重名。

## 创建智能体领域行为定义 agent_xx.py
在本部分您可以对任何智能体的行为进行编排与定制，当然如果您完全使用已有的Agent能力那么本部分并非必须。

在本节我们将重点介绍常用的智能体领域行为定义与您可能在实际智能体领域行为定义过程中使用的常用技巧。

### 创建Agent类对象
创建对应的Agent类对象并继承agentUniverse框架Agent基础类 `Agent`;

### 定制对应的智能体领域行为
常用可定制的智能体行为如下。

#### input_keys方法
代表agent智能体运行流程中用户的入参key列表，需要由各自定义Agent类实现。

例如：agent智能体运行只传入一个参数 question=xxx，则 input_keys 方法 return ['question']

#### output_keys方法
代表agent智能体运行结束时的出参key列表，需要由各自定义Agent类实现。

例如：agent智能体运行输出结果包含response，则 output_keys 方法 return ['response']

#### parse_input方法
agent执行前的输入处理节点, agent的输入可以是自然语言或者json等结构化数据，您可以将任何用户输入的内容在该部分做处理，需要由各自定义Agent类实现。

该部分拥有两个输入参数，如下:
* `input_object`: 智能体输入原始数据
  * 可通过 `input_object.get_data('input_key')` 方法获取input_object中的对应数据。  
  
    例如： 用户对智能体输入`question=xxx`， 我们可以通过`input_object.get_data('question')`获取用户当前的提问。

* `agent_input`: 智能体输入处理后的数据, `dict类型`
  * `agent_input`默认包含了如下关键参数key及数据
    * `chat_history`: 包含了多轮会话数据
    * `background`: 包含了背景知识
    * `date`: 包含了当前的时间日期
  * 将用户输入追加到`agent_input`中
    * 例如： `agent_input['input'] = input_object.get_data('question')`
  
`parse_input`方法的输出对象一般使用`agent_input`。

#### parse_result方法
agent执行前的输出处理节点, agent的输出可以是自然语言或者json等结构化数据，您可以将任何需要输出的内容在该部分做处理，需要由各自定义Agent类实现。

该部分拥有一个输入参数，如下:
* `agent_result`: 智能体输出数据, `dict类型`  
`parse_result`方法的输出对象一般使用`agent_result`

#### execute方法
该方法是agent最核心的执行流程入口， 用户通过重写该方法定制任何agent的执行方法。

##### 自定义execute实现
您通过重写execute方法，编排和定制agent的执行流程。

以下是自定义execute的一个例子：
```text
def execute(self, input_object: InputObject, agent_input: dict) -> dict:
    # Do anything, as exemplified below.
    
    # Invoke tool A to get data.
    
    # Analyze the data and determine whether it can answer the user's questions.
    
    # Organize language to answer questions.
    
    # Output the answer.
    
    return {'output': 'xxx'}
```

#### 获取智能体对象属性
智能体对象通过`self.agent_model`可以访问智能体的属性，在智能体领域行为的定义中，您可以读取在智能体配置部分配置的所有属性。

一个具体的例子如下:
```text
def process_llm(self, **kwargs) -> LLM:
    llm_name = kwargs.get('llm_name') or self.agent_model.profile.get('llm_model', {}).get('name')
    return LLMManager().get_instance_obj(llm_name)
```
在这段例子中通过`self.agent_model.profile`方式在执行方法内获取了智能体配置属性中的`profile`部分,进一步通过get方法最终获取了`llm_model`的实际名称。

智能体的属性与领域行为都依赖于agentUniverse框架中Agent基础类，其路径为`agentuniverse.agent.agent.Agent`, 我们在智能体及相关领域对象章节中也会重点介绍相关的底层对象，若您关注底层技术实现可进一步参考对应的代码与文档；

#### 一个智能体领域行为定义的实际样例
```python
from langchain_core.output_parsers import StrOutputParser

from agentuniverse.agent.agent import Agent
from agentuniverse.agent.input_object import InputObject
from agentuniverse.llm.llm import LLM
from agentuniverse.prompt.prompt import Prompt


class DemoAgent(Agent):
    def input_keys(self) -> list[str]:
        return ['input']

    def output_keys(self) -> list[str]:
        return ['output']

    def parse_input(self, input_object: InputObject, agent_input: dict) -> dict:
        agent_input['input'] = input_object.get_data('input')
        return agent_input

    def parse_result(self, agent_result: dict) -> dict:
        return {**agent_result, 'output': agent_result['output']}

    def execute(self, input_object: InputObject, agent_input: dict) -> dict:
        llm: LLM = self.process_llm()
        prompt: Prompt = self.process_prompt(agent_input)
        chain = prompt.as_langchain() | llm.as_langchain_runnable(
            self.agent_model.llm_params()) | StrOutputParser()
        res = self.invoke_chain(chain, agent_input, input_object)
        return {**agent_input, 'output': res}
```
上述是一个智能体领域行为定义的实际样例。

## 关注您定义的智能体所在的包路径
通过上面的agent配置与领域定义部分，您已经掌握了agent创建的所有步骤；接下去我们将使用这些agent，在使用前请关注创建的agent是否在正确的包扫描路径内。

在agentUniverse项目的config.toml中需要配置agent文件对应的package, 请再次确认您创建的文件所在的包路径是否在`CORE_PACKAGE`中`agent`路径或其子路径下。

以示例工程中的配置为例，如下：
```yaml
[CORE_PACKAGE]
# Scan and register agent components for all paths under this list, with priority over the default.
agent = ['sample_standard_app.intelligence.agentic.agent']
```

## agent创建的其他技巧
### 自由定制与编排agent的执行过程

在本文的[创建智能体领域行为定义](#创建智能体领域行为定义-agent_xx.py)的execute方法中我们已经详细说明了如何自定义一个execute方法，这个方法在实践过程经常用来根据用户的诉求定制流程与注入SOP(Standard Operating Procedure)。

### 如何通过用户的输入动态调整设置

**方法一(推荐)： 通过标准的prompt模版变量替换方式**  

我们在本文的 '一个智能体配置的实际样例' 小节中prompt中包含了类似`{background}`、`{input}`等变量，这是prompt变量模版替换功能，其目的是能够通过用户的输入去动态的影响prompt， 只需要在智能体配置的设置部分将文本使用`{变量名}`的方式进行定义，并在`parse_input`的`agent_input`中进行定义即可动态根据输入的部分替换对应的prompt。

例如在样例agent中 `sample_standard_app.intelligence.agentic.agent.agent_instance.demo_agent.py` 有如下的`parse_input`方法。
```text
def parse_input(self, input_object: InputObject, agent_input: dict) -> dict:
    agent_input['input'] = input_object.get_data('input')
    return agent_input
```
在其对应agent设置`sample_standard_app.intelligence.agentic.agent.agent_instance.demo_agent.yaml`中的`instruction`部分我们可以看到如下配置:
```text
instruction: |
    你需要遵守的规则是:
    1. 必须使用中文结合查询的背景信息结合你所拥有的知识回答用户提出的问题。
    2. 结构化答案生成，必要时通过空行提升阅读体验。
    3. 不采用背景信息中的错误信息。
    4. 要考虑答案和问题的相关性，不做对问题没有帮助的回答。
    5. 详尽回答问题，重点突出，不过多花哨词藻。
    6. 不说模糊的推测。
    7. 尽量多的使用数值类信息。

    背景信息是:
    {background}

    开始!

    需要回答的问题是: {input}
```

通过这个过程我们可以将每次的输入与记忆或背景知识及任何你关注的信息通过该方法带入agent与LLM的交互中。

**方法二： 在parse_input方法或execute方法中调整prompt**

通过agent的`parse_input`方法或`execute`方法我们可以对agent做数据或行为上的定制，同时结合`self.agent_model`中的get方法与智能体输入参数`input_object`，这意味着您可以通过这些定制方法将你的任意设置内容进行修改。

### 从已有的智能体对象创建
在实际的应用创建过程中，我们可能存在很多同类型的智能体，例如我们想创建许多检索增强RAG(Retrieval Augmented Generation)类型的智能体，运用于不同的问答场景中。分析他们的相同与不同点我们不难发现对于大部分RAG类型的智能体，他们具有如下共性与差异。
* 主要的相同点：他们往往都需要同时检索多种知识
* 主要的不同点： 不同场景的RAG智能体需要对应检索不同的工具，可能拥有不同的智能体全局设置

进一步分析，我们通过相同的领域行为与差异化的属性配置可以实现这一目标。这也就是为什么agent的创建过程中配置设置yaml是必须的, 但是领域行为定制是按实际需求的。

幸运的是我们拥有非常多有效的智能体类型, 并且他们仍在不断扩充。如这个case所述，结合agent领域组件的设置过程，我们仅需要设置非常多不同的类型的agent配置设置yaml，并且在agent的组件元信息中通过设置`metadata`完成这一目标。

例如：  
**金融类RAG agent设置**
```yaml
info:
  name: 'financial_RAG_agent'
  description: 'demo financial rag agent'
profile:
  introduction:  Demo introduction, such as you are a financial RAG agent.
  target: Demo target, such as use financial tools to retrieve and enhance answer performance.
  instruction: |
    demo instruction, xxx, {input}
  llm_model:
    name: 'demo_llm'
    model_name: 'gpt-4o'
action:
  tool:
    - 'financial_data_tool'
metadata:
  type: 'AGENT'
  module: 'agentuniverse.agent.default.rag_agent.rag_agent'
  class: 'RagAgent'
```

**科技类RAG agent设置**
```yaml
info:
  name: 'tech_RAG_agent'
  description: 'demo tech rag agent'
profile:
  introduction:  Demo introduction, such as you are a tech RAG agent.
  target: Demo target, such as use tech tools to retrieve and enhance answer performance.
  instruction: |
    demo instruction, xxx, {input}
  llm_model:
    name: 'demo_llm'
    model_name: 'gpt-4o'
action:
  tool:
    - 'tech_data_tool'
metadata:
  type: 'AGENT'
  module: 'agentuniverse.agent.default.rag_agent.rag_agent'
  class: 'RagAgent'
```

您可以在本文的[了解更多已有的智能体](#了解更多已有的智能体)部分查询更多的agent元信息并了解他们的作用。

# 如何使用智能体
在agentUniverse中所有agent实体都由全局agent管理器进行管理。在框架执行的任何过程中，如果您需要使用智能体，均可以通过agent管理器实现。同时通过框架的服务化能力，您可以将agent快速服务化并通过标准的http或rpc协议对其进行网络调用。

## 方案一: 使用agent管理器
通过agent管理器中的`get_instance_obj('agent_name_xxx')` 方法可以获取对应名称的agent实例，同时通过agent自身的`run(input='xxx')`方法使用agent，下面的test类中的`test_demo_agent(self)`方法就是使用该方式对于agent进行了调试。
```python
import unittest

from agentuniverse.agent.agent import Agent
from agentuniverse.agent.agent_manager import AgentManager
from agentuniverse.agent.output_object import OutputObject
from agentuniverse.base.agentuniverse import AgentUniverse


class DemoAgentTest(unittest.TestCase):
    """
    Test cases for the demo agent
    """

    def setUp(self) -> None:
        AgentUniverse().start(config_path='../../config/config.toml')

    def test_demo_agent(self):
        """Test demo agent."""
        instance: Agent = AgentManager().get_instance_obj('demo_agent')
        output_object: OutputObject = instance.run(input='英伟达股票大涨原因是什么？')
        print(output_object.get_data('output'))

if __name__ == '__main__':
    unittest.main()
```

## 方案二: 使用agent_serve服务化能力
agentUniverse提供多套标准的web serve能力, 同时提供标准的http与rpc协议。您可以进一步关注文档[服务注册与使用](../../技术组件/服务化/服务注册与使用.md)与[Web_Server](../../技术组件/服务化/Web_Server.md)部分。

# 了解更多已有的智能体
框架提供的智能体在`agentuniverse.agent.default`包路径下，您可以进一步查看对应代码或在我们的扩展组件介绍部分进一步了解他们。

# 总结
至此您已经掌握了智能体创建与使用的全部内容，让我们用这些内容去定制专属agent吧。