协议(Protocol)

Protocol 模块提供了抽象的 LLM API 客户端接口及其 OpenAI 兼容实现。它将 Twinkle 的 Trajectory / SamplingParams 数据类型与外部 LLM 推理服务连接起来。

API 基类

from abc import ABC, abstractmethod
from twinkle.data_format import Trajectory
from twinkle.data_format.message import Message
from twinkle.data_format.sampling import SamplingParams

class API(ABC):
    """抽象 LLM API 客户端:Trajectory + SamplingParams -> 助手 Message"""

    @abstractmethod
    def __call__(
        self,
        trajectory: Trajectory,
        sampling_params: SamplingParams,
        **kwargs,
    ) -> Union[Message, List[Message]]:
        raise NotImplementedError()

API 类定义了一个简单的契约:给定对话轨迹和采样参数,返回一条或多条助手消息。

OpenAI

OpenAI 是内置实现,兼容任何支持 /v1/chat/completions 协议的端点(OpenAI、Azure OpenAI、vLLM、SGLang、Ollama 等)。

from twinkle_agentic.protocol.openai import OpenAI

api = OpenAI(
    model='qwen3.5-32b',
    base_url='http://localhost:8000/v1',
    api_key='EMPTY',
)

参数

参数 类型 说明
model str API 请求中传递的模型名称。
api_key str API 密钥。默认使用 OPENAI_API_KEY 环境变量。
base_url str API 端点的基础 URL(如 http://localhost:8000/v1)。
client_kwargs Dict 转发给 openai.OpenAI 客户端构造函数的额外关键字参数。

使用方法

from twinkle.data_format import Trajectory
from twinkle.data_format.sampling import SamplingParams

trajectory = {
    'messages': [
        {'role': 'user', 'content': '法国的首都是什么?'},
    ]
}

sp = SamplingParams(temperature=0.7, max_tokens=512)
reply = api(trajectory, sp)
# reply 是一个 Message 字典:{'role': 'assistant', 'content': '...'}

特性

  • 工具调用:自动将 trajectory['tools'] 映射到 API 请求,并解析响应中的结构化 tool_calls

  • 推理内容:保留支持推理的模型返回的 reasoning_content(如 o1 风格推理)。

  • 完成原因:在返回消息中暴露 finish_reason,供多轮驱动器检测长度截断。

  • 多样本:当 sampling_params.num_samples > 1 时,返回消息列表(每个 choice 一条)。

自定义 API 客户端

要集成非 OpenAI API,请继承 API

from twinkle_agentic.protocol.base import API

class MyCustomAPI(API):

    def __call__(self, trajectory, sampling_params, **kwargs):
        # 调用自定义端点
        response = my_llm_client.chat(
            messages=trajectory['messages'],
            temperature=sampling_params.temperature,
        )
        return {'role': 'assistant', 'content': response.text}