This month’s AI agent tutorial on Towards Data Science takes you from installing Python and PyCharm to a running chat loop, and calls the result an agent. The habits around the .env file are correct and the client setup is clean, so I’d hand it to a junior without edits. My objection is the name. What runs is a single-question chatbot, and the gap between the code and the label is the work I end up doing when a client asks for the same thing on a real site.
What this AI agent tutorial gets right
The part worth keeping is the OpenAI client setup. You instantiate the SDK, point base_url at https://openrouter.ai/api/v1, and those two lines talk to any model OpenRouter routes to. The model name becomes a config string you swap, which matters on a WordPress build where the client will change their mind about the model three times. The key handling is also right. The key lives in .env, gets loaded with python-dotenv, and never gets committed.
OpenRouter keys take a per-key credit limit and an expiry date, and their docs note that keys created during onboarding get a $100 limit and expire after 180 days. On client work I’d create one capped key per site, so a leak costs a fixed amount and nothing more.
The three missing pieces
The loop is missing three things an agent needs. First, memory. The messages array is rebuilt inside the loop from just the system prompt and the current question, so the model forgets the previous answer every turn. Ask a follow-up and it has no idea what you meant. Fixing it takes a few lines that append each exchange to a running history, but that changes the cost profile, because every request now carries the whole transcript.
Second, tools. Nothing in the code can search, calculate, or call a function, and the article’s own definition section says agents use web search and their own memory to research and compare options. Third, a stop condition. while True runs until you kill the process, and the smallest honest fix is to break on an empty input or a quit word.
To be fair, the source is upfront that this is a first project, and it warns that free models are slow because they are shared. That matches what I see with anything labeled :free. A beginner tutorial that shipped tool calls and retry logic on day one would lose people.
The same three pieces in WordPress
The mapping to a WordPress build is close to one to one, which is why I like this tutorial despite the name. Key handling becomes a server environment read in wp-config.php, and the browser never sees it. Any chat UI in a plugin talks to my own REST endpoint, and the model call happens server-side. That’s the pattern I describe in how to call external APIs from WordPress the right way. Memory becomes a transient keyed to the session, or a custom table once transcripts get long. I’m not sure the transient holds up past a few thousand words of history, and past that point I’d move to the table. Termination stops being a choice, because shared hosting kills long PHP workers anyway, so the WordPress version is a chain of short requests rather than a loop.
Since WordPress 5.5 the REST API requires a permission_callback on every route, per the REST API handbook, so I write it deliberately even for a public chat route. The endpoint itself is small:
<?php
define( 'BBIOON_OPENROUTER_KEY', getenv( 'BBIOON_OPENROUTER_KEY' ) );
add_action( 'rest_api_init', function () {
register_rest_route( 'bbioon/v1', '/ask', array(
'methods' => 'POST',
'permission_callback' => '__return_true', // public chat widget
'callback' => 'bbioon_ask_model',
) );
} );
function bbioon_ask_model( WP_REST_Request $request ) {
$question = sanitize_text_field( $request->get_param( 'question' ) );
$res = wp_remote_post( 'https://openrouter.ai/api/v1/chat/completions', array(
'timeout' => 45,
'headers' => array(
'Authorization' => 'Bearer ' . BBIOON_OPENROUTER_KEY,
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array(
'model' => 'baidu/cobuddy:free',
'messages' => array(
array( 'role' => 'system', 'content' => 'You are a helpful sales assistant for this store.' ),
array( 'role' => 'user', 'content' => $question ),
),
) ),
) );
if ( is_wp_error( $res ) ) {
return new WP_Error( 'bbioon_model_unreachable', 'Model unreachable.', array( 'status' => 502 ) );
}
$body = json_decode( wp_remote_retrieve_body( $res ), true );
return rest_ensure_response( $body['choices'][0]['message']['content'] ?? '' );
}
The timeout is 45 because free models are slow and wp_remote_post defaults to 5 seconds, which fails before the model answers. And sanitize_text_field runs on the input before it goes anywhere. Before this reached production I’d add a nonce, a per-IP rate limit, and a check on what the user is allowed to ask about. Building custom endpoints like this instead of pushing everything through functions.php is what keeps the AI parts testable later.
What I’d do this week
If a client asked for a store assistant this week, I’d start with this shape: a capped OpenRouter key, the REST proxy above, transcripts in a session store, and plain question-and-answer behavior first. Tool calls come last, once the plain answers are useful. Tutorials skip that part, and it’s where most of my build time goes. If you’re setting up something similar on WordPress or WooCommerce, I’m happy to help scope it.