AgentScope 自定义模型集成:拆解基类契约与 4 个真实翻车点
【免费下载链接】agentscopeBuild and run agents you can see, understand and trust.项目地址: https://gitcode.com/GitHub_Trending/ag/agentscope
上周帮同事把一个企业内部 LLM 网关接进 AgentScope,第一版代码跑完直接抛AttributeError: 'str' object has no attribute 'content'。他盯着报错愣了很久——他明明已经"继承基类、实现了调用方法"了。
说白了,卡住的不是"怎么写调用",而是 AgentScope 整个运行时(ReAct 循环、中间件、上下文压缩)只认一种货币:ChatResponse。你在 _model_base.py 里看到基类ChatModelBase提供的__call__,它干的是包裹重试、累积流式块、处理取消这些脏活;真正要你写的只有一个抽象方法_call_api,签名是(model_name, messages, tools, tool_choice, **kwargs),返回ChatResponse或一个异步生成器(src/agentscope/model/_base.py)。
基类到底替你背了哪些锅
ChatResponse的契约在 _model_response.py 里:content是块列表(TextBlock/ToolCallBlock/ThinkingBlock),is_last标记是否完整,外加usage和finished_reason。别小看is_last——流式场景下它决定下游能不能拿到最终结果。
重试同理:__call__会循环max_retries + 1次,但只捕获你在_get_retryable_exceptions()里声明的异常类型,其余直接上抛。默认这个元组是空的,也就是不重试。我踩过的坑是:企业网关 429 限流抛的是自定义异常,不在声明里,第三次重试就没了,线上表现是"偶发失败",极难定位。对照OpenAIChatModel的写法,它声明了APIConnectionError、APITimeoutError、RateLimitError这类(见 src/agentscope/model/_openai_chat/_model.py),SDK 用惰性 import 包在方法里,依赖保持可选——私有网关异常照抄这个模式就行。
工具侧基类还有_validate_tool_choice,在tool_choice指定了具体函数名但tools里没这个名字时抛ValueError。这是给你用的护栏:想跳过它,不如想想是不是 schema 传递链断了。
流式分支怎么写才不会崩
这是翻车重灾区。流式时_call_api返回的生成器里,每个增量块都要is_last=False,基类的_stream()包装会帮你累积内容、吞掉空内容 carrier 块,并在流结束或取消时补一个is_last=True的最终块(取消会打FinishedReason.INTERRUPTED)。你只负责"把上游 delta 翻译成块":
async def _call_api(self, model_name, messages, tools=None, tool_choice=None, **kwargs): formatted = await self.formatter.format(messages) async for delta in self.client.stream(formatted, **kwargs): resp = ChatResponse(content=[], is_last=False) resp.append_text(delta.text) # 文本增量 for tc in delta.tool_calls: # 工具调用增量 resp.append_tool_call(tc.id, tc.name, tc.args) yield resp非流式直接return ChatResponse(content=[...], is_last=True)。增量文本用append_text,别自己拼字符串;工具参数是 JSON 片段,append_tool_call会按块 id 追加,基类累积后才是完整 JSON。stream这个构造参数必须原样透传给上游 API,漏传的后果就是"流式开关形同虚设":stream=True时上游整包返回,首 token 延迟拉满;stream=False时上游发 SSE,你按非流式解析,直接 500。
上线前必查的 4 件事
- 凭证要成对。模型构造函数收的是
CredentialBase子类,比如OpenAICredential。你自己的网关凭证继承它,给type字段一个唯一Literal默认值,再调CredentialFactory.register_credential注册,反序列化和前端表单才会认它(见 src/agentscope/credential/_factory.py)。 - Formatter 别裸写。
FormatterBase.format负责把Msg列表转成 API 要的形状,已有 OpenAI、Ollama 等实现可参考src/agentscope/formatter/;私有协议差异小就子类化,别在_call_api里硬转。 - 模型卡片是 yaml。
list_models读的是子类源码旁_models/目录下的 yaml(ModelCard),前端靠它动态渲染可选模型和参数表单。新模型不挂卡片,等于白接。 - token 估算有默认实现。
count_tokens按"文本字节数 / 4"粗估,私有 tokenizer 差异大再覆盖它,不覆盖也不会挂。
下一步看哪里
想对着真实实现抄骨架,scripts/model_examples/ 里每个 provider 都有xxx_call.py和xxx_multiagent.py两级示例,覆盖流式、工具调用、结构化输出;断言方式对照tests/model_openai_chat_test.py这类单测即可自测。你的私有网关如果是 OpenAI 兼容协议,可能连自定义类都不用写——先用OpenAIChatModel换个base_url试一把,不行再回来拆基类。
【免费下载链接】agentscopeBuild and run agents you can see, understand and trust.项目地址: https://gitcode.com/GitHub_Trending/ag/agentscope
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考