<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sergey Inozemtsev</title>
    <description>The latest articles on DEV Community by Sergey Inozemtsev (@inozem).</description>
    <link>https://dev.to/inozem</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2431738%2Fe9e02497-8195-4b93-98e8-fe2be2ae4a88.png</url>
      <title>DEV Community: Sergey Inozemtsev</title>
      <link>https://dev.to/inozem</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/inozem"/>
    <language>en</language>
    <item>
      <title>One Tool Calling Interface for OpenAI, Claude, and Gemini</title>
      <dc:creator>Sergey Inozemtsev</dc:creator>
      <pubDate>Thu, 12 Mar 2026 09:56:46 +0000</pubDate>
      <link>https://dev.to/inozem/one-tool-calling-interface-for-openai-claude-and-gemini-2l1c</link>
      <guid>https://dev.to/inozem/one-tool-calling-interface-for-openai-claude-and-gemini-2l1c</guid>
      <description>&lt;p&gt;&lt;strong&gt;llm-api-adapter&lt;/strong&gt; is an open‑source Python library designed to simplify working with multiple LLM providers.&lt;/p&gt;

&lt;p&gt;Many AI applications today need to support multiple LLM providers.&lt;/p&gt;

&lt;p&gt;Common reasons include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cost optimization&lt;/li&gt;
&lt;li&gt;fallback when a provider is unavailable&lt;/li&gt;
&lt;li&gt;access to different model capabilities&lt;/li&gt;
&lt;li&gt;experimentation with new models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In practice, the moment you try to support &lt;strong&gt;OpenAI, Claude, and Gemini&lt;/strong&gt;, the integration becomes messy.&lt;/p&gt;

&lt;p&gt;Tool calling alone already breaks portability:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Tool format&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;OpenAI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;tool_calls&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tool_use&lt;/code&gt; blocks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Gemini&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;functionCall&lt;/code&gt; / &lt;code&gt;functionResponse&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are not just syntax differences.\&lt;br&gt;
They require &lt;strong&gt;different request structures, response parsing, and execution loops&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Supporting multiple providers usually leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;provider-specific integration logic&lt;/li&gt;
&lt;li&gt;provider-specific request/response handling&lt;/li&gt;
&lt;li&gt;duplicated tool execution flows&lt;/li&gt;
&lt;li&gt;multiple SDK dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is more code, more bugs, and much harder provider switching.&lt;/p&gt;

&lt;p&gt;To simplify this, I built &lt;strong&gt;llm-api-adapter&lt;/strong&gt; — a small Python library that provides &lt;strong&gt;one unified interface for multiple LLM APIs&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Define tools once and run the same application logic across &lt;strong&gt;OpenAI, Anthropic, and Gemini&lt;/strong&gt;.&lt;/p&gt;


&lt;h1&gt;
  
  
  Architecture
&lt;/h1&gt;

&lt;p&gt;The adapter acts as a &lt;strong&gt;translation layer&lt;/strong&gt; between your application and LLM providers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;              Application Logic
                     │
                     ▼
           UniversalLLMAPIAdapter
                     │
                     ▼
          Provider Translation Layer
                     │
                     ▼
 ┌─────────────┬─────────────┬─────────────┐
 │   OpenAI    │  Anthropic  │   Gemini    │
 │ tool_calls  │  tool_use   │ functionCall│
 └─────────────┴─────────────┴─────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your application communicates with &lt;strong&gt;one interface&lt;/strong&gt;, while the adapter converts requests and responses to the provider-specific formats.&lt;/p&gt;




&lt;h1&gt;
  
  
  Installation
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;llm-api-adapter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  The "Strawberry" problem
&lt;/h1&gt;

&lt;p&gt;A classic example showing why tool calling matters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;How many "r" letters are in "strawberry"?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The correct answer is &lt;strong&gt;3&lt;/strong&gt;, but models often fail because they reason over &lt;strong&gt;tokens&lt;/strong&gt;, not characters.&lt;/p&gt;

&lt;p&gt;Best practice is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let the LLM reason, but delegate deterministic tasks to code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is exactly what &lt;strong&gt;tool calling&lt;/strong&gt; enables.&lt;/p&gt;




&lt;h1&gt;
  
  
  Defining a tool once
&lt;/h1&gt;

&lt;p&gt;With &lt;strong&gt;llm-api-adapter&lt;/strong&gt;, tools are defined using a &lt;strong&gt;provider-agnostic schema&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llm_api_adapter.models.tools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ToolSpec&lt;/span&gt;

&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;ToolSpec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count_letter_in_word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Count how many times a specific letter appears in a word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;properties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;letter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;string&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;minLength&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxLength&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;required&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;letter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;additionalProperties&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The adapter automatically converts this schema to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OpenAI &lt;code&gt;tools&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Anthropic &lt;code&gt;tool_use&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Gemini &lt;code&gt;functionCall&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Running the same code across providers
&lt;/h1&gt;

&lt;p&gt;The application logic remains identical.&lt;/p&gt;

&lt;p&gt;Only the &lt;strong&gt;provider name, model, and API key&lt;/strong&gt; change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llm_api_adapter.universal_adapter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UniversalLLMAPIAdapter&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llm_api_adapter.models.messages.chat_message&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;UserMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;AIMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ToolMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count_letter_in_word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;letter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;letter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;word&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;letter&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;letter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;providers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;openai_api_key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-haiku-4-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;anthropic_api_key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;google_api_key&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UniversalLLMAPIAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nc"&gt;UserMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;How many &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; letters are in &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strawberry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;tool_choice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;AIMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;ToolMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;tool_call_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;previous_response&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--- &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;org&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; / &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; ---&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Example output
&lt;/h1&gt;

&lt;p&gt;Even though the models use different tokenization internally, they all trigger the tool correctly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- openai / gpt-5.2 ---
There are 3 letters "r" in "strawberry".

--- anthropic / claude-haiku-4-5 ---
There are 3 "r" letters in "strawberry".

--- google / gemini-2.5-flash ---
There are three "r" letters in "strawberry".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Without vs with an adapter
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Problem&lt;/th&gt;
&lt;th&gt;Without Adapter&lt;/th&gt;
&lt;th&gt;With llm-api-adapter&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tool definitions&lt;/td&gt;
&lt;td&gt;Provider specific&lt;/td&gt;
&lt;td&gt;One universal schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool execution&lt;/td&gt;
&lt;td&gt;Custom logic per provider&lt;/td&gt;
&lt;td&gt;Unified interface&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response parsing&lt;/td&gt;
&lt;td&gt;Different formats&lt;/td&gt;
&lt;td&gt;Single response model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provider switching&lt;/td&gt;
&lt;td&gt;Rewrite code&lt;/td&gt;
&lt;td&gt;Change model string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;Multiple SDKs&lt;/td&gt;
&lt;td&gt;One library&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h1&gt;
  
  
  Why this matters
