<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Jupyter Blog - Mehmet Bektas</title><link href="https://jasongrout.github.io/medium-archive/pelican/" rel="alternate"/><link href="https://jasongrout.github.io/medium-archive/pelican/feeds/author-mehmet-bektas.atom.xml" rel="self"/><id>https://jasongrout.github.io/medium-archive/pelican/</id><updated>2025-02-24T18:03:00+00:00</updated><subtitle>The Project Jupyter blog: news, releases, and community stories, archived from blog.jupyter.org.</subtitle><entry><title>Building AI Agents for JupyterLab using Notebook Intelligence</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/" rel="alternate"/><published>2025-02-24T18:03:00+00:00</published><updated>2025-02-24T18:03:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2025-02-24:/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/</id><summary type="html">&lt;p&gt;It is now possible to build AI Agents for JupyterLab and access from Copilot Chat UI, using Notebook Intelligence!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;em&gt;Please note that this is not an official Jupyter subproject but an independent open-source tool for JupyterLab users who want to use GitHub Copilot as an AI coding assistant.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/notebook-intelligence/notebook-intelligence"&gt;Notebook Intelligence&lt;/a&gt; (NBI) is an AI coding assistant and extensible AI framework for JupyterLab. (&lt;em&gt;For an introduction to NBI see&lt;/em&gt; &lt;a href="/posts/2025/introducing-notebook-intelligence/"&gt;&lt;em&gt;Introducing Notebook Intelligence&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and for basics of extending NBI see&lt;/em&gt; &lt;a href="https://notebook-intelligence.github.io/notebook-intelligence/blog/2025/02/05/building-ai-extensions-for-jupyterlab.html"&gt;&lt;em&gt;Building AI Extensions for JupyterLab&lt;/em&gt;&lt;/a&gt; &lt;em&gt;blog posts.&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;GitHub Copilot and other AI coding assistants are great at generating code and answering coding related questions. But they can do a lot more than generating text and code thanks to LLM features such as tool calling and AI agents. NBI provides an extensible AI framework to integrate tool calling and AI agents into JupyterLab Copilot Chat.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="AI Agent extension example" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/001-1_x9r8gIqX1wbubuE8sT6GFg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;AI Agent extension example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="what-is-tool-calling-and-an-ai-agent"&gt;What is tool calling and an AI Agent?&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Tool calling&lt;/strong&gt; is a feature of LLMs. It lets you introduce your own functions to LLM so that they can be called in response to chat prompts. LLM can convert natural language prompts to function calls with arguments. Tool calls are executed on the client side (i.e. Jupyter server) by your extension and only the function schema is provided to the LLM. Tool calling lets LLM interact with real time data, proprietary or external apps and services.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI Agents&lt;/strong&gt; are collections of tools that can run tasks on behalf of the user. Given a natural language prompt, LLMs can reason, create an execution plan and call multiple tools in a chain. NBI provides a framework to build these type of AI Agent integrations and handles the orchestration between LLMs and your tools.&lt;/p&gt;
&lt;h2 id="ai-agent-extension-example"&gt;AI Agent Extension Example&lt;/h2&gt;
&lt;p&gt;Let’s build an AI Agent for JupyterLab using Notebook Intelligence extension APIs. (&lt;em&gt;The full source code for this extension is&lt;/em&gt; &lt;a href="https://github.com/notebook-intelligence/nbi-ai-agent-example"&gt;&lt;em&gt;available here&lt;/em&gt;&lt;/a&gt;.) This will be an AI agent for map creation and notebook sharing. It will have the following capabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Looking up geo-coordinates for an address&lt;/li&gt;
&lt;li&gt;Showing maps centered at an address in the Copilot Chat UI&lt;/li&gt;
&lt;li&gt;Creating notebooks that show maps centered at specified addresses&lt;/li&gt;
&lt;li&gt;Sharing notebooks publicly using &lt;a href="https://notebooksharing.space/"&gt;notebooksharing.space&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The tasks above will be run by the AI Agent in response to natural language prompts by the user.&lt;/p&gt;
&lt;p&gt;For this extension we will build four tools that will be integrated into JupyterLab Copilot Chat, for each of the tasks above. Tools are defined as classes derived from NBI &lt;code&gt;Tool&lt;/code&gt; abstract class. A tool needs to implement the methods and properties defined in this base class.&lt;/p&gt;
&lt;p&gt;Tool class provides the metadata information for the tool and implements the &lt;code&gt;pre_invoke&lt;/code&gt; and &lt;code&gt;handle_tool_call&lt;/code&gt; methods. &lt;code&gt;pre_invoke&lt;/code&gt; method is called right before &lt;code&gt;handle_tool_call&lt;/code&gt; with the tool arguments and it gives an opportunity for the tool to prompt for confirmation of the tool execution.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;schema&lt;/code&gt; property of the Tool is the function schema based on OpenAI’s function calling schema. It lets you describe your function and its parameters as an object. A Tool is expected to return an object as response from the &lt;code&gt;handle_tool_call&lt;/code&gt; method call.&lt;/p&gt;
&lt;h2 id="geo-coordinates-lookup-tool"&gt;Geo Coordinates Lookup Tool&lt;/h2&gt;
&lt;p&gt;This tool looks up geo-coordinates for an address using &lt;a href="https://github.com/geopy/geopy"&gt;Nominatim&lt;/a&gt; library. &lt;code&gt;pre_invoke&lt;/code&gt; method for this tool only shows a message in Chat UI before looking up for the geo-coordinates in &lt;code&gt;handle_tool_call&lt;/code&gt; method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;GeoCoordinateLookupTool&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="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;geo_coordinate_lookup&amp;quot;&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Get geo-coordinates from an address&amp;quot;&lt;/span&gt;
    
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;description&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&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;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;This is a tool that converts an address to a geo-coordinates&amp;quot;&lt;/span&gt;
    
    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&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;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;function&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s2"&gt;&amp;quot;function&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;parameters&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;object&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;properties&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="s2"&gt;&amp;quot;address&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;string&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                            &lt;span class="s2"&gt;&amp;quot;description&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Address to convert to geo-coordinates&amp;quot;&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="s2"&gt;&amp;quot;required&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;address&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;additionalProperties&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;address&amp;#39;&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;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Getting coordinates for &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&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;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&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="n"&gt;tool_args&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geolocator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geocode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;latitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;longitude&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
&lt;img alt="Get geo-coordinates tool" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/002-1_5qScMVUaGlIWWF7_F36Fsg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Get geo-coordinates tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="map-response-generator-tool"&gt;Map Response Generator Tool&lt;/h2&gt;
&lt;p&gt;This tool shows a map in Copilot Chat UI centered at geo-coordinates. In &lt;code&gt;pre_invoke&lt;/code&gt; method this method only shows a notification message in Chat UI. In &lt;code&gt;handle_tool_call&lt;/code&gt; method, this tool returns a &lt;code&gt;HTMLFrame&lt;/code&gt; response that uses HTML to show a map centered at the requested location using Google Maps.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MapResponseGeneratorTool&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="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&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;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Showing a map centered at latitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and longitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&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;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&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="n"&gt;tool_args&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&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;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HTMLFrameData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&amp;lt;iframe width=&amp;quot;100%&amp;quot; height=&amp;quot;100%&amp;quot; frameborder=&amp;quot;0&amp;quot; scrolling=&amp;quot;no&amp;quot; marginheight=&amp;quot;0&amp;quot; marginwidth=&amp;quot;0&amp;quot; id=&amp;quot;gmap_canvas&amp;quot; src=&amp;quot;https://maps.google.com/maps?width=400&amp;amp;amp;height=400&amp;amp;amp;hl=en&amp;amp;amp;q=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;amp;t=&amp;amp;amp;z=11&amp;amp;amp;ie=UTF8&amp;amp;amp;iwloc=B&amp;amp;amp;output=embed&amp;quot;&amp;gt;&amp;lt;/iframe&amp;gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;400&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;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;I showed the map&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Below is an example prompt showing map centered at “Golden Gate Bridge, San Francisco”. Note that an address was provided to Copilot as the input but Map Response Generator Tool accepts only geo-coordinates as input. This is where LLM automatically decided that it needs to first call the Geo Coordinates Lookup Tool to get geo-coordinates for this address and then it called the Map Response Generator Tool with the geo-coordinates. LLM automatically chained multiple tools and NBI handled this chaining to get the correct response for the user’s prompt.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Map Response Generator Tool" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/003-1_6o_XrO-44kKaGYmEefsXKQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Map Response Generator Tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="map-notebook-creator-tool"&gt;Map Notebook Creator Tool&lt;/h2&gt;
&lt;p&gt;This tool creates a notebook centered at the specified geo-coordinates. In &lt;code&gt;pre_invoke&lt;/code&gt; method this method only shows a notification message in Chat UI. In &lt;code&gt;handle_tool_call&lt;/code&gt; method, the tool creates a notebook using &lt;code&gt;nbformat&lt;/code&gt; library, saves it to disk and then opens the notebook in JupyterLab UI using the &lt;code&gt;response.run_ui_command&lt;/code&gt; NBI method.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;MapNotebookCreatorTool&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="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&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;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Creating a map notebook for latitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and longitude: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&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;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&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="n"&gt;tool_args&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;geo_coordinates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;geo_coordinates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;latitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;latitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;geo_coordinates&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;longitude&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;map_file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;map_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strftime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%Y%m&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s1"&gt;_%H%M%S&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.ipynb&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;nb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_notebook&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;        ### This map notebook was created by an AI Agent using [Notebook Intelligence](https://github.com/notebook-intelligence)&lt;/span&gt;
&lt;span class="s2"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;install_code_cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%%&lt;/span&gt;&lt;span class="s2"&gt;capture&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;%pip install folium&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;map_code_cell&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="s2"&gt;        import folium&lt;/span&gt;

