Last reviewed: 2026-08-11
Direct answer
Connect PydanticAI to CometAPI by creating an OpenAI-compatible AsyncOpenAI client, passing that client to OpenAIProvider, and using OpenAIResponsesModel for the model instance. Then give the agent a small deterministic function tool and declare a Pydantic output_type for the final decision.
The refetched CometAPI GPT-5.1 page lists both the Responses and Chat Completions endpoint families and describes GPT-5.1 as compatible with function calling in common orchestration frameworks. PydanticAI documents both the custom OpenAI client path and structured output through output_type. This guide uses the Responses model path so the provider, endpoint family, tool contract, and output validation all stay explicit.
Install the OpenAI extras for PydanticAI, then configure COMETAPI_BASE_URL with the v1 base URL documented on the CometAPI GPT-5.1 API page
. Provide COMETAPI_API_KEY through your process-level secret manager. The program below reads configuration from its environment and never prints it.
from __future__ import annotations
import os
from typing import Literal
from openai import AsyncOpenAI
from pydantic import BaseModel
from pydantic_ai import Agent
from pydantic_ai.models.openai import OpenAIResponsesModel
from pydantic_ai.providers.openai import OpenAIProvider
class TicketDecision(BaseModel):
ticket_id: str
action: Literal["respond", "escalate", "not_found"]
reason: str
TICKETS = {
"TKT-1042": {
"status": "open",
"priority": "high",
"summary": "Customer cannot export a report.",
}
}
def build_agent() -> Agent:
client = AsyncOpenAI(
api_key=os.environ["COMETAPI_API_KEY"],
base_url=os.environ["COMETAPI_BASE_URL"],
)
model = OpenAIResponsesModel(
"gpt-5.1",
provider=OpenAIProvider(openai_client=client),
)
agent = Agent(
model,
output_type=TicketDecision,
instructions=(
"Always call lookup_ticket before making a decision. "
"Do not invent ticket facts. "
"Use action not_found when the tool reports found as false. "
"Escalate only when priority is high."
),
)
@agent.tool_plain
def lookup_ticket(ticket_id: str) -> dict[str, str | bool]:
"""Return a deterministic ticket record for the requested identifier."""
ticket = TICKETS.get(ticket_id)
if ticket is None:
return {
"ticket_id": ticket_id,
"found": False,
"status": "not_found",
"priority": "unknown",
}
return {"ticket_id": ticket_id, "found": True, **ticket}
return agent
agent = build_agent()
result = agent.run_sync("Decide how to handle ticket TKT-1042.")
print(result.output.model_dump())
This is deliberately a narrow first agent. The local tool is deterministic, read-only, and based on a fixed fixture. That makes it possible to establish that the model calls a tool and returns a valid TicketDecision before you connect a database, queue, or action with real side effects.
Who this is for
This pattern is for Python developers who already have a CometAPI credential available to their application and want an agent boundary that is easier to test than free-form text. It is especially useful when a model needs to consult a controlled application function, such as looking up an order state, checking a feature flag, or retrieving a policy record.
It is not a shortcut around application authorization. The model decides whether to request a tool, but your function still owns access checks, data minimization, rate limits, and side-effect controls. Start with a read-only function and a non-production dataset. Add write operations only after your team has separate approval and audit controls around them.
Key takeaways
- CometAPI’s GPT-5.1 page documents both Responses and Chat Completions endpoint families; choose one deliberately rather than mixing their assumptions.
- PydanticAI supports a custom
AsyncOpenAIclient throughOpenAIProvider, which is the configuration point for an OpenAI-compatible CometAPI base URL. - PydanticAI function tools let the model request data or actions, while your Python code remains responsible for what the tool is allowed to do.
- A Pydantic
output_typegives the application a validated final object instead of making downstream code parse a conversational answer. - A valid schema does not prove the result is true. Keep source-of-truth facts in the tool, use narrow instructions, and test known fixtures.
Sources checked
The integration choices in this article are constrained to these refetched public sources:
- CometAPI GPT-5.1 API page documents the model identifier, the two endpoint families, OpenAI-compatible client configuration, and function-calling compatibility.
- Pydantic OpenAI documentation
documents
OpenAIResponsesModel,OpenAIProvider, and supplying a customAsyncOpenAIclient. - Pydantic Function Tools documentation
documents function tools and the
@agent.tool_plainregistration path used by the example. - Pydantic Output documentation
documents
output_type, tool-backed structured output, and Pydantic validation of model-returned data.
Contract details to verify
Before treating the example as an application integration, verify four contracts together.
First, verify the base URL and provider wiring. PydanticAI’s OpenAI provider documentation shows that a custom AsyncOpenAI client can be supplied to OpenAIProvider. Keep the base URL in one configuration variable and do not scatter endpoint strings across tools or prompt code.
Second, verify the endpoint family. The CometAPI source lists both POST /v1/responses and POST /v1/chat/completions. This example constructs OpenAIResponsesModel, so its smoke test must be evaluated against the Responses contract. Changing only a model name while leaving an incompatible endpoint assumption in place is a common integration error. For a focused check, use the site’s base URL and endpoint family guide
.
Third, verify the model and tool capability together. The refetched CometAPI page describes GPT-5.1 as compatible with function calling, but your first request should still confirm that the selected model, endpoint family, and installed client versions can produce the expected tool interaction. Keep the first tool name, input schema, and returned record small enough to inspect.
Fourth, verify the output boundary. PydanticAI documents that structured output can use the model’s tool-calling capability and that Pydantic validates returned data. In this example, TicketDecision is the only final shape accepted by application code. Do not bypass it by reading a raw model message when the result is supposed to drive a workflow. Before expanding the schema, compare it with the site’s tool-call payload review
.
Happy-path operator workflow
- Run the agent against the fixed
TKT-1042fixture in a non-production environment. - Confirm the tool receives the expected identifier and returns the known record.
- Confirm the final result is a
TicketDecisionobject withactionset toescalatefor the high-priority fixture. - Repeat with an unknown identifier and confirm the final action is
not_foundrather than an invented ticket state. - Record the model name, endpoint family, request outcome, tool-call count, validation outcome, and latency. Do not record prompts, raw responses, request headers, configuration values, or tool arguments.
Failure modes
A typed agent still needs a failure plan. Treat the following conditions as separate categories because each has a different owner and repair path.
An endpoint-family mismatch occurs when code is built for Responses behavior but is pointed at a Chat Completions assumption, or the reverse. The CometAPI source makes both families visible, while the PydanticAI source distinguishes the Responses model from the legacy chat model. Check the model construction and base URL configuration before rewriting prompts.
A tool-capability mismatch occurs when the selected model does not produce the expected tool call, uses a different schema behavior, or completes with text that does not satisfy the requested result. Do not turn this into a write-capable tool to force progress. Reduce the fixture, inspect the tool schema, and test a model and endpoint pairing that your account can actually call.
A validation failure occurs when returned fields cannot populate TicketDecision. PydanticAI’s output documentation describes validation as part of structured output. Treat that as a useful stop signal: preserve the schema error classification, keep the fixture unchanged, and decide whether the instructions or output model need adjustment.
A local tool failure occurs when the application cannot retrieve data or the returned data has an unexpected shape. The example makes an unknown ticket a controlled not_found result. Real integrations should distinguish that expected business outcome from unavailable storage, permission denial, or an exception in application code.
A transport, configuration, or permission failure can prevent the request from completing. Do not infer a model problem from a single failed request. Confirm that the process has the intended configuration, capture the observed status and error class, and retry only according to your application’s documented policy.
Error-path operator workflow
- Stop any downstream action when the tool call, final validation, or transport result is unsuccessful.
- Emit a sanitized event with only operational fields such as
request_id,model,endpoint_family,http_status,attempt,latency_ms,tool_name,tool_call_count,output_valid, anderror_class. - Exclude the prompt, raw response, headers, environment values, service credentials, and tool arguments from logs. Use
[REDACTED]in an incident bundle when a sensitive value would otherwise be included. - Reproduce the problem with the same small fixture and a read-only tool. This separates an integration fault from a changing production record.
- Escalate with the sanitized event, the endpoint family, model identifier, package versions, and timestamp. Do not paste credentials into a ticket or a support request.
safe_event = {
"request_id": request_id,
"model": "gpt-5.1",
"endpoint_family": "responses",
"http_status": http_status,
"attempt": attempt,
"latency_ms": latency_ms,
"tool_name": "lookup_ticket",
"tool_call_count": tool_call_count,
"output_valid": output_valid,
"error_class": error_class,
}
logger.info("typed_agent_event=%s", safe_event)
FAQ
Do I have to use the Responses API?
No. The refetched CometAPI source lists both Responses and Chat Completions. This article uses OpenAIResponsesModel because PydanticAI documents that model path and its custom provider configuration. If you choose the chat path, make the model class, request behavior, and test cases consistent with that decision.
Why use a Pydantic output model instead of text?
A Pydantic output model gives the application a known object shape and lets validation reject malformed results before downstream code acts on them. It is appropriate when a result drives routing, an interface state, or another deterministic application step.
Does Pydantic validation make the agent factually correct?
No. Validation checks shape and types, not the truth of the summary or decision. The example keeps ticket facts in lookup_ticket and tells the agent not to invent them. For important decisions, add application-level rules that verify the returned action against the retrieved record.
What should I log for a failed run?
Log the sanitized operational fields listed in the error workflow. Keep the record useful for diagnosis without retaining prompt content, raw responses, headers, tool arguments, or any credential material.
Reader next step
Start with the fixed fixture, verify one successful tool call and one not_found result, then replace TICKETS with a read-only adapter that enforces your application’s authorization rules. Once the endpoint family and output contract are stable, add a narrow evaluation case for every tool outcome before enabling a real workflow.
When you are ready to obtain API access for the integration, Start with CometAPI .