AgentScope扩展开发:自定义格式化器

AgentScope扩展开发:自定义格式化器

【免费下载链接】agentscope 【免费下载链接】agentscope 项目地址: https://gitcode.com/GitHub_Trending/ag/agentscope

引言:为什么需要自定义格式化器?

在AI代理开发中,格式化器(Formatter)扮演着至关重要的角色。它负责将AgentScope的消息对象转换为特定LLM API所需的格式。虽然AgentScope提供了多种内置格式化器支持主流API提供商,但在实际开发中,你可能会遇到以下场景:

  • 需要对接自定义或私有化的LLM服务
  • 特定的消息格式要求或协议约束
  • 特殊的上下文管理需求
  • 性能优化或定制化处理逻辑

本文将深入探讨如何在AgentScope中开发自定义格式化器,从基础概念到高级实现,帮助你掌握这一核心扩展能力。

格式化器架构深度解析

核心基类结构

AgentScope的格式化器系统建立在两个核心基类之上:

mermaid

消息处理流程

mermaid

实战:构建自定义格式化器

基础自定义格式化器实现

让我们从最简单的场景开始,创建一个支持自定义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中开发自定义格式化器。以下是关键总结:

核心要点

  1. 基类选择:根据需求选择FormatterBaseTruncatedFormatterBase
  2. 消息处理:正确处理各种消息块类型(文本、图像、音频、工具等)
  3. 异步优化:利用异步处理提高性能
  4. 缓存策略:实现合适的缓存机制减少重复计算
  5. 错误处理:健壮的错误处理和边界情况处理

性能优化建议

优化策略适用场景实现复杂度效果
异步处理大量消息或复杂处理中等
缓存机制重复消息模式中高
智能截断长上下文场景
并行处理多核环境很高

扩展思路

  1. 支持更多模态:视频、3D模型、传感器数据等
  2. 协议适配:gRPC、WebSocket等不同通信协议
  3. 压缩优化:消息压缩和序列化优化
  4. 监控集成:性能监控和日志记录

自定义格式化器是AgentScope扩展开发中的重要组成部分,掌握这项技能将帮助你更好地适应各种复杂的AI代理开发场景。通过本文的指导和示例代码,你应该能够快速上手并开发出满足特定需求的高质量格式化器。

【免费下载链接】agentscope 【免费下载链接】agentscope 项目地址: https://gitcode.com/GitHub_Trending/ag/agentscope

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值