&lt;/h1&gt;

&lt;p&gt;Supporting multiple LLM providers normally requires &lt;strong&gt;separate integrations and duplicated logic&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A unified interface lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep application logic provider-agnostic&lt;/li&gt;
&lt;li&gt;switch models without rewriting code&lt;/li&gt;
&lt;li&gt;simplify agent architectures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of adapting your code to each provider, you adapt the providers to your code.&lt;/p&gt;




&lt;h1&gt;
  
  
  GitHub
&lt;/h1&gt;

&lt;p&gt;The project is open source.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://github.com/Inozem/llm_api_adapter" rel="noopener noreferrer"&gt;https://github.com/Inozem/llm_api_adapter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You will find full documentation, examples, and the source code in the repository.&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>openai</category>
      <category>gemini</category>
    </item>
    <item>
      <title>Clean Architecture for AI Agents with Convo-Lang</title>
      <dc:creator>Sergey Inozemtsev</dc:creator>
      <pubDate>Tue, 10 Feb 2026 20:47:41 +0000</pubDate>
      <link>https://dev.to/inozem/clean-architecture-for-ai-with-convo-lang-93l</link>
      <guid>https://dev.to/inozem/clean-architecture-for-ai-with-convo-lang-93l</guid>
      <description>&lt;h2&gt;
  
  
  Decoupling Orchestration from Reasoning
&lt;/h2&gt;

&lt;p&gt;In this post, I’ll show how to design a &lt;strong&gt;clean, maintainable architecture for AI systems&lt;/strong&gt; using Convo-Lang.&lt;/p&gt;

&lt;p&gt;As a concrete example, I’ll use a &lt;strong&gt;hallucination-resistant AI agent that analyzes a job description, evaluates candidate fit against detailed professional experience, and generates a tailored resume only when the role is actually relevant&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this setup, &lt;strong&gt;all reasoning and decision logic lives in Convo-Lang&lt;/strong&gt;, while &lt;strong&gt;Python is used strictly for orchestration&lt;/strong&gt; — loading inputs, executing agents, and wiring the pipeline together.&lt;/p&gt;

&lt;p&gt;The goal of the example is not the resume itself. The goal is to demonstrate how to &lt;strong&gt;decouple orchestration from reasoning&lt;/strong&gt; and build an AI system that is easy to understand, extend, and maintain over time.&lt;/p&gt;

&lt;p&gt;The full working example is available in the Convo-Lang repository.&lt;/p&gt;

&lt;p&gt;You can explore the complete code here: &lt;a href="https://github.com/convo-lang/convo-lang/tree/main/packages/convo-lang-py/examples/02_patterns/resume_generator" rel="noopener noreferrer"&gt;https://github.com/convo-lang/convo-lang/tree/main/packages/convo-lang-py/examples/02_patterns/resume_generator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can clone it, run it locally, and experiment with it by simply replacing the job description and writing your own experience profile — the sample inputs live in the &lt;code&gt;data/&lt;/code&gt; folder.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Convo-Lang actually is
&lt;/h2&gt;

&lt;p&gt;Convo-Lang is not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a prompt template engine&lt;/li&gt;
&lt;li&gt;a thin wrapper around chat completions&lt;/li&gt;
&lt;li&gt;a “nicer way to write prompts”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Convo-Lang is a &lt;strong&gt;domain-specific language for LLM reasoning and agent workflows&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It allows you to define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explicit agent roles&lt;/li&gt;
&lt;li&gt;typed input and output contracts&lt;/li&gt;
&lt;li&gt;deterministic logic&lt;/li&gt;
&lt;li&gt;schema-enforced outputs&lt;/li&gt;
&lt;li&gt;multi-agent pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this lives in &lt;code&gt;.convo&lt;/code&gt; files — &lt;strong&gt;outside of application code&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why resumes are a good stress test
&lt;/h2&gt;

&lt;p&gt;Resume generation is a hostile domain for hallucinations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;inventing skills is unacceptable&lt;/li&gt;
&lt;li&gt;inventing companies or roles is unacceptable&lt;/li&gt;
&lt;li&gt;inventing dates is unacceptable&lt;/li&gt;
&lt;li&gt;decisions must be explainable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single “smart prompt” is the worst possible approach here.&lt;/p&gt;

&lt;p&gt;So instead of asking &lt;em&gt;how to prompt&lt;/em&gt;, I started by asking:&lt;br&gt;
&lt;strong&gt;how should this system be modeled?&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Modeling the system as Convo-Lang agents
&lt;/h2&gt;

&lt;p&gt;The solution is built as &lt;strong&gt;five Convo-Lang agents&lt;/strong&gt;, each responsible for exactly one thing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;JobDescriptionAnalyzer&lt;/strong&gt;&lt;br&gt;
Turns raw job text into structured requirements.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CandidateProfileAnalyzer&lt;/strong&gt;&lt;br&gt;
Converts free-form experience text into factual, structured data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ProfileJobMatcher&lt;/strong&gt;&lt;br&gt;
Matches experience to requirements and explicitly lists gaps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ResumeWriter&lt;/strong&gt;&lt;br&gt;
Generates a resume strictly from verified data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;FitEvaluator&lt;/strong&gt;&lt;br&gt;
Decides whether applying makes sense.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each agent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lives in its own &lt;code&gt;.convo&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;has a single responsibility&lt;/li&gt;
&lt;li&gt;communicates only through typed contracts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation is not cosmetic.&lt;br&gt;
It is the foundation of reliability.&lt;/p&gt;


&lt;h2&gt;
  
  
  Typed contracts instead of “return JSON please”
&lt;/h2&gt;

&lt;p&gt;In most LLM systems, structured output is a suggestion.&lt;/p&gt;

