Agent Loop docs
Implement a custom provider
Implement the LLMClient protocol with name, model, and
complete(request: ModelRequest) -> ModelResponse. This small client echoes the latest user
message to show the interface without provider-specific wire formats:
from agentloop import Agent
from agentloop.provider import ModelRequest, ModelResponse, StopReason, Usage
from agentloop.transcript import TextPart
class EchoClient:
name = "echo"
model = "echo-v1"
def complete(self, request: ModelRequest) -> ModelResponse:
last = request.messages[-1]
text = " ".join(
part.text for part in last.content if isinstance(part, TextPart)
)
return ModelResponse(
content=(TextPart(text=f"Echo: {text}"),),
stop_reason=StopReason.END_TURN,
usage=Usage(),
model=self.model,
provider=self.name,
)
agent = Agent([], client=EchoClient())
print(agent.run("hello"))
Output:
Echo: hello
Agent calls complete() with a provider-independent ModelRequest. The client returns
canonical content, a normalized stop reason, and usage. For a real provider, translate its
wire request and response inside the client, convert vendor failures into the
ProviderError hierarchy, and keep vendor types out of the public
method signature. Map unfamiliar stop reasons to StopReason.UNKNOWN and retain the
original value in raw_stop_reason. A response containing a ToolCallPart must use
StopReason.TOOL_USE unless it was cut off at StopReason.MAX_TOKENS.
Optional capabilities
- Pause. Return
StopReason.PAUSEDwhen the provider stopped partway through a turn it is running itself. The loop sends the unfinished turn back without a new user message and joins the continuation to the same assistant message. - Hosted tools. Expose a
hosted_toolsattribute (afrozensetof kinds such as"web_search") to acceptHostedTooldeclarations. Without it the client supports none, andAgentrejects them at construction. Seesupported_hosted_tools. - Exact replay. Set
ModelResponse.originto anOrigin(provider=..., api=..., model=..., raw=...)holding your wire form of the turn. When a later request contains a message whose origin matches your provider and API, replay its raw blocks; otherwise translate its canonical parts. - Empty turns.
ModelResponse.contentmay be empty only whenstop_reasonisStopReason.MAX_TOKENS; the loop discards that turn and can retry with a bigger budget. Convert other empty provider responses toResponseInvalid.
Agent borrows a client passed with client=. If your client owns a connection, close it in
your application code.
See the provider API.