core/agent: remove ChatClient protocol and ResponsesChatClient wrapper

This commit is contained in:
2026-07-29 00:36:19 +08:00
parent 6eaadd6ebf
commit 4562eaa1cb
3 changed files with 0 additions and 175 deletions
-3
View File
@@ -1,5 +1,4 @@
from .chat import ChatAgent as ChatAgent
from .client import ChatClient as ChatClient
from .compact import build_compacted_seed_messages as build_compacted_seed_messages
from .compact import summarize_messages as summarize_messages
from .events import AgentEvent as AgentEvent
@@ -14,8 +13,6 @@ from .events import ToolResultEvent as ToolResultEvent
from .events import UsageEvent as UsageEvent
from .loop import DEFAULT_MAX_ROUNDS as DEFAULT_MAX_ROUNDS
from .loop import run_chat_loop as run_chat_loop
from .responses_client import ResponsesChatClient as ResponsesChatClient
from .responses_client import wrap_openai_for_agent as wrap_openai_for_agent
from .tools import DangerClassifier as DangerClassifier
from .tools import ToolConfirmHook as ToolConfirmHook
from .tools import ToolDefinition as ToolDefinition
-35
View File
@@ -1,35 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Literal, Protocol, overload
if TYPE_CHECKING:
from collections.abc import AsyncIterator
from plyngent.lmproto.openai_compatible.model import (
ChatCompletionChunk,
ChatCompletionResponse,
ChatCompletionsParam,
)
class ChatClient(Protocol):
"""Structural protocol for OpenAI-compatible chat completion clients.
When ``stream=True``, implementations may return an async iterator from an
async method (``await client.chat_completions(..., stream=True)`` then
``async for chunk in stream``). That library shape is intentional.
"""
@overload
async def chat_completions(
self, param: ChatCompletionsParam, *, stream: Literal[False] = False
) -> ChatCompletionResponse: ...
@overload
async def chat_completions(
self, param: ChatCompletionsParam, *, stream: Literal[True]
) -> AsyncIterator[ChatCompletionChunk]: ...
async def chat_completions(
self, param: ChatCompletionsParam, *, stream: bool = False
) -> ChatCompletionResponse | AsyncIterator[ChatCompletionChunk]: ...
-137
View File
@@ -1,137 +0,0 @@
"""ChatClient adapter: agent chat loop over OpenAI Responses API."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Literal, overload
from msgspec import UNSET
from plyngent.agent.responses_bridge import (
chat_param_to_responses_kwargs,
finish_reason_chunk,
reasoning_delta_chunk,
response_to_assistant_message,
response_to_chat_completion,
responses_status_to_finish_reason,
text_delta_chunk,
tool_call_chunks_from_response,
usage_chunk_from_response,
)
from plyngent.lmproto.openai.model import ResponsesCreateParam
if TYPE_CHECKING:
from collections.abc import AsyncIterator, Sequence
from plyngent.lmproto.openai.client import OpenAIClient
from plyngent.lmproto.openai.model import Response
from plyngent.lmproto.openai_compatible.model import (
ChatCompletionChunk,
ChatCompletionResponse,
ChatCompletionsParam,
)
class ResponsesChatClient:
"""Present OpenAI Responses as :class:`~plyngent.agent.client.ChatClient`.
History and tool results remain chat-completions-shaped; only the HTTP call
uses ``POST /responses``. Optional *provider_tools* are hosted tools merged
into the request (not local registry handlers).
"""
_client: OpenAIClient
_provider_tools: list[dict[str, Any]]
def __init__(
self,
client: OpenAIClient,
*,
provider_tools: Sequence[dict[str, Any]] | None = None,
) -> None:
self._client = client
self._provider_tools = [dict(t) for t in provider_tools] if provider_tools else []
async def models(self) -> list[str]:
return await self._client.models()
@overload
async def chat_completions(
self, param: ChatCompletionsParam, *, stream: Literal[False] = False
) -> ChatCompletionResponse: ...
@overload
async def chat_completions(
self, param: ChatCompletionsParam, *, stream: Literal[True]
) -> AsyncIterator[ChatCompletionChunk]: ...
async def chat_completions(
self, param: ChatCompletionsParam, *, stream: bool = False
) -> ChatCompletionResponse | AsyncIterator[ChatCompletionChunk]:
kwargs = chat_param_to_responses_kwargs(
param,
provider_tools=self._provider_tools or None,
)
create = ResponsesCreateParam(**kwargs)
if stream:
return self._stream_as_chat_chunks(create, model=param.model)
response = await self._client.responses(create, stream=False)
return response_to_chat_completion(response)
async def _stream_as_chat_chunks(
self,
create: ResponsesCreateParam,
*,
model: str,
) -> AsyncIterator[ChatCompletionChunk]:
stream = await self._client.responses(create, stream=True)
final: Response | None = None
async for event in stream:
etype = event.type
if etype == "response.output_text.delta" and isinstance(event.delta, str) and event.delta:
yield text_delta_chunk(model=model, content=event.delta)
continue
if (
etype
in {
"response.reasoning_summary_text.delta",
"response.reasoning_text.delta",
}
and isinstance(event.delta, str)
and event.delta
):
yield reasoning_delta_chunk(model=model, content=event.delta)
continue
if etype == "response.completed" and event.response is not UNSET:
# Decode full response for tools + usage (field is dict | Unset).
import msgspec
from plyngent.lmproto.openai.model import Response as ResponseModel
try:
final = msgspec.convert(event.response, ResponseModel)
except TypeError, ValueError, msgspec.ValidationError:
final = None
if final is None:
# Stream ended without response.completed — signal missing terminal.
# Loop will treat empty + no finish_reason as a glitch.
return
assistant = response_to_assistant_message(final)
has_tools = assistant.tool_calls is not UNSET and bool(assistant.tool_calls)
finish = responses_status_to_finish_reason(final, has_tool_calls=has_tools)
for chunk in tool_call_chunks_from_response(final, model=model):
yield chunk
yield finish_reason_chunk(model=model, finish_reason=finish)
usage = usage_chunk_from_response(final, model=model)
if usage is not None:
yield usage
def wrap_openai_for_agent(
client: OpenAIClient,
*,
provider_tools: Sequence[dict[str, Any]] | None = None,
) -> ResponsesChatClient:
"""Wrap a platform OpenAI client so the agent uses Responses by default."""
return ResponsesChatClient(client, provider_tools=provider_tools)