Code
OpenAI Agents SDK
The Agents SDK runs on nokeep through chat completions. Give it an AsyncOpenAI client with nokeep’s base URL and wrap the model in OpenAIChatCompletionsModel.
Checked 29 October 2026
Install and connect
pip install openai-agents
export NOKEEP_API_KEY="sk-nokeep-test" # your own key once it connectsimport os
from openai import AsyncOpenAI
from agents import Agent, OpenAIChatCompletionsModel, set_tracing_disabled
client = AsyncOpenAI(
base_url="https://nokeep.ai/v1",
api_key=os.environ["NOKEEP_API_KEY"],
)
set_tracing_disabled(True) # traces go to OpenAI's dashboard, which a nokeep key can't reach
model = OpenAIChatCompletionsModel(model="deepseek-v4.1-flash-uncensored", openai_client=client)
from agents import Runner
agent = Agent(name="Editor", instructions="You are a blunt editor.", model=model)
result = Runner.run_sync(agent, "Cut this opening line to ten words: ...")
print(result.final_output)The SDK calls the Responses API (/v1/responses) unless told otherwise, and nokeep has only chat completions. Wrapping the model in OpenAIChatCompletionsModel is what makes it work.
Or set it once for every agent
import os
from openai import AsyncOpenAI
from agents import Agent, set_default_openai_api, set_default_openai_client, set_tracing_disabled
set_default_openai_client(AsyncOpenAI(base_url="https://nokeep.ai/v1", api_key=os.environ["NOKEEP_API_KEY"]), use_for_tracing=False)
set_default_openai_api("chat_completions")
set_tracing_disabled(True)
agent = Agent(name="Editor", instructions="You are a blunt editor.", model="deepseek-v4.1-flash-uncensored")Agents then take the model id as a plain string. Handoffs between agents work the same way.
Tools
from agents import Agent, Runner, function_tool
@function_tool
def get_weather(city: str) -> str:
"""Get the weather in a city."""
return f"18°C and clear in {city}"
agent = Agent(
name="Weather",
instructions="Answer questions about the weather.",
model=model,
tools=[get_weather],
)
print(Runner.run_sync(agent, "What's the weather in Lisbon?").final_output)The SDK sends each tool with strict: true and runs the loop: the model asks for a tool, the SDK runs it and sends the result back until the model answers. See Tool calls.
Stream and usage
import asyncio
from agents import Runner
from openai.types.responses import ResponseTextDeltaEvent
async def main():
result = Runner.run_streamed(agent, "Tell me a story in three lines.")
async for event in result.stream_events():
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):
print(event.data.delta, end="", flush=True)
print("\n", result.context_wrapper.usage)
asyncio.run(main())The SDK asks for usage on a stream, so context_wrapper.usage holds the token counts for the whole run, every model call added up.
Reasoning effort and other settings
from agents import Agent, ModelSettings
from openai.types.shared import Reasoning
planner = Agent(
name="Planner",
instructions="Plan before you answer.",
model=model,
model_settings=ModelSettings(
reasoning=Reasoning(effort="high"), # sent as reasoning_effort
max_tokens=4000,
extra_body={"min_p": 0.05}, # fields ModelSettings has no argument for
),
)Thinking counts toward max_tokens, so give a thinking agent room. See Thinking.
Gotchas
- `reasoning_effort` takes only the model’s own values.
deepseek-v4.1-flash-uncensoredtakesnone,low,high,max;glm-5.3-flash-uncensoredtakeslow,high. Other apps’minimal,mediumandxhighrun at the model’s nearest level (mediumashigh,xhighasmaxwhere the model has it); any other value gets400 invalid_reasoning_effort. See Thinking. - GLM picks its own tools.
glm-5.3-flash-uncensoredtakestool_choiceautoornoneonly. Forcing a tool (required, or naming a function) gets400 unsupported_tool_choice. Use DeepSeek when you need to force one. In the SDK that isModelSettings(tool_choice="required"), or a tool name. - Fields a model can’t take are dropped, not refused. The call still answers, and the
x-nokeep-ignoredresponse header names what was left out. GLM keeps onlytools,tool_choiceandreasoning_effort, so itstemperatureandresponse_formatare dropped. See which fields each model takes. - Chat completions only. There is no
/v1/responsesand no/v1/embeddings; a framework that defaults to either needs to be pointed at chat completions, and embeddings need another provider.
Hosted tools, such as web search, file search and code interpreter, run on OpenAI’s own servers through the Responses API. They don’t work with any other API, nokeep included. Write them as function tools.
Questions
Do I need an OpenAI key as well?
No. Every model call goes to nokeep with your nokeep key. Tracing uploads to OpenAI’s dashboard; with tracing on and no OpenAI key set, the SDK prints OPENAI_API_KEY is not set, skipping trace export and the agent runs anyway. set_tracing_disabled(True) keeps it quiet.
Which model should an agent use?
deepseek-v4.1-flash-uncensored: it can be made to call a tool, reads pictures and takes every setting. glm-5.3-flash-uncensored costs less and works for agents that let the model choose its tools.