&lt;p&gt;In Convo-Lang, it is a &lt;strong&gt;contract&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here is a real schema used by the &lt;code&gt;CandidateProfileAnalyzer&lt;/code&gt; agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;define
ProfileData = struct(
  workExperience: array(
    struct(
      title: string
      companyName: string
      firstDate: string
      lastDate?: string
      summary: string
      experience: array(string)
    )
  )
  projects?: array(
    struct(
      title: string
      firstDate: string
      lastDate?: string
      experience: array(string)
    )
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This immediately changes system behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;required fields must exist&lt;/li&gt;
&lt;li&gt;optional fields are explicit&lt;/li&gt;
&lt;li&gt;invented fields are invalid&lt;/li&gt;
&lt;li&gt;downstream agents can trust the data shape&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hallucinations don’t silently propagate.&lt;br&gt;
They violate the contract.&lt;/p&gt;


&lt;h2&gt;
  
  
  Validating inputs before any reasoning happens
&lt;/h2&gt;

&lt;p&gt;Hallucinations often start &lt;strong&gt;before generation&lt;/strong&gt;.&lt;br&gt;
They start when invalid or ambiguous input quietly enters the system.&lt;/p&gt;

&lt;p&gt;Convo-Lang allows agents to &lt;strong&gt;validate inputs explicitly&lt;/strong&gt;, before any reasoning takes place.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;define
JobData = struct(
  title: string
  mustRequirements: array(string)
  niceToHaveRequirements: array(string)
  keywords: array(string)
)

&amp;gt;do
jobData = new(JobData job_data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single line enforces a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;checks that &lt;code&gt;job_data&lt;/code&gt; exists&lt;/li&gt;
&lt;li&gt;validates required fields&lt;/li&gt;
&lt;li&gt;enforces correct types&lt;/li&gt;
&lt;li&gt;rejects malformed input early&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the input does not match &lt;code&gt;JobData&lt;/code&gt;, the agent does not proceed.&lt;/p&gt;

&lt;p&gt;The model never reasons over invalid data.&lt;/p&gt;

&lt;p&gt;Here, &lt;strong&gt;input validation is part of the agent’s contract&lt;/strong&gt;, not an afterthought.&lt;/p&gt;




&lt;h2&gt;
  
  
  Explainable matching instead of opaque scoring
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ProfileJobMatcher&lt;/code&gt; agent does not produce a mysterious score.&lt;/p&gt;

&lt;p&gt;It produces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;only relevant roles and projects&lt;/li&gt;
&lt;li&gt;explicit &lt;code&gt;matchReasons&lt;/code&gt; for each item&lt;/li&gt;
&lt;li&gt;two concrete gap lists: must-have and nice-to-have
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MatchData = struct(
  coverageProfileData: struct(
    workExperience: array(
      title: string
      companyName: string
      firstDate: string
      lastDate?: string
      summary: string
      experience: array(string)
      matchReasons: array(string)
    )
    projects?: array(...)
  )
  gaps: struct(
    mustRequirements: array(string)
    niceToHaveRequirements: array(string)
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing is hidden.&lt;br&gt;
Every match and every gap is inspectable.&lt;/p&gt;

&lt;p&gt;This output becomes the single source of truth for all downstream steps.&lt;/p&gt;


&lt;h2&gt;
  
  
  Deterministic logic inside the agent (not in prose)
&lt;/h2&gt;

&lt;p&gt;A key feature of Convo-Lang is that &lt;strong&gt;deterministic logic lives next to reasoning&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;FitEvaluator&lt;/code&gt;, the final decision is not guessed.&lt;br&gt;
It is calculated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;do
jobData = new(JobData job_data)
matchData = new(MatchData match_data)

totalConfidence = 100
jobRequirementsAmount = jobData.mustRequirements.length
requirementPoints = div(totalConfidence jobRequirementsAmount)

requirementGapAmount = matchData.gaps.mustRequirements.length
mainConfidence = mul(
  sub(jobRequirementsAmount requirementGapAmount)
  requirementPoints
)

decision = "apply"

if (lt(mainConfidence 70)) then (
  decision = "skip"
)
elif (lt(mainConfidence 90)) then (
  decision = "maybe apply"
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is business logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;readable&lt;/li&gt;
&lt;li&gt;reviewable&lt;/li&gt;
&lt;li&gt;testable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The LLM explains the decision — but it does not invent the rules.&lt;/p&gt;




&lt;h2&gt;
  
  
  Schema-enforced output with &lt;code&gt;@json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Convo-Lang does not rely on “please return JSON”.&lt;/p&gt;

&lt;p&gt;It enforces it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@json RecommendationData
&amp;gt;user
Help the candidate decide whether applying for this job makes sense.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the output does not match &lt;code&gt;RecommendationData&lt;/code&gt;, it is invalid.&lt;/p&gt;

&lt;p&gt;Structured output is no longer a best-effort promise.&lt;br&gt;
It is a guarantee.&lt;/p&gt;




&lt;h2&gt;
  
  
  Python as an orchestrator, not a reasoning layer
&lt;/h2&gt;

&lt;p&gt;So where does Python fit into this architecture?&lt;/p&gt;

&lt;p&gt;Python is intentionally boring.&lt;/p&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;contain prompts&lt;/li&gt;
&lt;li&gt;contain business rules&lt;/li&gt;
&lt;li&gt;interpret free-form model output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It only:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;loads input data&lt;/li&gt;
&lt;li&gt;executes agents&lt;/li&gt;
&lt;li&gt;passes validated JSON between them&lt;/li&gt;
&lt;li&gt;handles I/O
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;job_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;convo_job_description_analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;profile_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;convo_candidate_profile_analyzer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;match_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;convo_profile_job_matcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;resume_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;convo_resume_writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;convo_fit_evaluator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All intelligence lives in &lt;code&gt;.convo&lt;/code&gt;.&lt;br&gt;
Python is just the runtime.&lt;/p&gt;

&lt;p&gt;This separation is deliberate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this separation matters
&lt;/h2&gt;

&lt;p&gt;By keeping reasoning in Convo-Lang and orchestration in Python:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI logic becomes portable&lt;/li&gt;
&lt;li&gt;behavior is consistent across CLI, editor, and SDK&lt;/li&gt;
&lt;li&gt;prompt changes don’t require backend redeploys&lt;/li&gt;
&lt;li&gt;agent logic can be reviewed like code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;agents folder becomes the product&lt;/strong&gt;.&lt;br&gt;
The SDK becomes an implementation detail.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this example actually demonstrates
&lt;/h2&gt;

&lt;p&gt;This post is not really about resumes.&lt;/p&gt;

&lt;p&gt;It demonstrates that Convo-Lang lets you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;treat LLM logic as first-class code&lt;/li&gt;
&lt;li&gt;build multi-agent systems without prompt chaos&lt;/li&gt;
&lt;li&gt;validate inputs and outputs explicitly&lt;/li&gt;
&lt;li&gt;make hallucinations visible instead of hidden&lt;/li&gt;
&lt;li&gt;scale reasoning without rewriting everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is why Convo-Lang is worth using.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;Hallucinations are rarely a model problem.&lt;br&gt;
They are almost always an &lt;strong&gt;architecture problem&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Convo-Lang gives you the tools to fix that at the right level.&lt;/p&gt;




&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Convo-Lang core: &lt;a href="https://github.com/convo-lang/convo-lang" rel="noopener noreferrer"&gt;https://github.com/convo-lang/convo-lang&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Convo-Lang Python SDK: &lt;a href="https://github.com/convo-lang/convo-lang/tree/main/packages/convo-lang-py" rel="noopener noreferrer"&gt;https://github.com/convo-lang/convo-lang/tree/main/packages/convo-lang-py&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Resume agent example:
&lt;a href="https://github.com/convo-lang/convo-lang/tree/main/packages/convo-lang-py/examples/02_patterns/resume_generator" rel="noopener noreferrer"&gt;https://github.com/convo-lang/convo-lang/tree/main/packages/convo-lang-py/examples/02_patterns/resume_generator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Documentation: &lt;a href="https://learn.convo-lang.ai/" rel="noopener noreferrer"&gt;https://learn.convo-lang.ai/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>architecture</category>
      <category>python</category>
    </item>
    <item>
      <title>Prompts are logic, not strings: Why I contributed to Convo-Lang</title>
      <dc:creator>Sergey Inozemtsev</dc:creator>
      <pubDate>Sun, 04 Jan 2026 13:36:56 +0000</pubDate>
      <link>https://dev.to/inozem/prompts-are-logic-not-strings-why-i-contributed-to-convo-lang-172d</link>
      <guid>https://dev.to/inozem/prompts-are-logic-not-strings-why-i-contributed-to-convo-lang-172d</guid>
      <description>&lt;p&gt;If you’ve built anything non-trivial with LLMs, you’ve probably written code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Analyze this job description: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job_description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Analyze this candidate profile: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;candidate_profile&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Decide whether the candidate is a good fit.
Return JSON.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works.&lt;br&gt;
Until your project grows.&lt;/p&gt;


&lt;h2&gt;
  
  
  The problem: prompt spaghetti and technical debt
&lt;/h2&gt;

&lt;p&gt;Hardcoding prompts directly into application code feels convenient at first.&lt;br&gt;
But very quickly it turns into long-term technical debt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompts become unreadable f-string monsters&lt;/li&gt;
&lt;li&gt;Prompt changes require code changes and redeploys&lt;/li&gt;
&lt;li&gt;Prompt versions drift across files and branches&lt;/li&gt;
&lt;li&gt;Prompt engineers and copywriters are afraid to touch &lt;code&gt;.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Prompt logic, business logic, and orchestration logic get mixed together&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At that point, prompts are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hard to test&lt;/li&gt;
&lt;li&gt;hard to reuse&lt;/li&gt;
&lt;li&gt;hard to reason about&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We already solved this problem for SQL, HTML, configs, and templates.&lt;br&gt;
LLM prompts deserve the same treatment.&lt;/p&gt;


&lt;h2&gt;
  
  
  Prompts are not strings — they are logic
&lt;/h2&gt;

&lt;p&gt;A modern LLM “prompt” is not just text.&lt;/p&gt;

&lt;p&gt;It contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;structure&lt;/li&gt;
&lt;li&gt;contracts&lt;/li&gt;
&lt;li&gt;conditions&lt;/li&gt;
&lt;li&gt;branching&lt;/li&gt;
&lt;li&gt;deterministic steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treating it as a Python string literal is the fastest way to lose control over your AI system.&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;Convo-Lang&lt;/strong&gt; comes in.&lt;/p&gt;


&lt;h2&gt;
  
  
  Convo-Lang as an AI-native DSL
&lt;/h2&gt;

&lt;p&gt;Convo-Lang is an open-source, AI-native DSL for building conversations and agent workflows.&lt;/p&gt;

&lt;p&gt;Instead of embedding prompts into code, you define them in &lt;code&gt;.convo&lt;/code&gt; files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explicit schemas&lt;/li&gt;
&lt;li&gt;role-based messages&lt;/li&gt;
&lt;li&gt;deterministic logic blocks&lt;/li&gt;
&lt;li&gt;structured outputs&lt;/li&gt;
&lt;li&gt;multi-agent workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your prompt becomes a &lt;strong&gt;first-class artifact&lt;/strong&gt;, not a string buried in code.&lt;/p&gt;


&lt;h2&gt;
  
  
  How it works: Python as a thin runtime
&lt;/h2&gt;

&lt;p&gt;Here’s all the Python code required to run a single agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Running FitEvaluator agent...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;convo_fit_evaluator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Conversation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent_configs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;convo_fit_evaluator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_convo_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agents/fitEvaluator.convo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;job_apply_decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;convo_fit_evaluator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;job_data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;job_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;match_data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;match_data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice what’s missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no prompt text&lt;/li&gt;
&lt;li&gt;no formatting logic&lt;/li&gt;
&lt;li&gt;no hidden reasoning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Python is just the runtime.&lt;/strong&gt;&lt;br&gt;
All intelligence lives in &lt;code&gt;.convo&lt;/code&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  Typed contracts instead of free-form prompts
&lt;/h2&gt;

&lt;p&gt;The core idea that changed how I think about prompts:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Agents should communicate through &lt;strong&gt;typed contracts&lt;/strong&gt;, not vague instructions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Example schema definitions used by an agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;define
JobData = struct(
  title:string
  mustRequirements:array(string)
  niceToHaveRequirements:array(string)
)

RecommendationData = struct(
  recommendation: struct(
    decision: enum("apply","maybe apply","skip")
    confidence: number
    summary: string
  )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;explicit input shapes&lt;/li&gt;
&lt;li&gt;explicit output contracts&lt;/li&gt;
&lt;li&gt;predictable agent behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent no longer “guesses” what to return.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deterministic logic lives next to the prompt
&lt;/h2&gt;

&lt;p&gt;Convo-Lang is not just a prompt format.&lt;br&gt;
It allows you to define &lt;strong&gt;explicit, deterministic logic&lt;/strong&gt; inside the agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt;do
jobData = new(JobData job_data)
matchData = new(MatchData match_data)

total = 100
reqCount = jobData.mustRequirements.length
niceCount = jobData.niceToHaveRequirements.length

reqPoints = div(total reqCount)
nicePoints = div(reqPoints 4)

reqGaps = matchData.gaps.mustRequirements.length
niceGaps = matchData.gaps.niceToHaveRequirements.length

confidence = add(
  mul(sub(reqCount reqGaps) reqPoints)
  mul(sub(niceCount niceGaps) nicePoints)
)

decision = "apply"
if (lt(confidence 70)) then (decision = "skip")
elif (lt(confidence 90)) then (decision = "maybe apply")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is &lt;strong&gt;business logic&lt;/strong&gt;, not prompt prose.&lt;/p&gt;




&lt;h2&gt;
  
  
  Schema-enforced output with &lt;code&gt;@json&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@json RecommendationData
&amp;gt;user
Return recommendation for this candidate and job.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This is not a suggestion.&lt;/strong&gt;&lt;br&gt;
It is schema‑enforced output validation.&lt;/p&gt;

&lt;p&gt;Malformed or invalid responses don’t silently pass through.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cross-SDK portability by design
&lt;/h2&gt;

&lt;p&gt;Because the Convo-Lang core is implemented in TypeScript, it guarantees identical behavior across environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VS Code preview&lt;/li&gt;
&lt;li&gt;CLI&lt;/li&gt;
&lt;li&gt;Python runtime&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your prompt passes validation in the editor, it will behave the same way in your Python backend.&lt;/p&gt;

&lt;p&gt;Write once. Run anywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture: a smart bridge, not a rewrite
&lt;/h2&gt;

&lt;p&gt;The Python SDK does not reimplement the Convo-Lang engine.&lt;/p&gt;

&lt;p&gt;Instead, it acts as a high‑performance bridge to the Node.js core, which handles parsing, validation, and async I/O.&lt;/p&gt;

&lt;p&gt;This preserves full syntax and behavior parity across SDKs.&lt;/p&gt;




&lt;h2&gt;
  
  
  Separation of concerns — for real
&lt;/h2&gt;

&lt;p&gt;With this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.convo&lt;/code&gt; files own AI reasoning and decision logic&lt;/li&gt;
&lt;li&gt;Python only orchestrates execution&lt;/li&gt;
&lt;li&gt;Prompt engineers don’t touch backend code&lt;/li&gt;
&lt;li&gt;Developers don’t rewrite prompts&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The &lt;code&gt;agents/&lt;/code&gt; folder is the product.&lt;br&gt;
Python is just the runtime.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why I contributed to the Python SDK
&lt;/h2&gt;

&lt;p&gt;I believe AI workflows need standards.&lt;/p&gt;

&lt;p&gt;Prompts should be portable, testable, and explicit.&lt;/p&gt;

&lt;p&gt;That’s why I helped bring Convo-Lang to Python.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Resume generator example (Python):
&lt;a href="https://github.com/convo-lang/convo-lang/tree/main/packages/convo-lang-py/examples/02_patterns/resume_generator" rel="noopener noreferrer"&gt;https://github.com/convo-lang/convo-lang/tree/main/packages/convo-lang-py/examples/02_patterns/resume_generator&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Convo-Lang core:
&lt;a href="https://github.com/convo-lang/convo-lang" rel="noopener noreferrer"&gt;https://github.com/convo-lang/convo-lang&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Convo-Lang Python SDK:
&lt;a href="https://github.com/convo-lang/convo-lang/tree/main/packages/convo-lang-py" rel="noopener noreferrer"&gt;https://github.com/convo-lang/convo-lang/tree/main/packages/convo-lang-py&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Documentation:
&lt;a href="https://learn.convo-lang.ai/" rel="noopener noreferrer"&gt;https://learn.convo-lang.ai/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>llm</category>
      <category>promptengineering</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>How I built a bulletproof CI/CD for my LLM Python library</title>
      <dc:creator>Sergey Inozemtsev</dc:creator>
      <pubDate>Wed, 24 Dec 2025 09:20:35 +0000</pubDate>
      <link>https://dev.to/inozem/how-i-built-a-cicd-pipeline-with-e2e-tests-via-testpypi-107k</link>
      <guid>https://dev.to/inozem/how-i-built-a-cicd-pipeline-with-e2e-tests-via-testpypi-107k</guid>
      <description>&lt;p&gt;When building an open-source library that integrates with multiple LLM providers (OpenAI, Anthropic, Google), reliability matters. Users expect upgrades to be safe and predictable.&lt;/p&gt;

&lt;p&gt;This post describes the CI/CD setup I use for &lt;strong&gt;llm-api-adapter&lt;/strong&gt;. The key idea is simple: &lt;strong&gt;test not only the code, but the actual published package&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Strategy: two pipelines, three stages
&lt;/h2&gt;

&lt;p&gt;I use a dual-pipeline setup aligned with GitHub Flow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dev pipeline&lt;/strong&gt; — runs on every push to &lt;code&gt;dev&lt;/code&gt;. Its job is early feedback and validating the distribution process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Main pipeline&lt;/strong&gt; — runs on &lt;code&gt;main&lt;/code&gt; and version tags. Its job is stable, repeatable releases to PyPI.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most CI setups stop at unit or integration tests. This one goes further by validating the artifact installed from TestPyPI.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Dev pipeline: pre-flight validation
&lt;/h2&gt;

&lt;p&gt;The dev workflow is where most of the safety guarantees come from.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage A: Unit &amp;amp; Integration tests
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Executed with &lt;code&gt;pytest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tests are separated via markers (&lt;code&gt;unit&lt;/code&gt;, &lt;code&gt;integration&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Fast feedback on logic and provider integration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stage B: publish to TestPyPI
&lt;/h3&gt;

&lt;p&gt;After tests pass, the package is built and published to &lt;strong&gt;TestPyPI&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This step catches issues that tests alone cannot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Incorrect &lt;code&gt;pyproject.toml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Missing files in the source distribution&lt;/li&gt;
&lt;li&gt;Broken dependency declarations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stage C: E2E tests from TestPyPI
&lt;/h3&gt;

&lt;p&gt;This is the critical part of the pipeline.&lt;/p&gt;

&lt;p&gt;The job:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Waits for TestPyPI to index the new release&lt;/li&gt;
&lt;li&gt;Installs the package &lt;strong&gt;from TestPyPI&lt;/strong&gt;, not from source&lt;/li&gt;
&lt;li&gt;Pulls dependencies from the real PyPI&lt;/li&gt;
&lt;li&gt;Runs real end-to-end tests using live API keys
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;pip install --index-url https://test.pypi.org/simple/ \&lt;/span&gt;
            &lt;span class="s"&gt;--extra-index-url https://pypi.org/simple \&lt;/span&gt;
            &lt;span class="s"&gt;llm-api-adapter&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, the CI environment matches what users will experience after &lt;code&gt;pip install&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Main pipeline: controlled release
&lt;/h2&gt;

&lt;p&gt;Once the package is validated in &lt;code&gt;dev&lt;/code&gt;, changes move to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What runs on &lt;code&gt;main&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Full unit + integration test suite on every PR&lt;/li&gt;
&lt;li&gt;No publishing on pushes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What triggers a release
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A version tag (&lt;code&gt;vX.Y.Z&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Build and publish to &lt;strong&gt;PyPI&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Credentials handled via GitHub Secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the time a tag is pushed, the same artifact has already passed E2E tests via TestPyPI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this setup works
&lt;/h2&gt;

&lt;p&gt;Before publishing to PyPI, I know that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The code behaves correctly (unit + integration tests)&lt;/li&gt;
&lt;li&gt;The package is installable from a registry (TestPyPI)&lt;/li&gt;
&lt;li&gt;External LLM providers respond as expected (E2E tests)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, this approach prevents broken versions from ever being published to PyPI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If your library depends on external APIs, testing only the source code is not enough.&lt;/p&gt;

&lt;p&gt;Testing the &lt;strong&gt;published artifact&lt;/strong&gt; is what makes releases predictable and safe.&lt;/p&gt;

&lt;p&gt;The full setup is fully public and reproducible:&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Repository:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/Inozem/llm_api_adapter" rel="noopener noreferrer"&gt;https://github.com/Inozem/llm_api_adapter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;GitHub Actions workflows:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/Inozem/llm_api_adapter/tree/main/.github/workflows" rel="noopener noreferrer"&gt;https://github.com/Inozem/llm_api_adapter/tree/main/.github/workflows&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Question for you
&lt;/h2&gt;

&lt;p&gt;How do you usually set up CI for your open-source projects?&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>cicd</category>
      <category>devops</category>
    </item>
    <item>
      <title>Python LLM: reasoning is disabled by default in llm-api-adapter</title>
      <dc:creator>Sergey Inozemtsev</dc:creator>
      <pubDate>Sun, 14 Dec 2025 09:00:27 +0000</pubDate>
      <link>https://dev.to/inozem/python-llm-reasoning-is-disabled-by-default-in-llm-api-adapter-45h0</link>
      <guid>https://dev.to/inozem/python-llm-reasoning-is-disabled-by-default-in-llm-api-adapter-45h0</guid>
      <description>&lt;p&gt;Reasoning improves LLM output quality, but it is &lt;strong&gt;expensive&lt;/strong&gt; and often &lt;strong&gt;unnecessary&lt;/strong&gt;. Worse: most providers enable it implicitly or hide it behind non-obvious parameters.&lt;/p&gt;

&lt;p&gt;Result: developers pay for reasoning even when they don’t need it.&lt;/p&gt;




&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;p&gt;Reasoning is handled inconsistently across providers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI&lt;/strong&gt;: often enabled implicitly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini&lt;/strong&gt;: controlled via &lt;code&gt;thinkingConfig&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic (Claude)&lt;/strong&gt;: may enforce minimum reasoning tokens (e.g. 1024).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nano/Mini models&lt;/strong&gt;: sometimes impossible to disable reasoning entirely.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hidden costs&lt;/li&gt;
&lt;li&gt;provider-specific conditionals&lt;/li&gt;
&lt;li&gt;easy-to-miss misconfiguration&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  The Approach: Off by Default
&lt;/h3&gt;

&lt;p&gt;Starting from &lt;strong&gt;llm_api_adapter v0.2.3&lt;/strong&gt;, reasoning is &lt;strong&gt;disabled by default&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If it is not explicitly enabled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no reasoning tokens are used&lt;/li&gt;
&lt;li&gt;no extra cost is incurred&lt;/li&gt;
&lt;li&gt;existing code keeps working&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Costly features should be &lt;strong&gt;opt-in&lt;/strong&gt;, not opt-out.&lt;/p&gt;




&lt;h3&gt;
  
  
  Enabling Reasoning Explicitly
&lt;/h3&gt;

&lt;p&gt;When reasoning is actually required, it can be enabled via a single unified parameter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;String levels: &lt;code&gt;"none" | "low" | "medium" | "high"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Numeric values: &lt;code&gt;256&lt;/code&gt;, &lt;code&gt;512&lt;/code&gt;, &lt;code&gt;1024&lt;/code&gt;, &lt;code&gt;2048&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The adapter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;maps the value to provider-specific fields&lt;/li&gt;
&lt;li&gt;applies correct formats per API&lt;/li&gt;
&lt;li&gt;respects provider minimums&lt;/li&gt;
&lt;li&gt;prevents invalid configurations&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;llm-api-adapter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llm_api_adapter.universal_adapter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UniversalLLMAPIAdapter&lt;/span&gt;

&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Explain quantum computing simply.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# Pick a provider (same interface)
&lt;/span&gt;&lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UniversalLLMAPIAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;openai_api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# or
# adapter = UniversalLLMAPIAdapter(
#     organization="google",
#     model="gemini-2.5-pro",
#     api_key=google_api_key,
# )
&lt;/span&gt;
&lt;span class="c1"&gt;# or
# adapter = UniversalLLMAPIAdapter(
#     organization="anthropic",
#     model="claude-sonnet-4-5",
#     api_key=anthropic_api_key,
# )
&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;reasoning_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# off by default, enabled explicitly
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lower and predictable costs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No accidental reasoning usage&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cleaner application code&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unified control across OpenAI, Claude, and Gemini&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Repository
&lt;/h3&gt;

&lt;p&gt;Source code and documentation: &lt;a href="https://github.com/Inozem/llm-api-adapter" rel="noopener noreferrer"&gt;https://github.com/Inozem/llm-api-adapter&lt;/a&gt;&lt;/p&gt;

</description>
      <category>performance</category>
      <category>api</category>
      <category>python</category>
      <category>llm</category>
    </item>
    <item>
      <title>Structured prompts: how YAML cut my LLM costs by 30%</title>
      <dc:creator>Sergey Inozemtsev</dc:creator>
      <pubDate>Wed, 05 Nov 2025 09:34:01 +0000</pubDate>
      <link>https://dev.to/inozem/structured-prompts-how-yaml-cut-my-llm-costs-by-30-3a56</link>
      <guid>https://dev.to/inozem/structured-prompts-how-yaml-cut-my-llm-costs-by-30-3a56</guid>
      <description>&lt;p&gt;&lt;strong&gt;Result Summary:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Original Prompt&lt;/th&gt;
&lt;th&gt;YAML Prompt&lt;/th&gt;
&lt;th&gt;Savings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tokens&lt;/td&gt;
&lt;td&gt;355&lt;/td&gt;
&lt;td&gt;251&lt;/td&gt;
&lt;td&gt;−104 (−29.3%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;0.00001775 USD&lt;/td&gt;
&lt;td&gt;0.00001255 USD&lt;/td&gt;
&lt;td&gt;−29.3%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;I'll show how this works using a popular prompt taken from the internet, rewritten in YAML format to show whether structured phrasing can reduce token count without harming quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fewer tokens → lower cost per request.&lt;/li&gt;
&lt;li&gt;YAML forces clarity and structure, improving consistency of answers.&lt;/li&gt;
&lt;li&gt;Easier to maintain and version prompts in code.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  Original Prompt (PROMPT_A)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;prompt_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
You are the &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Architect Guide,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; specialized in assisting programmers who are experienced in individual module development but are looking to enhance their skills in understanding and managing entire project architectures.
Your primary roles and methods of guidance include:

Basics of Project Architecture: Start with foundational knowledge, focusing on principles and practices of inter-module communication and standardization in modular coding.
Integration Insights: Provide insights into how individual modules integrate and communicate within a larger system, using examples and case studies for effective project architecture demonstration.
Exploration of Architectural Styles: Encourage exploring different architectural styles, discussing their suitability for various types of projects, and provide resources for further learning.
Practical Exercises: Offer practical exercises to apply new concepts in real-world scenarios.
Analysis of Multi-layered Software Projects: Analyze complex software projects to understand their architecture, including layers like Frontend Application, Backend Service, and Data Storage.
Educational Insights: Focus on reviewing project readme files and source code for comprehensive understanding.
Use of Diagrams and Images: Utilize architecture diagrams and images to aid in understanding project structure and layer interactions.
Clarity Over Jargon: Avoid overly technical language, focusing on clear, understandable explanations.
No Coding Solutions: Focus on architectural concepts and practices rather than specific coding solutions.
Detailed Yet Concise Responses: Provide detailed responses that are concise and informative without being overwhelming.
Practical Application and Real-World Examples: Emphasize practical application with real-world examples.
Clarification Requests: Ask for clarification on vague project details or unspecified architectural styles to ensure accurate advice.
Professional and Approachable Tone: Maintain a professional yet approachable tone.
Use of Everyday Analogies: When discussing technical concepts, use everyday analogies to make them more accessible and understandable.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Optimized YAML Prompt (PROMPT_B)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="s"&gt;prompt_b = """&lt;/span&gt;
&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;Role: "Architect Guide"&lt;/span&gt;
  &lt;span class="s"&gt;Purpose: Help developers skilled in module-level coding grow into understanding and managing full project architectures.&lt;/span&gt;

&lt;span class="na"&gt;guidelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;- Teach project architecture fundamentals: modular communication, standardization, and structure.&lt;/span&gt;
  &lt;span class="s"&gt;- Explain module integration within larger systems using examples and case studies.&lt;/span&gt;
  &lt;span class="s"&gt;- Compare architectural styles, discuss suitability, and share learning resources.&lt;/span&gt;
  &lt;span class="s"&gt;- Provide practical exercises for real-world application.&lt;/span&gt;
  &lt;span class="s"&gt;- Analyze multi-layered software (frontend, backend, data storage) to illustrate architecture.&lt;/span&gt;
  &lt;span class="s"&gt;- Offer educational insights: review README files and source code for comprehension.&lt;/span&gt;
  &lt;span class="s"&gt;- Use diagrams and visuals to clarify system interactions.&lt;/span&gt;
  &lt;span class="s"&gt;- Prefer clarity over jargon; use plain, accessible language.&lt;/span&gt;
  &lt;span class="s"&gt;- Focus on architecture concepts — no coding solutions.&lt;/span&gt;
  &lt;span class="s"&gt;- Be detailed yet concise; avoid information overload.&lt;/span&gt;
  &lt;span class="s"&gt;- Include real-world examples for practical relevance.&lt;/span&gt;
  &lt;span class="s"&gt;- Ask clarifying questions about unclear project details.&lt;/span&gt;
  &lt;span class="s"&gt;- Maintain a professional, approachable tone.&lt;/span&gt;
  &lt;span class="s"&gt;- Use everyday analogies for complex concepts.&lt;/span&gt;

&lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;Clear, didactic, structured.&lt;/span&gt;
  &lt;span class="s"&gt;Encourage understanding of architecture as a living system, not just code components.&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Observation:&lt;/strong&gt; The output quality didn’t just stay the same — it improved. ChatGPT understood the intent better, and responses became more focused.&lt;/p&gt;




&lt;h3&gt;
  
  
  Experiment Code Example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Universal adapter
# using [llm-api-adapter](https://github.com/Inozem/llm_api_adapter)
# makes it easy to switch between different providers for testing
# can be installed easily via: pip install llm-api-adapter
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llm_api_adapter.universal_adapter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UniversalLLMAPIAdapter&lt;/span&gt;

&lt;span class="n"&gt;messages_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt_a&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Help me to create weather application.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;messages_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt_b&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Help me to create weather application.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UniversalLLMAPIAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5-nano&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;openai_api_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Runs
&lt;/span&gt;&lt;span class="n"&gt;resp_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;resp_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Token savings
&lt;/span&gt;&lt;span class="n"&gt;tokens_a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_tokens&lt;/span&gt;
&lt;span class="n"&gt;tokens_b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resp_b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_tokens&lt;/span&gt;
&lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokens_a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tokens_b&lt;/span&gt;
&lt;span class="n"&gt;rel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saved&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;tokens_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tokens_a&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROMPT A tokens:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens_a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PROMPT B tokens:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens_b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Saved tokens:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;saved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Relative saving: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rel&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Cost
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cost A:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost_input&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp_a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cost B:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp_b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost_input&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;resp_b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Prompt I used to generate the new YAML-formatted version:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Optimize this prompt into YAML format
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Conclusion:&lt;/strong&gt; Structured prompts are not just cleaner — they’re cheaper. Try YAML structuring in your next LLM project. It’s simple, reproducible, and can cut your costs by ~30%.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>performance</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Unifying 3 LLM APIs in Python: OpenAI, Anthropic &amp; Google with one SDK</title>
      <dc:creator>Sergey Inozemtsev</dc:creator>
      <pubDate>Tue, 04 Nov 2025 12:34:19 +0000</pubDate>
      <link>https://dev.to/inozem/unifying-3-llm-apis-in-python-openai-anthropic-google-with-one-sdk-4l2</link>
      <guid>https://dev.to/inozem/unifying-3-llm-apis-in-python-openai-anthropic-google-with-one-sdk-4l2</guid>
      <description>&lt;p&gt;A year ago, I released the first version of &lt;strong&gt;LLM API Adapter&lt;/strong&gt; — a lightweight SDK that unified OpenAI, Anthropic, and Google APIs under one interface.  &lt;/p&gt;

&lt;p&gt;It got &lt;strong&gt;7 ⭐ on GitHub&lt;/strong&gt; and valuable feedback from early users.&lt;br&gt;&lt;br&gt;
That was enough motivation to take it to the next level.  &lt;/p&gt;


&lt;h2&gt;
  
  
  What changed in the new version
&lt;/h2&gt;

&lt;p&gt;The new version (&lt;strong&gt;v0.2.2&lt;/strong&gt;) is now:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SDK-free&lt;/strong&gt; — it talks directly to provider APIs, no external dependencies.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified&lt;/strong&gt; — one &lt;code&gt;chat()&lt;/code&gt; interface for all models (OpenAI, Anthropic, Google).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent&lt;/strong&gt; — automatic token and cost tracking.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resilient&lt;/strong&gt; — consistent error taxonomy across providers (auth, rate, timeout, token limits).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tested&lt;/strong&gt; — 98% unit test coverage.
&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Example: chat with any LLM
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llm_api_adapter.universal_adapter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UniversalLLMAPIAdapter&lt;/span&gt;

&lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UniversalLLMAPIAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;system&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Be concise.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Explain how LLM adapters work.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Switching models is as simple as changing two parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UniversalLLMAPIAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                 &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# or
&lt;/span&gt;&lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UniversalLLMAPIAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                 &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Token &amp;amp; cost tracking example
&lt;/h2&gt;

&lt;p&gt;Every response now includes full token and cost accounting — no manual math needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llm_api_adapter.universal_adapter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UniversalLLMAPIAdapter&lt;/span&gt;

&lt;span class="n"&gt;google&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UniversalLLMAPIAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;google_api_key&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;chat_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost_input&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost_output&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cost_total&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;currency&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;512 tokens (0.00025 USD)
137 tokens (0.00010 USD)
649 tokens (0.00035 USD)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why I built this
&lt;/h2&gt;

&lt;p&gt;Working with multiple LLMs used to mean rewriting the same code — again and again.&lt;br&gt;&lt;br&gt;
Each SDK had its own method names, parameter names, and error classes.  &lt;/p&gt;

&lt;p&gt;So I built a unified interface that abstracts those details.&lt;br&gt;&lt;br&gt;
One adapter — one consistent experience.  &lt;/p&gt;


&lt;h2&gt;
  
  
  Join the project
&lt;/h2&gt;

&lt;p&gt;You can try it now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;llm-api-adapter
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docs &amp;amp; examples: &lt;a href="https://github.com/Inozem/llm_api_adapter" rel="noopener noreferrer"&gt;github.com/Inozem/llm_api_adapter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you like the idea — ⭐ star it or share feedback in Issues.  &lt;/p&gt;

</description>
      <category>python</category>
      <category>openai</category>
      <category>anthropic</category>
      <category>gemini</category>
    </item>
    <item>
      <title>LLM API Adapter SDK for Python</title>
      <dc:creator>Sergey Inozemtsev</dc:creator>
      <pubDate>Thu, 14 Nov 2024 10:49:57 +0000</pubDate>
      <link>https://dev.to/inozem/llm-api-adapter-sdk-for-python-2bck</link>
      <guid>https://dev.to/inozem/llm-api-adapter-sdk-for-python-2bck</guid>
      <description>&lt;p&gt;Here is my LLM API Adapter SDK for Python that allows you to easily switch between different LLM APIs.&lt;/p&gt;

&lt;p&gt;At the moment, it supports: OpenAI, Anthropic, and Google. And only the chat function (for now).&lt;/p&gt;

&lt;p&gt;It simplifies integration and debugging as it has standardized error classes across all supported LLMs.&lt;/p&gt;

&lt;p&gt;It also manages request parameters like temperature, max tokens, and other settings for better control.&lt;/p&gt;

&lt;p&gt;To use the adapter, you need to download the library and obtain API keys for the LLMs you want. In the code, I demonstrated how easy it is to use it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llm_api_adapter.messages.chat_message&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AIMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UserMessage&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;llm_api_adapter.universal_adapter&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;UniversalLLMAPIAdapter&lt;/span&gt;

&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;Prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a friendly assistant who explains complex concepts &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;in simple terms.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;UserMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi! Can you explain how artificial intelligence works?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;AIMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sure! Artificial intelligence (AI) is a system that can perform &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tasks requiring human-like intelligence, such as recognizing images &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;or understanding language. It learns by analyzing large amounts of &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data, finding patterns, and making predictions.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;UserMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;How does AI learn?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;gpt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UniversalLLMAPIAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-3.5-turbo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;openai_api_key&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;gpt_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gpt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_chat_answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gpt_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;claude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UniversalLLMAPIAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anthropic&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-3-haiku-20240307&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;anthropic_api_key&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;claude_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;claude&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_chat_answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claude_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;google&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;UniversalLLMAPIAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;organization&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;google&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-1.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;google_api_key&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;google_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_chat_answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;google_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have explained everything in more detail in the documentation: &lt;a href="https://github.com/Inozem/llm_api_adapter" rel="noopener noreferrer"&gt;https://github.com/Inozem/llm_api_adapter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the first stage, and it is just the beginning. I'd love to hear your thoughts, feedback, or ideas on where it could go next.&lt;/p&gt;

&lt;h1&gt;
  
  
  GenAi #Python #LLM #OpenAI #GPT #Anthropic #Claude #Google #Gemini
&lt;/h1&gt;

</description>
    </item>
  </channel>
</rss>
