AgentScope扩展开发:自定义格式化器
【免费下载链接】agentscope 项目地址: https://gitcode.com/GitHub_Trending/ag/agentscope
引言:为什么需要自定义格式化器?
在AI代理开发中,格式化器(Formatter)扮演着至关重要的角色。它负责将AgentScope的消息对象转换为特定LLM API所需的格式。虽然AgentScope提供了多种内置格式化器支持主流API提供商,但在实际开发中,你可能会遇到以下场景:
- 需要对接自定义或私有化的LLM服务
- 特定的消息格式要求或协议约束
- 特殊的上下文管理需求
- 性能优化或定制化处理逻辑
本文将深入探讨如何在AgentScope中开发自定义格式化器,从基础概念到高级实现,帮助你掌握这一核心扩展能力。
格式化器架构深度解析
核心基类结构
AgentScope的格式化器系统建立在两个核心基类之上:
消息处理流程
实战:构建自定义格式化器
基础自定义格式化器实现
让我们从最简单的场景开始,创建一个支持自定义JSON格式的格式化器:
from typing import Any
from agentscope.formatter import FormatterBase
from agentscope.message import Msg
class CustomJSONFormatter(FormatterBase):
"""自定义JSON格式格式化器"""
async def format(self, msgs: list[Msg]) -> list[dict[str, Any]]:
"""将消息转换为自定义JSON格式"""
self.assert_list_of_msgs(msgs)
formatted_messages = []
for msg in msgs:
# 构建自定义格式的消息体
formatted_msg = {
"role": msg.role,
"name": msg.name or "unknown",
"timestamp": msg.timestamp.isoformat() if msg.timestamp else None,
"content": self._extract_content(msg),
"metadata": msg.metadata or {}
}
formatted_messages.append(formatted_msg)
return formatted_messages
def _extract_content(self, msg: Msg) -> str:
"""从消息块中提取文本内容"""
content_blocks = msg.get_content_blocks()
text_content = []
for block in content_blocks:
if block.get("type") == "text":
text_content.append(block.get("text", ""))
return "\n".join(text_content) if text_content else ""
支持截断的高级格式化器
对于需要处理长上下文的场景,我们可以继承TruncatedFormatterBase:
from typing import Any
from agentscope.formatter import TruncatedFormatterBase
from agentscope.message import Msg
from agentscope.token import TokenCounterBase
class AdvancedCustomFormatter(TruncatedFormatterBase):
"""支持截断功能的自定义格式化器"""
def __init__(
self,
token_counter: TokenCounterBase | None = None,
max_tokens: int | None = None,
custom_config: dict = None
) -> None:
super().__init__(token_counter, max_tokens)
self.custom_config = custom_config or {}
async def _format_agent_message(
self,
msgs: list[Msg],
is_first: bool = True
) -> list[dict[str, Any]]:
"""格式化代理消息"""
formatted_messages = []
for msg in msgs:
formatted_msg = await self._format_single_message(msg)
if formatted_msg:
formatted_messages.append(formatted_msg)
return formatted_messages
async def _format_tool_sequence(
self,
msgs: list[Msg]
) -> list[dict[str, Any]]:
"""格式化工具序列"""
return await self._format_agent_message(msgs, is_first=False)
async def _format_single_message(self, msg: Msg) -> dict[str, Any]:
"""格式化单个消息"""
return {
"type": "message",
"role": msg.role,
"agent": msg.name,
"content": self._process_content_blocks(msg),
"tools": self._extract_tool_info(msg)
}
def _process_content_blocks(self, msg: Msg) -> list[dict]:
"""处理消息内容块"""
processed_blocks = []
for block in msg.get_content_blocks():
block_type = block.get("type")
if block_type == "text":
processed_blocks.append({
"type": "text",
"content": block.get("text", "")
})
elif block_type == "image":
processed_blocks.append({
"type": "image",
"source": block.get("source", {})
})
# 可以继续添加对其他块类型的支持
return processed_blocks
def _extract_tool_info(self, msg: Msg) -> list[dict]:
"""提取工具使用信息"""
tool_info = []
for block in msg.get_content_blocks():
if block.get("type") == "tool_use":
tool_info.append({
"tool_name": block.get("name"),
"tool_id": block.get("id"),
"parameters": block.get("input", {})
})
return tool_info
多模态消息处理实战
现代LLM往往支持多模态输入,下面展示如何处理包含图像和音频的消息:
import base64
from typing import Any
from agentscope.formatter import TruncatedFormatterBase
from agentscope.message import Msg, ImageBlock, AudioBlock
class MultimodalCustomFormatter(TruncatedFormatterBase):
"""支持多模态消息的自定义格式化器"""
async def _format_agent_message(
self,
msgs: list[Msg],
is_first: bool = True
) -> list[dict[str, Any]]:
"""处理包含多模态内容的代理消息"""
formatted_content = []
for msg in msgs:
message_content = await self._process_multimodal_content(msg)
if message_content:
formatted_msg = {
"role": msg.role,
"name": msg.name,
"content": message_content,
"format": "multimodal"
}
formatted_content.append(formatted_msg)
return formatted_content
async def _process_multimodal_content(self, msg: Msg) -> list[dict]:
"""处理多模态内容块"""
content_blocks = []
for block in msg.get_content_blocks():
block_type = block.get("type")
if block_type == "text":
content_blocks.append({
"type": "text",
"text": block.get("text", "")
})
elif block_type == "image":
image_data = await self._process_image_block(block)
content_blocks.append(image_data)
elif block_type == "audio":
audio_data = await self._process_audio_block(block)
content_blocks.append(audio_data)
return content_blocks
async def _process_image_block(self, block: dict) -> dict:
"""处理图像块"""
source = block.get("source", {})
source_type = source.get("type")
if source_type == "url":
return {
"type": "image_url",
"image_url": {"url": source["url"]}
}
elif source_type == "base64":
return {
"type": "image",
"image": f"data:{source['media_type']};base64,{source['data']}"
}
else:
return {"type": "image", "error": "unsupported_source_type"}
async def _process_audio_block(self, block: dict) -> dict:
"""处理音频块"""
source = block.get("source", {})
source_type = source.get("type")
if source_type == "url":
return {
"type": "audio_url",
"audio_url": source["url"]
}
elif source_type == "base64":
return {
"type": "audio",
"audio": f"data:{source['media_type']};base64,{source['data']}"
}
else:
return {"type": "audio", "error": "unsupported_source_type"}
高级特性:自定义截断策略
默认的FIFO截断策略可能不适用于所有场景,我们可以实现更智能的截断逻辑:
from typing import Any
from agentscope.formatter import TruncatedFormatterBase
from agentscope.message import Msg
class SmartTruncationFormatter(TruncatedFormatterBase):
"""智能截断格式化器"""
async def _truncate(self, msgs: list[Msg]) -> list[Msg]:
"""实现智能截断策略"""
if len(msgs) <= 1:
return msgs
# 保留系统消息
system_msg = msgs[0] if msgs[0].role == "system" else None
other_msgs = msgs[1:] if system_msg else msgs
# 智能截断策略:保留最近的消息和重要的工具调用
important_indices = self._identify_important_messages(other_msgs)
truncated_msgs = [other_msgs[i] for i in important_indices]
# 如果还有空间,添加一些历史消息
remaining_capacity = len(other_msgs) - len(truncated_msgs)
if remaining_capacity > 0:
recent_msgs = other_msgs[-remaining_capacity:]
truncated_msgs.extend(recent_msgs)
# 重新排序并添加系统消息
truncated_msgs.sort(key=lambda x: x.timestamp or 0)
if system_msg:
truncated_msgs.insert(0, system_msg)
return truncated_msgs
def _identify_important_messages(self, msgs: list[Msg]) -> list[int]:
"""识别重要消息"""
important_indices = []
for i, msg in enumerate(msgs):
# 包含工具调用的消息通常很重要
if any(block.get("type") == "tool_use"
for block in msg.get_content_blocks()):
important_indices.append(i)
# 系统消息或包含关键信息的消息
elif msg.role == "system" or self._contains_keywords(msg):
important_indices.append(i)
return important_indices
def _contains_keywords(self, msg: Msg) -> bool:
"""检查消息是否包含关键词"""
keywords = ["important", "critical", "essential", "must", "required"]
content = " ".join(
block.get("text", "")
for block in msg.get_content_blocks()
if block.get("type") == "text"
).lower()
return any(keyword in content for keyword in keywords)
性能优化与最佳实践
异步处理优化
import asyncio
from typing import Any
from agentscope.formatter import TruncatedFormatterBase
from agentscope.message import Msg
class AsyncOptimizedFormatter(TruncatedFormatterBase):
"""异步优化格式化器"""
async def _format_agent_message(
self,
msgs: list[Msg],
is_first: bool = True
) -> list[dict[str, Any]]:
"""并行处理多个消息"""
# 创建异步任务列表
tasks = [self._format_single_message_async(msg) for msg in msgs]
# 并行执行所有任务
formatted_messages = await asyncio.gather(*tasks)
# 过滤掉空结果
return [msg for msg in formatted_messages if msg is not None]
async def _format_single_message_async(self, msg: Msg) -> dict[str, Any]:
"""异步格式化单个消息"""
# 模拟一些异步操作
await asyncio.sleep(0.001) # 短暂的延迟
content_blocks = []
for block in msg.get_content_blocks():
processed_block = await self._process_block_async(block)
if processed_block:
content_blocks.append(processed_block)
return {
"role": msg.role,
"name": msg.name,
"content": content_blocks,
"timestamp": msg.timestamp.isoformat() if msg.timestamp else None
}
async def _process_block_async(self, block: dict) -> dict:
"""异步处理内容块"""
block_type = block.get("type")
if block_type == "text":
return {"type": "text", "text": block.get("text", "")}
elif block_type == "image":
return await self._process_image_async(block)
# 可以添加其他块类型的处理
return None
async def _process_image_async(self, block: dict) -> dict:
"""异步处理图像块"""
# 这里可以添加图像预处理逻辑
source = block.get("source", {})
return {
"type": "image",
"source_type": source.get("type"),
"processed": True
}
缓存策略实现
from typing import Any
from functools import lru_cache
from agentscope.formatter import FormatterBase
from agentscope.message import Msg
class CachedFormatter(FormatterBase):
"""带缓存功能的格式化器"""
def __init__(self):
super().__init__()
self._cache = {}
async def format(self, msgs: list[Msg]) -> list[dict[str, Any]]:
"""带缓存的格式化方法"""
cache_key = self._generate_cache_key(msgs)
if cache_key in self._cache:
return self._cache[cache_key]
formatted = await self._format_uncached(msgs)
self._cache[cache_key] = formatted
# 简单的缓存清理策略
if len(self._cache) > 1000:
self._cleanup_cache()
return formatted
def _generate_cache_key(self, msgs: list[Msg]) -> str:
"""生成缓存键"""
key_parts = []
for msg in msgs:
key_parts.append(f"{msg.role}:{msg.name}:{msg.timestamp}")
for block in msg.get_content_blocks():
if block.get("type") == "text":
key_parts.append(block.get("text", "")[:50])
return hash("".join(key_parts))
async def _format_uncached(self, msgs: list[Msg]) -> list[dict[str, Any]]:
"""实际的格式化逻辑"""
self.assert_list_of_msgs(msgs)
formatted_messages = []
for msg in msgs:
formatted_msg = {
"role": msg.role,
"name": msg.name,
"content": self._extract_content(msg),
"metadata": msg.metadata or {}
}
formatted_messages.append(formatted_msg)
return formatted_messages
def _cleanup_cache(self):
"""清理缓存"""
# 简单的LRU缓存清理
if len(self._cache) > 1000:
# 保留最近500个条目
keys = list(self._cache.keys())
for key in keys[:-500]:
del self._cache[key]
测试与验证
单元测试示例
import pytest
import asyncio
from agentscope.message import Msg, TextBlock
from your_custom_formatter import CustomJSONFormatter
class TestCustomFormatter:
"""自定义格式化器测试类"""
@pytest.mark.asyncio
async def test_basic_formatting(self):
"""测试基础格式化功能"""
formatter = CustomJSONFormatter()
# 创建测试消息
test_msgs = [
Msg("user", "Hello, world!", "user"),
Msg("assistant", "Hi there!", "assistant")
]
# 执行格式化
result = await formatter.format(test_msgs)
# 验证结果
assert len(result) == 2
assert result[0]["role"] == "user"
assert result[1]["role"] == "assistant"
assert "Hello, world!" in result[0]["content"]
@pytest.mark.asyncio
async def test_empty_messages(self):
"""测试空消息列表"""
formatter = CustomJSONFormatter()
result = await formatter.format([])
assert result == []
@pytest.mark.asyncio
async def test_invalid_input(self):
"""测试无效输入"""
formatter = CustomJSONFormatter()
with pytest.raises(TypeError):
await formatter.format("not a list")
with pytest.raises(TypeError):
await formatter.format([{"not": "a msg object"}])
集成测试示例
import asyncio
import json
from agentscope.message import Msg, TextBlock, ToolUseBlock, ToolResultBlock
from your_custom_formatter import AdvancedCustomFormatter
async def integration_test():
"""集成测试:模拟真实场景"""
formatter = AdvancedCustomFormatter()
# 创建复杂的消息序列
messages = [
Msg("system", "You are a helpful assistant", "system"),
Msg("user", "What's the weather like today?", "user"),
Msg("assistant",
[ToolUseBlock(type="tool_use", name="get_weather", id="1", input={})],
"assistant"),
Msg("system",
[ToolResultBlock(type="tool_result", name="get_weather", id="1",
output=[TextBlock(type="text", text="Sunny, 25°C")])],
"system"),
Msg("assistant", "It's sunny and 25°C today. Perfect weather!", "assistant")
]
# 执行格式化
result = await formatter.format(messages)
print("格式化结果:")
print(json.dumps(result, indent=2, ensure_ascii=False))
# 验证关键特性
assert len(result) > 0
assert any("tool" in str(msg) for msg in result)
assert any("weather" in str(msg) for msg in result)
if __name__ == "__main__":
asyncio.run(integration_test())
总结与最佳实践
通过本文的深入探讨,我们了解了如何在AgentScope中开发自定义格式化器。以下是关键总结:
核心要点
- 基类选择:根据需求选择
FormatterBase或TruncatedFormatterBase - 消息处理:正确处理各种消息块类型(文本、图像、音频、工具等)
- 异步优化:利用异步处理提高性能
- 缓存策略:实现合适的缓存机制减少重复计算
- 错误处理:健壮的错误处理和边界情况处理
性能优化建议
| 优化策略 | 适用场景 | 实现复杂度 | 效果 |
|---|---|---|---|
| 异步处理 | 大量消息或复杂处理 | 中等 | 高 |
| 缓存机制 | 重复消息模式 | 低 | 中高 |
| 智能截断 | 长上下文场景 | 高 | 高 |
| 并行处理 | 多核环境 | 高 | 很高 |
扩展思路
- 支持更多模态:视频、3D模型、传感器数据等
- 协议适配:gRPC、WebSocket等不同通信协议
- 压缩优化:消息压缩和序列化优化
- 监控集成:性能监控和日志记录
自定义格式化器是AgentScope扩展开发中的重要组成部分,掌握这项技能将帮助你更好地适应各种复杂的AI代理开发场景。通过本文的指导和示例代码,你应该能够快速上手并开发出满足特定需求的高质量格式化器。
【免费下载链接】agentscope 项目地址: https://gitcode.com/GitHub_Trending/ag/agentscope
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



