<?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: Emilien Lancelot</title>
    <description>The latest articles on DEV Community by Emilien Lancelot (@docteurrs).</description>
    <link>https://dev.to/docteurrs</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%2F337944%2F49505872-226c-4b44-a831-97f6db11d37f.jpeg</url>
      <title>DEV Community: Emilien Lancelot</title>
      <link>https://dev.to/docteurrs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/docteurrs"/>
    <language>en</language>
    <item>
      <title>Build Your MCP Client: Because Copy-Pasting JSON Isn’t Engineering</title>
      <dc:creator>Emilien Lancelot</dc:creator>
      <pubDate>Thu, 12 Jun 2025 23:21:15 +0000</pubDate>
      <link>https://dev.to/docteurrs/build-an-mcp-client-because-copy-pasting-json-isnt-engineering-3d9k</link>
      <guid>https://dev.to/docteurrs/build-an-mcp-client-because-copy-pasting-json-isnt-engineering-3d9k</guid>
      <description>&lt;p&gt;&lt;em&gt;MCP is a lot more complex than AI influencers would have you believe. Sure, if by “understanding MCP” you mean putting together a JSON file that points to the server, then I guess you’re all set. And if you’re working on nuclear power plants, do let me know where, I’d rather not spend my holidays nearby.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What you’ll get from this article
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;I’ll explain the architecture and protocols used by MCP clients.&lt;/li&gt;
&lt;li&gt;Then I’ll give you the complete code of an HTTP MCP client and server. The client can connect to any HTTP MCP server and list tools. It won’t use an LLM as YOU will be the LLM.^^&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  I. The two types of MCP clients
&lt;/h2&gt;

&lt;p&gt;There are 2 types of MCP servers… Ideally, your client should be able to deal with both types.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. STDIO clients
&lt;/h3&gt;

&lt;p&gt;I’ll try to keep this short: &lt;em&gt;DO NOT DO THAT&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For some unknown, stupid reason, Anthropic pushed for a strange server communication (transport) protocol: STDIO&lt;/p&gt;

&lt;p&gt;This doesn’t rely on any HTTP socket bound to your localhost to interact, but instead uses the f*cking filesystem…&lt;/p&gt;

&lt;p&gt;We have built client/server software using localhost for 40 years. But hey… Let’s use the FS instead this time. This way our servers can’t be exposed on the internet, and no clients can connect except the ones on the same machines! YEAY&lt;br&gt;&lt;br&gt;
Very future-proof!&lt;/p&gt;

&lt;p&gt;For a client to use this stupid implementation, you first have to set the filepath of your server in the global MCP config.&lt;/p&gt;

&lt;p&gt;So your mcp.json config should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"todoist"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.pipedream.net/aabc9cef-a206-545f-8094-1b9fgdf09203/todoist"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"todo_local"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"uv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"--with"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"mcp[cli]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"run"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/path/to/your/server.py"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This config tells the MCP client to look at the file &lt;code&gt;/path/to/your/server.py&lt;/code&gt; which contains the source code of your server. By reading its content, it will discover the different tools that can be “served”.&lt;/p&gt;

&lt;p&gt;When the client starts, its first task is to boot the server. YES. You read that right. The client starts the server… It makes total sense! (No, it doesn’t)&lt;/p&gt;

&lt;p&gt;When the client exits, it must stop the server process that was forked during boot.&lt;/p&gt;

&lt;p&gt;To summarize: &lt;em&gt;DON’T WRITE STDIO SERVERS&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As for the client, you can use a software bridge to port the stupid STDIO transport to HTTP (better). For example, mcp-proxy does exactly that. But you can find many more on Google. This way, you only have to care about one transport mechanism and not two.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. HTTP clients
&lt;/h3&gt;

&lt;p&gt;Obviously, HTTP clients deal with HTTP servers. It’s way better than STDIO as you will be able to expose servers on the internet, allowing remote clients to connect from anywhere.&lt;/p&gt;

&lt;p&gt;This is what the future will look like. We’ll all have our own LLM connecting to remote MCP servers and take actions on our behalf. Like ordering food, booking flights, etc.&lt;/p&gt;

&lt;p&gt;As if things weren’t complicated enough, I also have to specify the exact HTTP protocol used by these servers: SSE&lt;/p&gt;

&lt;h3&gt;
  
  
  Never heard of SSE?
&lt;/h3&gt;

&lt;p&gt;It’s Server-Sent Events, meaning that the server can push messages to the clients without the client having to request anything. It’s like an HTTP socket but unidirectional (server =&amp;gt; client only).&lt;/p&gt;

&lt;p&gt;What’s dumb, IMHO, is that your client makes a POST request to get the list of available tools, and the server responds using SSE. So it’s kind of weird to use a protocol designed to push data to the client proactively… only when it’s explicitly asked for data.&lt;/p&gt;

&lt;p&gt;I may see a point in the future where MCP servers will send some data to the client out of the blue, and the client could react to it. But as of today, it feels very strange to ask for data and retrieve it using SSE.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;Also, the SSE protocol specifies that each message should end with a double \n and start with data:.&lt;/p&gt;

&lt;p&gt;So, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data: message1\n
\n
data: message2\n
\n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, what happens if the server sends some unknown unstructured data containing any number of consecutive line endings?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It will go BOOM is what will happen.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You could send the message as a base64 string to fix this. But this would imply that the client can recognise base64 and decode it. Something that isn’t going to happen anytime soon unless it is specified in Anthropic’s MCP spec. So we’ll see… Just keep in mind that the server can’t send random stuff, as it might break the client's ability to read messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  II. Creating a demo server
&lt;/h2&gt;

&lt;p&gt;To create our client, we first need a basic server implementing the MCP spec. This way we’ll have the ability to learn what are the correct JSON responses.&lt;/p&gt;

