自定义代理¶
简介¶
CustomizeAgent
类提供了一个灵活的框架,用于创建专门的 LLM 驱动的代理。它允许定义具有明确定义的输入、输出、自定义提示模板和可配置解析策略的代理,使其适合快速原型设计和部署特定领域的代理。
主要特性¶
- 无需自定义代码:通过配置而不是编写自定义代理类来创建专门的代理
- 灵活的输入/输出定义:明确定义代理接受的输入和产生的输出
- 可自定义的解析策略:多种解析模式,用于从 LLM 响应中提取结构化数据
- 可重用组件:保存和加载代理定义,以便在项目之间重用
基本用法¶
简单代理¶
创建 CustomizeAgent
的最简单方法是只使用名称、描述和提示:
import os
from dotenv import load_dotenv
from evoagentx.models import OpenAILLMConfig
from evoagentx.agents import CustomizeAgent
load_dotenv()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# 配置 LLM
openai_config = OpenAILLMConfig(model="gpt-4o-mini", openai_key=OPENAI_API_KEY)
# 创建一个简单代理
simple_agent = CustomizeAgent(
name="SimpleAgent",
description="A basic agent that responds to queries",
prompt="Answer the following question: {question}",
llm_config=openai_config,
inputs=[
{"name": "question", "type": "string", "description": "The question to answer"}
]
)
# 执行代理
response = simple_agent(inputs={"question": "What is a language model?"})
print(response.content.content) # 访问原始响应内容
在这个例子中:
1. 由于提示需要输入,我们在 inputs
参数中指定了输入信息(包括其名称、类型和描述)。
2. 此外,当使用 simple_agent(...)
执行代理时,您应该在 inputs
参数中提供所有输入。
执行代理后的输出是一个 Message
对象,其中包含 message.content.content
中的原始 LLM 响应。
Note
在 CustomizeAgent(inputs=[...])
中指定的所有输入名称都应该出现在 prompt
中。否则,将引发错误。
结构化输出¶
CustomizeAgent
最强大的功能之一是能够定义结构化输出。这允许您将非结构化的 LLM 响应转换为更容易以编程方式处理的明确定义的数据结构。
基本结构化输出¶
以下是定义结构化输出的简单示例:
from evoagentx.core.module_utils import extract_code_blocks
# 创建一个具有结构化输出的代码编写代理
code_writer = CustomizeAgent(
name="CodeWriter",
description="Writes Python code based on requirements",
prompt="Write Python code that implements the following requirement: {requirement}",
llm_config=openai_config,
inputs=[
{"name": "requirement", "type": "string", "description": "The coding requirement"}
],
outputs=[
{"name": "code", "type": "string", "description": "The generated Python code"}
],
parse_mode="custom", # 使用自定义解析函数
parse_func=lambda content: {"code": extract_code_blocks(content)[0]} # 提取第一个代码块
)
# 执行代理
message = code_writer(
inputs={"requirement": "Write a function that returns the sum of two numbers"}
)
print(message.content.code) # 直接访问解析后的代码
在这个例子中:
1. 我们在 outputs
参数中定义了一个名为 code
的输出字段。
2. 我们设置 parse_mode="custom"
来使用自定义解析函数。
3. parse_func
从 LLM 响应中提取第一个代码块。
4. 我们可以通过 message.content.code
直接访问解析后的代码。
您也可以通过 message.content.content
访问原始 LLM 响应。
Note
-
如果在
CustomizeAgent
中设置了outputs
参数,代理将尝试根据输出字段名称解析 LLM 响应。如果您不想解析 LLM 响应,则不应设置outputs
参数。可以通过message.content.content
访问原始 LLM 响应。 -
CustomizeAgent 支持不同的解析模式,如
['str', 'json', 'xml', 'title', 'custom']
。有关更多详细信息,请参阅解析模式部分。
多个结构化输出¶
您可以定义多个输出字段来创建更复杂的结构化数据:
# 生成代码和解释的代理
analyzer = CustomizeAgent(
name="CodeAnalyzer",
description="Generates and explains Python code",
prompt="""
Write Python code for: {requirement}
Provide your response in the following format:
## code
[Your code implementation here]
## explanation
[A brief explanation of how the code works]
## complexity
[Time and space complexity analysis]
""",
llm_config=openai_config,
inputs=[
{"name": "requirement", "type": "string", "description": "The coding requirement"}
],
outputs=[
{"name": "code", "type": "string", "description": "The generated Python code"},
{"name": "explanation", "type": "string", "description": "Explanation of the code"},
{"name": "complexity", "type": "string", "description": "Complexity analysis"}
],
parse_mode="title" # 使用默认的标题解析模式
)
# 执行代理
result = analyzer(inputs={"requirement": "Write a binary search algorithm"})
# 分别访问每个结构化输出
print("CODE:")
print(result.content.code)
print("\nEXPLANATION:")
print(result.content.explanation)
print("\nCOMPLEXITY:")
print(result.content.complexity)
解析模式¶
CustomizeAgent 支持不同的方式来解析 LLM 输出:
1. 字符串模式 (parse_mode="str"
)¶
使用原始 LLM 输出作为每个输出字段的值。适用于不需要结构化解析的简单代理。
agent = CustomizeAgent(
name="SimpleAgent",
description="Returns raw output",
prompt="Generate a greeting for {name}",
inputs=[{"name": "name", "type": "string", "description": "The name to greet"}],
outputs=[{"name": "greeting", "type": "string", "description": "The generated greeting"}],
parse_mode="str",
# 其他参数...
)
执行代理后,您可以通过 message.content.content
或 message.content.greeting
访问原始 LLM 响应。
2. 标题模式 (parse_mode="title"
,默认)¶
提取与输出字段名称匹配的标题之间的内容。这是默认的解析模式。
agent = CustomizeAgent(
name="ReportGenerator",
description="Generates a structured report",
prompt="Create a report about {topic}",
outputs=[
{"name": "summary", "type": "string", "description": "Brief summary"},
{"name": "analysis", "type": "string", "description": "Detailed analysis"}
],
# 默认标题模式是 "## {title}"
title_format="### {title}", # 可选:自定义标题格式
# 其他参数...
)
使用此配置,应该指示 LLM 将其响应格式化为:
Note
LLM 输出的章节标题应该与输出字段名称完全相同。否则,解析将失败。例如,在上面的例子中,如果 LLM 输出 ### Analysis
,这与输出字段名称 analysis
不同,解析将失败。
3. JSON 模式 (parse_mode="json"
)¶
解析 LLM 输出的 JSON 字符串。JSON 字符串的键应该与输出字段名称完全相同。
agent = CustomizeAgent(
name="DataExtractor",
description="Extracts structured data",
prompt="Extract key information from this text: {text}",
inputs=[
{"name": "text", "type": "string", "description": "The text to extract information from"}
],
outputs=[
{"name": "people", "type": "string", "description": "Names of people mentioned"},
{"name": "places", "type": "string", "description": "Locations mentioned"},
{"name": "dates", "type": "string", "description": "Dates mentioned"}
],
parse_mode="json",
# 其他参数...
)
使用此模式时,LLM 应该输出一个有效的 JSON 字符串,其键与输出字段名称匹配。例如,您应该指示 LLM 输出:
如果 LLM 响应中有多个 JSON 字符串,将只使用第一个。
4. XML 模式 (parse_mode="xml"
)¶
解析 LLM 输出的 XML 字符串。XML 字符串的键应该与输出字段名称完全相同。
agent = CustomizeAgent(
name="DataExtractor",
description="Extracts structured data",
prompt="Extract key information from this text: {text}",
inputs=[
{"name": "text", "type": "string", "description": "The text to extract information from"}
],
outputs=[
{"name": "people", "type": "string", "description": "Names of people mentioned"},
],
parse_mode="xml",
# 其他参数...
)
使用此模式时,LLM 应该生成包含与输出字段名称匹配的 XML 标签的文本。例如,您应该指示 LLM 输出:
如果 LLM 输出包含多个具有相同名称的 XML 标签,将只使用第一个。
5. 自定义解析 (parse_mode="custom"
)¶
为了获得最大的灵活性,您可以定义自定义解析函数:
from evoagentx.core.registry import register_parse_function
@register_parse_function # 注册函数以便序列化
def extract_python_code(content: str) -> dict:
"""从 LLM 响应中提取 Python 代码"""
code_blocks = extract_code_blocks(content)
return {"code": code_blocks[0] if code_blocks else ""}
agent = CustomizeAgent(
name="CodeExplainer",
description="Generates and explains code",
prompt="Write a Python function that {requirement}",
inputs=[
{"name": "requirement", "type": "string", "description": "The requirement to generate code for"}
],
outputs=[
{"name": "code", "type": "string", "description": "The generated code"},
],
parse_mode="custom",
parse_func=extract_python_code,
# 其他参数...
)
Note
-
解析函数应该有一个输入参数
content
,它接收原始 LLM 响应作为输入,并返回一个字典,其键与输出字段名称匹配。 -
建议使用
@register_parse_function
装饰器来注册解析函数以便序列化,这样您就可以保存代理并在以后加载它。
保存和加载代理¶
您可以保存代理定义以便以后重用:
# 保存代理配置。默认情况下,`llm_config` 不会被保存。
code_writer.save_module("./agents/code_writer.json")
# 从文件加载代理(需要再次提供 llm_config)
loaded_agent = CustomizeAgent.from_file(
"./agents/code_writer.json",
llm_config=openai_config
)
高级示例:多步骤代码生成器¶
以下是一个更高级的示例,展示了如何创建一个具有多个结构化输出的专门代码生成代理:
from pydantic import Field
from evoagentx.actions import ActionOutput
from evoagentx.core.registry import register_parse_function
class CodeGeneratorOutput(ActionOutput):
code: str = Field(description="The generated Python code")
documentation: str = Field(description="Documentation for the code")
tests: str = Field(description="Unit tests for the code")
@register_parse_function
def parse_code_documentation_tests(content: str) -> dict:
"""将 LLM 输出解析为代码、文档和测试部分"""
sections = content.split("## ")
result = {"code": "", "documentation": "", "tests": ""}
for section in sections:
if not section.strip():
continue
lines = section.strip().split("\n")
section_name = lines[0].lower()
section_content = "\n".join(lines[1:]).strip()
if "code" in section_name:
# 从代码块中提取代码
code_blocks = extract_code_blocks(section_content)
result["code"] = code_blocks[0] if code_blocks else section_content
elif "documentation" in section_name:
result["documentation"] = section_content
elif "test" in section_name:
# 如果存在,从代码块中提取代码
code_blocks = extract_code_blocks(section_content)
result["tests"] = code_blocks[0] if code_blocks else section_content
return result
# 创建高级代码生成器代理
advanced_generator = CustomizeAgent(
name="AdvancedCodeGenerator",
description="Generates complete code packages with documentation and tests",
prompt="""
Create a complete implementation based on this requirement:
{requirement}
Provide your response in the following format:
## Code
[Include the Python code implementation here]
## Documentation
[Include clear documentation explaining the code]
## Tests
[Include unit tests that verify the code works correctly]
""",
llm_config=openai_config,
inputs=[
{"name": "requirement", "type": "string", "description": "The coding requirement"}
],
outputs=[
{"name": "code", "type": "string", "description": "The generated Python code"},
{"name": "documentation", "type": "string", "description": "Documentation for the code"},
{"name": "tests", "type": "string", "description": "Unit tests for the code"}
],
output_parser=CodeGeneratorOutput,
parse_mode="custom",
parse_func=parse_code_documentation_tests,
system_prompt="You are an expert Python developer specialized in writing clean, efficient code with comprehensive documentation and tests."
)
# 执行代理
result = advanced_generator(
inputs={
"requirement": "Create a function to validate if a string is a valid email address"
}
)
# 访问结构化输出
print("CODE:")
print(result.content.code)
print("\nDOCUMENTATION:")
print(result.content.documentation)
print("\nTESTS:")
print(result.content.tests)
这个高级示例展示了如何创建一个专门的代理,它可以从单个 LLM 调用中产生多个结构化输出,提供包含实现、文档和测试的完整代码包。