&lt;span class="s2"&gt;        map = folium.Map(location=[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;], zoom_start=13)&lt;/span&gt;
&lt;span class="s2"&gt;        map&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

        &lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cells&amp;#39;&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;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_markdown_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_code_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;install_code_cell&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;v4&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_code_cell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;map_code_cell&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;kernelspec&amp;quot;&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="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;python3&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;nbf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;map_file_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;await&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;run_ui_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;docmanager:open&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;path&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;map_file_name&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;I created and opened the map notebook&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
&lt;img alt="Map Notebook Creator Tool" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/004-1_2BZGe7aGPnNTPY79ZNhUDQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Map Notebook Creator Tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="notebook-share-tool"&gt;Notebook Share Tool&lt;/h2&gt;
&lt;p&gt;This tool shares a notebook publicly by uploading it to &lt;a href="https://notebooksharing.space/"&gt;notebooksharing.space&lt;/a&gt; and displays the link to the shared notebook.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;pre_invoke&lt;/code&gt; method implementation, this tool asks for confirmation first as this operation is an undoable share of the notebook publicly. Only after the user confirms, &lt;code&gt;handle_tool_call&lt;/code&gt; is executed. In &lt;code&gt;handle_tool_call&lt;/code&gt; method the tool uploads the notebook at the &lt;code&gt;notebook_file_path&lt;/code&gt; using &lt;a href="https://github.com/notebook-sharing-space/nbss-upload"&gt;nbss_upload&lt;/a&gt; library and then shows the link to the shared notebook on &lt;a href="https://notebooksharing.space/"&gt;notebooksharing.space&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;NotebookShareTool&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="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;pre_invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_args&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;notebook_file_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&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;ToolPreInvokeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Sharing notebook &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;confirmationTitle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Confirm sharing&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;confirmationMessage&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Are you sure you want to share the notebook at &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;? This will upload the notebook to public internet and cannot be undone.&amp;quot;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_tool_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ChatRequest&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;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_context&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="n"&gt;tool_args&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="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;file_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tool_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;notebook_file_path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;share_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nbss_upload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upload_notebook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://notebooksharing.space&amp;#39;&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;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AnchorData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;share_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Click here to view the shared notebook &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;result&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Notebook &amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;file_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#39; has been shared at: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;share_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;figure&gt;
&lt;img alt="Notebook Share Tool" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/building-ai-agents-for-jupyterlab-using-notebook/images/005-1_CYXJbXzmMGnuW_c_ktwkgA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Notebook Share Tool&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="tool-call-schema-definitions"&gt;Tool call schema definitions&lt;/h2&gt;
&lt;p&gt;It is important to define schemas of the tools clearly and disambiguate the tools as much as possible so that the LLM can invoke the proper tool based on the user prompt. LLM parses the user prompt, decides which tools to call and generates the input parameters for the call.&lt;/p&gt;
&lt;p&gt;If current file or selection is made visible by the user, NBI can provide the file paths and content as context to the LLM. That way LLM can use those as additional context for a tool call. That is how Notebook Share Tool was able to access the current notebook file.&lt;/p&gt;
&lt;h2 id="tool-chaining"&gt;Tool chaining&lt;/h2&gt;
&lt;p&gt;After parsing the user prompt, LLM creates an execution plan and can call multiple tools in a chain. NBI handles this tool chaining for you. It is important to define your schemas with the chaining in mind. Consider defining matching tool outputs and inputs so that the output of a tool can be passed onto another one directly if needed.&lt;/p&gt;
&lt;p&gt;Notice that in this extension example MapResponseGeneratorTool and MapNotebookCreatorTool both take in geo_coordinates (latitude, longitude) as input and GeoCoordinateLookupTool outputs geo_coordinates. This lets LLM to directly pass the output of GeoCoordinateLookupTool to MapResponseGeneratorTool and MapNotebookCreatorTool. It also lets a user to use an address to trigger MapResponseGeneratorTool and MapNotebookCreatorTool, because LLM knows that there is another tool it can call to generate input (geo_coordinates) from address for these tools.&lt;/p&gt;
&lt;h2 id="chat-participant"&gt;Chat Participant&lt;/h2&gt;
&lt;p&gt;In NBI AI framework, AI Agents are defined as chat participants and tools are tied to specific chat participants. For our extension we create &lt;code&gt;AIAgentChatParticipant&lt;/code&gt; as our participant (for more details on NBI extensions and chat participants see &lt;a href="http://127.0.0.1:4000/notebook-intelligence/blog/2025/02/04/building-ai-extensions-for-jupyterlab.html"&gt;this blog&lt;/a&gt;). Our chat participant returns list of tools it defines from the &lt;code&gt;tools&lt;/code&gt; property.&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;handle_chat_request&lt;/code&gt; method our chat participant passes the request to the base &lt;code&gt;ChatParticipant&lt;/code&gt; class to handle tool calling for us.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;AIAgentChatParticipant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ChatParticipant&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@property&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ai-agent&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="vi"&gt;@property&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="no"&gt;Tool&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="no"&gt;GeoCoordinateLookupTool&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;MapResponseGeneratorTool&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;MapNotebookCreatorTool&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;NotebookShareTool&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;async&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_chat_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ChatRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;ChatResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dict&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;await&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle_chat_request_with_tools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="nbi-extension"&gt;NBI Extension&lt;/h2&gt;
&lt;p&gt;Finally we create our NBI extension class &lt;code&gt;AIAgentExtension&lt;/code&gt;. This class basically registers our chat participant to NBI on extension activation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;AIAgentExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NotebookIntelligenceExtension&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="o"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AIAgentChatParticipant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register_chat_participant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;AI Agent example extension activated&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That is all there is to create an AI Agent for JupyterLab using Notebook Intelligence. The &lt;a href="https://github.com/notebook-intelligence/nbi-ai-agent-example"&gt;full source code&lt;/a&gt; for this example is available along with installation instructions for you to use as a reference and/or build on top.&lt;/p&gt;
&lt;h2 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h2&gt;
&lt;p&gt;I am looking forward to seeing the AI Agents built by the community. Please try the extension APIs and share your feedback using project’s &lt;a href="https://github.com/notebook-intelligence/notebook-intelligence/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community will shape the project’s roadmap.&lt;/p&gt;
&lt;h2 id="about-the-author"&gt;About the Author&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He is the author of Notebook Intelligence, and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="AI"/><category term="JupyterLab"/></entry><entry><title>Introducing Notebook Intelligence!</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/" rel="alternate"/><published>2025-01-13T16:28:00+00:00</published><updated>2025-01-13T16:28:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2025-01-13:/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/</id><summary type="html">&lt;p&gt;I am thrilled to announce the release of Notebook Intelligence! NBI is an AI coding assistant for JupyterLab powered by GitHub Copilot…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;&lt;em&gt;Please note that this is not an official Jupyter subproject but an independent open-source tool for JupyterLab users who want to use GitHub Copilot as an AI coding assistant.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I am thrilled to announce the release of &lt;a href="https://github.com/mbektas/notebook-intelligence"&gt;Notebook Intelligence&lt;/a&gt; (NBI)! NBI is an AI coding assistant and extensible AI framework for JupyterLab. It uses &lt;a href="https://github.com/features/copilot"&gt;GitHub Copilot&lt;/a&gt; under the hood and is inspired by its design principles. NBI greatly boosts the productivity of JupyterLab users with AI assistance powered by GitHub Copilot.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate code" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/001-1_X4bZbN5zydCMu6AJV3bP7A.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate code&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="generate-code-iterate-on-it"&gt;Generate code, iterate on it&lt;/h2&gt;
&lt;p&gt;NBI integrates tightly with the notebook document. Using cell toolbar item “Generate code” or keyboard shortcut “Cmd + G” / “Ctrl + G”, you can launch the inline coding assistant popover to generate code cells.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate code popover" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/002-1_WnT4YBV94oGF_qvT7rpMZg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate code popover&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If the inline coding assistant is launched for a cell with existing code, then the generated code is shown in a diff view for approval.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate code with diff viewer" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/003-1_4hM3aV5c3HycBY2HWQSaBQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate code with diff viewer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If you are not satisfied with the generated code, you can re-generate with an updated prompt. Diff viewer also lets you edit the generated code manually before accepting it.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Fix code in cell with NBI" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/004-1_8GgXLlEYjGtHNNw_m1VK4A.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Fix code in cell with NBI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="explain-and-fix-code-troubleshoot-errors-reported"&gt;Explain and fix code, troubleshoot errors reported&lt;/h2&gt;
&lt;p&gt;NBI adds a new sub menu to notebook cell context menu. Copilot can explain code in a cell or suggest fixes for any issues in it. Clicking these menu items opens Copilot Chat and generates a suggestion.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Copilot context menu" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/005-1_ItUuGiP1VvhgxQ4BzBDb5w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Copilot context menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If the code cell has an output you can ask Copilot to explain it. If there are any errors reported, you can have Copilot to troubleshoot as well. These actions also take you to Copilot Chat interface.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Explain, fix, troubleshoot" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/006-1_X7c10sZzH5qbv5kpAUOMBA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Explain, fix, troubleshoot&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="inline-completions"&gt;Inline Completions&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence integrates with JupyterLab’s inline completion APIs and provides code suggestions as you type in a code cell or a Python file.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Inline completions" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/007-1_EG7lvC6mjTyHLi6mcAGEIQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Inline completions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Code suggestions are generated using GitHub Copilot. They are blazing fast and relevant to document you are working on. In addition to the code cell you are working on, the code in the surrounding cells are also used as context when generating suggestions.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Inline completions example" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/008-1_BkIHAJsG4Fs7-8aD3k-4GA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Inline completions example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="copilot-chat"&gt;Copilot Chat&lt;/h2&gt;
&lt;p&gt;NBI provides a user friendly chat interface to chat with GitHub Copilot. You can ask questions related to coding.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Copilot Chat" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/009-1_ebEfHbxI6JnOWu9sSgP-nA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Copilot Chat&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If Copilot generates code snippets, they are rendered in a special format in a section with an action toolbar. Toolbar will have buttons to copy, insert, create new Python file and notebook from the snippet. Using these you can easily integrate the generated code into your project.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Copilot chat toolbar actions" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/010-1_aAOuJZrObHMhZ2aaxhWRuA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Copilot chat toolbar actions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="chat-commands"&gt;Chat Commands&lt;/h3&gt;
&lt;p&gt;Chat interface also provides commands to generate new notebooks and Python code files based on your task described in the prompt. Commands start with “/” and a command auto-complete list is shown as you type. You can navigate between the commands using keyboard and choose a suggestion using “Enter” or “Tab” keys.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Chat command auto-complete" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/011-1_WBLVr1CtBxhVpMr2KYPVuQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Chat command auto-complete&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="newnotebook-command"&gt;/newNotebook command&lt;/h3&gt;
&lt;p&gt;You can generate new notebooks from a prompt with the &lt;strong&gt;/newNotebook&lt;/strong&gt; command. Notebook generation is shown interactively, a new empty notebook is created and opened, then code and markdown cells are added onto the notebook as they are generated by NBI and GitHub Copilot.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate notebook example" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/012-1_Dkhv6nZt2vExa-UjMTkaRA.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate notebook example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="newpythonfile-command"&gt;/newPythonFile command&lt;/h3&gt;
&lt;p&gt;You can also create new Python files from a prompt using the &lt;strong&gt;/newPythonFile&lt;/strong&gt; command.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Generate Python file example" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/013-1_H6fNVs50mmQcsYlTAE17bg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Generate Python file example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="getting-started-with-notebook-intelligence"&gt;Getting Started with Notebook Intelligence&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence is a JupyterLab extension published as a Python package. Simply install the package and restart JupyterLab. NBI will add a new sidebar item for Copilot Chat, a notebook context sub-menu, a cell toolbar item for “Generate code” and a status bar item for GitHub Copilot login status to JupyterLab UI. It will also be integrated with inline completion (AI suggestions for code completion).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;notebook-intelligence
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="authentication-with-github-copilot"&gt;Authentication with GitHub Copilot&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence requires a &lt;a href="https://github.com/features/copilot"&gt;GitHub Copilot&lt;/a&gt; subscription. NBI provides a user friendly interface to sign into your GitHub Copilot account from JupyterLab UI to activate access to your subscription.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="GitHub Copilot authentication" src="https://jasongrout.github.io/medium-archive/pelican/posts/2025/introducing-notebook-intelligence/images/014-1_5qQDXByT8dHSHMs86geeiQ.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;GitHub Copilot authentication&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="extensible-framework"&gt;Extensible Framework&lt;/h2&gt;
&lt;p&gt;Notebook Intelligence provides APIs to let developers extend its capabilities. You can add custom agents / chat participants, define tools (function calling) and add RAG capabilities to provide your own context to LLM for code / chat response generation. Stay tuned for my next blog post where I will walk you though extensibility features.&lt;/p&gt;
&lt;h2 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/mbektas/notebook-intelligence"&gt;Notebook Intelligence&lt;/a&gt; is currently in beta and designed for Python (support for more languages coming soon). Please try it out and share your feedback and any feature requests using project’s &lt;a href="https://github.com/mbektas/notebook-intelligence/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community will shape the project’s roadmap.&lt;/p&gt;
&lt;h2 id="about-the-author"&gt;About the Author&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He is the author of Notebook Intelligence, and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="AI"/><category term="JupyterLab"/></entry><entry><title>JupyterLab Desktop UI Modes</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/" rel="alternate"/><published>2024-03-11T21:09:00+00:00</published><updated>2024-03-11T21:09:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2024-03-11:/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/</id><summary type="html">&lt;p&gt;We are pleased to announce UI Mode features recently added to JupyterLab Desktop (JLD). JupyterLab Desktop now provides a convenient way to…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;We are pleased to announce UI Mode features recently added to JupyterLab Desktop (JLD). JupyterLab Desktop now provides a convenient way to set the JupyterLab UI layout per project and introduces an new layout mode: “Zen Mode”.&lt;/p&gt;
&lt;h3 id="ui-mode-menu"&gt;UI Mode menu&lt;/h3&gt;
&lt;p&gt;There is now a new sub-menu under session menu, titled “UI Mode”, which lets you change the UI layout mode. You can choose one of the pre-defined modes or leave the layout management to the web app.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="UI Mode menu" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/001-1_YPG0bV4tNk2-LWmd3LrNcg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;UI Mode menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If you select “Zen Mode”, “Single document IDE” or “Multi document IDE” for a project, UI layout will update to the selected mode and it will also be reset to this mode next time the project is opened. Even if you made modifications to the layout using JupyterLab View menu, UI layout will still reset when the project is reopened. You can choose “Managed by web app” option if you would like the selections on View menu to persist.&lt;/p&gt;
&lt;p&gt;“Reset to session default” option resets the UI mode to either to default of “UI Mode for opening a single file” or “UI Mode” as defined in the settings dialog (described in UI Mode configuration below) based on the number of files opened by the session.&lt;/p&gt;
&lt;h3 id="multi-document-ide-mode"&gt;Multi document IDE mode&lt;/h3&gt;
&lt;p&gt;This mode is the default layout mode in JupyterLab web app. Activity bar and side bar on the left, activity bar on the right and status bar are visible in this mode. Multiple documents and terminals can be opened at the same time.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Multi document IDE mode" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/002-1_7r_IE0ATYbHnwJ1-d_xb1Q.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Multi document IDE mode&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="single-document-ide-mode"&gt;Single document IDE mode&lt;/h3&gt;
&lt;p&gt;This mode corresponds to the “Simple Interface“ mode in JupyterLab web app. Activity bar, side bar and status bar visibilities are the same as “Multi document IDE” mode but tab bar is not shown and only one document or terminal can be open at a time.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Single document IDE mode" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/003-1_av6nSBGg052bbsM8aRAXTg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Single document IDE mode&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="zen-mode"&gt;Zen Mode&lt;/h3&gt;
&lt;p&gt;“Zen Mode” is a newly introduced mode in JLD which simplifies “Simple Interface” further by hiding activity bars, side bars and status bar. Zen Mode is great for focusing on a notebook document. It is the new default mode for opening a single notebook or creating a new notebook in JLD.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Zen Mode" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/004-1_zRM-Fi9MT-S3WGq0mN-R2w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Zen Mode&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Below is a demo of “Zen Mode” in action. Notice that using menus or keyboard shortcuts you can still very easily navigate between different notebooks and terminals.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Zen Mode demo" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/005-1_ZmM2yk2twSA01msHOZU2sg.mp4" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Zen Mode demo&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="ui-mode-configuration"&gt;UI Mode configuration&lt;/h3&gt;
&lt;p&gt;Default UI Mode for sessions and for opening a single notebook can be configured from the Settings dialog as shown below. Setting both options to “Manage by web app” makes JLD behave the same as the JupyterLab web app.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="UI Mode configuration" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/jupyterlab-desktop-ui-modes/images/006-1_Q6vDiTD9ct3_7sBMUbvDrw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;UI Mode configuration&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/releases"&gt;Try the latest JupyterLab Desktop&lt;/a&gt; and share your feedback with us using project’s &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community shapes the project roadmap.&lt;/p&gt;
&lt;p&gt;For more information and updates on the project, follow us on &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop"&gt;GitHub&lt;/a&gt; and &lt;a href="https://blog.jupyter.org/"&gt;Jupyter Blog&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="about-the-author"&gt;About the Author&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He maintains and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="JupyterLab"/></entry><entry><title>Python environment management using JupyterLab Desktop CLI</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/" rel="alternate"/><published>2024-03-01T20:41:00+00:00</published><updated>2024-03-01T20:41:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2024-03-01:/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/</id><summary type="html">&lt;p&gt;JupyterLab Desktop CLI provides several commands and options to manage Python environments for use in the application…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;JupyterLab Desktop (JLD) CLI provides several commands and options to manage Python environments for use in the application. In my &lt;a href="/posts/2024/python-environment-management-in-jupyterlab-desktop/"&gt;previous blog post&lt;/a&gt; I had covered using JLD UI to manage Python environments. You can use &lt;strong&gt;jlab&lt;/strong&gt; CLI commands to do the same and for additional environment management options.&lt;/p&gt;
&lt;h3 id="setting-up-jlab-cli"&gt;Setting up jlab CLI&lt;/h3&gt;
&lt;p&gt;JupyterLab Desktop installers for Windows and Linux create &lt;strong&gt;jlab&lt;/strong&gt; CLI command as part of the installation process. On macOS, the CLI command is created at first program launch. Since this requires elevated user permissions, users may need to do a one time manual approval on macOS UI. Visit Settings dialog on macOS to verify that CLI command is installed properly.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jlab CLI status on Settings dialog" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/images/001-1_HS9E-UDLEIo2U0qvMFngeA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jlab CLI status on Settings dialog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Setting up CLI is only an operation to configure the command for use in the Terminal. It doesn’t install any additional software, the functionality is already built into the desktop app.&lt;/p&gt;
&lt;h3 id="jlab-env-cli-commands"&gt;jlab env CLI commands&lt;/h3&gt;
&lt;p&gt;“jlab env &lt;action&gt; [options]” CLI commands provide environment management functionality. Below are the list of actions supported with overview of each one. Each action can have a variety of parameter options. See the Python environment management &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/blob/master/python-env-management.md#python-environment-management-using-cli"&gt;CLI documentation&lt;/a&gt; on JLD GitHub repo for the details on parameter options.&lt;/p&gt;
&lt;p&gt;Under the hood, these commands use conda and pip that are available on your system. conda and pip could be pre-installed or they become available with the bundled Python environment installation.&lt;/p&gt;
&lt;h3 id="jlab-env-info"&gt;jlab env info&lt;/h3&gt;
&lt;p&gt;This command prints app’s Python environment configuration on the system. JLD initializes this config at launch time by discovering the Python configuration on your system. These settings can be updated by using the UI or CLI commands that are listed below.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jlab env info command output" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/images/002-1_KRDJFM_g3Jauf4FO3cxxiw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jlab env info command output&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="jlab-env-list"&gt;jlab env list&lt;/h3&gt;
&lt;p&gt;Lists discovered and user set Python environments available to the app. Python paths, python and jupyterlab package versions and whether it was created by JLD are also listed.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jlab env list command output" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/images/003-1_lgXUXSxJYoR82UQoWe4XbQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jlab env list command output&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="jlab-env-create"&gt;jlab env create&lt;/h3&gt;
&lt;p&gt;Creates a new Python environment and adds to the application registry, making it ready to use by the app. Environments can be created by using the bundled environment installer or by downloading packages from registry. “jupyterlab” Python package is required for compatibility with JLD and it is automatically installed in most cases. Additional Python packages and conda channels to use can also be specified.&lt;/p&gt;
&lt;p&gt;This CLI command provides more options compared to Python environment management UI. Additional environment sources can be used. You can create an environment from a pre-archived conda-pack bundle, conda-lock file or a conda environment file in addition to options available on the UI. These source files can be local file paths on the system or remote URLs.&lt;/p&gt;
&lt;p&gt;Using remote sources, environments can be created using archives and environment definition files created by conda-store and similar tools.&lt;/p&gt;
&lt;p&gt;Additional list of packages can be installed on top of packages defined in an environment definition file. Below is an example of creating a numpy environment using a remote environment definition file and installing scikit-learn in addition.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jlab env create commad example" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/images/004-1_-PEjCqxG5nEddcZRVulleg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jlab env create commad example&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="jlab-env-activate"&gt;jlab env activate&lt;/h3&gt;
&lt;p&gt;Activates a Python environment in system Terminal. Environments can be activated by name or full prefix path.&lt;/p&gt;
&lt;h3 id="jlab-env-set-python-envs-path"&gt;jlab env set-python-envs-path&lt;/h3&gt;
&lt;p&gt;Sets Python environment install directory used when creating new environments.&lt;/p&gt;
&lt;h3 id="jlab-env-set-conda-path"&gt;jlab env set-conda-path&lt;/h3&gt;
&lt;p&gt;Sets the base conda executable path. Base conda executable is used when creating new conda environments, and running conda commands by the app.&lt;/p&gt;
&lt;h3 id="jlab-env-set-conda-channels"&gt;jlab env set-conda-channels&lt;/h3&gt;
&lt;p&gt;Sets conda channels to use when installing new conda packages.&lt;/p&gt;
&lt;h3 id="jlab-env-set-system-python-path"&gt;jlab env set-system-python-path&lt;/h3&gt;
&lt;p&gt;Sets Python executable path to use when creating new venv environments.&lt;/p&gt;
&lt;h3 id="jlab-env-update-registry"&gt;jlab env update-registry&lt;/h3&gt;
&lt;p&gt;Updates the app environment registry. This command checks all the environments registered for compatibility and fetches Python and jupyterlab versions. This action is normally done at desktop app launch.&lt;/p&gt;
&lt;h3 id="setting-project-python-environments-using-cli"&gt;Setting project Python environments using CLI&lt;/h3&gt;
&lt;p&gt;You can set the Python environment used for a project very easily by using the Python environment selection popup on the UI. You can also set it by using the CLI command “jlab config set”.&lt;/p&gt;
&lt;p&gt;Below is an example command to set a project’s Python environment using jlab CLI.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jlab config set commad to set project Python path" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-using-jupyterlab-desktop/images/005-1_hJV8jjrhpQFsq019FV3AqA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jlab config set commad to set project Python path&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="additional-resources"&gt;Additional resources&lt;/h3&gt;
&lt;p&gt;For additional details on environment management CLI commands see the &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/blob/master/python-env-management.md#python-environment-management-using-cli"&gt;Python environment management CLI documentation&lt;/a&gt; at JupyterLab Desktop GitHub repo. Command parameters and options with plenty of examples can be found there.&lt;/p&gt;
&lt;p&gt;JLD provides other CLI commands to launch and configure the application. Check out the &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/blob/master/cli.md"&gt;CLI documentation&lt;/a&gt; for other available commands.&lt;/p&gt;
&lt;h3 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/releases"&gt;Try the latest JupyterLab Desktop&lt;/a&gt; and share your feedback with us using project’s &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community shapes the project roadmap.&lt;/p&gt;
&lt;p&gt;For more information and updates on the project, follow us on &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop"&gt;GitHub&lt;/a&gt; and &lt;a href="https://blog.jupyter.org/"&gt;Jupyter Blog&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="about-the-author"&gt;About the Author&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He maintains and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="JupyterLab"/></entry><entry><title>Python environment management in JupyterLab Desktop</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/" rel="alternate"/><published>2024-02-19T22:26:00+00:00</published><updated>2024-02-19T22:26:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2024-02-19:/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/</id><summary type="html">&lt;p&gt;We are pleased to announce Python environment management features recently added to JupyterLab Desktop. Now you can create new Python env…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;We are pleased to announce Python environment management features recently added to JupyterLab Desktop (JLD). Now you can create new Python environments and manage existing ones on your system, right from JupyterLab Desktop UI.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Python environment management dialog" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/001-1_OHi6WzYg6MXIxl8EyPqWkQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Python environment management dialog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="python-environments-and-their-use-in-jupyterlab-desktop"&gt;Python environments and their use in JupyterLab Desktop&lt;/h3&gt;
&lt;p&gt;The standard distribution of JupyterLab is a Web Application which is available as a Python package named “jupyterlab”. JupyterLab Desktop makes installing and launching JupyterLab much easier by bundling it together with Python and other scientific computing dependencies in the form of a Python environment.&lt;/p&gt;
&lt;p&gt;Python environments provide isolation of package installations and they let you work on different projects with different set of packages and/or package versions. JLD comes with a bundled Python environment installer and lets users install it as the default environment for the app from the UI and effortlessly. It can also use existing Python environments on a system to launch JupyterLab web app.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab Desktop — Python environment selection" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/002-1_h5A_Mqdns44zGQ9HIDglcA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab Desktop — Python environment selection&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;With the latest updates, JLD supports creating custom Python environments from the UI and the CLI. Bundled environment can now be installed to multiple locations, as different virtual environments. JLD also allows you to create new environments by downloading packages from conda or PyPI registries.&lt;/p&gt;
&lt;h3 id="why-support-environment-management-in-jupyterlab-desktop"&gt;Why support environment management in JupyterLab Desktop?&lt;/h3&gt;
&lt;p&gt;It is common for our users to use different Python environments for different projects as they may require different set of Python packages or package versions. Also, in order to use a Python environment in JLD, certain requirements need to be satisfied, such as existence of “jupyterlab” package. These are some of the motivations behind adding support for Python environment management to JupyterLab Desktop.&lt;/p&gt;
&lt;p&gt;Normally, creating a Python environment requires installing Python and/or conda onto the system first and getting familiar with terminal commands required to manage the environments. JLD simplifies this process by eliminating the need to make additional installations and by providing a user friendly interface, making it possible to create new environments with few UI clicks.&lt;/p&gt;
&lt;p&gt;conda and Python are shipped with JupyterLab Desktop within its bundled environment installer. Once the bundled environment is installed then JLD can use the bundled conda and Python to create new environments without requiring any further conda/Python installation from the user.&lt;/p&gt;
&lt;p&gt;Python environments created in JLD are compatible with the application itself and they are added to the environment registry of JLD automatically for use in sessions. This prevents dealing with compatibility issues and manually adding environments to JLD registry.&lt;/p&gt;
&lt;h3 id="python-environment-management-dialog"&gt;Python environment management dialog&lt;/h3&gt;
&lt;p&gt;JupyterLab Desktop now has a new dialog which lets you manage Python environments and settings. You can access this dialog by clicking “Manage Python environments” menu item on session menu (hamburger menu on top-right).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Managing existing environments&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;“Environments” tab lists the environments discovered on your system. If you have other compatible environments available on your system but not discovered then you can add those by clicking “Add existing” button. You can choose the Python executable path in the system dialog that will be shown and the environment will be validated and added to JLD registry. Any validation errors will be shown above the environment list.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Python environment context menu" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/003-1_SS4TOzXRuUo1KjtWLsu62w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Python environment context menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can hover over the environment list to see additional details on the environment such as JupyterLab version. You can access actions for each environment by clicking the menu on the right end of the row. You can copy the Python path or detailed environment info to clipboard by using the first two menu items. Clicking “Launch Terminal” opens a system terminal and activates the selected environment. You can browse the environment’s file system by clicking “Reveal in Finder/Explorer” . “Delete” action is available for environments created by JLD. It lets you remove the environment installation from your system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Creating new environments&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;“Create new” tab lets you create new environments using the bundled installer or packages from registry.&lt;/p&gt;
&lt;p&gt;“Copy of the bundled environment” installs a copy of the bundled environment in a different location on your system. You can set a name for it and the installation path will be shown below. The parent directory for new environment installations can be configured as will be shown in the sections below.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Create new environment using bundled installer" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/004-1_GvVlKNy1A9vMMfjPd_vYww.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Create new environment using bundled installer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;“New environment” option lets you create a new environment from scratch by downloading packages from conda or PyPI registries based on your environment type selection. In order to be compatible with JLD, “jupyterlab” Python package is installed into the environment by default. You can uncheck “Include jupyterlab” if you don’t plan to use the environment with JLD. You can install additional Python packages into the environment using “Additional Python packages” field and by entering the list of packages separated by space. The preview of the environment create command will be shown below. You can click “Show output” during installation to see the progress or to inspect the output from installer.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Create new environment using packages from registry" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/005-1_ozkNZe_GYy14tI4P2pc91A.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Create new environment using packages from registry&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Newly created environments will be listed in “Environments” tab and they will be available to use in JLD sessions immediately.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Python environment settings&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;“Settings” tab lets you set Python environment configuration for JLD. The settings on this dialog are auto populated based on defaults or information discovered on your system.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Python environment settings" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/006-1_oJQrdfgcD2YO2rzhuT7msw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Python environment settings&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The default Python environment to use for JLD can be set on the top. This setting used to be in the “Settings dialog” and moved here.&lt;/p&gt;
&lt;p&gt;“New Python environment install directory” defines the parent directory for newly created environments.&lt;/p&gt;
&lt;p&gt;“conda path” is the path of conda executable to use to create new conda environments and to activate conda sub environments.&lt;/p&gt;
&lt;p&gt;“conda channels” sets the channels to use when installing conda packages.&lt;/p&gt;
&lt;p&gt;Python path setting at the bottom controls which Python executable to use when creating new venv environments.&lt;/p&gt;
&lt;h3 id="updates-to-python-environment-selection-popup"&gt;Updates to Python environment selection popup&lt;/h3&gt;
&lt;p&gt;When local JupyterLab Desktop sessions are created, the active environment information is shown on top-right section of the title bar. This session info button is clickable and when clicked it shows the Python environment selection popup. New features are added to this popup with the latest release.&lt;/p&gt;
&lt;p&gt;On top row of this popup, the current Python path used in the session is shown now. There are two new buttons on the right of this path. The first one lets you restart the JupyterLab server running for the session. This could be useful to restart JupyterLab web app after installing packages, without restarting the whole app. The second button lets you copy session information to the clipboard, such as the URL of the JupyterLab server running.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Session environment info and action buttons" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/007-1_ulCxlvKfiJ84gFABBnJnaA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Session environment info and action buttons&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Now you can filter the Python environments listed on the environment select popup. As you type into the path filter field, the environments will be filtered by the match in Python path.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Filter Python environments" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/008-1_TYPfThb5Ks1rBZDueXW-Jw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Filter Python environments&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Clicking the gear icon next to filter input field takes you to the Python environment management dialog.&lt;/p&gt;
&lt;h3 id="updating-bundled-python-environment-installation"&gt;Updating bundled Python environment installation&lt;/h3&gt;
&lt;p&gt;When JupyterLab Desktop is updated, the bundled Python environment installation is not updated automatically. There wasn’t a clear indication of this out of sync installation and users had to go to Settings dialog to manually update the bundled environment.&lt;/p&gt;
&lt;p&gt;Now with the latest version, notification badge is shown when the environment update is available and auto-update of the bundled environment installation made possible. You can configure the environment update options on “Advanced” tab of the “Settings dialog”. Note that “Update bundled environment automatically when app is updated” option will delete the existing installation and make a fresh install of the bundled environment.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Bundled environment update settings" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/009-1_QLH8DrYK4QKPMKlS_niQ5A.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Bundled environment update settings&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When there is an update available for the bundled environment installation a notification badge is shown on the session title bar with a red circle over the server icon.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Bundled environment update notification badge" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/010-1_5FtuxOZX2cxN5O70D_m9BQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Bundled environment update notification badge&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Also, a new action button is shown on environment selection popup on the right of the current Python path label, in orange color. You can update the bundled environment installation by simply clicking this button.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Bundled environment update button" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/011-1_zcPcX9XNoBC0b4KbkmassA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Bundled environment update button&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Bundled environment updates require app restart and update is installed before the next launch of the application.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Bundled environment update progress" src="https://jasongrout.github.io/medium-archive/pelican/posts/2024/python-environment-management-in-jupyterlab-desktop/images/012-1_plmarhiU7kTDrpBrpKzkiw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Bundled environment update progress&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="coming-up-next"&gt;Coming up next!&lt;/h3&gt;
&lt;p&gt;Stay tuned for my next blog post which will be on the Python environment management using JupyterLab Desktop CLI! CLI commands provide additional options for creating Python environments such as installation using environment definition files and conda-pack bundles.&lt;/p&gt;
&lt;h3 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/releases"&gt;Try the latest JupyterLab Desktop&lt;/a&gt; and share your feedback with us using project’s &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community shapes the project roadmap.&lt;/p&gt;
&lt;p&gt;For more information and updates on the project, follow us on &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop"&gt;GitHub&lt;/a&gt; and &lt;a href="https://blog.jupyter.org/"&gt;Jupyter Blog&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="about-the-author"&gt;About the Author&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at Netflix and a Jupyter Distinguished Contributor. He maintains and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="JupyterLab"/></entry><entry><title>Introducing the new JupyterLab Desktop!</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/" rel="alternate"/><published>2023-02-09T10:21:00+00:00</published><updated>2023-02-09T10:21:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2023-02-09:/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/</id><summary type="html">&lt;p&gt;We are pleased to announce a major update to JupyterLab Desktop which adds many new features with main focus on the user experience…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;We are pleased to announce a major update to JupyterLab Desktop which adds many new features with main focus on the user experience. JupyterLab Desktop is the cross-platform desktop application for JupyterLab and it is the quickest and easiest way to get started with Jupyter notebooks on your personal computer.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab Desktop" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/001-1_8xMIZPHtmjJFVoJ8Vh6Lig.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab Desktop&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="welcome-page"&gt;Welcome Page&lt;/h3&gt;
&lt;p&gt;Users are now presented with the Welcome Page when the app is first launched. It contains links to several session create options on the left and the Jupyter News feed on the right. The news feed is populated using the Jupyter blog contents and is aimed to keep you up to date with the news and events related to Jupyter ecosystem projects. Clicking on a news item opens the blog post in browser.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Welcome Page" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/002-1_lu1U8Ru7DrUj-nWSry7vww.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Welcome Page&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="sessions-and-projects"&gt;Sessions and Projects&lt;/h3&gt;
&lt;p&gt;With this release we are introducing the concept of sessions and projects. Sessions are representations of local project launches and connections to existing JupyterLab servers. Each JupyterLab UI window in the app is associated with a separate session and sessions can be restored with the same configuration at the next launch.&lt;/p&gt;
&lt;p&gt;Each launch of JupyterLab in a different working directory is a separate project and projects can have their own configuration such as Python environment and UI layout. You can separate your work into different projects with their own notebook files and Python environment configuration, based on the project tasks and dependency libraries.&lt;/p&gt;
&lt;p&gt;The “Start” section on the Welcome Page provides several options to create projects and sessions.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Start session links" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/003-1_5x1IOAPb1TsH6zEAVBSQ-Q.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Start session links&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="new-notebook-and-new-session-with-one-click"&gt;New notebook and new session with one click&lt;/h3&gt;
&lt;p&gt;If you want to quickly get started with a new notebook, you can simply click the “New notebook…” link on the Welcome Page. It will automatically launch a new JupyterLab session in the default working directory using the default Python environment and create a new empty notebook to get you started.&lt;/p&gt;
&lt;p&gt;“New session…” link on the Start section works similarly, it launches a new JupyterLab session with the same configuration as above but doesn’t create a notebook automatically.&lt;/p&gt;
&lt;h3 id="open-files-or-folders-from-ui"&gt;&lt;strong&gt;Open files or folders from UI&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;In the Start section of the Welcome Page there are links to open files or folders directly from UI. You can use “Open…” to select files or folders you would like to start a new JupyterLab session with.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Open files or folders" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/004-1_qMqZifqOSiWnOmhDgWddPw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Open files or folders&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If files are chosen, selected files’ parent directory becomes the working directory and selected files are opened in the session. This is also one of the methods to create or open a project in JupyterLab Desktop. On Windows and Linux “Open Folder…” and “Open File…” options are presented as separate items due to OS requirements.&lt;/p&gt;
&lt;h3 id="drag-and-drop-support-to-create-sessions"&gt;Drag and drop support to create sessions&lt;/h3&gt;
&lt;p&gt;If you drop files or a folder onto the Welcome Page then they will be opened in a new session. Dropping a folder this way is the easiest way to get started with a new notebook project in your folder of choice. Dropping files creates a new session at their parent directory and opens them when session is up and ready.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Drag and drop folder to create session" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/005-1_bo31w0W6o976uu1RAk5SWA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Drag and drop folder to create session&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="connect-to-existing-jupyterlab-servers"&gt;Connect to existing JupyterLab servers&lt;/h3&gt;
&lt;p&gt;JupyterLab Desktop can connect to an existing JupyterLab server running locally or remotely. JupyterLab servers that are locally running on your computer are automatically detected and listed in the Connect dialog.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Connect dialog" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/006-1_tiIZvmHQZHCpXxvIpCO6JA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Connect dialog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="session-restore"&gt;Session restore&lt;/h3&gt;
&lt;p&gt;“Recent sessions” list on the Welcome Page shows the last sessions launched by the user. Clicking the items in the list restores the session whether it was a local project, single notebook file or a remote server connection. List items are ordered by the last launch time and items can be removed from the list in place by clicking the remove button on the right.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Recent sessions list" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/007-1_ecw9wsZL5VSq9neBjtFbJg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Recent sessions list&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="multiple-session-window-support"&gt;Multiple session window support&lt;/h3&gt;
&lt;p&gt;With this release we are adding support for multiple session windows. You can create a new session window by clicking the “New Window” menu item on the app menu at top right. New app launches from CLI, and double clicking .ipynb files create new session windows as well.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Create new window" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/008-1_U6Gbbp1E7fEKM7ErzzDp5g.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Create new window&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Using this feature, you can now open multiple projects in different session windows side by side. If your startup mode is set to “Restore last sessions”, then your session windows will be restored at the next launch in the positions you laid them out previously.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Multiple session windows" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/009-1_-RYiRPe7dKnzoUgtm0X89w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Multiple session windows&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="python-environment-auto-discovery-and-user-friendly-switch"&gt;Python environment auto discovery and user-friendly switch&lt;/h3&gt;
&lt;p&gt;JupyterLab Desktop shows the server status on the title bar of the session window. If you hover on it you will see the details of the server along with the active Python environment info such as its path and certain package versions.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Python environment selector menu" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/010-1_KkxypNf_ArQLKZQHa4cN9w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Python environment selector menu&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Python environments installed on your computer that are compatible with the app are now automatically discovered at launch time. Clicking on the server status button on the title bar opens the Python environment selector menu. Python environments discovered and any previously used environments will be listed in the menu. Hovering over the menu items shows additional details such as package versions.&lt;/p&gt;
&lt;p&gt;You can also browse or enter the path of a Python executable on your computer using this menu to set the Python environment.&lt;/p&gt;
&lt;p&gt;Once you select a new environment, JupyterLab server will be restarted using the new environment without restarting the application. The selected environment is stored as part of the project settings of the working directory and reapplied when the same project directory is opened.&lt;/p&gt;
&lt;h3 id="custom-working-directory-and-default-python-environment"&gt;Custom working directory and default Python environment&lt;/h3&gt;
&lt;p&gt;The default JupyterLab server root directory which is the default working directory of the app is set as user home directory. This default now can be changed using the Server tab of the Settings dialog. Default Python path is set as the path to the bundled Python environment. This can also be customized from the same settings tab. These two settings determine where the new notebooks are created and new JupyterLab sessions are launched by default.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Default working directory and Python environment settings" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/011-1_Jc0gaj9BIyufUd-B6fzjXA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Default working directory and Python environment settings&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="startup-mode"&gt;Startup mode&lt;/h3&gt;
&lt;p&gt;Welcome Page is the new default launch screen but the startup mode can be changed from the Settings dialog. “Start new session” option lets you launch with a new session in the default working directory using the default Python environment. “Restore last sessions” option lets you continue where you left off last time by restoring last active session windows.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Startup mode" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/012-1_YHVhUs0kP65Q_bPXyRjwpQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Startup mode&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="progress-views-with-action-links"&gt;Progress views with action links&lt;/h3&gt;
&lt;p&gt;New user friendly progress views are now shown while the app is busy with time consuming tasks or when errors occur. Based on the type of error, action links with resolution options are presented.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Progress views" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/013-1_dvUpEkim5A1iu8DBMqkedg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Progress views&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="cli-updates"&gt;CLI updates&lt;/h3&gt;
&lt;p&gt;With the added CLI options, you can now launch the app with a custom Python environment. You can also launch in a specific working directory and open files relative to it by using CLI. Check &lt;code&gt;jlab --help&lt;/code&gt; command to learn more about the launch options.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="CLI options" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/014-1_X5leMTxvjzD6mxjHsJ8U7w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;CLI options&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="security-and-privacy"&gt;Security and privacy&lt;/h3&gt;
&lt;p&gt;We release frequent updates to JupyterLab Desktop to keep it up to date with JupyterLab core application and Electron. Following the best security practices, the app uses context isolation to prevent access to user’s computer from scripts running in the embedded browsers. Browser session data and other user data stored by the app can also be cleared from the Settings dialog now.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Clear history" src="https://jasongrout.github.io/medium-archive/pelican/posts/2023/introducing-the-new-jupyterlab-desktop/images/015-1_bnE1NDA7n9HEqEo7f8lr8A.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Clear history&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="improved-configuration-options"&gt;Improved configuration options&lt;/h3&gt;
&lt;p&gt;JupyterLab Desktop now stores user settings, project settings and application data in separate JSON files. User settings file contains application settings such as default Python path and theme. These settings can be configured from Settings dialog in the application UI. Project settings file contains project (working directory) specific overrides of user settings. Application data file contains data used by the application, e.g. recent sessions list, news feed cache, Python environment list cache.&lt;/p&gt;
&lt;h3 id="and-more"&gt;And more…&lt;/h3&gt;
&lt;p&gt;In addition to these major updates mentioned, this release contains several other smaller enhancements, improvements to logging and troubleshooting features, and bug fixes as well.&lt;/p&gt;
&lt;h3 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/releases"&gt;Try the latest JupyterLab Desktop&lt;/a&gt; and share your feedback with us using project’s &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community shapes the project roadmap.&lt;/p&gt;
&lt;p&gt;For more information and updates on the project, follow us on &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop"&gt;GitHub&lt;/a&gt; and &lt;a href="https://blog.jupyter.org/"&gt;Jupyter Blog&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="about-the-author"&gt;About the Author&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://netflix.com/"&gt;Netflix&lt;/a&gt; and a Jupyter Distinguished Contributor. He maintains and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="JupyterLab"/></entry><entry><title>JupyterLab Desktop — 2022 recap</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2022/jupyterlab-desktop-2022-recap/" rel="alternate"/><published>2022-12-13T23:07:00+00:00</published><updated>2022-12-13T23:07:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2022-12-13:/medium-archive/pelican/posts/2022/jupyterlab-desktop-2022-recap/</id><summary type="html">&lt;p&gt;JupyterLab Desktop is the cross-platform desktop application distribution of JupyterLab. It is the quickest and easiest way to get started…&lt;/p&gt;
</summary><content type="html">&lt;p&gt;JupyterLab Desktop is the cross-platform desktop application distribution of JupyterLab. It is the quickest and easiest way to get started with Jupyter notebooks, with the flexibility for advanced use cases.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab Desktop" src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/jupyterlab-desktop-2022-recap/images/001-1_aZfy2BCz8UCBS6GaqpyH2w.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab Desktop&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;It has been slightly over a year since we &lt;a href="/posts/2021/jupyterlab-desktop-app-now-available/"&gt;relaunched JupyterLab Desktop&lt;/a&gt;. As we close the year 2022, we would like to share some of the recent updates and highlight the major features we implemented since the relaunch.&lt;/p&gt;
&lt;h2 id="cli-and-double-click-to-launch"&gt;CLI and double click to launch&lt;/h2&gt;
&lt;p&gt;JupyterLab Desktop can be launched from the GUI of your operating system by clicking the application’s icon or by using the &lt;strong&gt;jlab&lt;/strong&gt; command from the command line. jlab command allows you to launch the application from specific directories and open files at the specified path.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# launch in the current directory&lt;/span&gt;
jlab&lt;span class="w"&gt; &lt;/span&gt;.
&lt;span class="c1"&gt;# launch in a directory at the relative path&lt;/span&gt;
jlab&lt;span class="w"&gt; &lt;/span&gt;../notebooks
&lt;span class="c1"&gt;# launch notebook at the path&lt;/span&gt;
jlab&lt;span class="w"&gt; &lt;/span&gt;../notebooks/test.ipynb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Double clicking to open notebook &lt;strong&gt;.ipynb&lt;/strong&gt; files is also supported. This will launch the app in file’s parent directory and load the notebook file clicked.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Right click menu on notebook file" src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/jupyterlab-desktop-2022-recap/images/002-1_H7jc3sm2jsBv9jOwpfKFuQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Right click menu on notebook file&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="custom-python-environment-support"&gt;Custom Python environment support&lt;/h2&gt;
&lt;p&gt;The app comes bundled with a conda Python environment that includes the latest JupyterLab along with several popular Python libraries ready to use in scientific computing and data science workflows. A JupyterLab server instance is launched and used as the backend of the app using this Python environment as the default.&lt;/p&gt;
&lt;p&gt;For more advanced use cases and specific needs, you can change the Python environment used by JupyterLab Desktop to another &lt;strong&gt;conda&lt;/strong&gt;, &lt;strong&gt;venv&lt;/strong&gt;, or &lt;strong&gt;pyenv&lt;/strong&gt; virtual environment available on your computer.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Python environment selection dialog" src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/jupyterlab-desktop-2022-recap/images/003-1_dXe3e1Ivh_F4vRczx6jdMQ.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Python environment selection dialog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="remote-server-connection"&gt;Remote Server Connection&lt;/h2&gt;
&lt;p&gt;In addition to automatically launching a JupyterLab Server instance locally and using it as the backend of the application, JupyterLab Desktop can also connect to an existing JupyterLab server instance that is running remotely.&lt;/p&gt;
&lt;p&gt;JupyterLab Desktop can connect to remote server instances that require authentication such as Single Sign-On (SSO) as well. User is presented with the login screens provided by the authentication service they are using and the data is stored securely in browser sessions. The session information can be persisted to automatically re-login on the next launch.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Remote server connection settings dialog" src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/jupyterlab-desktop-2022-recap/images/004-1_BIr9ftlsQetW76mqO1D01A.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Remote server connection settings dialog&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="theme-support"&gt;Theme support&lt;/h2&gt;
&lt;p&gt;JupyterLab Desktop now supports light and dark themes. User can choose light / dark or system theme option in Preferences. System theme basically applies the light / dark theme selected for the OS to the application.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Theme applied to JupyterLab view" src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/jupyterlab-desktop-2022-recap/images/005-1_TnLU3QpuMrGemDf02CO6fg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Theme applied to JupyterLab view&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The theme selection is applied to the JupyterLab view and dialogs of the application.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Theme applied to dialogs" src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/jupyterlab-desktop-2022-recap/images/006-1_etwh9xez4D1QUvidnWSLtw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;Theme applied to dialogs&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="custom-dialogs-using-jupyter-ui-toolkit"&gt;Custom dialogs using jupyter-ui-toolkit&lt;/h2&gt;
&lt;p&gt;JupyterLab Desktop now uses &lt;a href="https://github.com/jupyterlab-contrib/jupyter-ui-toolkit/"&gt;jupyter-ui-toolkit&lt;/a&gt; components for its dialogs to provide a unified and modern look &amp;amp; feel across the application. jupyter-ui-toolkit is a UI toolkit providing UI components with theming support for Jupyter eco-system projects.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="jupyter-ui-toolkit components in JupyterLab Desktop dialogs" src="https://jasongrout.github.io/medium-archive/pelican/posts/2022/jupyterlab-desktop-2022-recap/images/007-1_mgKvubVj6SFsC5s9mDBIJg.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;jupyter-ui-toolkit components in JupyterLab Desktop dialogs&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="auto-update"&gt;Auto-update&lt;/h2&gt;
&lt;p&gt;JupyterLab Desktop regularly checks for available updates and notifies user for new versions. On macOS, automatic updates are supported as well. This feature automatically downloads a new version and installs it at the next launch. We are also in the process of adding auto-update support to Windows.&lt;/p&gt;
&lt;h2 id="upgraded-to-latest-jupyterlab-and-electron"&gt;Upgraded to latest JupyterLab and Electron&lt;/h2&gt;
&lt;p&gt;We frequently release updates to JupyterLab Desktop to keep it in sync with JupyterLab core application and Electron. We address user issues and pain points reported in &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/issues"&gt;GitHub issues&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Electron related security issues are also constantly in our radar. With the latest release of &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/releases/tag/v3.5.1-1"&gt;v3.5.1–1&lt;/a&gt;, we upgraded to Electron v22 and enabled context isolation to all browser sessions. Context isolation blocks access to user’s computer from scripts running in embedded browser and provides the highest level of security for Electron apps.&lt;/p&gt;
&lt;h2 id="try-it-out-and-share-your-feedback"&gt;Try it out and share your feedback!&lt;/h2&gt;
&lt;p&gt;If you haven’t already, &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/releases"&gt;try the latest JupyterLab Desktop&lt;/a&gt; and share your feedback with us using project’s &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop/issues"&gt;GitHub issues&lt;/a&gt;! User feedback from the community shapes the project’s roadmap.&lt;/p&gt;
&lt;p&gt;For more information and updates on the project, follow us on &lt;a href="https://github.com/jupyterlab/jupyterlab-desktop"&gt;GitHub&lt;/a&gt; and &lt;a href="https://blog.jupyter.org/"&gt;Jupyter Blog&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="about-the-author"&gt;About the Author&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://netflix.com/"&gt;Netflix&lt;/a&gt; and a Jupyter Distinguished Contributor. He maintains and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="JupyterLab"/></entry><entry><title>JupyterLab Desktop App now available!</title><link href="https://jasongrout.github.io/medium-archive/pelican/posts/2021/jupyterlab-desktop-app-now-available/" rel="alternate"/><published>2021-09-22T13:36:00+00:00</published><updated>2022-02-16T01:57:00+00:00</updated><author><name>Mehmet Bektas</name></author><id>tag:jasongrout.github.io,2021-09-22:/medium-archive/pelican/posts/2021/jupyterlab-desktop-app-now-available/</id><summary type="html">&lt;p&gt;We are pleased to announce the release of desktop application for JupyterLab!&lt;/p&gt;
</summary><content type="html">&lt;p&gt;We are pleased to announce the release of desktop application for JupyterLab!&lt;/p&gt;
&lt;h2 id="standalone-and-self-contained"&gt;Standalone and self-contained&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyterlab/jupyterlab_app"&gt;JupyterLab App&lt;/a&gt; is the cross-platform standalone application distribution of &lt;a href="https://github.com/jupyterlab/jupyterlab"&gt;JupyterLab&lt;/a&gt;. It is a self-contained desktop application which bundles a Python environment with several popular Python libraries ready to use in scientific computing and data science workflows.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="JupyterLab App running on macOS" src="https://jasongrout.github.io/medium-archive/pelican/posts/2021/jupyterlab-desktop-app-now-available/images/001-1_ZbqjB3evW_yB_SKFjqliWA.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;JupyterLab App running on macOS&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="cross-platform"&gt;Cross-platform&lt;/h2&gt;
&lt;p&gt;JupyterLab App works on Debian and Fedora based Linux, macOS and Windows operating systems. One-click installers are available for each platform.&lt;/p&gt;
&lt;p&gt;You can download the installers from &lt;a href="https://github.com/jupyterlab/jupyterlab_app#download"&gt;the project’s GitHub page&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="One-click installer on macOS" src="https://jasongrout.github.io/medium-archive/pelican/posts/2021/jupyterlab-desktop-app-now-available/images/002-1_NmiVFxvco2bgm4S3dUYRIw.webp" loading="lazy" data-body-image=""&gt;
&lt;figcaption&gt;One-click installer on macOS&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="built-in-python-environment"&gt;Built-in Python environment&lt;/h2&gt;
&lt;p&gt;JupyterLab App is based on &lt;a href="https://www.electronjs.org/"&gt;Electron&lt;/a&gt; and it runs the front-end of JupyterLab inside an embedded browser. The backend of the JupyterLab along with Python libraries are provided via a bundled conda Python environment. Libraries included in the environment are numpy, scipy, pandas, ipywidgets and matplotlib.&lt;/p&gt;
&lt;h2 id="this-is-just-the-re-start"&gt;This is just the re-start&lt;/h2&gt;
&lt;p&gt;This release updates and modernizes the JupyterLab App after couple of years from the initial release and forms a new baseline for the new features to be built-on. Follow the project on &lt;a href="https://github.com/jupyterlab/jupyterlab_app"&gt;GitHub&lt;/a&gt; and &lt;a href="https://blog.jupyter.org/"&gt;Jupyter Blog&lt;/a&gt; for updates.&lt;/p&gt;
&lt;p&gt;We are looking forward to user feedback from the community to define project’s roadmap. Please provide your valuable input using project’s &lt;a href="https://github.com/jupyterlab/jupyterlab_app/issues"&gt;GitHub issues&lt;/a&gt; or by other available channels.&lt;/p&gt;
&lt;h3 id="about-the-author"&gt;About the Author&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.linkedin.com/in/mehmet-bektas"&gt;Mehmet Bektas&lt;/a&gt; is a Senior Software Engineer at &lt;a href="https://netflix.com"&gt;Netflix&lt;/a&gt; and a Jupyter Core Developer. He maintains and contributes to JupyterLab, JupyterLab Desktop and several other projects in the Jupyter eco-system.&lt;/p&gt;
</content><category term="JupyterLab"/></entry></feed>