&lt;p&gt;You can follow &lt;a href="https://mcp-framework.com/docs/http-quickstart/" rel="noopener noreferrer"&gt;this good tutorial&lt;/a&gt;. It’s using NodeJS, but it’s very easy to understand and use.&lt;br&gt;&lt;br&gt;
I’ll add everything here for posterity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install the MCP framework globaly (there's a CLI)&lt;/span&gt;
npm i &lt;span class="nt"&gt;-g&lt;/span&gt; mcp-framework

&lt;span class="c"&gt;# Create the boilerplate code of an MCP weather server&lt;/span&gt;
mcp create weather-http-server &lt;span class="nt"&gt;--http&lt;/span&gt; &lt;span class="nt"&gt;--port&lt;/span&gt; 1337 &lt;span class="nt"&gt;--cors&lt;/span&gt;

&lt;span class="nb"&gt;cd &lt;/span&gt;weather-http-server

&lt;span class="c"&gt;# Add a tool to the server&lt;/span&gt;
mcp add tool weather
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update src/tools/WeatherTool.ts with this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MCPTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mcp-framework&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;WeatherInput&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WeatherTool&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;MCPTool&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WeatherInput&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;weather&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Get weather information for a city&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;City name to get weather for&lt;/span&gt;&lt;span class="dl"&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;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;city&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;WeatherInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// In a real scenario, this would call a weather API&lt;/span&gt;
    &lt;span class="c1"&gt;// For now, we return this sample data&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;city&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Sunny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;humidity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;45&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;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;WeatherTool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Build the project&lt;/span&gt;
npm run build

&lt;span class="c"&gt;# Start the server&lt;/span&gt;
npm start

&lt;span class="c"&gt;# Your HTTP MCP server is now running at http://localhost:1337/mcp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can try to connect with existing clients like Claude or Copilot. Simply set the above server URL in the MCP global config file, and you should be good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  III. Building an MCP client
&lt;/h2&gt;

&lt;p&gt;Now that I’ve stopped ranting about what’s dumb in MCP and we have a working server, let’s write the client.&lt;/p&gt;

&lt;p&gt;But because I want to iterate rapidly, I will not include any LLM in this piece of code. This means that doing the tool calling and parsing will be done manually. DON’T PANIC. The longest methods are not the ones that interest us the most. It’s only parsing.&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="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
Simple MCP (Model Context Protocol) HTTP Client
Connects to remote MCP servers via Streamable HTTP transport and provides a basic chat interface.
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&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;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&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="n"&gt;Optional&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dataclass&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urljoin&lt;/span&gt;


&lt;span class="nd"&gt;@dataclass&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;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="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
    &lt;span class="n"&gt;input_schema&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MCPClient&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;server_url&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;server_url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rstrip&lt;/span&gt;&lt;span class="p"&gt;(&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="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&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;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;Tool&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&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="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Initialize connection with the MCP server&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Initialize the protocol
&lt;/span&gt;            &lt;span class="n"&gt;init_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_make_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initialize&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;protocolVersion&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;2025-03-26&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;capabilities&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;tools&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="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;clientInfo&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;name&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;yacana-mcp-client&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;version&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;0.1.0&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;server_info&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;init_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;serverInfo&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connected to MCP server: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;server_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&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;Unknown&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; v&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;server_info&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;version&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;Unknown&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&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="c1"&gt;# List available tools
&lt;/span&gt;            &lt;span class="n"&gt;tools_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_make_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools/list&lt;/span&gt;&lt;span class="sh"&gt;"&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_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tool_info&lt;/span&gt; &lt;span class="ow"&gt;in&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&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tool&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="n"&gt;tool_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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="n"&gt;tool_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="n"&gt;input_schema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tool_info&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;inputSchema&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;self&lt;/span&gt;&lt;span class="p"&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&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="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool&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;Available tool: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&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;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;tools&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;No tools available on this server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initialized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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;Failed to initialize MCP client: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&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="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_make_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;method&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;params&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;=&lt;/span&gt; &lt;span class="bp"&gt;None&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Make a JSON-RPC request to the MCP server using Streamable HTTP transport&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;request_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;payload&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;jsonrpc&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;2.0&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;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;method&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;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;params&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;

        &lt;span class="n"&gt;headers&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;Content-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;application/json&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;Accept&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;application/json, text/event-stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;# Add session ID header if we have one
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mcp-Session-Id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;

        &lt;span class="k"&gt;try&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&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="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;# Check if server returned a session ID during initialization
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;initialize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mcp-Session-Id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&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;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt; &lt;span class="o"&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mcp-Session-Id&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Server assigned session ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&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="c1"&gt;# Handle different response types
&lt;/span&gt;            &lt;span class="n"&gt;content_type&lt;/span&gt; &lt;span class="o"&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;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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-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="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="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;application/json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;content_type&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="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;MCP Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&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="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text/event-stream&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Handle SSE response - for simplicity, we'll read the first JSON response
&lt;/span&gt;                &lt;span class="c1"&gt;# In a production client, you'd want to properly handle the SSE stream
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_handle_sse_response&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="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# If no specific content type, try to parse as JSON
&lt;/span&gt;                &lt;span class="k"&gt;try&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="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;MCP Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&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="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;Unexpected response format: &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;text&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="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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;e&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;status_code&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;405&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;This server is too old and doesn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t fully support SSE but probably a mix of HTTP+SEE. This is not supported by this client...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;HTTP Error &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&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;status_code&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;e&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;text&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_handle_sse_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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="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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle Server-Sent Events response (simplified implementation)&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="c1"&gt;# This is a simplified SSE parser - in production you'd want a proper SSE client
&lt;/span&gt;        &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&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;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&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;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;data&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;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;  &lt;span class="c1"&gt;# Remove 'data: ' prefix
&lt;/span&gt;                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;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;result&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;MCP Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;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;error&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&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="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No valid JSON-RPC response found in SSE stream&lt;/span&gt;&lt;span class="sh"&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;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_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;arguments&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Call a tool on the MCP server&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initialized&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Client not initialized&lt;/span&gt;&lt;span class="sh"&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;tool_name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&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;Tool &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; not available. Available tools: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&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="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_make_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tools/call&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;name&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_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arguments&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_available_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;List&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get list of available tool names&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keys&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;get_tool_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get information about a specific tool&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&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;disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Explicitly disconnect from the server&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;headers&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;Mcp-Session-Id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;pass&lt;/span&gt;  &lt;span class="c1"&gt;# Ignore errors during cleanup
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&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;detect_tool_calls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;List&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Detect tool calls in the format: @tool_name({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;param1&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="s"&gt;value1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, ...})
    Returns a list of dicts: {&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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="s"&gt;arguments&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

    &lt;span class="n"&gt;tool_calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;@([\w-]+)\((\{.*?\})\)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finditer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&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;match&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;tool_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&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="n"&gt;args_str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;arguments&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;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&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="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Arguments must be a JSON object&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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;Failed to parse arguments for @&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&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;e&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="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid param. Retry.&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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&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_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arguments&lt;/span&gt;&lt;span class="sh"&gt;"&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tool_calls&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Main chat loop&lt;/span&gt;&lt;span class="sh"&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;=== Yacana MCP Client ===&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Get server URL from user
&lt;/span&gt;    &lt;span class="n"&gt;server_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Enter MCP server URL (e.g., https://mcp.deepwiki.com/mcp): &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;server_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;server_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://mcp.deepwiki.com/mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Initialize client
&lt;/span&gt;    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MCPClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initialize&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;Failed to connect to MCP server. Exiting.&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="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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;=== Chat Loop Started ===&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;Type your messages below. Use @tool_name(arg1=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;value1&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;) to call tools.&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;Type &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; to exit, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tools&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; to list available tools.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;user_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;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;strip&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;user_input&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="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quit&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;exit&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user_input&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="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tools&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Available tools:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_available_tools&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;  No tools available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;else&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;tool_name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_available_tools&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                            &lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_tool_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&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;tool_name&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;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&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="c1"&gt;# Show required parameters
&lt;/span&gt;                            &lt;span class="n"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;input_schema&lt;/span&gt;
                            &lt;span class="k"&gt;if&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="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                                &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;schema&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="n"&gt;params_info&lt;/span&gt; &lt;span class="o"&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                                &lt;span class="n"&gt;param_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&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;any&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                                &lt;span class="n"&gt;required_marker&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
                                &lt;span class="n"&gt;params_info&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="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;k&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;param_type&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;required_marker&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;    Parameters: &lt;/span&gt;&lt;span class="si"&gt;{&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params_info&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;continue&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;

                &lt;span class="c1"&gt;# Detect tool calls in the message
&lt;/span&gt;                &lt;span class="k"&gt;try&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="nf"&gt;detect_tool_calls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;continue&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tool_calls&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Detected tool calls:&lt;/span&gt;&lt;span class="sh"&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;call&lt;/span&gt; &lt;span class="ow"&gt;in&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;tool_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                        &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arguments&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Calling &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; with &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;arguments&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="k"&gt;try&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="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_name&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="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;  Result: &lt;/span&gt;&lt;span class="si"&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="n"&gt;indent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&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="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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;  Error calling &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;tool_name&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;e&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="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="c1"&gt;# In a real implementation, you would send this to your LLM
&lt;/span&gt;                    &lt;span class="c1"&gt;# and let the LLM decide whether to call tools
&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;Assistant: I received your message. In a full implementation, this would be processed by an LLM that could decide to call tools.&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;For now, use @tool_name({&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;param_name&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}) syntax to test tool calls.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&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;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&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="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;disconnect&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="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Goodbye!&lt;/span&gt;&lt;span class="sh"&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;__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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="nf"&gt;main&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;DON’T PANIC!&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
You’ll be okay! I know it feels like a loooot of code. But most of it is to perform the task of the LLM programmatically.&lt;/p&gt;

&lt;p&gt;Let me explain, and you’ll be fine. I promised.&lt;/p&gt;

&lt;p&gt;Starting from the top:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;strong&gt;init&lt;/strong&gt;()&lt;/strong&gt;: The constructor takes the server URL as input.
initialize(): This method will try to connect to the server and send greetings. Upon success, it will request available tools from the server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;_make_request()&lt;/strong&gt;: This method is used to interact with the server. It’s used to do the initial connection and list available tools. Also, we use request to store the session token that identifies our client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;_handle_sse_response()&lt;/strong&gt;: This method will parse the response from the server. The SSE protocol starts with the bytes “data: ” so we strip this from the string. Also, the message delimiters are \n\n so we can go through it line by line. Each line should be a different message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;call_tool()&lt;/strong&gt;: This method will ask the MCP server to call a tool by its name and with the parameters you gave using the client’s CLI.
detect_tool_calls(): This function should be entirely replaced by classic LLM “function calling”. For this demo, I parse the tool name and its arguments programmatically by following the convention@tool_name({"arg_name": "value1"}). However, this should be replaced by a “function call” as specified by OpenAI spec’s.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;main()&lt;/strong&gt;: The main chat loop.
I hope you survived. Open the code in your favorite Python IDE, and I’m sure it will look less scary!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  IV. Using the client
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Connecting to our local demo server
&lt;/h3&gt;

&lt;p&gt;Now that we have a client and a server. Let’s connect them!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 client.py
=== Demo MCP Client ===
Enter MCP server URL (e.g., https://mcp.deepwiki.com/mcp): http://localhost:1337/mcp
Server assigned session ID: 03da4b6c-124a-4274-8e25-8b53e41a5e37
Connected to MCP server: weather-http-server v0.0.1
Available tool: example_tool - An example tool that processes messages
Available tool: weather - Get weather information for a city

=== Chat Loop Started ===
Type your messages below. Use @tool_name(arg1="value1") to call tools.
Type 'quit' to exit, 'tools' to list available tools.


&amp;gt; @weather({"city": "paris"})

Detected tool calls:
  Calling weather with {'city': 'Paris'}
  Result: {
  "content": [
    {
      "type": "text",
      "text": "{\"city\":\"Paris\",\"temperature\":22,\"condition\":\"Sunny\",\"humidity\":45}"
    }
  ]
}

&amp;gt; ^C
Goodbye!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above output, we connected to &lt;code&gt;http://localhost:1337/mcp&lt;/code&gt; which is our local server.&lt;/p&gt;

&lt;p&gt;We can see that the server sent information about the available tools. We ask for the weather tool with the argument “Paris”: @weather({"city": "Paris"}) . Then the server answers with the fake response, which should be added to the LLM context if we had one.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Connecting to a real remote MCP server
&lt;/h3&gt;

&lt;p&gt;Do you know DeepWiki? It’s a tool that scans GitHub repositories and allows you to chat with an LLM about the repo’s content. I hate to say this because it was made by the guys who made the Devin crap. But this time it works quite well…&lt;/p&gt;

&lt;p&gt;They have a fully working MCP server with 3 tools. Let’s try to connect to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 client.py
=== Yacana MCP Client ===
Enter MCP server URL (e.g., https://mcp.deepwiki.com/mcp): https://mcp.deepwiki.com/mcp
Server assigned session ID: 68d8083a519da262c385212ad6aeb08c91733479d484af2ad7065fa9614678e2
Connected to MCP server: DeepWiki v0.0.1
Available tool: read_wiki_structure - Get a list of documentation topics for a GitHub repository
Available tool: read_wiki_contents - View documentation about a GitHub repository
Available tool: ask_question - Ask any question about a GitHub repository

=== Chat Loop Started ===
Type your messages below. Use @tool_name(arg1="value1") to call tools.
Type 'quit' to exit, 'tools' to list available tools.


&amp;gt; tools

Available tools:
  - read_wiki_structure: Get a list of documentation topics for a GitHub repository
    Parameters: repoName(string)*
  - read_wiki_contents: View documentation about a GitHub repository
    Parameters: repoName(string)*
  - ask_question: Ask any question about a GitHub repository
    Parameters: repoName(string)*, question(string)*

&amp;gt; @ask_question({"repoName": "rememberSoftwares/yacana", "question": "What is yacana?"})

Detected tool calls:
  Calling ask_question with {'repoName': 'rememberSoftwares/yacana', 'question': 'What is Yacana?'}
  Result: {
  "content": [
    {
      "type": "text",
      "text": "Yacana is an open-source, task-driven multi-agent framework designed to simplify the creation of LLM-powered applications. It focuses on chaining tasks together rather than solely on agents, offering a flexible and intuitive API for both beginners and advanced AI users. \n\n## Core Concepts\n\nThe Yacana framework revolves around two primary classes: `Agent` and `Task`.\n\n### Agent\nAn `Agent` in Yacana represents an LLM (Large Language Model) instance. You can create agents using either `OllamaAgent` for local models or `OpenAiAgent` for OpenAI-compatible APIs. \n\n*   **`OllamaAgent`**: Used for interacting with Ollama inference servers. It requires a `name` and `model_name`, and can optionally take a `system_prompt` and `endpoint`. \n*   **`OpenAiAgent`**: Used for interacting with OpenAI-compatible APIs. It also requires a `name` and `model_name`, along with an `api_token` and `endpoint`. \n\nAgents maintain their own conversation history, allowing for contextual interactions across multiple tasks. \n\n### Task\nA `Task` is the fundamental execution unit in Yacana. It encapsulates a prompt and is executed by an `Agent`. \n\n*   To create a task, you provide a `prompt` (the instruction for the LLM) and an `agent` instance. \n*   The `solve()` method of a `Task` executes the prompt using the assigned agent and returns a `GenericMessage` object, which contains the LLM's response in its `content` attribute. \n\n## Key Features\n\nYacana offers several features to enhance LLM interactions:\n\n*   **Task Chaining**: Tasks can be linked together by reusing the same agent instance, allowing for multi-turn conversations where each task builds upon the agent's history. \n*   **Tool Calling**: Yacana enables LLMs to invoke Python functions (tools) to perform specific actions. This is a core strength, allowing even small open-source models to use tools effectively. \n    *   Tools are defined using the `Tool` class, specifying a `tool_name`, `function_description`, and a `function_ref` (the Python function to be called). \n    *   Unlike other frameworks, tools are made available at the `Task` level, not permanently assigned to agents, reducing \"noise\" and improving relevance. \n*   **Structured Output**: This feature allows you to define a Pydantic model for the LLM's response, ensuring type-safe JSON outputs. \n*   **Streaming Responses**: Yacana supports token-by-token processing of LLM responses, useful for real-time user feedback. \n*   **Multi-Agent Collaboration**: The framework supports orchestrating conversations between multiple agents, enabling complex problem-solving scenarios. \n\n## Installation\nYou can install Yacana using pip: `pip install yacana` \n\n## Example Usage\n\nHere's a basic example demonstrating agent creation and task solving:\n```

python\nfrom yacana import OllamaAgent, Task\n\n# Create agent\nagent = OllamaAgent(\n    \"Research Assistant\", \n    \"llama3.1:8b\",\n    system_prompt=\"You are a helpful research assistant\"\n) \n\n# Execute tasks with chaining\nTask(\"What is machine learning?\", agent).solve() \nclarification = Task(\"Explain that in simpler terms\", agent).solve().content \n\nprint(f\"Simplified explanation: {clarification}\") \n\n# Check conversation history\nagent.history.pretty_print() \n

```\n\n## Notes\nYacana was initially designed for Ollama but now supports any OpenAI-compatible endpoints.  The framework is open-source under the MIT license. \n\nWiki pages you might want to explore:\n- [Quick Start Guide (rememberSoftwares/yacana)](/wiki/rememberSoftwares/yacana#2.1)\n- [Advanced Features (rememberSoftwares/yacana)](/wiki/rememberSoftwares/yacana#6)\n\nView this search on DeepWiki: https://deepwiki.com/search/what-is-yacana_e1a73f0f-7ad2-4c53-8028-9513cca5c37e\n"
    }
  ]
}

&amp;gt; ^C
Goodbye!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above output, we connected to &lt;code&gt;https://mcp.deepwiki.com/mcp&lt;/code&gt;.   During initialization, we retrieve and print the list of available tools.&lt;/p&gt;

&lt;p&gt;We use the tools command to print information about the tools parameters. We then call the ask_question tool with two parameters: repoName and question. The MCP server answers our question.&lt;/p&gt;

&lt;h2&gt;
  
  
  V. Conclusion
&lt;/h2&gt;

&lt;p&gt;MCP is not that simple, and the protocol will evolve. I guess that the STDIO transport will slowly die. Hopefully.&lt;/p&gt;

&lt;p&gt;For authentication, you can use the headers member variable to deal with different authorisation flows.&lt;/p&gt;




&lt;p&gt;In the meantime, if you want to have fun with LLMs locally or otherwise, go try &lt;a href="https://github.com/rememberSoftwares/yacana" rel="noopener noreferrer"&gt;Yacana&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8e22nlat2a58laoq5zf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq8e22nlat2a58laoq5zf.png" alt="Image description" width="407" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It’s an agent framework that focuses on simplicity with powerful features.   It will soon have MCP support. ;-)&lt;/p&gt;

&lt;p&gt;I’ll update this article when I get deeper into this.&lt;/p&gt;

&lt;p&gt;HF&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>llm</category>
      <category>ai</category>
    </item>
    <item>
      <title>Making VLLM work on WSL2</title>
      <dc:creator>Emilien Lancelot</dc:creator>
      <pubDate>Fri, 17 Jan 2025 13:24:54 +0000</pubDate>
      <link>https://dev.to/docteurrs/making-vllm-work-on-wsl2-482e</link>
      <guid>https://dev.to/docteurrs/making-vllm-work-on-wsl2-482e</guid>
      <description>&lt;p&gt;Running a small llama3 model for demonstration purposes on WSL2 using VLLM&lt;/p&gt;

&lt;p&gt;➡️ Read this &lt;a href="https://medium.com/@docteur_rs/making-vllm-work-on-wsl2-61182235424f" rel="noopener noreferrer"&gt;article on Medium&lt;/a&gt; for a better experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;WSL version 2&lt;/li&gt;
&lt;li&gt;Python: 3.8–3.12&lt;/li&gt;
&lt;li&gt;GPU ABOVE GTX1080 (Did not manage to make it work on 1080 as it told me the hardware was too old. :-( Ollama is less picky).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Preflight checks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Checking NVCC
&lt;/h3&gt;

&lt;p&gt;In WSL, do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nvcc --version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;▶️ Command not found?&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing NVCC: Nvidia Drivers Installation
&lt;/h3&gt;

&lt;p&gt;Visit &lt;a href="https://developer.nvidia.com/cuda-downloads?target_os=Linux&amp;amp;target_arch=x86_64&amp;amp;Distribution=WSL-Ubuntu&amp;amp;target_version=2.0&amp;amp;target_type=deb_network" rel="noopener noreferrer"&gt;Nvidia's official website&lt;/a&gt; to download and install the Nvidia drivers for WSL. Choose Linux &amp;gt; x86_64 &amp;gt; WSL-Ubuntu &amp;gt; 2.0 &amp;gt; deb (network)  &lt;/p&gt;

&lt;p&gt;Follow the instructions provided on the page.  &lt;/p&gt;

&lt;p&gt;Add the following lines to your .bashrc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export PATH="/usr/local/cuda-12.6/bin:$PATH"
export LD_LIBRARY_PATH="/usr/local/cuda-12.6/lib64:$LD_LIBRARY_PATH"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;⚠️ ⚠️ ⚠️ Check the content of "/usr/local" to be sure that you do have the "cuda-12.6" folder. Yours might have a different version.  &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Reload your configuration and check that all is working as expected&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source ~/.bashrc
nvcc --version
nvidia-smi.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ "nvidia-smi" isn't available on WSL so just verify that the .exe one detects your hardware. Both commands should displayed gibberish but no apparent errors.  &lt;/p&gt;

&lt;h2&gt;
  
  
  3. Creating the environment
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;--version&lt;/span&gt; &lt;span class="c"&gt;# copy the version&lt;/span&gt;
conda create &lt;span class="nt"&gt;-n&lt;/span&gt; myenv &lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.10 &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="c"&gt;# Update the python version with your own&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;▶️ Don't have conda?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/miniconda3
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh &lt;span class="nt"&gt;-O&lt;/span&gt; ~/miniconda3/miniconda.sh
bash ~/miniconda3/miniconda.sh &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/miniconda3
&lt;span class="nb"&gt;rm&lt;/span&gt; ~/miniconda3/miniconda.sh
&lt;span class="nb"&gt;source&lt;/span&gt; ~/miniconda3/bin/activate

conda create &lt;span class="nt"&gt;-n&lt;/span&gt; myenv &lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.10 &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="c"&gt;# Update the python version with your own&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Installing VLLM
&lt;/h2&gt;

&lt;p&gt;Installing VLLM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install vllm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trying to start the inference server with a tiny LLM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;vllm serve facebook/opt-125m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;▶️ Runtime crash of VLLM?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    from torch._C import *  # noqa: F403
    ^^^^^^^^^^^^^^^^^^^^^^
ImportError: /home/xxxx/vllm_serve/lib/python3.11/site-packages/torch/lib/../../nvidia/cusparse/lib/libcusparse.so.12: undefined symbol: __nvJitLinkComplete_12_4, version libnvJitLink.so.12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, this is where sh*t hits the fan. I recommend that you try the fix below. However, if it doesn't work, I can only wish you good luck. It's another one of those technologies that have error messages written by a depressive data scientist. So just scroll to the top of the stacktrace and hope for the best. Google is your friend. Have faith.  &lt;/p&gt;

&lt;h3&gt;
  
  
  Potential VLLM serve fix:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python -m pip uninstall torch torchvision torchaudio
python -m pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu121
vllm serve facebook/opt-125m # Should be working now...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/pytorch/pytorch/issues/111469" rel="noopener noreferrer"&gt;https://github.com/pytorch/pytorch/issues/111469&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Running VLLM
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Let's try with a tiny Facebook LLM.&lt;/em&gt;  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create an account on Hugging Face and then create a api-key.&lt;br&gt;
Then go to the model page you want to try out.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For us: &lt;a href="https://huggingface.co/meta-llama/Llama-3.2-1B-Instruct" rel="noopener noreferrer"&gt;https://huggingface.co/meta-llama/Llama-3.2-1B-Instruct&lt;/a&gt;  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;At the top of the page there will be some kind of form you must apply to.  &lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9407a6jkg6fco6jphnm4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9407a6jkg6fco6jphnm4.png" alt="Accepting the Facebook agreement. For the one time they do something good for humanity." width="800" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When done, you'll get an email 10 minutes later telling you you've got access. 🎉  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;To connect to the VLLM server try this piece of code:
&lt;/li&gt;
&lt;/ol&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;vllm&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLM&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;huggingface_hub&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;login&lt;/span&gt;

&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;REPLACE-ME-WITH-YOUR-HUGGING-FACE_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Replace this !
&lt;/span&gt;
&lt;span class="c1"&gt;# Load the LLama 3 8B model
&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LLM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meta-llama/Llama-3.2-1B-Instruct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;transformers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;

&lt;span class="c1"&gt;# Load the tokenizer
&lt;/span&gt;&lt;span class="n"&gt;tokenizer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AutoTokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_pretrained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;meta-llama/Llama-3.2-1B-Instruct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Prepare the input message
&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="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;What is the capital of France?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="n"&gt;formatted_prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokenizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply_chat_template&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="n"&gt;tokenize&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;add_generation_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Generate the output
&lt;/span&gt;&lt;span class="n"&gt;output&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="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatted_prompt&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;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it and in all the mess you'll see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text='The capital of France is Paris.'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Serving using OpenAI style
&lt;/h3&gt;

&lt;p&gt;VLLM is OpenAI compatible to some extent. We can use this ability to use the OpenAI client instead of the Hugging Face one.&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;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000/v1&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;token-abc123&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;completion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&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;meta-llama/Llama-3.2-1B-Instruct&lt;/span&gt;&lt;span class="sh"&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="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;Hello!&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="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;completion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Should output something like:&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="nc"&gt;ChatCompletionMessage&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="s"&gt;Hello! How can I assist you today?&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;refusal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;audio&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;function_call&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&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="p"&gt;[])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Manage installed models
&lt;/h2&gt;

&lt;h3&gt;
  
  
  List models
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ls ~/.cache/huggingface/hub
models--facebook--opt-125m  version.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Delete models
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rm -rf ~/.cache/huggingface/hub/models--&amp;lt;model-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;In need of a simple yet effective multi-agent framework with working tool calling for any LLMs ?&lt;br&gt;
I've got you covered! Go try &lt;a href="https://remembersoftwares.github.io/yacana/" rel="noopener noreferrer"&gt;Yacana&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fld9tvs3d0aohr0dgn60v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fld9tvs3d0aohr0dgn60v.png" alt="Yacana logo" width="407" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;HF.  &lt;/p&gt;

</description>
      <category>vllm</category>
      <category>ia</category>
      <category>inference</category>
      <category>llm</category>
    </item>
    <item>
      <title>The long road to an Agentic framework that Won’t Burn You Out</title>
      <dc:creator>Emilien Lancelot</dc:creator>
      <pubDate>Wed, 25 Sep 2024 22:11:12 +0000</pubDate>
      <link>https://dev.to/docteurrs/the-long-road-to-an-agentic-framework-that-wont-burn-you-out-15la</link>
      <guid>https://dev.to/docteurrs/the-long-road-to-an-agentic-framework-that-wont-burn-you-out-15la</guid>
      <description>&lt;p&gt;▶️▶️ &lt;a href="https://itnext.io/the-long-road-to-an-agentic-framework-that-wont-burn-you-out-ec2933cf8c1f" rel="noopener noreferrer"&gt;Read this article on Medium.&lt;/a&gt;◀️◀️&lt;/p&gt;

&lt;h1&gt;
  
  
  I. The Leak That Sparked Open Source Innovation
&lt;/h1&gt;

&lt;p&gt;Like most of us, I started my AI journey with a simple &lt;code&gt;llama.cpp&lt;/code&gt; wrapper. It was a simple chat loop with no memory between messages but… &lt;strong&gt;IT SPOKE!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;And that it did very well. This was an exciting perspective that pulled us back in time. In 2008 exactly. At a time when the first IronMan aired in theaters and speaking AI was to become one of the most trending things in movies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ot6rzfd2vsjklmmb80x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ot6rzfd2vsjklmmb80x.png" alt="Jarvis in IronMan 1 that blew our mind away"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since then we have had many speaking "smart" assistants on smartphones. Even though it was fun, I must have used it no more than 10 minutes in total during the 9 years I owned my Galaxy-S4.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffg5smxh19doe44urpafp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffg5smxh19doe44urpafp.png" alt="XKCD"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The human AI…But speaking AIs continued spawning, each time getting us closer to a functional Jarvis. But the more we would come close to it, the more it fainted. Showing limitations that killed the experience quicker than it took us to deactivate the power-hungry functionality. No Google Home or Alexa would save us from dumb AIs.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;But then it happened!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ChatGPT came out of nowhere and a new spark was created. A shimmering light showed us a glimpse of hope. Hope that we didn't know was to be a new revolution, a gift we weren't ready to receive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgjqv9n5lqtdm9b25jra.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjgjqv9n5lqtdm9b25jra.png" alt="Spark"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But only a spark it was. And to get us all back in the game we needed a fire!&lt;/p&gt;

&lt;p&gt;Hopefully, someone took the responsibility and lit the blaze.&lt;/p&gt;

&lt;p&gt;Some guy, which we still don't know anything about, leaked the first Large Language Model (LLM) on 4Chan. It was Facebook's, for which I won't cry as they have fed on us for far too long. This long-coming reckoning was to be the spark that lit the entire forest inside developers! We could boot an LLM directly on our computers! We would speak to it, and it responded. And damn it was good. At the same time, image generation was on the loose, too, with Midjourney and the first open source models being released every week. Not one day had passed since a new web UI was created. Not one day had passed without one new model iteration being revealed.&lt;/p&gt;

&lt;p&gt;The Jarvis dream was back, and this time, it was real!&lt;/p&gt;

&lt;h1&gt;
  
  
  II. I tested them all…
&lt;/h1&gt;

&lt;p&gt;To use an LLM you need 3 things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An LLM&lt;/li&gt;
&lt;li&gt;An inference server to run the above LLM&lt;/li&gt;
&lt;li&gt;A way to interact with the above inference server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an inference server, I started with LMstudio which had the good taste of displaying the amount of RAM needed to run the model. A fitting functionality that showed once more that successful tech is driven by small details.&lt;/p&gt;

&lt;p&gt;I then switched to Ollama because, as a developer, I'm more into command lines. Also, the performance was a bit better, and layers would be loaded in RAM by the software itself. Less to configure yourself is always better as you don't have infinite time to deal with the ever-changing landscape of AI.&lt;/p&gt;

&lt;p&gt;Both LMstudio and Ollama had chat integration but what to do with it?&lt;/p&gt;

&lt;p&gt;The Jarvis ideal could only be achieved if we had a way to programmatically interact with the LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  CrewAI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkeg98e3mcqqm8842k43u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkeg98e3mcqqm8842k43u.png" alt="CrewAI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Many of us started there. The notion of Agents embodying one instance of an LLM had us started with CrewAI for one reason. A reason that existed since the dawn of time!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;It was EASY!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9r31t4iajtm9evsjdtue.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9r31t4iajtm9evsjdtue.jpeg" alt="CrewAi agent definition"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;A simple and straightforward definition of an agent&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;CrewAI had this straightforward agent definition that allowed developers to start prototyping quickly. It mostly used multi-turn chat, which let us believe that LLMs speaking with themselves would solve all our problems, including world hunger.&lt;/p&gt;

&lt;p&gt;Unfortunately, making them speak was not an issue. But getting things done was a whole other fight, and CrewAI just didn't deliver. Prompts were too complex for our small 8B LLMs that had reasoning troubles. I guess that plugging CrewAI with a frontier model like ChatGPT yielded better results, but the open source community couldn't use CrewAI as performance was just too bad.&lt;/p&gt;

&lt;h2&gt;
  
  
  LangGraph
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6molrkbikged8g5afypa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6molrkbikged8g5afypa.png" alt="LangGraph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;LangGraph reinvented how to deal with agents.&lt;/p&gt;

&lt;p&gt;It was said that "compiling a graph of predicted prompts would help guide the LLM toward truthness" and was the way to go with our dumb open source LLMs. And they were right! At least the conceptual idea was good.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhrkadaghno57zeb1bpm7.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhrkadaghno57zeb1bpm7.jpg" alt="Compiled LangGraph graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Example of a compiled graph&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It shed light on how bad the performances of open source LLMs were and how much guidance they needed to actually achieve computable results.&lt;/p&gt;

&lt;p&gt;But the issue with LangGraph was the sheer complexity of it. Also, the bad documentation didn't help. It was outdated, missed parts, and had many wrong code snippets.&lt;/p&gt;

&lt;p&gt;On the graph side, the hardcoded links lacked flexibility as software development needs room to evolve during its creation cycle!&lt;/p&gt;

&lt;p&gt;Modifying the graph representation of possible interactions was a lot of work, and prompt engineering pseudo-science made the whole thing exhausting. Programming doesn't like hard links between things. Programming is about iterating a lot and having things stitched to each other will inevitably slow you down.&lt;/p&gt;

&lt;p&gt;In the end, I raged-quit LangChain as it was too difficult to learn and the documentation was too unhelpful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tool Calling disaster
&lt;/h2&gt;

&lt;p&gt;Things started to look bad when we discovered that chatting programmatically with an LLM was cool but didn't achieve anything. To create something with our LLM it had to interact with the exterior world. Not just write poems or bad jokes.&lt;/p&gt;

&lt;p&gt;This is why "tool calling" was created. It allowed LLMs to call 'classic' programming functions. For instance, it could get the weather of a city by calling a real weather API or accessing files on your computer. The possibilities were endless.&lt;/p&gt;

&lt;p&gt;This was the start of something awesome and we all went looking for a way to make this real.&lt;/p&gt;

&lt;p&gt;The tool-calling mechanism was first available in chatGPT. It had this silly name of "function calling" that made us all believe that chatGPT could magically call a programming function.&lt;/p&gt;

&lt;p&gt;We were wrong. 'Function calling' was merely a way of outputting text in a standardized way. The way in question was JSON.&lt;/p&gt;

&lt;p&gt;The LLM would produce a known JSON architecture that was parsed by the software running the LLM. Recognizing the pattern it would call the given function with the right parameters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxmr1tfkd354nbrm1cr03.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxmr1tfkd354nbrm1cr03.PNG" alt="Function calling from openAI"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;OpenAI's "function calling" JSON to trigger a tool call&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The idea was good. But the JSON architecture was not. When tried with small 8B models it failed to output the JSON correctly hence making the tool calling impossible. 😭&lt;/p&gt;

&lt;p&gt;▶️ Therefore, the community was stuck with text-spewing LLMs that couldn't enter the tool-calling league of frontier models.&lt;/p&gt;

&lt;p&gt;Following this, new models came with specific training on tool calling to help them output the correct JSON hence splitting models into two categories. The one with tool-calling abilities and the one without.&lt;/p&gt;

&lt;p&gt;The one without became of no interest and the one that could, still didn't have this good of a success rate. Better than basic LLMs but not that great... Calling a function was still a complex endeavor.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CrewAI&lt;/strong&gt; was incapable of calling any tools. Making it simple but useless.&lt;br&gt;
&lt;strong&gt;LangGraph&lt;/strong&gt; on the other hand could output the JSON but not call the function by itself. It would have been your job to parse the JSON and call it yourself. Also, for opensource models, it only worked with Ollama but only had partial support because… Why care about opensource ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my quest to find the best framework I tested many others: Autogen, BabyAGI, AutoGPT, Langoid, etc. None that had a correct Agent implementation and tool-calling support for local open source LLMs. &lt;strong&gt;None&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My very own Jarvis dream was about to go to rest once again as I would never plug anything into chatGPT. As a strong open source believer and even though I love chatGPT I'm not letting it access my emails, my domotic, my life… It must be open source or nothing. And nothing it was…&lt;/p&gt;

&lt;h1&gt;
  
  
  III. The solution
&lt;/h1&gt;

&lt;p&gt;On the blink of dispair some unknown tool came up. It took the form of a llama in a deep dark sky. A constellation of small sparkles that may light the path ahead after all!&lt;/p&gt;

&lt;h2&gt;
  
  
  Yacana
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdg0amec7fh8bpgbe934f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdg0amec7fh8bpgbe934f.png" alt="Yacana logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Github: &lt;a href="https://github.com/rememberSoftwares/yacana" rel="noopener noreferrer"&gt;https://github.com/rememberSoftwares/yacana&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Read The Docs: &lt;a href="https://remembersoftwares.github.io/yacana" rel="noopener noreferrer"&gt;https://remembersoftwares.github.io/yacana&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;I. Installation&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;All it took was a simple pip install:&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;yacana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;II. Agents&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The 'Agents' concept is the human way of speaking to LLMs. We have anthropomorphized it so that it feels more natural. Yacana understands that and allows creating Agents with a name (and an optional system_prompt to set their behavior)&lt;/p&gt;

&lt;p&gt;⚠️Just a heads-up: for now, Yacana only supports Ollama. If you don't have it installed already, it's only one command away.&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;yacana&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;

&lt;span class="n"&gt;agent1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Experience book writter&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;llama3.1:8b&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;The second parameter is the LLM model name used by Ollama.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;III. Tasks&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Chaining prompts together was an idea of LangGraph. Even though no graphs are available in Yacana we do get the same feeling of linking prompts together but not hard linking them!&lt;/p&gt;

&lt;p&gt;We can soft link tasks with something we, as developers, have done all our lives: programming! No fancy web UIs needed! To chain two tasks together you simply have to call them one after the other. This might look silly right now, but you'll soon see why it's not.&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;yacana&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;

&lt;span class="n"&gt;task1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write a few lines about the deep blue sky&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;task2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Change the subject to &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ocean&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;task1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;task2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python writer.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff42pu7cv09j6o68dfuba.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff42pu7cv09j6o68dfuba.PNG" alt="custom Tasks std output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Output of our two tasks that the agent solved&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yacana shows prompts in green and the LLM answer in purple. This makes tracking conversations a bit easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's do some basic programming!
&lt;/h2&gt;

&lt;p&gt;So, Yacana doesn't provide graphs. But you don't need graphs! You've got something way better! Object Oriented Programming (OOP)!&lt;/p&gt;

&lt;p&gt;We'll start by making an Animal base class:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;self&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="n"&gt;name&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_sound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;NotImplementedError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Subclass must implement abstract method&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;This will be the parent class for the next two classes &lt;code&gt;Cat&lt;/code&gt; and &lt;code&gt;Dog&lt;/code&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="c1"&gt;# Subclass Cat
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Animal&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;make_sound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; says: Meow!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="c1"&gt;# Subclass Dog
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Animal&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;make_sound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; says: Woof!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instantiating our pets:&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;cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Cat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Whiskers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buddy&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;cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make_sound&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;dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make_sound&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94jdlqptmibnhtxbrfby.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F94jdlqptmibnhtxbrfby.PNG" alt="custom Tasks std output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔼 Output of this very complexe program...&lt;/p&gt;




&lt;p&gt;Now let's change our hardcoded pets to &lt;strong&gt;AI cyber pets!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fry5jhuwazkb5mg7i3goa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fry5jhuwazkb5mg7i3goa.png" alt="Cyber cat and cyber dog. AI generated."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;CyberCat and CyberDog playing Doom via Bluetooth&lt;/em&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;yacana&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;

&lt;span class="c1"&gt;# Parent class
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;system_prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llama3:8b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;system_prompt&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;make_sound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;NotImplementedError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Subclass must implement abstract method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Subclass Cat
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CyberCat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Animal&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;make_sound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do a cat noise&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

&lt;span class="c1"&gt;# Subclass Dog
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CyberDog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Animal&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;make_sound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do a dog noise&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

&lt;span class="c1"&gt;# Example usage
&lt;/span&gt;&lt;span class="n"&gt;cyber_cat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CyberCat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Whiskers&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;You are a cat called Whiskers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cyber_dog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CyberDog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buddy&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;You are a dog called Buddy&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;cyber_cat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make_sound&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;cyber_dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make_sound&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now have&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An Agent defined in the &lt;code&gt;Animal&lt;/code&gt; class.&lt;/li&gt;
&lt;li&gt;Two Cyber AI pets &lt;code&gt;CyberCat&lt;/code&gt; and &lt;code&gt;CyberDog&lt;/code&gt; that each take a name and a &lt;code&gt;system_prompt&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;.make_soud()&lt;/code&gt; method now has the task of generating a sound based on the &lt;code&gt;sytem_prompt&lt;/code&gt; representing itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcga84zguhirm6eb37vlz.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcga84zguhirm6eb37vlz.PNG" alt="custom Tasks std output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ℹ️ The default logging can be deactivated to get a cleaner output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool calling
&lt;/h2&gt;

&lt;p&gt;What's great with cyber pets is that they can buy their kibble themselves!&lt;/p&gt;

&lt;p&gt;Let's make a tool that simulates calling a web API to order more kibble.&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;order_kibble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal&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;weight_grams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Validation
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;animal&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="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;animal&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="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ToolError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Animal parameter can only be one word which describes your class like &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; or &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dog&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weight_grams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ToolError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Parameter weight_grams mut be an integer.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Making a fake call to https://order-kibble.com
&lt;/span&gt;
    &lt;span class="c1"&gt;# Fake API return
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;weight_grams&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;To much kibble was ordered for one day. Please order less or you&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ll become a fat pet...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&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;Successfully ordered &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;weight_grams&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; grams of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;animal&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; kibble on order-kibble.com!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important parts of this function are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The function name&lt;/strong&gt; will be used by Yacana so choose something meaningful.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duck typing the arguments:&lt;/strong&gt; Yacana will use this information to infer the types of each parameter for the function call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implementing validation and raising meaningful errors:&lt;/strong&gt; Same as classic server-side validation we cannot trust the LLM to send us valid data. You should at least check for types. If something is wrong raise a meaningful ToolError(...) exception message that the LLM will use to try and call the tool again!&lt;/li&gt;
&lt;li&gt;If it's a success then &lt;strong&gt;return a meaningful string&lt;/strong&gt; relevant to the tool call. &lt;em&gt;Note that the final result of the Task will be the Tool return value.&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Creating a Tool from a function is as simple as choosing a tool name, description, and function reference:&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="nc"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Kibble_order&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;Takes your animal type (cat, doc, etc) and the quantity of kibble to order as input and places an order.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="n"&gt;order_kibble&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Now let's add a &lt;code&gt;.run()&lt;/code&gt; method to our &lt;code&gt;Animal&lt;/code&gt; class. Each time it's called it loses 100 calories (max 200) and when reaching 0 calories the pet orders a kibble refill by itself. Good boy!&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Running!
&lt;/span&gt;    &lt;span class="n"&gt;running_task_output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do running noises.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

    &lt;span class="c1"&gt;# Losing calories
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calorie&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calorie&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Need to eat! Let's order some kibble!
&lt;/span&gt;        &lt;span class="n"&gt;tool_output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&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 hungry. Order kibble.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kibble_order_tool&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;content&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;tool_output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calorie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;running_task_output&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full code below before making our pets run:&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;yacana&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ToolError&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;order_kibble&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;animal&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;weight_grams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Validation
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;animal&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="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;animal&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="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ToolError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Animal parameter can only be one word which describes your class like &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cat&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; or &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dog&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weight_grams&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ToolError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Parameter weight_grams mut be an integer.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Making a call to https://order-kibble.com
&lt;/span&gt;
    &lt;span class="c1"&gt;# Fake API return
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;weight_grams&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;To much kibble was ordered for one day. Please order less or you&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ll become a fat pet...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&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;Successfully ordered &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;weight_grams&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; grams of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;animal&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="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; kibble on order-kibble.com!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Parent class
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&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;system_prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;llama3:8b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kibble_order_tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Kibble_order&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;Takes your animal type (cat, doc, etc) and the quantity of kibble to order as input and places an order.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order_kibble&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calorie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_sound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;NotImplementedError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Subclass must implement abstract method&lt;/span&gt;&lt;span class="sh"&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&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Running!
&lt;/span&gt;        &lt;span class="n"&gt;running_task_output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do running noises.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

        &lt;span class="c1"&gt;# Losing calories
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calorie&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calorie&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Need to eat! Let's order some kibble!
&lt;/span&gt;            &lt;span class="n"&gt;tool_output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&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 hungry. Order kibble.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kibble_order_tool&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;content&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;tool_output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calorie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;running_task_output&lt;/span&gt;

&lt;span class="c1"&gt;# Subclass Cat
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CyberCat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Animal&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;make_sound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do a cat noise&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

&lt;span class="c1"&gt;# Subclass Dog
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CyberDog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Animal&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;make_sound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Do a dog noise&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Let's make our dog run twice so that its calories reach 0 and it needs to refill!&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;cyber_dog&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CyberDog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Buddy&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;You are a dog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;cyber_dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;cyber_dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;Let's break down the output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9q4xdnkxue9ms4wk0sug.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9q4xdnkxue9ms4wk0sug.PNG" alt="custom Tasks std output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔼 The dog ran twice as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xsyinebe1l5l6a4aovh.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2xsyinebe1l5l6a4aovh.PNG" alt="custom Tasks std output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ccrhuhiyyh8m9u3esxy.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ccrhuhiyyh8m9u3esxy.PNG" alt="custom Tasks std output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔼 As the calories reached 0 it triggered the tool call to order kibble. Here you can witness Yacana's Tool-calling magic! But something went wrong 😨! Can you spot the issue??&lt;br&gt;
…&lt;br&gt;
The &lt;code&gt;weight_grams&lt;/code&gt; value was sent as a string : &lt;code&gt;{"annimal": "dog", "weight_grams": "500"}&lt;/code&gt; Although, it was ducked typed as an int!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxaq9q2tkhoawcix2t0x7.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxaq9q2tkhoawcix2t0x7.PNG" alt="custom Tasks std output"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;🔼 Lastly, we can see that Yacana got our raise() message about the parameter type and retried calling the tool.&lt;/p&gt;

&lt;p&gt;The LLM took into account the new information and this time, weight_grams was sent as an integer and the tool was called successfully!&lt;/p&gt;

&lt;p&gt;Our cyber dog placed its order! 🐶&lt;/p&gt;

&lt;p&gt;In the end, Yacana does not provide a static graph. However, it does integrate quite nicely into standard development practices. From Yacana's only POV this is how it is conceptually created:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrl1kmbwomhlr89knb2q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrl1kmbwomhlr89knb2q.png" alt="Yacana call graph as standard class approach"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Graph nodes version of Yacana&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  IV. Going further
&lt;/h1&gt;

&lt;p&gt;We'll wrap up here for this introduction to Yacana. However, there are many more functionalities to explore and many use cases other than dystopic cyber pets to imagine. From routing to multi-turn chat and history management, the framework has many things to propose.&lt;/p&gt;

&lt;p&gt;Consider giving a like to this article and following me on Medium and dev.to so you won't miss the coming series of articles about Yacana.&lt;/p&gt;

&lt;p&gt;Have fun with the framework and read the doc: &lt;a href="https://remembersoftwares.github.io/yacana" rel="noopener noreferrer"&gt;https://remembersoftwares.github.io/yacana&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Calling code with local LLM is a hoax</title>
      <dc:creator>Emilien Lancelot</dc:creator>
      <pubDate>Mon, 20 May 2024 16:26:02 +0000</pubDate>
      <link>https://dev.to/docteurrs/calling-code-with-local-llm-is-a-hoax-2n51</link>
      <guid>https://dev.to/docteurrs/calling-code-with-local-llm-is-a-hoax-2n51</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fntyyfig9mlw9prk3tmho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fntyyfig9mlw9prk3tmho.png" alt="Image description" width="800" height="1028"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having a local LLM spewing text is good. But what you need is the LLM to execute YOUR code!&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Is calling tools even doable? Sure chatGPT makes it easy. But what of your local LLMs. In this article, we'll be trying multiple agent frameworks with tool-calling capabilities and see if our local LLM can use them.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My configuration is:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RTX4090 with 32GB of RAM
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Using the following LLMs for testing:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;llama3:8b&lt;/li&gt;
&lt;li&gt;dolphin-mixtral:8x7b-v2.7-q4_K_M&lt;/li&gt;
&lt;li&gt;mistral:latest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Powered locally&lt;/strong&gt; by Ollama.&lt;/p&gt;

&lt;h1&gt;
  
  
  I. AutoGPT
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpz112ytyyw7md1rn4x00.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpz112ytyyw7md1rn4x00.png" alt="Image description" width="600" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Significant-Gravitas/AutoGPT" rel="noopener noreferrer"&gt;AutoGPT &lt;/a&gt;is a framework that seems nice. It has a cool CLI and a flutter UI to create agents from the browser. Its main purpose is to work with your local stuff (documents, audio, videos, etc)  &lt;/p&gt;

&lt;p&gt;BUT  &lt;/p&gt;

&lt;p&gt;It mostly relies on chatGPT or any proprietary LLM providers to do the heavy lifting. At least that's how I understand it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using local models
&lt;/h2&gt;

&lt;p&gt;Here you can find the &lt;a href="https://github.com/Significant-Gravitas/AutoGPT/blob/master/autogpts/autogpt/.env.template" rel="noopener noreferrer"&gt;configuration file&lt;/a&gt; where we must set our config.&lt;br&gt;&lt;br&gt;
We must trick AutoGPT to use the Ollama endpoint like it's chatGPT.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;## OPENAI_API_KEY - OpenAI API Key (Example: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)&lt;/span&gt;
&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"helloworld"&lt;/span&gt;

...

&lt;span class="c"&gt;## OPENAI_API_BASE_URL - Custom url for the OpenAI API, useful for connecting to custom backends. No effect if USE_AZURE is true, leave blank to keep the default url&lt;/span&gt;
&lt;span class="c"&gt;# the following is an example:&lt;/span&gt;
&lt;span class="nv"&gt;OPENAI_API_BASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http://localhost:11434/v1

...

&lt;span class="c"&gt;## SMART_LLM - Smart language model (Default: gpt-4-turbo)&lt;/span&gt;
&lt;span class="nv"&gt;SMART_LLM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dolphin-mixtral:8x7b-v2.7-q4_K_M

&lt;span class="c"&gt;## FAST_LLM - Fast language model (Default: gpt-3.5-turbo)&lt;/span&gt;
&lt;span class="nv"&gt;FAST_LLM&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mistral:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should do the trick.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./autogpt.sh run

value is not a valid enumeration member&lt;span class="p"&gt;;&lt;/span&gt; permitted: &lt;span class="s1"&gt;'text-embedding-ada-002'&lt;/span&gt;, &lt;span class="s1"&gt;'text-embedding-3-small'&lt;/span&gt;, &lt;span class="s1"&gt;'text-embedding-3-large'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-3.5-turbo-0301'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-3.5-turbo-0613'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-3.5-turbo-16k-0613'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-3.5-turbo-1106'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-3.5-turbo-0125'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-3.5-turbo'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-3.5-turbo-16k'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-0314'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-32k-0314'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-0613'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-32k-0613'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-1106-preview'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-1106-vision-preview'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-0125-preview'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-turbo-2024-04-09'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-32k'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-turbo'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-turbo-preview'&lt;/span&gt;, &lt;span class="s1"&gt;'gpt-4-vision-preview'&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;type_error.enum&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;enum_values&lt;/span&gt;&lt;span class="o"&gt;=[&lt;/span&gt;&amp;lt;OpenAIModelName.EMBEDDING_v2: &lt;span class="s1"&gt;'text-embedding-ada-002'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.EMBEDDING_v3_S: &lt;span class="s1"&gt;'text-embedding-3-small'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.EMBEDDING_v3_L: &lt;span class="s1"&gt;'text-embedding-3-large'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT3_v1: &lt;span class="s1"&gt;'gpt-3.5-turbo-0301'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT3_v2: &lt;span class="s1"&gt;'gpt-3.5-turbo-0613'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT3_v2_16k: &lt;span class="s1"&gt;'gpt-3.5-turbo-16k-0613'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT3_v3: &lt;span class="s1"&gt;'gpt-3.5-turbo-1106'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT3_v4: &lt;span class="s1"&gt;'gpt-3.5-turbo-0125'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT3_ROLLING: &lt;span class="s1"&gt;'gpt-3.5-turbo'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT3_ROLLING_16k: &lt;span class="s1"&gt;'gpt-3.5-turbo-16k'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_v1: &lt;span class="s1"&gt;'gpt-4-0314'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_v1_32k: &lt;span class="s1"&gt;'gpt-4-32k-0314'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_v2: &lt;span class="s1"&gt;'gpt-4-0613'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_v2_32k: &lt;span class="s1"&gt;'gpt-4-32k-0613'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_v3: &lt;span class="s1"&gt;'gpt-4-1106-preview'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_v3_VISION: &lt;span class="s1"&gt;'gpt-4-1106-vision-preview'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_v4: &lt;span class="s1"&gt;'gpt-4-0125-preview'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_v5: &lt;span class="s1"&gt;'gpt-4-turbo-2024-04-09'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_ROLLING: &lt;span class="s1"&gt;'gpt-4'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_ROLLING_32k: &lt;span class="s1"&gt;'gpt-4-32k'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_TURBO: &lt;span class="s1"&gt;'gpt-4-turbo'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_TURBO_PREVIEW: &lt;span class="s1"&gt;'gpt-4-turbo-preview'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;, &amp;lt;OpenAIModelName.GPT4_VISION: &lt;span class="s1"&gt;'gpt-4-vision-preview'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seems like it's not... The model name MUST be a proprietary name like "GPT4-turbo" or any other from the above list. Unfortunately, my models are not named like that.&lt;/p&gt;




&lt;p&gt;Now to see If it could go a bit further with a fake (but compliant) model name I set "GPT4-turbo" and ran again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./autogpt.sh run
2024-05-19 16:03:01,937 ERROR  Invalid OpenAI API key! Please &lt;span class="nb"&gt;set &lt;/span&gt;your OpenAI API key &lt;span class="k"&gt;in&lt;/span&gt; .env or as an environment variable.
2024-05-19 16:03:01,938 INFO  You can get your key from https://platform.openai.com/account/api-keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It doesn't like my API key. I've tried many different keys. It won't go further.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Clonclusion on autoGPT
&lt;/h2&gt;

&lt;p&gt;To fix the model names you could create a custom model in Ollama called GPT4-turbo which you would base on any local model you already have. It's just a way to rename your model and trick AutoGPT. But that wouldn't fix the API key error.  &lt;/p&gt;

&lt;p&gt;Also as mentioned &lt;a href="https://github.com/Significant-Gravitas/AutoGPT/issues/6336#issuecomment-2119252849" rel="noopener noreferrer"&gt;HERE&lt;/a&gt; you could maybe duplicate the OpenAi model provider file from AutoGPT and remove any non-compliant parts. But I'm unsure how to perform such an operation.  &lt;/p&gt;

&lt;p&gt;The documentation doesn't have anything about using local models and doesn't mention calling tools.  &lt;/p&gt;

&lt;p&gt;In the end, I don't think that AutoGPT is ready for local model use and you should wait and hope the paradigm shifts toward a more local approach.  &lt;/p&gt;

&lt;h1&gt;
  
  
  II. LangChain &amp;amp; LangGraph
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0gvfczrll1hfkjhxsjj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw0gvfczrll1hfkjhxsjj.png" alt="Image description" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Langchain has been at the core of many projects since the beginning of the AI gold rush. Why it's not the king already is probably because of its complex syntax that many developers don't have the time to learn.&lt;br&gt;&lt;br&gt;
Langchain has a way of using the most obscure Python functionalities and makes you feel like you never have read Python code before.  &lt;/p&gt;

&lt;p&gt;For instance:&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;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&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;outputparser&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Question.&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;The LCEL system of Python uses pipes ("|") to string things up. This is rendered possible in Python by overriding Python's &lt;code&gt;__or__&lt;/code&gt; magic method. in other words, Langchain overrides operators like you would in C++.  &lt;/p&gt;

&lt;p&gt;But did we really need this kind of idea? I'll let you make up your mind…  &lt;/p&gt;




&lt;p&gt;Now about using local models:  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Langchain has 2 plugins:&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://python.langchain.com/v0.1/docs/integrations/chat/ollama/" rel="noopener noreferrer"&gt;Ollama chat&lt;/a&gt;: Allows you to chat with an LLM&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://python.langchain.com/v0.1/docs/integrations/chat/ollama_functions/" rel="noopener noreferrer"&gt;Ollama-functions&lt;/a&gt;:  Allows an LLM to answer using a specific formatting output. For instance, if you want your LLM to answer as JSON or as YAML then you can define the format type, keys, and values type that you expect.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Beware with the "function calling" capability ! It's a troll from OpenAI… Worst feature naming possible! It doesn't calls functions like "using tools" would. It's only about formating the output of the LLM.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;=&amp;gt; Now, what about tool calling (aka executing real code locally)?  &lt;/p&gt;

&lt;p&gt;Well… The Ollama plugin doesn't have this functionality…&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="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;second_number&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Multiplies two numbers together.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;first_number&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;second_number&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ChatOllama&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;mistral:latest&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_with_tools&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="nf"&gt;bind_tools&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;# &amp;lt;== Binding tool here
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running this will output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ChatOllama doesn't have a method bind_tools()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And I can confirm it doesn't… So we're F*****.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion on Langchain &amp;amp; Langraph
&lt;/h2&gt;

&lt;p&gt;I'm a bit disappointed. As this framework powers many others like CrewAI etc I thought that it would have a nice integration with local tools. In the end, it's not that great. It's just a complicated mess that doesn't fix our main concern.&lt;/p&gt;

&lt;h1&gt;
  
  
  III. Rivet
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftztux621hz824ehnuogn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftztux621hz824ehnuogn.png" alt="Image description" width="800" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I must say that I love this one! It's kind of new but has tremendous potential in the future.  &lt;/p&gt;

&lt;p&gt;It's some kind of IDE for LLM interactions that uses a canvas to create an execution diagram (DAG). It can run in the browser but you can also export the DAG and run it as code to empower your software.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyyu93tfrj4m6qwkyv8jt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyyu93tfrj4m6qwkyv8jt.png" alt="Image description" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Look at this! How cool it looks! There is an Ollama plugin so you can use it locally.  &lt;/p&gt;

&lt;p&gt;Just make sure to click the 3 dots in the top right and change the executor to "node" otherwise it might not run.  &lt;/p&gt;

&lt;p&gt;Unfortunately, I didn't find a way to call custom tools and the documentation is quite lacking anyway. It's clearly a project that needs to be watched for further updates!  &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion on Rivet
&lt;/h2&gt;

&lt;p&gt;Cool software! Free and open source. Love the canvas system that feels like what LangGraph should have.  &lt;/p&gt;

&lt;p&gt;Still requires tool calling before being useful. But have fun with it if you have a chatGPT account.  &lt;/p&gt;

&lt;h1&gt;
  
  
  IV. AutoGen
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39w72ln4vacdi9e825fo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39w72ln4vacdi9e825fo.png" alt="Image description" width="373" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One of the best candidates on this list. Autogen is backed up by one of the largest tech companies out there.  &lt;/p&gt;

&lt;p&gt;I have done the tutorial and I must say… I don't understand most of what I'm doing! If the first pages are okay, the situation rapidly gets out of control and then you would need an AI agent framework to explain to you how all of this works.  &lt;/p&gt;

&lt;p&gt;However, it does have all you need and supports Ollama out of the box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;code_writer_agent = ConversableAgent(
    "code_writer_agent",
    system_message=code_writer_system_message,
    llm_config={"config_list": 
      [{"model": "dolphin-mixtral:8x7b-v2.7-q4_K_M",
      "api_key": "hello world",
      "base_url": "http://127.0.0.1:11434/v1"}]},
    code_execution_config=False,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The best functionalities are IMO:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating code on the fly and executing it&lt;/li&gt;
&lt;li&gt;Call tools (aka calling your code)&lt;/li&gt;
&lt;li&gt;Human input&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;But does tool calling works ?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Still doesn't… Only OpenAI-compatible tools calling LLMs can use this. So Ollama + Mistral won't make the cut. However, the code generation and execution thingy works quite well. Also, note that calling LangChain tools is &lt;strong&gt;not&lt;/strong&gt; supported.&lt;/p&gt;




&lt;h2&gt;
  
  
  Available chat mechanisms you can use
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two chats pattern:&lt;/strong&gt; Two LLMs speak to each other to complete the task&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3xgfxm7wv2dshcxcv3q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv3xgfxm7wv2dshcxcv3q.png" alt="Image description" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sequential chats:&lt;/strong&gt; Tasks will be evaluated in the order you specified&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvsdm3wsfc1lln4321du.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvsdm3wsfc1lln4321du.png" alt="Image description" width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is starting to get complicated. The carryover mechanism which contains the context accumulated over the multiple conversations is a hard concept to grasp. And why is each task still is a conversation between 2 agents ?? And why is it A=&amp;gt;B,  A=&amp;gt;C, A=&amp;gt;D, A=&amp;gt;E ? Why always start with A ? God knows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Group chat:&lt;/strong&gt; Don't expect an explanation...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zvb8t1hk0xjkt009scv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9zvb8t1hk0xjkt009scv.png" alt="Image description" width="800" height="754"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is when things get out of hand! One agent seems to be the brain, installing some kind of hierarchy between the agents.   If the concept is appealing, the examples from the documentation are not really helpful.&lt;/p&gt;




&lt;p&gt;It also supports the last trending prompting stuff like:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ReAct:&lt;/strong&gt; Allows to decompose actions and make a plan. Then it tries to follow each step and if things go wrong it makes another plan and starts again. It's all about creating context that has a semantic meaning to the LLM and helps it focus on what it should do right now.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reflection:&lt;/strong&gt; It's kind of like ReAct but with an emphasis on its own output. After "speaking" it will ask itself "Is this correct ?". And it seems that iterating over its own answers yields better results.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As always, "better results" means "fewer hallucinations" as this is the main issue with LLMs.&lt;/p&gt;




&lt;h2&gt;
  
  
  AutogenStudio
&lt;/h2&gt;

&lt;p&gt;Also if you don't want to mess with code you can download the AutoGenStudio software that allows you to define agents without the need of coding. It's an interesting piece of software but doesn't really help you grasp the core functionality of the framework. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8g44tohsqfl0vvmt5lw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn8g44tohsqfl0vvmt5lw.jpg" alt="Image description" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion on AutoGen
&lt;/h2&gt;

&lt;p&gt;AutoGen clearly has a bright future in front of it. As it's made by Microsoft, we can only hope that they won't pull the plug on it or make it an OpenAI only software.  &lt;/p&gt;

&lt;p&gt;However, still, no tool calling is available with local LLMs. :-(&lt;/p&gt;

&lt;h1&gt;
  
  
  V. CrewAI
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F339q1jrk3x0m29yt6xkt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F339q1jrk3x0m29yt6xkt.png" alt="Image description" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another excellent piece of software.  &lt;/p&gt;

&lt;p&gt;If the documentation is okay and the framework simple it does have a few issues.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On the brightside :&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ollama support&lt;/li&gt;
&lt;li&gt;LangChain tools calling&lt;/li&gt;
&lt;li&gt;Custom tools calling&lt;/li&gt;
&lt;li&gt;Human input&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;On the dark side:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool calling still isn't working!&lt;/li&gt;
&lt;li&gt;Human input doesn't always trigger&lt;/li&gt;
&lt;li&gt;Low consistency with infinite loops&lt;/li&gt;
&lt;li&gt;Bugs&lt;/li&gt;
&lt;li&gt;Soooooo many prompts to write&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Available chat mechanisms you can use
&lt;/h2&gt;

&lt;p&gt;There are "sequential" and "hierarchical". Sequential will allow your LLMs to go through the tasks in the order you choose. Hierarchical on the other side will create a ghost agent that automatically decides which one of your agents should be triggered using its description.  &lt;/p&gt;

&lt;p&gt;Hierarchical would be great if only it worked. There are constant errors about agents that can find their co-workers. It rapidly gets tedious.&lt;/p&gt;




&lt;p&gt;The Framework proposes 3 types of classes:  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, you have the Agents which have the following prompts bound to them:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A role: What it does for a living &lt;/li&gt;
&lt;li&gt;A goal: What it should do in the team&lt;/li&gt;
&lt;li&gt;A backstory: A story of its life…
&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;writer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Writer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;goal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Write a fake anecdote using a number.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;backstory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;An experienced writer with vivid imagination.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ollama_mistral&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&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;Then you have the Tasks that also have prompts:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;description: What should the task do &lt;/li&gt;
&lt;li&gt;expected_output: The output that is expected of this task
&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;teacher_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&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;Decompose the arithmetic operations.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;A consise list of operation to execute&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;teacher&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;Finally, you have the Tools:&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Tools can be bound to Agents to give them capabilities. But for some reason, they can also be bound to tasks… Which I don't think makes much sense.&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="nd"&gt;@tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sleep&lt;/span&gt;&lt;span class="sh"&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;my_sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nb_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Will sleep the amount of specified seconds provided as a number&lt;/span&gt;&lt;span class="sh"&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;nb_seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nb_seconds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I like having the &lt;code&gt;@tool&lt;/code&gt; decorator. You simply have to pass a string that describes your tool and the LLM should know if it should use it or not.  &lt;/p&gt;

&lt;p&gt;In the end, you'll have so many prompts to write that you'll lose yourself.  &lt;/p&gt;

&lt;p&gt;Does this prompt belong to a task or an agent? Does this tool belong to this agent or this one? Or maybe the tool should be bound to the task itself… So many questions but so few answers as the CrewAI documentation is quite scarce!  &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion on crewAI
&lt;/h2&gt;

&lt;p&gt;If you wish to have agents speak to each other then it's the simpler framework out there. Besides having to many prompts to write it's quick and easy. &lt;em&gt;However, calling tools don't work so we still have the same issue.&lt;/em&gt;  &lt;/p&gt;

&lt;p&gt;Also, the consistency is quite bad. Often will you see you're agents going into infinite loops.   &lt;/p&gt;




&lt;p&gt;A note on the constant Youtube AI trend bullshit: Haven't you noticed how many YouTubers have made videos on Agent frameworks. The subject is always about writing about stupid AI trends and making poor RAG systems. Well, that's because there is currently not much you can do as calling local tools isn't a thing right now. Except if you use chatGPT, Grok, or Claude.  &lt;/p&gt;

&lt;h1&gt;
  
  
  VI. Conclusion of all conclusions
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;We're screwed.&lt;/em&gt;  &lt;/p&gt;




&lt;p&gt;Honestly, it's time that we get a way to better integrate low-cost LLMs in our applications. Calling tools is the way to go but needs a simpler architecture and one that doesn't rely on openAI's complex format.  &lt;/p&gt;

&lt;p&gt;What use of small models like PHI would we have on our mobile devices if the only thing it can do is spew text and can't integrate any of it with our applications?  &lt;/p&gt;




&lt;p&gt;If I have made mistakes or overseen anything please let me know in the comments.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Any idea how to get local code executed is good to know. Please advise in the comment section !&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;Thx for reading. Leave a thumbs up if you liked this article. ❤  &lt;/p&gt;




&lt;p&gt;In need of an agent framework that works with both local and remote inference servers?&lt;/p&gt;

&lt;p&gt;▶️ &lt;a href="https://remembersoftwares.github.io/yacana/" rel="noopener noreferrer"&gt;Checkout Yacana&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Shielding Your Kubernetes Network: Mastering iptables for Enhanced Security</title>
      <dc:creator>Emilien Lancelot</dc:creator>
      <pubDate>Sun, 11 Feb 2024 14:38:32 +0000</pubDate>
      <link>https://dev.to/docteurrs/shielding-your-kubernetes-network-mastering-iptables-for-enhanced-security-39o7</link>
      <guid>https://dev.to/docteurrs/shielding-your-kubernetes-network-mastering-iptables-for-enhanced-security-39o7</guid>
      <description>&lt;h1&gt;
  
  
  I. Introduction
&lt;/h1&gt;

&lt;p&gt;Would you like to hear the good news or the bad news first?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let's begin with the downside. If you've solely relied on securing the internal cluster network with NetworkPolicy during your recent on-premise K8S installation, you've missed out on 50% of the security measures. Many threats originate from within, but they could just as easily infiltrate from outside. Bummer… I know!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hey! At least you did half of it! I'm sure that some people reading this blog are like "Huh what are NetworkPolicies anyway ?". Those cannot be saved… May god have mercy on their souls.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Now to the good news part! Securing incoming requests to your cluster can also be achieved and I've got you covered!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why you should shield your cluster from the exterior world?
&lt;/h2&gt;

&lt;p&gt;As mentioned previously, numerous threats may arise from within. For instance, the act of pulling and executing a rogue Docker image could potentially corrupt your nodes, propagate across applications, and ultimately compromise your entire infrastructure.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevi7wd8no2uwxfr07k3i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevi7wd8no2uwxfr07k3i.png" alt="A container that seems fishy; Something doesn't look right…🤨" width="800" height="1029"&gt;&lt;/a&gt;⬆️&lt;em&gt;A container that seems fishy; Something doesn't look right…&lt;/em&gt;🤨&lt;/p&gt;

&lt;p&gt;Let's not dwell on that frightening idea any longer and address our specific concerns! &lt;/p&gt;




&lt;p&gt;Kubernetes is composed of masters, workers and ETCD nodes. All these components are working from inside virtual machines (VMs) that are part of a network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvglf10st3qv91p2wuupl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvglf10st3qv91p2wuupl.png" alt="Image description" width="800" height="541"&gt;&lt;/a&gt;⬆️&lt;em&gt;Network around Kubernetes components&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The network that connects these VMs has 3 purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow each VM (K8S component) to communicate with each other (In green 📗)&lt;/li&gt;
&lt;li&gt;Allow developers to interact with Kubernetes to deploy their payloads (In blue 📘)&lt;/li&gt;
&lt;li&gt;Allow engineers to connect to the underlying VMs that support K8S for debugging and stuff (In orange📙)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Securing this network is of paramount importance as one of the VMs could get infected and spread to all others.  &lt;/p&gt;

&lt;p&gt;On one hand, if your VMs are part of a private network then you might feel safe but in reality, it's private until it's not… And that's when sh*t hits the fan!  &lt;/p&gt;

&lt;p&gt;On the other hand, if your Kubernetes cluster is available directly from the internet then you really should read this tutorial carefully as all your nodes might currently be screaming &lt;strong&gt;H4CK ME PLEASE!&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4x26f1ysd2tfvajwqud.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn4x26f1ysd2tfvajwqud.png" alt="Image description" width="800" height="528"&gt;&lt;/a&gt;⬆️&lt;em&gt;Hackers attacking exposed VMs that have no protection from incoming requests&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;There are only two ways to access a system. Physically or from a network endpoint. So unless you find yourself tied up to your chair with a man equipped with a malevolent USB key, the primary threat will definitely come from the network.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c66wkt2hvxx18ilnn5h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1c66wkt2hvxx18ilnn5h.png" alt="Image description" width="800" height="1028"&gt;&lt;/a&gt;⬆️&lt;em&gt;If that's you then you and your clusters are in deep trouble!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In general, hackers search for open ports on the exposed VMs. As most ports are dedicated to a specific program or protocol, for instance, port 22 is always SSH, listing opened ports gives a good idea of what runs on the target. With this list, the hacker can then search for an exploit and hack his way in. So the fewer open ports, the better.  &lt;/p&gt;

&lt;p&gt;Benefits of limiting opened ports on underlying VMs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;protects your infrastructure from infection&lt;/li&gt;
&lt;li&gt;slows propagation of malware in case of infection&lt;/li&gt;
&lt;li&gt;allows better control of what (custom) software runs on the VMs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What technology to use for restricting incoming requests and logging them
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9am3dq8i0qqqz0dut04k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9am3dq8i0qqqz0dut04k.jpg" alt="Image description" width="620" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On Linux, there are many ways to secure incoming network requests. It's called "firewalls". You may have heard of "firewallD" or "UFW" (&lt;strong&gt;U&lt;/strong&gt;ncomplicated &lt;strong&gt;F&lt;/strong&gt;ire &lt;strong&gt;W&lt;/strong&gt;all (it's a lie)). The truth is, I can't just give you the instructions for all existing firewalls. Moreover, I did tests using a few and didn't quite achieve adequate results and broke the Kubernetes cluster quite a few times.  &lt;/p&gt;

&lt;p&gt;In the end, I think that the best technology to use when it comes to networks and you don't have one hundred rules to write is: "&lt;code&gt;iptables&lt;/code&gt;"!  &lt;/p&gt;

&lt;p&gt;I'm sure you've heard of it. It's older than you. It was created during the Jurassic era but is still relevant today.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyelm6n07ngzh0whe9v2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgyelm6n07ngzh0whe9v2.png" alt="Image description" width="800" height="1028"&gt;&lt;/a&gt;⬆️&lt;em&gt;iptables hype during Jurassic&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Even better, most firewalls are simple overlays of iptables, manipulating their rules for you like a wand and dark magic.  &lt;/p&gt;

&lt;p&gt;But fear not. Old doesn't (always) mean that it sucks. I have mastered the beast and if you read thoroughly the next sections I'm sure that I can pass on this knowledge to you as it was passed to me by an old but wise sage.  &lt;/p&gt;

&lt;h1&gt;
  
  
  II. How to use iptables dark magic?
&lt;/h1&gt;

&lt;h2&gt;
  
  
  A network metaphor that you'll love
&lt;/h2&gt;

&lt;p&gt;When a happy network packet is received by a VM it goes through the iptables administration. The packet will go through about 7 different rooms before reaching its destination.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bqkzbl27qi5nscy4691.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7bqkzbl27qi5nscy4691.png" alt="Image description" width="800" height="266"&gt;&lt;/a&gt;⬆️&lt;em&gt;The process of a network packet&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Each room above is called a "Table" in iptable jargon&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each room is responsible for evaluating the network packet and decides if it can continue the process onto the next room or gets kicked out!&lt;/p&gt;

&lt;p&gt;To achieve this selection each room has multiple desks to which the packet must report. Each room has about 1 to 5 desks depending on the room type.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Those desks are called "chains" in iptable jargon&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sat to those desks, are overly suspicious old ladies analyzing packets. They have a list of rules that a packet criteria can match and take action if it does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0v9pv5i4juesj4kirjr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0v9pv5i4juesj4kirjr.png" alt="Image description" width="800" height="1028"&gt;&lt;/a&gt;⬆️&lt;em&gt;Meet "Ms. Input" with her suspicious look. No weird packets at her desk will go unnoticed by this expert eye…&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If the packet matches one of the criteria (rules) then two things can happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It can be ACCEPTED immediately and go to the next room &lt;strong&gt;even&lt;/strong&gt; if there are other desks left in the room. Its process can continue! Wishing it good luck with the next room!&lt;/li&gt;
&lt;li&gt;It can be REJECTED and gets killed on the spot! 😱&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;IMPORTANT CONCEPT HERE&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;=&amp;gt; By default, if the packet doesn't match any rules at any desks, it will still arrive at destination! This behaviour is called "&lt;strong&gt;default accept&lt;/strong&gt;". When we finish setting our rules to match the packets we trust , we will change the default behaviour to "&lt;strong&gt;default deny&lt;/strong&gt;". This means that if a packet didn't match any rules at any desks it will be killed by default when leaving the last desk available!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;strong&gt;But, good news traveler!&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;One positive aspect is that we're solely focused on one room and one desk! And that's fantastic - do you know why? Because I don't come close to understanding what half the other old ladies do and neither will you (or you wouldn't be reading this tutorial in the first place).  &lt;/p&gt;

&lt;p&gt;So let's focus on the only room and desk that interests us. &lt;strong&gt;It's the Filter room!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywhm7hj2hoxjw4hk8075.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fywhm7hj2hoxjw4hk8075.png" alt="Image description" width="800" height="266"&gt;&lt;/a&gt;⬆️In red is the only room of iptables that we will cover&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the filter table and INPUT chain
&lt;/h2&gt;

&lt;p&gt;Let's lose the metaphor for a bit and repeat what is what. A room is called a "table" which the binary "iptables" gets its name from. And each desk is called a "chain". You'll get used to it.  &lt;/p&gt;

&lt;p&gt;For some reason, chains are always written in caps. I guess that the guy who invented iptables was so old and deaf that people constantly yelled at him so he could hear them. Hence the cap! It's just a theory though…  &lt;/p&gt;

&lt;p&gt;The filter table (room) is all about filtering... That you may have figured out and you're right, WP! That's perfect because we want to filter incoming packets!  &lt;/p&gt;

&lt;p&gt;The Filter table has 3 default chains (desks)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;INPUT&lt;/li&gt;
&lt;li&gt;FORWARD&lt;/li&gt;
&lt;li&gt;OUTPUT&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5e91p27eztzi88fdt30.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj5e91p27eztzi88fdt30.png" alt="Image description" width="800" height="1028"&gt;&lt;/a&gt;⬆️&lt;em&gt;We are only interested in Mrs. Input and her list of rules&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And do you know what chain (desk) a paquet would go through first when entering the Filter table? The…. THE…… INNNNNPUUTTT chain. Yes, yes. Well done.&lt;/p&gt;




&lt;p&gt;The chain concept is easy to grasp. It's like functions in programming but instead of containing actual code they contain rules. The INPUT, FORWARD and OUTPUT chains are the default ones but you can create your own chains! You can have your own desk and an old lady just for your use case! Isn't that great? You just have to call this new chain from the INPUT chain like a programming function would!  &lt;/p&gt;

&lt;h2&gt;
  
  
  Checking our current configuration
&lt;/h2&gt;

&lt;p&gt;Let's see how are our iptables are configured using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-xvnL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzigk6wx38v23ei5gdojv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzigk6wx38v23ei5gdojv.png" alt="Image description" width="800" height="324"&gt;&lt;/a&gt;⬆️&lt;em&gt;The content of our "filter" table and 4 chains&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At first, this might be a bit overwhelming with information but if you decompose it chain by chain it's quite easy. It's composed of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 3 default chains of the filter table: INPUT, FORWARD and OUTPUT&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A custom chain&lt;/strong&gt; called DOCKER added by the docker engine&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can see that the INPUT chain is empty. There are no rules so no old suspicious lady will look at our packets… ☹️&lt;/p&gt;

&lt;p&gt;The FORWARD chain on the other hand has a lot of rules but OUTPUT has none. Then the custom DOCKER one has only 2.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;ℹ️ Remember that the Filter table is displayed by default because it's the most commonly used. However, you can list other tables if you wished using "&lt;code&gt;-t &amp;lt;table_name&amp;gt;&lt;/code&gt;".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Order of evaluation of rules
&lt;/h2&gt;

&lt;p&gt;That's a simple one! All incoming packets are evaluated by the rules from top to bottom.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgxc1fjfzxs0eh0574uq0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgxc1fjfzxs0eh0574uq0.png" alt="Image description" width="800" height="324"&gt;&lt;/a&gt;⬆️&lt;em&gt;For example, using the FORWARD chain&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the example above, we can look at the FORWARD chain that has 14 rules. Each packet will undergo evaluation against rules 1 through 14 unless it matches one of these rules before reaching the 14th (If so it will either be ACCEPTED or REJECTED!).  &lt;/p&gt;

&lt;h2&gt;
  
  
  The Macdonald Default Policy
&lt;/h2&gt;

&lt;p&gt;Macdo's commercials are always like "Come as you are". Well… By default, iptables is the same. If the incoming packet doesn't match any rules and attains the last rule available then it will continue its journey whether it's legit or not. So, by default, iptables is useless, L.O.L.  &lt;/p&gt;

&lt;p&gt;But, it's good news because we don't want to mess things up with a wrong rule. It's only when we feel we are ready that we'll change the default accept to a default deny. When this happens, all packets that were not explicitly accepted will be dropped (killed ☠️).  &lt;/p&gt;

&lt;p&gt;You can look for the current Policy displayed in the previous screenshot. Look at the first line and you'll see: &lt;code&gt;chain INPUT (policy ACCEPT...&lt;/code&gt; The last step of this tutorial will be to change this to &lt;code&gt;(policy DENY...&lt;/code&gt;.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of a rule
&lt;/h2&gt;

&lt;p&gt;Let's take an incoming packet with the following attributes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Protocole: TCP&lt;/li&gt;
&lt;li&gt;Target port: 22&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We receive a TCP packet on our VM. It's on our port 22. We can search what program listens on our port 22, if any, with the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;netstat &lt;span class="nt"&gt;-tupln&lt;/span&gt; |grep 22
...
openssh &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 22 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows that a process named OpenSSH listens on our port 22 with PID 7.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mqzs60f8dn8yiu4yuuc.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mqzs60f8dn8yiu4yuuc.jpg" alt="Image description" width="662" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's logical as port 22 is commonly used by SSH! Let's accept this packet explicitly and allow OpenSSH to deal with it.  &lt;/p&gt;

&lt;p&gt;To allow the packet we can use the &lt;code&gt;iptables&lt;/code&gt; command line as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-m&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 22 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;💁Do not execute this command yet&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What does all of this mean 😨 ? &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;iptables&lt;/code&gt;: It's the binary, I hope you understand that 😁&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-A INPUT&lt;/code&gt;: Means "append" this rule to the INPUT chain. You don't have to specify a specific table because "filter" is selected by default (convenient!).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-p tcp&lt;/code&gt;: Means, filter packets on their protocol type. Can be tcp, udp, icmp, or all. If the incoming packet had been an UDP packet then this rule &lt;strong&gt;wouldn't match it&lt;/strong&gt; and the packet would move on to the next rule.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--dport 22&lt;/code&gt;: Means, filter on packets that have a destination port set to 22. If our packet was meant to be addressed to another port (80, 53, etc) then this rule &lt;strong&gt;wouldn't match it&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-j ACCEPT&lt;/code&gt;: Means, what action to take when the rule matches a packet. Can be "accept", "drop" or jumping to another chain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Preventing failures
&lt;/h2&gt;

&lt;p&gt;🎉Good news! If you inadvertently lock yourself out by blocking SSH or else, you should know that by default all iptables are restored when the VM reboots! So if you get locked out, simply reboot using the hypervisor. All clouders and VM providers have a button somewhere to hard reboot a VM. No need to destroy it!  &lt;/p&gt;

&lt;p&gt;Also, you should save the current rules you have just in case :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables-save &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; iptables_save.log 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding a custom chain
&lt;/h2&gt;

&lt;p&gt;We want to avoid putting our custom K8S rules in the INPUT chain. Instead, we'll create a new chain that will be dedicated to Kubernetes stuff and will be called &lt;strong&gt;from inside&lt;/strong&gt; the INPUT chain.  &lt;/p&gt;

&lt;p&gt;Let's create a new chain in the filter table called KUBE-PROTECTION&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-N&lt;/span&gt; KUBE-PROTECTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if we list our chains again&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-xvnL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn624wz4cjbr4ud0loeqh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn624wz4cjbr4ud0loeqh.png" alt="Image description" width="800" height="357"&gt;&lt;/a&gt;⬆️&lt;em&gt;Our new custom chain has been created&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We can see the KUBE-PROTECTION chain but it's empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding rules to a custom chain
&lt;/h2&gt;

&lt;p&gt;For example, let's say we want an "allow SSH" rule to be stored in the KUBE-PROTECTION chain. We would use the command like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-m&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 22 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;It's simply "-A "&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Listing our rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-xvnL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqhah1q5xth7pf1yygul0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqhah1q5xth7pf1yygul0.png" alt="Image description" width="800" height="363"&gt;&lt;/a&gt;⬆️&lt;em&gt;A rule has been added to our custom chain&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yahoo! The rule is correctly stored in the KUBE-PROTECTION chain.  &lt;/p&gt;

&lt;p&gt;It's fairly easy to read: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;pkts&lt;/strong&gt;: 0 =&amp;gt; Is the number of packets that have matched this rule. For now, no packets have matched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;target&lt;/strong&gt;: ACCEPT =&amp;gt; Means that if the packet matches then it's accepted and can directly go through to the next iptable's table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;prot&lt;/strong&gt;: TCP =&amp;gt; The protocol the packet must match to be evaluated by this rule&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;source&lt;/strong&gt;: 0.0.0.0/0 =&amp;gt; Means that the packet must be sent from a specific IP address so the rule can match it. "0.0.0.0/0" means all source IPs can match. &lt;/li&gt;
&lt;li&gt;"dpt:22" =&amp;gt; Means that the packet must be sent to our port 22 to match this rule.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Calling our custom chain
&lt;/h2&gt;

&lt;p&gt;Having a custom chain to store custom rules is great. But for now, our incoming packet will not magically be redirected to our custom chain. As I mentioned earlier chains act like functions in programming. So we must call our custom chain from the INPUT chain (which, remember, is always the first chain to be called in the filter table)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-j&lt;/span&gt; KUBE-PROTECTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This might look a bit weird. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "&lt;code&gt;-A INPUT&lt;/code&gt;" means: Add a rule to the INPUT chain.&lt;/li&gt;
&lt;li&gt;The "&lt;code&gt;-J KUBE-PROTECTION&lt;/code&gt;" means: Let's take the action of &lt;strong&gt;jumping&lt;/strong&gt; to a chain called KUBE-PROTECTION. This might feel strange because previously we saw that -j was meant to ACCEPT or REJECT a packet. Fortunately, this is the last action that can be specified with -j , there is no fourth option.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now if we take a look at our filter table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-xvnL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7c1990xdk34clky3r9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz7c1990xdk34clky3r9d.png" alt="Image description" width="800" height="376"&gt;&lt;/a&gt;⬆️&lt;em&gt;We added an action rule in INPUT to jump to KUBE-PROTECTION&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We now have 1 rule in our INPUT chain. Its "target" is "KUBE-PROTECTION". So any packet that reaches this line will jump to the specified chain.  &lt;/p&gt;

&lt;p&gt;We can also see that the number of matched packets ("pkts" circled in red) in our custom rule is now rising. As I'm using SSH, all my packets on port 22 are now matching this rule and being accepted!  &lt;/p&gt;

&lt;p&gt;In programming, functions automatically return to their caller when finished. It's the same here.  &lt;/p&gt;

&lt;p&gt;When all rules inside our KUBE-PROTECTION chain have been executed and the packet hasn't matched any rules then the execution goes back to the INPUT chain to the next rule.  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note that it could also reach another jump rule and continue to yet another custom chain…&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Removing a rule
&lt;/h2&gt;

&lt;p&gt;For many reasons, you may need to remove a rule. The simplest way is to list the rules with their number and remove the rule you want using the specific ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-xvnL&lt;/span&gt; &lt;span class="nt"&gt;--line-numbers&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fymfc0lgwp25tqq2799m2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fymfc0lgwp25tqq2799m2.png" alt="Image description" width="800" height="359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's remove the "allow SSH" rule from the KUBE-PROTECTION chain. It's rule number 1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-D&lt;/span&gt; KUBE-PROTECTION 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's pretty simple: it's the chain name followed by the rule number to remove.  &lt;/p&gt;

&lt;h2&gt;
  
  
  A few more useful iptables options
&lt;/h2&gt;

&lt;p&gt;Before we dive into the specific Kubernetes use case let's see one last set of options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION
         &lt;span class="nt"&gt;-s&lt;/span&gt; 10.11.12.13,10.21.22.23
         &lt;span class="nt"&gt;-i&lt;/span&gt; eth0
         &lt;span class="nt"&gt;-p&lt;/span&gt; TCP
         &lt;span class="nt"&gt;-m&lt;/span&gt; multiport &lt;span class="nt"&gt;--dport&lt;/span&gt; 2379:2380
         &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"This is a comment"&lt;/span&gt;
         &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few new selectors here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-s &amp;lt;IP,IP,...&amp;gt;&lt;/code&gt;: Means, match only packets coming from one of these source IPs. If the packet is not sent from one of these IPs then the rule doesn't match and the packet carries on.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-i &amp;lt;network_interface&amp;gt;&lt;/code&gt;: Means, match only packets that are received on this specific network interface. Depending on your OS and version you will have one typical default interface name. Either "&lt;em&gt;eth0&lt;/em&gt;", "&lt;em&gt;ens5&lt;/em&gt;" or "&lt;em&gt;enp&lt;/em&gt;". Newer systems tend to use "&lt;em&gt;ens5&lt;/em&gt;" and older "&lt;em&gt;eth0&lt;/em&gt;". Note that your VM will probably have other network interfaces but we won't care about them. Other processes deal with them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To find your main interface you can use :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ip &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="c"&gt;# or&lt;/span&gt;
ifconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These  will list your network interfaces and you should find a familiar name lost in there (eth, ens, enp)…  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-m multiport&lt;/code&gt;: The "-m" is tricky because it can be used multiple times in one command to activate further options which is kind of dumb. In this case, it makes the next option possible.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--dport &amp;lt;portA:portB&amp;gt;&lt;/code&gt;: Means allow all ports in the range A to B. Port ranges are available due to the previous use of the "-m multiport" option.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-m comment&lt;/code&gt;: Means, active the comment option… I think that you see why this whole "-m" stuff is silly. Why not just allow comments on all rules… Brrr. Though I won't complain to the dead guys who developed this in 1999. At least it works!&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--comment "&amp;lt;Some comment&amp;gt;"&lt;/code&gt; : Self-explanatory. You should comment on all your rules to describe what protocol or software they cover. For instance, for the "allow 22" rule we should rewrite the rule with
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"Allow SSH connectivity"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  III. Building our Kubernetes protection rules
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Strict minimum rules to have in the INPUT chain
&lt;/h2&gt;

&lt;p&gt;We'll add all the Kubernetes stuff in a private chain and call it from the INPUT chain. However, you should check that your INPUT chain has the following rules evaluated before jumping to your custom chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-m&lt;/span&gt; conntrack &lt;span class="nt"&gt;--ctstate&lt;/span&gt; RELATED,ESTABLISHED &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-i&lt;/span&gt; lo &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-m&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 22 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-p&lt;/span&gt; icmp
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; INPUT &lt;span class="nt"&gt;-j&lt;/span&gt; KUBE-PROTECTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before applying these rules check if you don't already have them using &lt;code&gt;iptables -xvnL&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Line 1&lt;/strong&gt;: (BORING STUFF, CONSIDERING MOVING ALONG) The &lt;code&gt;-m contrack&lt;/code&gt; module allows us to use the following option &lt;code&gt;--ctstate&lt;/code&gt;. It has two values: "&lt;em&gt;related&lt;/em&gt;" and "&lt;em&gt;established&lt;/em&gt;". The &lt;em&gt;established&lt;/em&gt; field allows currently opened connections to stay alive. The &lt;em&gt;related&lt;/em&gt; field on the other hand is used when a server returns an answer (to a request) on a different port than the one it was received on. For instance, you could have an FTP server that receives a packet on port 21 and answers on port 1234 (strange… I know!). If you don't allow &lt;em&gt;related&lt;/em&gt; connections then the server won't be able to answer! This is quite frequent so this rule is a must-have.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 2&lt;/strong&gt;: Accept packets on the loopback interface which basically means that you can talk to yourself. If you're a human then maybe consider a psychotherapist but for machines, it's all good!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 3&lt;/strong&gt;: Accept SSH on port 22. Don't lose this one…&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 4&lt;/strong&gt;: Accept packets of type ICMP. ICMP is the protocol used by ping to allow anyone to ping your VM. You should remove it if you're directly connected to the internet. But if your VM is part of a private network then it's better to allow ping. It's good to know if the VM is alive or dead and ping does an amazing job of that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line 5&lt;/strong&gt;: If a packet has not been accepted or rejected yet and attains this rule it jumps onto our custom chain KUBE-PROTECTION.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What are the known legit Kubernetes packets to allow?
&lt;/h2&gt;

&lt;p&gt;Kubernetes provides information about ports on VMs that must be opened to allow it to work properly: &lt;a href="https://kubernetes.io/docs/reference/networking/ports-and-protocols/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3rawxo9y7ghgv3b5krx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3rawxo9y7ghgv3b5krx.png" alt="Image description" width="800" height="526"&gt;&lt;/a&gt;⬆️&lt;em&gt;Needed ports for Kubernetes to work taken from the documentation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Based on the above screenshot, the allowed ports to listen on our 3 types of VMs depend on their role:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Master nodes:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6443 TCP =&amp;gt; Main entry point for API-server&lt;/li&gt;
&lt;li&gt;10250 TCP =&amp;gt; Kubelet&lt;/li&gt;
&lt;li&gt;8472 UDP (Canal/Flannel VXLAN overlay networking)&lt;/li&gt;
&lt;li&gt;30000:32767 =&amp;gt; Node ports services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Worker nodes:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10250 TCP =&amp;gt; Kubelet&lt;/li&gt;
&lt;li&gt;30000:32767 =&amp;gt; Node ports services&lt;/li&gt;
&lt;li&gt;53 TCP/UDP =&amp;gt; CoreDNS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;ETCDs:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2379:2380 TCP =&amp;gt;Main entry point for ETCD communication&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Methodology to authorize Kubernetes-related requests and not break everything
&lt;/h2&gt;

&lt;p&gt;All that is not Kubernetes-related network must be investigated!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm 99% sure that if you only open the above ports and switch to "default deny" then things will break… &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's why we must monitor incoming packets and understand what is sending them to us and why! For instance, your VMs could have monitoring software checking if the VM is healthy, and cutting off all accesses to these legitimate external services will go BOOM…  &lt;/p&gt;

&lt;p&gt;So we need a way to log these packets and decide if we write a rule to match them or not. If we don't explicitly reject them they will be rejected by the "default deny" when we activate it.&lt;/p&gt;




&lt;p&gt;We can achieve this by adding a logging rule. Each packet that goes through this rule will be logged with all its details :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-j&lt;/span&gt; LOG &lt;span class="nt"&gt;--log-prefix&lt;/span&gt; &lt;span class="s2"&gt;"iptables: "&lt;/span&gt; &lt;span class="nt"&gt;--log-level&lt;/span&gt; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This rule will be added at the end of our chain and then log packets that none of our rules matched.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtvokd9fp07623vccmip.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtvokd9fp07623vccmip.png" alt="Image description" width="800" height="416"&gt;&lt;/a&gt;⬆️&lt;em&gt;A log rule has been added at the end of our chain; Also note that I have added to INPUT the rules mentioned earlier&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now let's look at the logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;journalctl &lt;span class="nt"&gt;-f&lt;/span&gt; |grep iptables
Jan 29 23:02:06 vps651163 kernel: iptables: &lt;span class="nv"&gt;IN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eth0 &lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;MAC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fa:17:3e:a6:79:bc:e6:5b:1f:4e:18:32:08:00 &lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;143.188.50.238 &lt;span class="nv"&gt;DST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.65.42.44 &lt;span class="nv"&gt;LEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;52 &lt;span class="nv"&gt;TOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 &lt;span class="nv"&gt;PREC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 &lt;span class="nv"&gt;TTL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;42 &lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;53443 DF &lt;span class="nv"&gt;PROTO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;SPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;35163 &lt;span class="nv"&gt;DPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;22 &lt;span class="nv"&gt;WINDOW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1191 &lt;span class="nv"&gt;RES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 ACK &lt;span class="nv"&gt;URGP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
Jan 29 23:02:07 vps651163 kernel: iptables: &lt;span class="nv"&gt;IN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eth0 &lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;MAC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fa:17:3e:a6:79:bc:e6:5b:1f:4e:18:32:08:00 &lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;137.114.37.1 &lt;span class="nv"&gt;DST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.65.42.44 &lt;span class="nv"&gt;LEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;32 &lt;span class="nv"&gt;TOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x08 &lt;span class="nv"&gt;PREC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 &lt;span class="nv"&gt;TTL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5 &lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 DF &lt;span class="nv"&gt;PROTO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ICMP &lt;span class="nv"&gt;TYPE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8 &lt;span class="nv"&gt;CODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;7479 &lt;span class="nv"&gt;SEQ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
Jan 29 23:02:07 vps651163 kernel: iptables: &lt;span class="nv"&gt;IN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eth0 &lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;MAC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fa:17:3e:a6:79:bc:e6:5b:1f:4e:18:32:08:00 &lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;82.242.156.152 &lt;span class="nv"&gt;DST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.65.42.44 &lt;span class="nv"&gt;LEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;84 &lt;span class="nv"&gt;TOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 &lt;span class="nv"&gt;PREC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 &lt;span class="nv"&gt;TTL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;243 &lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24788 DF &lt;span class="nv"&gt;PROTO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;SPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;49834 &lt;span class="nv"&gt;DPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1234 &lt;span class="nv"&gt;WINDOW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1026 &lt;span class="nv"&gt;RES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 ACK PSH &lt;span class="nv"&gt;URGP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
Jan 29 23:02:07 vps651163 kernel: iptables: &lt;span class="nv"&gt;IN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eth0 &lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;MAC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fa:17:3e:a6:79:bc:e6:5b:1f:4e:18:32:08:00 &lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;76.242.156.152 &lt;span class="nv"&gt;DST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.65.42.44 &lt;span class="nv"&gt;LEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;84 &lt;span class="nv"&gt;TOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 &lt;span class="nv"&gt;PREC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 &lt;span class="nv"&gt;TTL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;243 &lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24789 DF &lt;span class="nv"&gt;PROTO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;SPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;49834 &lt;span class="nv"&gt;DPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;6443 &lt;span class="nv"&gt;WINDOW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1026 &lt;span class="nv"&gt;RES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 ACK PSH &lt;span class="nv"&gt;URGP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
Jan 29 23:02:07 vps651163 kernel: iptables: &lt;span class="nv"&gt;IN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eth0 &lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;MAC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fa:17:3e:a6:79:bc:e6:5b:1f:4e:18:32:08:00 &lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;30.242.156.152 &lt;span class="nv"&gt;DST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.65.42.44 &lt;span class="nv"&gt;LEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;84 &lt;span class="nv"&gt;TOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 &lt;span class="nv"&gt;PREC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 &lt;span class="nv"&gt;TTL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;243 &lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24790 DF &lt;span class="nv"&gt;PROTO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;SPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;49834 &lt;span class="nv"&gt;DPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;21 &lt;span class="nv"&gt;WINDOW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1026 &lt;span class="nv"&gt;RES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 ACK PSH &lt;span class="nv"&gt;URGP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
Jan 29 23:02:07 vps651163 kernel: iptables: &lt;span class="nv"&gt;IN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eth0 &lt;span class="nv"&gt;OUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;MAC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;fa:17:3e:a6:79:bc:e6:5b:1f:4e:18:32:08:00 &lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;27.242.156.152 &lt;span class="nv"&gt;DST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.65.42.44 &lt;span class="nv"&gt;LEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;84 &lt;span class="nv"&gt;TOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 &lt;span class="nv"&gt;PREC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 &lt;span class="nv"&gt;TTL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;243 &lt;span class="nv"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;24791 DF &lt;span class="nv"&gt;PROTO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;SPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;49834 &lt;span class="nv"&gt;DPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;22 &lt;span class="nv"&gt;WINDOW&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1026 &lt;span class="nv"&gt;RES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0x00 ACK PSH &lt;span class="nv"&gt;URGP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(scroll to the right)&lt;/em&gt;&lt;br&gt;
There you'll see all the packets that didn't match any of our rules. If we had activated the "default deny" they all would have been dropped (killed 😵).  &lt;/p&gt;
&lt;h2&gt;
  
  
  Anatomy of a packet log
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Taking one random line from above and removing useless information for convenience:&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;IN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eth0 &lt;span class="nv"&gt;SRC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;82.242.156.152 &lt;span class="nv"&gt;DST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.65.42.44 &lt;span class="nv"&gt;PROTO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;TCP &lt;span class="nv"&gt;SPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;49834 &lt;span class="nv"&gt;DPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1234
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What you should extract from each log line is:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SRC&lt;/strong&gt;: That's the IP who contacted you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DST&lt;/strong&gt;: It's… You. So we don't care. Just being clear.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PROTO&lt;/strong&gt;: The protocol used by the incoming packet: TCP/UDP/ICMP&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DPT&lt;/strong&gt;: The DestinationPort on your VM where the packet wants to arrive. For instance, if "DPT=22" then it's probably a packet for the SSH service.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to do with these packet logs?
&lt;/h2&gt;

&lt;p&gt;Don't ask me?? It's not like I can explicitly tell you if the TCP packet on port &lt;em&gt;1234&lt;/em&gt; from &lt;em&gt;82.242.156.152&lt;/em&gt; is legit!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BUT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I can tell you how you should proceed next…&lt;/p&gt;

&lt;p&gt;For each log line check if a program is listening on your VM on this specific port. Looking for _1234 _port :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;netstat &lt;span class="nt"&gt;-tupln&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;1234
tcp 0 0 0.0.0.0:1234 0.0.0.0:&lt;span class="k"&gt;*&lt;/span&gt;  LISTEN  14182/random-software
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There you go. A program called "random-software" (pid 14182) is listening on this port. Is this legit? You should check with your boss but if something is already listening on the VM you should probably ACCEPT the packet.  &lt;/p&gt;

&lt;p&gt;If it's legit then add a new ACCEPT rule like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-m&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 1234 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"random-software"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the contrary, if no processes are listening on this port then you really should investigate the IP address from which it's coming. It will help you determine why it's contacting you again and again. If it doesn't seem legit then do nothing! When we go live and activate the "default deny" it will be blocked!&lt;/p&gt;

&lt;h2&gt;
  
  
  Allowing only certain sources
&lt;/h2&gt;

&lt;p&gt;This part is for more advanced users, consider skipping ahead for now! The "-s" option of iptables allows packets to match rules only if they come from specific IP sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables ... &lt;span class="nt"&gt;-s&lt;/span&gt; 10.1.2.3,10.4.5.6,...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful to restrict access to the Kubernetes-related ports to only the Kubernetes VMs themselves.  &lt;/p&gt;

&lt;p&gt;It's a bit tricky if you want to do some fine-grained work. But you can at least rewrite the policies using the IPs of all your masters, worker nodes, and ETCDs specifying all their IP addresses. This way only the cluster components can talk to each other and random external IPs won't.  &lt;/p&gt;

&lt;p&gt;However, this will need some level of automation on your side. Because when the cluster expands or shrinks (node-wise) then you must update all the iptables "-s" list with the new set of authorized IPs.  &lt;/p&gt;

&lt;p&gt;Ansible could help you resync rules for instance. 😉  &lt;/p&gt;

&lt;h2&gt;
  
  
  A little upstart
&lt;/h2&gt;

&lt;p&gt;To get started, here is the minimum list of rules based on the Kubernetes documentation and experimentations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Master nodes:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 6443 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"API-server"&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 10250 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"Kubelet"&lt;/span&gt;

iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-m&lt;/span&gt; multiport &lt;span class="nt"&gt;--dport&lt;/span&gt; 30000:32767 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"NodePort"&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 9200 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"Node Exporter"&lt;/span&gt;
&lt;span class="c"&gt;# Common network virt protocol for pod to pod communication &lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; udp &lt;span class="nt"&gt;--dport&lt;/span&gt; 8472 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"VXLAN overlay"&lt;/span&gt;

&lt;span class="c"&gt;# If using cilium&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-m&lt;/span&gt; multiport &lt;span class="nt"&gt;--dport&lt;/span&gt; 4240 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"Cilium"&lt;/span&gt;
&lt;span class="c"&gt;# If using Flannel&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-m&lt;/span&gt; multiport &lt;span class="nt"&gt;--dport&lt;/span&gt; 8285 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"Flannel"&lt;/span&gt; 

&lt;span class="c"&gt;# Logging packets that did not match any previous rules&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-j&lt;/span&gt; LOG &lt;span class="nt"&gt;--log-prefix&lt;/span&gt; &lt;span class="s2"&gt;"iptables: "&lt;/span&gt; &lt;span class="nt"&gt;--log-level&lt;/span&gt; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worker nodes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 10250 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"Kubelet"&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-m&lt;/span&gt; multiport &lt;span class="nt"&gt;--dport&lt;/span&gt; 30000:32767 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"NodePort"&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;--dport&lt;/span&gt; 53 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"CoreDNS"&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; udp &lt;span class="nt"&gt;--dport&lt;/span&gt; 53 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"CoreDNS"&lt;/span&gt;

&lt;span class="c"&gt;# Logging packets that did not match any previous rules&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-j&lt;/span&gt; LOG &lt;span class="nt"&gt;--log-prefix&lt;/span&gt; &lt;span class="s2"&gt;"iptables: "&lt;/span&gt; &lt;span class="nt"&gt;--log-level&lt;/span&gt; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-i&lt;/span&gt; eth0 &lt;span class="nt"&gt;-p&lt;/span&gt; tcp &lt;span class="nt"&gt;-m&lt;/span&gt; multiport &lt;span class="nt"&gt;--dport&lt;/span&gt; 2379:2380 &lt;span class="nt"&gt;-j&lt;/span&gt; ACCEPT &lt;span class="nt"&gt;-m&lt;/span&gt; comment &lt;span class="nt"&gt;--comment&lt;/span&gt; &lt;span class="s2"&gt;"ETCD"&lt;/span&gt;

&lt;span class="c"&gt;# Logging packets that did not match any previous rules&lt;/span&gt;
iptables &lt;span class="nt"&gt;-A&lt;/span&gt; KUBE-PROTECTION &lt;span class="nt"&gt;-j&lt;/span&gt; LOG &lt;span class="nt"&gt;--log-prefix&lt;/span&gt; &lt;span class="s2"&gt;"iptables: "&lt;/span&gt; &lt;span class="nt"&gt;--log-level&lt;/span&gt; 6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;⚠️Remember to update "&lt;em&gt;eth0&lt;/em&gt;" with your public interface.&lt;br&gt;&lt;br&gt;
Also, you can have fun with the "&lt;code&gt;-s &amp;lt;IPs&amp;gt;&lt;/code&gt;" if you wish.  &lt;/p&gt;

&lt;p&gt;Finally, you must monitor the remaining packets and decide what to do with them. Accept? Reject? Do nothing (same as reject when we activate default deny)?  &lt;/p&gt;
&lt;h2&gt;
  
  
  Changing the default ACCEPT to default DENY
&lt;/h2&gt;

&lt;p&gt;It's time!  &lt;/p&gt;

&lt;p&gt;You have allowed all Kubernetes ports and other proprietary stuff that runs on the VMs. The rest must die! &lt;/p&gt;

&lt;p&gt;Time to reject all that doesn't match our rules by activating the great denier !! (kinda scary)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;iptables &lt;span class="nt"&gt;-P&lt;/span&gt; INPUT DROP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means "When a packet reaches the end of the INPUT chain without having matched any rules then DROP it. It dies… Astalavista el packet ! Adios!  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The default should shift from:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;iptables &lt;span class="nt"&gt;-xvnL&lt;/span&gt;
Chain INPUT &lt;span class="o"&gt;(&lt;/span&gt;policy ACCEPT 0 packets, 0 bytes&lt;span class="o"&gt;)&lt;/span&gt;
    pkts bytes target prot opt &lt;span class="k"&gt;in &lt;/span&gt;out  &lt;span class="nb"&gt;source &lt;/span&gt;destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(See "policy ACCEPT" ?)&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;To:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;iptables &lt;span class="nt"&gt;-xvnL&lt;/span&gt;
Chain INPUT &lt;span class="o"&gt;(&lt;/span&gt;policy DROP 0 packets, 0 bytes&lt;span class="o"&gt;)&lt;/span&gt;
   pkts bytes target prot opt &lt;span class="k"&gt;in &lt;/span&gt;out  &lt;span class="nb"&gt;source &lt;/span&gt;destination
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(See "policy DROP" ?)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;ℹ️ Remember that if all goes to sh*t you can just reboot the VM.   You could also revert the default policy with &lt;code&gt;iptables -P INPUT ACCEPT&lt;/code&gt; if needed.&lt;br&gt;&lt;br&gt;
I also recommend that you do this on one VM at a time. Start with a worker node. If all goes well then do all the workers. Then one ETCD, all ETCD, and finally one master, all masters. If the cluster is still alive then GG!  &lt;/p&gt;

&lt;p&gt;If not, then go back to your VMs and log the dropped packets with the logging command (cf above) to find what you should have left open!  &lt;/p&gt;
&lt;h1&gt;
  
  
  IV. Keeping our iptables configuration after reboots
&lt;/h1&gt;

&lt;p&gt;It's time to persist our configuration. Now this will mostly depend on your OS but I have a few suggestions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using a Linux package
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install &lt;/span&gt;iptables-persistent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;During the installation, you will be prompted to save the current IPv4 and IPv6 rules. Choose "Yes" to save the rules.&lt;/p&gt;

&lt;p&gt;Usage:&lt;br&gt;&lt;br&gt;
The &lt;code&gt;iptables-persistent&lt;/code&gt; package provides two commands: &lt;code&gt;iptables-save&lt;/code&gt; and &lt;code&gt;iptables-restore&lt;/code&gt;.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;To save the current iptables rules:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables-save &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/iptables/rules.v4
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip6tables-save &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/iptables/rules.v6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;To restore iptables rules at startup:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;netfilter-persistent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, on each system startup, the netfilter-persistent service will read the rules from /etc/iptables/rules.v4 for IPv4 and /etc/iptables/rules.v6 for IPv6 and apply them.  &lt;/p&gt;

&lt;p&gt;Alternatively, you can manually restore the rules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;iptables-restore &amp;lt; /etc/iptables/rules.v4
&lt;span class="nb"&gt;sudo &lt;/span&gt;ip6tables-restore &amp;lt; /etc/iptables/rules.v6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind that the exact procedure might vary slightly based on your Linux distribution, especially for non-Debian-based systems.  &lt;/p&gt;

&lt;h2&gt;
  
  
  When the main interface goes UP
&lt;/h2&gt;

&lt;p&gt;We have bound our rules to the public interface of the VM using "&lt;code&gt;-i eth0&lt;/code&gt;" (or "&lt;em&gt;ens5&lt;/em&gt;", etc). We can tell Linux to bring up our rules before the relevant interface is activated. This is, in my opinion, the safest way to go as there won't be any milliseconds where the VM is not firewalled during a reboot. Sometimes a millisecond is all it takes to get hacked.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a shell script &lt;code&gt;/etc/firewall-start&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&amp;lt;Add your iptables rules here&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you added all your custom rules in a custom chain then your script should perform the following iptables tasks:&lt;/p&gt;

&lt;p&gt;A) &lt;strong&gt;Flush&lt;/strong&gt; the chain (see iptables --flush)  &lt;/p&gt;

&lt;p&gt;B) &lt;strong&gt;Repopulate&lt;/strong&gt; the content of your custom chain using the iptables rules mentioned earlier.  &lt;/p&gt;

&lt;p&gt;C) &lt;strong&gt;Check&lt;/strong&gt; the existence of the rule that &lt;strong&gt;calls&lt;/strong&gt; your custom chain from INPUT using the &lt;code&gt;iptables -c&lt;/code&gt; option. If this returns true then your script doesn't need to add the jump rule as it already exists. If it returns false then add the jump instruction to the INPUT chain.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Edit &lt;code&gt;/etc/network/interfaces&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;auto eth0
iface eth0 inet static
pre-up /etc/firewall-start &lt;span class="c"&gt;# &amp;lt;= Add this line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file will be read during startup and execute the script &lt;code&gt;/etc/firewall-start&lt;/code&gt; which will populate your rules. Then the associated interface (_eth0 _here) will be brought up.&lt;/p&gt;

&lt;h1&gt;
  
  
  V. Conclusion
&lt;/h1&gt;

&lt;p&gt;You made it alive. GG! 🎉  &lt;/p&gt;

&lt;p&gt;This may not be the most robust config that exists in the industry but you now have a working firewall on your cluster and that's a start! In the future, you may look at dealing with firewalls using Cilium and all the EBPF stuff that makes no sense. It will be great one day. Just not today. Today was iptables day! ^^  &lt;/p&gt;

&lt;p&gt;HF&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>network</category>
      <category>firewalls</category>
      <category>security</category>
    </item>
    <item>
      <title>Installing PrivateGPT on WSL with GPU support</title>
      <dc:creator>Emilien Lancelot</dc:creator>
      <pubDate>Sat, 20 Jan 2024 23:29:33 +0000</pubDate>
      <link>https://dev.to/docteurrs/installing-privategpt-on-wsl-with-gpu-support-1m2a</link>
      <guid>https://dev.to/docteurrs/installing-privategpt-on-wsl-with-gpu-support-1m2a</guid>
      <description>&lt;p&gt;[ UPDATED 23/03/2024 ]&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/imartinez/privateGPT" rel="noopener noreferrer"&gt;PrivateGPT &lt;/a&gt;is a production-ready AI project that allows you to ask questions about your documents using the power of Large Language Models (LLMs), even in scenarios without an Internet connection. 100% private, no data leaves your execution environment at any point.&lt;/p&gt;

&lt;p&gt;Running it on Windows Subsystem for Linux (WSL) with GPU support can significantly enhance its performance. In this guide, I will walk you through the step-by-step process of installing PrivateGPT on WSL with GPU acceleration.&lt;/p&gt;

&lt;p&gt;Installing this was a pain in the a** and took me 2 days to get it to work. Hope this can help you on your own journey… Good luck !&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we begin, make sure you have the latest version of Ubuntu WSL installed. You can choose from versions such as Ubuntu-22–04–3 LTS or Ubuntu-22–04–6 LTS available on the Windows Store.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Updating Ubuntu
sudo apt-get update
sudo apt-get upgrade
sudo apt-get install build-essential
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ “upgrade” is very important as python stuff will explode later if you don’t&lt;/p&gt;

&lt;h2&gt;
  
  
  Cloning the PrivateGPT repo
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/imartinez/privateGPT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting Up Python Environment
&lt;/h2&gt;

&lt;p&gt;To manage Python versions, we’ll use pyenv. Follow the commands below to install it and set up the Python environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get install git gcc make openssl libssl-dev libbz2-dev libreadline-dev libsqlite3-dev zlib1g-dev libncursesw5-dev libgdbm-dev libc6-dev zlib1g-dev libsqlite3-dev tk-dev libssl-dev openssl libffi-dev
curl https://pyenv.run | bash
export PATH="/home/$(whoami)/.pyenv/bin:$PATH"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following lines to your .bashrc file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export PYENV_ROOT="$HOME/.pyenv"
[[ -d $PYENV_ROOT/bin ]] &amp;amp;&amp;amp; export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reload your terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source ~/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install important missing pyenv stuff&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt-get install lzma
sudo apt-get install liblzma-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Python 3.11 and set it as the global version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pyenv install 3.11
pyenv global 3.11
pip install pip --upgrade
pyenv local 3.11
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Poetry Installation
&lt;/h2&gt;

&lt;p&gt;Install poetry to manage dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sSL https://install.python-poetry.org | python3 -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following line to your .bashrc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export PATH="/home/&amp;lt;YOU USERNAME&amp;gt;/.local/bin:$PATH"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ Replace  by your WSL username (&lt;code&gt;$ whoami&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Reload your configuration&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source ~/.bashrc
poetry --version # should display something without errors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Installing PrivateGPT Dependencies
&lt;/h2&gt;

&lt;p&gt;Navigate to the PrivateGPT directory and install dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd privateGPT
poetry install --extras "ui embeddings-huggingface llms-llama-cpp vector-stores-qdrant"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;In need of a free and open-source Multi-Agents framework built for running local LLMs?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;▶️ &lt;a href="https://remembersoftwares.github.io/yacana/" rel="noopener noreferrer"&gt;Checkout Yacana&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Nvidia Drivers Installation
&lt;/h2&gt;

&lt;p&gt;Visit &lt;a href="https://developer.nvidia.com/cuda-downloads?target_os=Linux&amp;amp;target_arch=x86_64&amp;amp;Distribution=WSL-Ubuntu&amp;amp;target_version=2.0&amp;amp;target_type=deb_network" rel="noopener noreferrer"&gt;Nvidia’s official website&lt;/a&gt; to download and install the Nvidia drivers for WSL. Choose Linux &amp;gt; x86_64 &amp;gt; WSL-Ubuntu &amp;gt; 2.0 &amp;gt; deb (network)&lt;/p&gt;

&lt;p&gt;Follow the instructions provided on the page.&lt;/p&gt;

&lt;p&gt;Add the following lines to your .bashrc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export PATH="/usr/local/cuda-12.4/bin:$PATH"
export LD_LIBRARY_PATH="/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ Maybe check the content of “/usr/local” to be sure that you do have the “cuda-12.4” folder. Yours might have a different version.&lt;/p&gt;

&lt;p&gt;Reload your configuration and check that all is working as expected&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source ~/.bashrc
nvcc --version
nvidia-smi.exe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ “nvidia-smi” isn’t available on WSL so just verify that the .exe one detects your hardware. Both commands should displayed gibberish but no apparent errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building and Running PrivateGPT
&lt;/h2&gt;

&lt;p&gt;Finally, install LLAMA CUDA libraries and Python bindings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CMAKE_ARGS='-DLLAMA_CUBLAS=on' poetry run pip install --force-reinstall --no-cache-dir llama-cpp-python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let private GPT download a local LLM for you (mixtral by default):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;poetry run python scripts/setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run PrivateGPT, use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will initialize and boot PrivateGPT with GPU support on your WSL environment.&lt;/p&gt;

&lt;p&gt;ℹ️ You should see “blas = 1” if GPU offload is working.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...............................................................................................
llama_new_context_with_model: n_ctx      = 3900
llama_new_context_with_model: freq_base  = 1000000.0
llama_new_context_with_model: freq_scale = 1
llama_kv_cache_init:      CUDA0 KV buffer size =   487.50 MiB
llama_new_context_with_model: KV self size  =  487.50 MiB, K (f16):  243.75 MiB, V (f16):  243.75 MiB
llama_new_context_with_model: graph splits (measure): 3
llama_new_context_with_model:      CUDA0 compute buffer size =   275.37 MiB
llama_new_context_with_model:  CUDA_Host compute buffer size =    15.62 MiB
AVX = 1 | AVX_VNNI = 0 | AVX2 = 1 | AVX512 = 0 | AVX512_VBMI = 0 | AVX512_VNNI = 0 | FMA = 1 | NEON = 0 | ARM_FMA = 0 | F16C = 1 | FP16_VA = 0 | WASM_SIMD = 0 | BLAS = 1 | SSE3 = 1 | SSSE3 = 1 | VSX = 0 |
18:50:50.097 [INFO    ] private_gpt.components.embedding.embedding_component - Initializing the embedding model in mode=local 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ℹ️ Go to &lt;code&gt;127.0.0.1:8001&lt;/code&gt; in your browser&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1saf30ttizfp578cxnvn.PNG" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1saf30ttizfp578cxnvn.PNG" alt="Image description" width="800" height="672"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Uploaded the Orca paper and asking random stuff about it.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By following these steps, you have successfully installed PrivateGPT on WSL with GPU support. Enjoy the enhanced capabilities of PrivateGPT for your natural language processing tasks.&lt;/p&gt;

&lt;p&gt;If something went wrong then open your window and throw your computer away. Then start again at step 1.&lt;/p&gt;

&lt;p&gt;You can also remove the WSL with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;wsl.exe --list -v
wsl --unregister &amp;lt;name of the wsl to  remove&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this article help you in any way consider giving it a like ! Thx&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;Having a crash when asking a question or doing make run ? Here are the issues I encountered and how I fixed them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cuda error
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CUDA error: the provided PTX was compiled with an unsupported toolchain.
  current device: 0, in function ggml_cuda_op_flatten at /tmp/pip-install-3kkz0k8s/llama-cpp-python_a300768bdb3b475da1d2874192f22721/vendor/llama.cpp/ggml-cuda.cu:9119
  cudaGetLastError()
GGML_ASSERT: /tmp/pip-install-3kkz0k8s/llama-cpp-python_a300768bdb3b475da1d2874192f22721/vendor/llama.cpp/ggml-cuda.cu:271: !"CUDA error"
huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...
To disable this warning, you can either:
        - Avoid using `tokenizers` before the fork if possible
        - Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)
make: *** [Makefile:36: run] Aborted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one comes from downloading the latest CUDA stuff and your drivers are not up to date. So open the Nvidia "Geforce experience" app from Windows and upgrade to the latest version and then reboot.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU only&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If privateGPT still sets BLAS to 0 and runs on CPU only, try to close all WSL2 instances. Then reopen one and try again.&lt;br&gt;&lt;br&gt;
If it's still on CPU only then try rebooting your computer. This is not a joke… Unfortunatly.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on using LM Studio as backend
&lt;/h2&gt;

&lt;p&gt;I tried to use the server of LMStudio as fake OpenAI backend. It does work but not very well. Need to do more tests on that and I’ll update here.&lt;/p&gt;

&lt;p&gt;For now what I did is start the LMStudio server on the port 8002 and unchecked “Apply Prompt Formatting”.&lt;/p&gt;

&lt;p&gt;On PrivateGPT I edited &lt;code&gt;“settings-vllm.yaml”&lt;/code&gt; and updated “openai &amp;gt; api_base” to &lt;code&gt;“http://localhost:8002/v1"&lt;/code&gt; and the model to “dolphin-2.7-mixtral-8x7b.Q5_K_M.gguf” which is the one I use in LMStudio. It’s displayed in LMStudio if your wondering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other authors you might like
&lt;/h2&gt;

&lt;p&gt;▶️ &lt;a href="https://remembersoftwares.github.io/yacana/" rel="noopener noreferrer"&gt;Checkout Yacana&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
      <category>privategpt</category>
      <category>llm</category>
      <category>wsl</category>
      <category>chatgpt</category>
    </item>
    <item>
      <title>Goodbye Sealed Secrets, hello SOPS</title>
      <dc:creator>Emilien Lancelot</dc:creator>
      <pubDate>Sat, 30 Jul 2022 20:12:00 +0000</pubDate>
      <link>https://dev.to/docteurrs/goodbye-sealed-secrets-hello-sops-1ken</link>
      <guid>https://dev.to/docteurrs/goodbye-sealed-secrets-hello-sops-1ken</guid>
      <description>&lt;p&gt;This tutorial will provide you with all the steps and commands to setup SOPS in your &lt;strong&gt;shell&lt;/strong&gt;, &lt;strong&gt;Kubernetes&lt;/strong&gt;, &lt;strong&gt;Helm&lt;/strong&gt; and &lt;strong&gt;Visual Studio Code&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
We assume that you are on Windows and using WSL as Linux environment. We will setup Sops in both.  &lt;/p&gt;

&lt;p&gt;Want to dive right in ? Click here to jump directly to the installation section.  &lt;/p&gt;

&lt;h2&gt;
  
  
  What is SOPS
&lt;/h2&gt;

&lt;p&gt;Sops is a binary able to encrypt configuration files. But rather than encrypting the whole file Sops understands format (JSON, YAML, INI, etc) and will only encrypt the values of each line (in a key/value pair).  &lt;/p&gt;

&lt;p&gt;The aim is to store encrypted configurations containing sensitive information into your versionning system (Git, SVN, etc) and then be able to work with these encrypted files effortlessly.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sops provide the following advantages :&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Encrypting values but not keys

&lt;ul&gt;
&lt;li&gt;Preserves file structure even when encrypted&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Safely saving encrypted files to Git&lt;/li&gt;
&lt;li&gt;Editing encrypted files seemlessly

&lt;ul&gt;
&lt;li&gt;Sops will open the unencrypted version of the file in your favorite text editor&lt;/li&gt;
&lt;li&gt;When saving, the file will be automatically reencrypted with its new content&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Encrypting specific keys and not the whole file

&lt;ul&gt;
&lt;li&gt;Perfect for long configuration files that have few pieces of sensitive information inside.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Working with all configuration files and not only Kubernetes secrets

&lt;ul&gt;
&lt;li&gt;Can encrypt your Helm’s values.yaml files to safely store sensitive pieces of data inside it (passwords, etc)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;No backend in Kubernetes needed

&lt;ul&gt;
&lt;li&gt;The only requirement is to install Sops locally&lt;/li&gt;
&lt;li&gt;Managing encrypted files will be done locally&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Plugins are available for all major tools to support working with Sops&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Sops encryption example
&lt;/h3&gt;

&lt;p&gt;Let’s say we have this classic Kubernetes secret. The secret value is not encrypted but only encoded to base64.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: v1
data:
  secret_value: U29wcyBydWxlcw== 📖
kind: Secret
metadata:
  creationTimestamp: null
  name: my-secret
  namespace: default


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After being given to Sops this is what you get:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: ENC[AES256_GCM,data:PFk=,iv:,[...]tag:G9mg==,type:str]🔒
data:
    secret_value: ENC[AES256_GCM,data:Ex2KhoAlCG4w==,[...]🔒
kind: ENC[AES256_GCM,data:eW1JrnrV,[...]🔒
metadata:
    creationTimestamp: null
    name: ENC[AES256_GCM,data:fkWXVtQS/C62,iv:GCTfamtu4ixP4w=[...]🔒
    namespace: ENC[AES256_GCM,data:J96kuhjMUA==,iv:QZMnvDGqX[...]🔒
sops:
[...]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, all the values have been encrypted. But the keys are still in plain text.&lt;/p&gt;

&lt;p&gt;Sops can also encrypt specific values based on keys that you give it.&lt;br&gt;
Here, let’s say we want to ONLY encrypt the “secret_value” key :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

apiVersion: v1📖
data:
    secret_value: ENC[AES256_GCM,data:9VBm+32QSCOaU+9exohSg==[...]🔒
kind: Secret📖
metadata:
    creationTimestamp: null
    name: my-secret📖
    namespace: default📖
sops:
[...]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Only the “secret_value” value has been encrypted. The rest of the file remains untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we stopped using Sealed Secrets
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What does it do ?
&lt;/h3&gt;

&lt;p&gt;We have been using SealedSecrets for a year but it’s time for a change. This (cool) technology allows you to encrypt a Kubernetes secret on disk and have it commited on GIT. This way, even if a hacker gets its hands on your repository, he won’t have access to the content of your secrets.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why it’s not perfect ?
&lt;/h3&gt;

&lt;p&gt;SealedSecret is great for a lot of usecase. But in our opinion it does come short here and there:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Must install a backend in the cluster&lt;/li&gt;
&lt;li&gt;Certificate rotation might get complicated&lt;/li&gt;
&lt;li&gt;Default encryption mode doesn’t allow to move secret from one namespace to another&lt;/li&gt;
&lt;li&gt;On the Kubernetes cluster the generated secret is still in plain text
But the most problematic part is…&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;We have sensitive data stored in plain text inside helm charts, for instance passwords inside the “values.yaml” of helm charts. This is not covered at all by sealed secrets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Installation of Sops
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install the Sops binary on WSL
&lt;/h3&gt;

&lt;p&gt;Download the binary for linux : &lt;a href="https://github.com/mozilla/sops/releases" rel="noopener noreferrer"&gt;https://github.com/mozilla/sops/releases&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ wget &amp;lt;URL_OF_THE_BIN_FROM_GITHUB&amp;gt;

$ sudo mv sops-&amp;lt;VERSION&amp;gt; /usr/local/bin/sops


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Install Age
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrmfxtchguoxhb0mjbt9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrmfxtchguoxhb0mjbt9.png" alt="Age tech logo"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sops doesn’t do the encryption itself. It leverages other encryption technologies like PGP, AGE, GCP KMS, Azure key vault, Hashicorp Vault, etc&lt;/p&gt;

&lt;p&gt;As PGP is kind of deprecated, we’ll install the new kid on the block : &lt;strong&gt;Age&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Age is a tool to encrypt files. It doesn’t care about the format of the file itself. To encrypt only the values or parts of a file we need to rely on Sops. Age is only a tool that Sops will use.&lt;/p&gt;

&lt;p&gt;Let’s install Age…&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ AGE_VERSION=$(curl -s "https://api.github.com/repos/FiloSottile/age/releases/latest" | grep -Po '"tag_name": "(v.*)"' |grep -Po '[0-9].*[0-9]')

$ curl -Lo age.tar.gz "https://github.com/FiloSottile/age/releases/latest/download/age-v${AGE_VERSION}-linux-amd64.tar.gz"

$ tar xf age.tar.gz

$ sudo mv age/age /usr/local/bin

$ sudo mv age/age-keygen /usr/local/bin


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Generate public and private keys
&lt;/h3&gt;

&lt;p&gt;Now that Age is installed, you must create a public and a private key.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ age-keygen -o key.txt

$ cat key.txt
# created: 2022-07-30T17:02:43+02:00
# public key: age1rdje4gwnm2cc6uu6lvggzjj8gktu4cw5ng8yyjhrluwvqq387cls58dsy4
AGE-SECRET-KEY-18LMDYJRYZMRM52CYQA9MZW79M85CPR0HJHCASXF5ADT03KJ290HS88599E

$ mkdir ~/.sops

$ mv ./key.txt ~/.sops


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;▶️The private and public keys created by Age are stored inside the file &lt;code&gt;key.txt&lt;/code&gt;. We moved this file inside a .sops directory inside the home folder.&lt;/p&gt;

&lt;p&gt;To tell Sops where everything is, add these ENV variables to your shell configuration (&lt;code&gt;~/.bashrc&lt;/code&gt; or &lt;code&gt;~/.zshrc&lt;/code&gt;)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

export SOPS_AGE_KEY_FILE=$HOME/.sops/key.txt
export EDITOR=nano # Replace by your favorite editor


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;ℹ️ Dont forget to &lt;code&gt;source&lt;/code&gt; your shell profile before continuing or at least open a new shell.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Sops and Age are working
&lt;/h3&gt;

&lt;p&gt;Let’s encrypt a file…&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# Creating a test file
$ echo "my-key: my-value" &amp;gt; config.yaml

# Encrypting the file
sops --encrypt --age $(cat ~/.sops/key.txt |grep -oP "public key: \K(.*)") ./config.yaml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;▶️ we used the &lt;code&gt;--age&lt;/code&gt; parameter to specify what public key to use. Here, we rely on bash to replace during runtime the public key present in key.txt.&lt;/p&gt;

&lt;p&gt;Now to save the file to the disk a simple right redirection will do the trick :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ sops --encrypt --age $(cat ~/.sops/key.txt |grep -oP "public key: \K(.*)") ./config.yaml &amp;gt; config_enc.yaml

$ cat config_enc.yaml
my-key: ENC[AES256_GCM,data:Jw1oRQcV8=,tag:Tzd/oUQ[...]==,type:str]
sops:
[...]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Edit the file using Sops
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ sops config_enc.yaml
&amp;lt;Your favorite editor should have opened&amp;gt;
&amp;lt;The file in front of you should be unencrypted&amp;gt;
&amp;lt;When saving, modifications you made will be encrypted&amp;gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;On the fly decryption and re-encryption is a fabulous feature. You don’t even know the file is encrypted. It’s all automatic !&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Configure the tools
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Add aliases to your interpretor
&lt;/h3&gt;

&lt;p&gt;This should work effortlessly on bash and zsh.&lt;/p&gt;

&lt;p&gt;As the syntax to encrypt a file is bit tedious (due to the fact that you must pass the Age public key to Sops) we have prepared an alias that can be added to your &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.zshrc&lt;/code&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

function cypher {
    filename=$(basename -- "$1")
    extension="${filename##*.}"
    filename="${filename%.*}"
    sops --encrypt --age $(cat ~/.sops/key.txt |grep -oP "public key: \K(.*)") $2 $3 $1 &amp;gt; "$filename.enc.$extension"
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Remember to always &lt;code&gt;source&lt;/code&gt; your profile before trying new aliases.&lt;/p&gt;

&lt;p&gt;Usage:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# Creating test files
$ echo "some_key: some_value" &amp;gt; test_alias.yaml

# Encrypting
$ cypher test_alias.yaml

$ ls
test_alias.enc.yaml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You could also update the alias to edit the file in place and not generate a new one. This should look like this :&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

function cypher_inplace {
    sops --encrypt --in-place --age $(cat ~/.sops/key.txt |grep -oP "public key: \K(.*)") $2 $3 $1
}


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To encrypt only parts of a file you must explicit the keys using a regex. You won’t need to be a regex master to achieve this. Check out this example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ echo "key1: value1" &amp;gt;  test_regex.yaml
$ echo "key2: value2" &amp;gt;&amp;gt; test_regex.yaml
$ echo "key3: value3" &amp;gt;&amp;gt; test_regex.yaml

$ cat test_regex.yaml
key1: value1
key2: value2
key3: value3

$ cypher test_regex.yaml  --encrypted-regex='^(key1|key3)$'

$ cat test_regex.enc.yaml
key1: ENC[AES256_GCM,[...]tag:qHy9z22CIxcb9ykpPAWikQ==,type:str]
key2: value2
key3: ENC[AES256_GCM,data:3Faqn,[...]tag:1VB3/wrLekmg==,type:str]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As you can see, only the values for key1 and key3 have been encrypted. key2 has been left untouched.&lt;/p&gt;

&lt;p&gt;You can add any keys you want separated by &lt;code&gt;|&lt;/code&gt; . For instance &lt;code&gt;--encrypted-regex='^(&amp;lt;a_first_key&amp;gt;|&amp;lt;a_second_key&amp;gt;|&amp;lt;a_third_key&amp;gt;)$&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Sops for Visual Studio Code
&lt;/h3&gt;

&lt;p&gt;As we have seen, Sops allows to edit encrypted file seemlessly. Visual Studio Code can do the same, provided it has a Sops plugin.&lt;/p&gt;

&lt;p&gt;Download the Sops binary for Windows (or whatever OS your VS code runs on)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/mozilla/sops/releases" rel="noopener noreferrer"&gt;https://github.com/mozilla/sops/releases&lt;/a&gt;
Add the binary into your OS path&lt;/li&gt;
&lt;li&gt;On Windows that would be &lt;code&gt;c:\Windows\system32&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Rename it to &lt;code&gt;sops.exe&lt;/code&gt;
On VScode, download the Sops plugin @signageos/vscode-sops.&lt;/li&gt;
&lt;li&gt;You shouldn’t have to, and yet, to configure the plugin in VScode go to &lt;code&gt;file &amp;gt; preferences &amp;gt; settings &amp;gt; plugins &amp;gt; Sops&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copy the keys that were setup on linux (WSL) to your Windows partition:
```
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;$ mkdir /mnt/c/Users//AppData/Roaming/sops/age -p&lt;/p&gt;

&lt;p&gt;$ cp ~/.sops/key.txt /mnt/c/Users//AppData/Roaming/sops/age/keys.txt&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;▶️ The path `AppData/Roaming/sops/age` is where VSCode expects your keys to be. It can be configured, in the plugin settings if needed.

### Installing Sops for Helm

A great Sops advantage is that you can leave plain text sensitive data inside the `values.yaml` of your helm chart and simply let Sops encrypt the file.

❗ Therefore the file cannot be used with helm directly as it won’t natively be able to read encrypted files…
▶️️ This is why you should install this Helm plugin. It allows to use all the classic helm commands but when encountering an encrypted file, it will automatically decrypt it and continue the installation process.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;$ helm plugin install &lt;a href="https://github.com/jkroepke/helm-secrets" rel="noopener noreferrer"&gt;https://github.com/jkroepke/helm-secrets&lt;/a&gt; --version v3.14.0&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;To use this plugin, you simply have to add the word `secrets` after the helm binary.
For instance when installing a helm chart with an encrypted values.yaml :
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;$ helm secrets install minecraft-server itzg/minecraft&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Installing Sops for Kubectl
There isn’t any good solution to mix kubectl and sops for now. But piping the output of Sops to kubectl apply is quite simple.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;$ sops -d .yaml | kubectl apply -f -&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
Thank you for following this tutorial. If it helped you setup your Sops installation please consider giving a 👏.

Written by [Emilien Lancelot](www.linkedin.com/in/emilien-lancelot-62943389)

[Stack Overflow](https://stackoverflow.com/users/5512455/doctor)

In need of Function As A Service on Kubernetes ? Say no more ! [Checkout Gitfaas now !](https://github.com/rememberSoftwares/gitfaas) Free for all, opensource for everyone.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>sops</category>
      <category>kubernetes</category>
      <category>helm</category>
      <category>vscode</category>
    </item>
  </channel>
</rss>
