{"id":31961,"date":"2025-08-27T05:00:00","date_gmt":"2025-08-27T03:00:00","guid":{"rendered":"https:\/\/sii.pl\/blog\/?p=31961"},"modified":"2025-11-25T16:54:19","modified_gmt":"2025-11-25T15:54:19","slug":"koncepcja-human-in-the-loop-czlowiek-w-procesie-decyzyjnym-agentow-ai-na-przykladzie-langgraph","status":"publish","type":"post","link":"https:\/\/sii.pl\/blog\/koncepcja-human-in-the-loop-czlowiek-w-procesie-decyzyjnym-agentow-ai-na-przykladzie-langgraph\/","title":{"rendered":"Koncepcja Human-in-the-loop \u2013 cz\u0142owiek w procesie decyzyjnym agent\u00f3w AI (na przyk\u0142adzie LangGraph)"},"content":{"rendered":"\n<p>Zanim zaczniemy, celem lepszego zrozumienia wyst\u0119puj\u0105cych w artykule poj\u0119\u0107, zach\u0119cam zapoznanie si\u0119 z kilkoma kluczowymi definicjami:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>LLM (Large Language Model) \u2013 du\u017cy model j\u0119zykowy, czyli algorytm g\u0142\u0119bokiego uczenia, kt\u00f3ry mo\u017ce wykonywa\u0107 zadania przetwarzania j\u0119zyka naturalnego (NLP).<\/li>\n\n\n\n<li>Prompt \u2013 zapytanie w j\u0119zyku naturalnym przekazywane do LLM-a.<\/li>\n\n\n\n<li>Augmented LLM (Augmented Large Language Model) \u2013 rozszerzony model j\u0119zykowy, kt\u00f3ry poza standardowym operowaniem na w\u0142asnej bazie wiedzy mo\u017ce u\u017cywa\u0107 dodatkowych narz\u0119dzi, np. bazy wiedzy firmy, w celu poprawy jako\u015bci odpowiedzi.<\/li>\n\n\n\n<li>RAG (Retrieval-Augmented Generation) \u2013 technika, w kt\u00f3rej model j\u0119zykowy generuje odpowiedzi na podstawie dokument\u00f3w pozyskanych z zewn\u0119trznych baz wiedzy.<\/li>\n\n\n\n<li>Agent AI \u2013 autonomiczny system AI podejmuj\u0105cy decyzje i wykonuj\u0105cy akcje z wykorzystaniem modeli j\u0119zykowych i narz\u0119dzi.<\/li>\n\n\n\n<li>Graf \u2013 oznacza proces (tzw. workflow) w LangGraph, sk\u0142adaj\u0105cy si\u0119 z w\u0119z\u0142\u00f3w oraz ich powi\u0105za\u0144 (akcji).<\/li>\n\n\n\n<li>Orkiestracja \u2013 zaawansowany proces zarz\u0105dzania, koordynowania i kontrolowania proces\u00f3w w spos\u00f3b, kt\u00f3ry zapewnia ich sp\u00f3jne i harmonijne dzia\u0142anie.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Wprowadzenie do Human-in-the-loop<\/strong><\/h2>\n\n\n\n<p>Koncepcja <strong>Human-in-the-loop <\/strong>(w skr\u00f3cie HILP) oznacza zaanga\u017cowanie cz\u0142owieka w procesy decyzyjne system\u00f3w AI.<\/p>\n\n\n\n<p>Pomimo \u017ce <strong>HILP<\/strong> funkcjonuje w \u015bwiecie technologii od dawna, to w\u0142a\u015bnie tu \u2013 tj. w dziedzinie AI \u2013 ten proces staje si\u0119 wyj\u0105tkowo u\u017cyteczny. Dzieje si\u0119 tak dlatego, \u017ce systemy oparte na sztucznej inteligencji \u2013 pomimo \u017ce obecnie s\u0105 ju\u017c niezwykle skuteczne \u2013 wci\u0105\u017c maj\u0105 scenariusze, w kt\u00f3rych ludzka ocena i weryfikacja s\u0105 wr\u0119cz niezb\u0119dne. W szczeg\u00f3lno\u015bci, gdy chodzi o precyzj\u0119 i bezpiecze\u0144stwo wynik\u00f3w.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Jak dzia\u0142a HILP w praktyce?<\/strong><\/h2>\n\n\n\n<p>System AI, w momencie napotkania scenariusza, kt\u00f3ry nie zosta\u0142 zawarty w zestawie regu\u0142, zatrzymuje proces (a dok\u0142adniej \u2013 p\u0119tl\u0119 procesu), oczekuj\u0105c na odpowied\u017a od cz\u0142owieka. Dzi\u0119ki zatwierdzeniu lub wprowadzeniu poprawek \u2013 Agent kontynuuje prac\u0119 w okre\u015blonym przez \u201eludzkiego nadzorc\u0119\u201d kierunku.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>LangGraph, czyli budowanie AI-owych proces\u00f3w<\/strong><\/h2>\n\n\n\n<p>Aplikacje Agent\u00f3w AI mo\u017cna budowa\u0107 z u\u017cyciem podstawowych bibliotek (SDK) udost\u0119pnianych przez firmy tworz\u0105ce modele j\u0119zykowe. Obecnie jedn\u0105 z popularniejszych jest <a href=\"https:\/\/openai.github.io\/openai-agents-python\/\" rel=\"nofollow\" >SDK firmy OpenAI<\/a> \u2013 odpowiedzialn\u0105 m.in. za model ChatGPT.<\/p>\n\n\n\n<p>Id\u0105c krok dalej, wraz z rozwojem oprogramowania opartego o LLM-y powsta\u0142y dodatkowe frameworki, kt\u00f3re znacznie rozszerzy\u0142y mo\u017cliwo\u015bci bibliotek standardowych.<\/p>\n\n\n\n<p>Frameworki pozwalaj\u0105 na \u0142\u0105czenie si\u0119 z modelami r\u00f3\u017cnych producent\u00f3w oraz zapewniaj\u0105 szereg dodatkowych funkcji u\u0142atwiaj\u0105cych budowanie aplikacji AI, jak r\u00f3wnie\u017c ich orkiestracj\u0119.<\/p>\n\n\n\n<p>Jednym z popularniejszych na chwil\u0119 obecn\u0105 jest <strong>LangGraph<\/strong> \u2013 framework umo\u017cliwiaj\u0105cy budowanie zaawansowanych proces\u00f3w z u\u017cyciem LLM-\u00f3w. LangGraph pozwala w relatywnie prosty spos\u00f3b w\u0142\u0105cza\u0107 r\u00f3\u017cne komponenty: narz\u0119dzia, pami\u0119\u0107 zewn\u0119trzn\u0105. U\u0142atwia te\u017c implementacj\u0119 koncepcji takich jak HILP \u2013 wszystko to w ramach konkretnego procesu (tzw. grafu).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Czym jest graf procesu?<\/strong><\/h3>\n\n\n\n<p>LangGraph pozwala definiowa\u0107 etapy procesu w formie grafu, kt\u00f3ry sk\u0142ada si\u0119 z w\u0119z\u0142\u00f3w o r\u00f3\u017cnym przeznaczeniu.<\/p>\n\n\n\n<p>Oto kilka z nich, kt\u00f3rych u\u017cyjemy w naszej aplikacji:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>W\u0119ze\u0142 agenta<\/strong> \u2013 odpowiedzialny za komunikacj\u0119 z LLM-em (przekazywanie danych z\/do LLM-a).<\/li>\n\n\n\n<li><strong>W\u0119ze\u0142 narz\u0119dzi <\/strong>\u2013 przyk\u0142adowo narz\u0119dzie przeszukuj\u0105ce dodatkow\u0105 baz\u0119 wiedzy.<\/li>\n\n\n\n<li><strong>W\u0119ze\u0142 decyzji cz\u0142owieka<\/strong> \u2013 gdzie u\u017cytkownik wp\u0142ywa na proces, kontroluj\u0105c \u015bcie\u017ck\u0119, kt\u00f3r\u0105 aplikacja ma pod\u0105\u017ca\u0107.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Technika RAG<\/strong><\/h3>\n\n\n\n<p>Istnieje jeszcze jedna wa\u017cna technika, niezb\u0119dna, aby stworzy\u0107 nasz Augmented LLM \u2013 jest ni\u0105 technika RAG.<\/p>\n\n\n\n<p>RAG rozszerza mo\u017cliwo\u015bci wbudowanej\/treningowej bazy wiedzy LLM-a poprzez dodanie dodatkowego wyszukiwania, kt\u00f3re pozwala modelowi na dost\u0119p do zewn\u0119trznych \u017ar\u00f3de\u0142 danych. Dzi\u0119ki tej technice model mo\u017ce generowa\u0107 dok\u0142adniejsze odpowiedzi, poniewa\u017c uwzgl\u0119dnia kontekst dodatkowych danych, niedost\u0119pnych nigdzie indziej \u2013 przyk\u0142adowo danymi mo\u017ce by\u0107 wewn\u0119trzny FAQ organizacji.<\/p>\n\n\n\n<p>Ostatecznie pozyskanymi danymi mo\u017cemy zasila\u0107 aplikacj\u0119 ChatBota AI, aby udzieli\u0107 precyzyjnych odpowiedzi pracownikom firmy.<\/p>\n\n\n\n<p>W naszym kodzie <strong>RAG<\/strong> umo\u017cliwia wyszukanie dokument\u00f3w zaindeksowanych w bazie wektorowej.<\/p>\n\n\n\n<p>Sama tematyka baz wektorowych jest bardzo szeroki i znacznie wykracza poza zakres obecnego artyku\u0142u. Jednak\u017ce, warto zapami\u0119ta\u0107, \u017ce bazy wektorowe s\u0105 niezb\u0119dne, aby\u015bmy mogli skonwertowa\u0107 tekst na wektory, czyli matematyczne reprezentacje danych, aby ostatecznie przekaza\u0107 je przez aplikacj\u0119 AI do LLM-a.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Implementacja Human-in-the-loop w LangGraph<\/strong><\/h2>\n\n\n\n<p>Obecnie LangGraph jest frameworkiem udost\u0119pniaj\u0105cym SDK dla dw\u00f3ch j\u0119zyk\u00f3w programowania \u2013 Python oraz JavaScript. W naszej aplikacji u\u017cyli\u015bmy j\u0119zyka Python.<\/p>\n\n\n\n<p>Dodatkowo \u201em\u00f3zg\u201d naszego systemu stanowi model j\u0119zykowy ChatGPT w wersji <strong>4o-mini<\/strong> \u2013 st\u0105d, aby uruchomi\u0107 aplikacj\u0119, nale\u017cy wygenerowa\u0107 <a href=\"https:\/\/platform.openai.com\/api-keys\" target=\"_blank\" rel=\"noopener\" title=\"\" rel=\"nofollow\" >klucz API<\/a>, na dedykowanej platformie firmy OpenAI (w tym celu b\u0119dzie wymagane za\u0142o\u017cenie konta u\u017cytkownika, je\u017celi go nie posiadasz).<\/p>\n\n\n\n<p>Co wi\u0119cej, u\u017cycie API OpenAI mo\u017ce wi\u0105za\u0107 si\u0119 r\u00f3wnie\u017c z niewielkimi kosztami, je\u017celi NIE zdecydujemy si\u0119 udost\u0119pnia\u0107 danych naszej komunikacji z modelem, dla cel\u00f3w treningowych. <a href=\"https:\/\/platform.openai.com\/settings\/organization\/data-controls\/sharing\" target=\"_blank\" rel=\"noopener\" title=\"\" rel=\"nofollow\" >Wi\u0119cej informacji znajdziesz tutaj<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>Konfiguracja \u015brodowiska developerskiego<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>zainstalowany interpreter Python \u2013 w wersji 3.1.X lub nowszej<\/li>\n\n\n\n<li>zainstalowany manager pakiet\u00f3w PIP\n<ul class=\"wp-block-list\">\n<li>w tym pakiety:\n<ul class=\"wp-block-list\">\n<li>pip install chromadb + pip install langchain-unstructured<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>klucz API z platformy OpenAI > <a href=\"https:\/\/platform.openai.com\/api-keys\" target=\"_blank\" rel=\"noopener\" title=\"\" rel=\"nofollow\" >https:\/\/platform.openai.com\/api-keys<\/a>\n<ul class=\"wp-block-list\">\n<li>klucz umieszczamy w pliku .env (przyk\u0142ad pliku znajdziesz na ko\u0144cu artyku\u0142u)<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>kolejny plik to plik tekstowy z rozszerzon\u0105 baz\u0105 wiedzy knowledge-base.txt \u2013 r\u00f3wnie\u017c za\u0142\u0105czony na dole artyku\u0142u\n<ul class=\"wp-block-list\">\n<li>plik umieszczamy w katalogu, w kt\u00f3rym znajduje si\u0119 aplikacja<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>(opcjonalnie), je\u017celi mamy potrzeb\u0119 u\u017cycia Debugera, nale\u017cy zainstalowa\u0107 bibliotek\u0119 python3.10-dev<\/li>\n<\/ul>\n\n\n\n<p>Poni\u017cej zalecana organizacja plik\u00f3w w aplikacji:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n|-- \/projects\/hitl-example-app\/\n  |\n  |-- .env (zmienne \u015brodowiskowe)\n  |-- app-hitl.py (kod aplikacji)\n  |-- knowledge-base.txt (lokalna baza wiedzy) \n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Om\u00f3wienie kodu programu<\/strong><\/h3>\n\n\n\n<p>W pierwszej kolejno\u015bci importujemy niezb\u0119dne biblioteki i pakiety (sekcje <strong>import<\/strong> i <strong>from<\/strong>).<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n&quot;&quot;&quot;\nBase Python libraries\n&quot;&quot;&quot;\nimport os\nimport uuid\n\nfrom pathlib import Path\nfrom typing import Literal\nfrom dotenv import load_dotenv\nfrom IPython.display import Image, display\n\n&quot;&quot;&quot;\nRequired LangGraph and LangChain libraries\n&quot;&quot;&quot;\nfrom langchain_core.messages import SystemMessage\nfrom langchain_core.tools import tool\nfrom langchain_openai import ChatOpenAI, OpenAIEmbeddings\n\nfrom langgraph.checkpoint.memory import MemorySaver\nfrom langgraph.graph import StateGraph, MessagesState, START, END\nfrom langgraph.prebuilt import ToolNode\nfrom langgraph.types import Command, interrupt\n\n&quot;&quot;&quot;\nContext search libraries\n&quot;&quot;&quot;\nfrom langchain_community.vectorstores import Chroma\nfrom langchain_community.vectorstores.utils import filter_complex_metadata\nfrom langchain_unstructured import UnstructuredLoader\nfrom langchain_text_splitters import RecursiveCharacterTextSplitter\n<\/pre><\/div>\n\n\n<p>Nast\u0119pnie przeprowadzamy podstawow\u0105 konfiguracj\u0119 skryptu:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n&quot;&quot;&quot;\n-------------\nConfiguration\n-------------\n\nUstawiamy tryb rozbudowanego logowania komunikat\u00f3w (debug)\n&quot;&quot;&quot;\ndetailed_model_response = False\n\n&quot;&quot;&quot;\n\u0141adujemy niezb\u0119dne zmienne \u015brodowiskowe jak klucz API itp.\n&quot;&quot;&quot;\nload_dotenv(dotenv_path=Path(&quot;.env&quot;))\n\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nData source \/ Knowledge Base configuration\n--------------------------------------------------------------------------------------------------------------------\n\nWskazujemy nazw\u0119\/lokalizacj\u0119 pliku bazy wiedzy, kt\u00f3ra ma by\u0107 zamieniona na wektory i udost\u0119pniona dla LLM-a\n&quot;&quot;&quot;\ndatasource = &#039;knowledge-base.txt&#039;\n\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nBuild memory\n--------------------------------------------------------------------------------------------------------------------\n\nDefiniujemy checkpointer, kt\u00f3ry przechowuje stanu grafu (wiadomo\u015bci, informacje o aktualnym w\u0119\u017ale itp.)\nWA\u017bNE: W przyk\u0142adzie zastosowali\u015bmy nietrwa\u0142y zapis stanu do pami\u0119ci operacyjnej.\n&quot;&quot;&quot;\nmemory = MemorySaver()\n<\/pre><\/div>\n\n\n<p>Kolejny krok stanowi konfiguracja modelu j\u0119zykowego i narz\u0119dzi, z kt\u00f3rych b\u0119dzie korzysta\u0107 LLM:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n&quot;&quot;&quot;\nDefiniujemy funkcj\u0119 narz\u0119dzia (anotacja `@tool`) do przeszukania bazy wiedzy\nFunkcja przyjmuje zapytanie bezposrednio z LLM-a (z u\u017cyciem parametru `query`).\nKa\u017cdorazowe jej wywo\u0142anie wi\u0105\u017ce si\u0119 z za\u0142adowaniem danych z pliku, ich podzia\u0142em na cz\u0119\u015bci i ostateczn\u0105 konwersj\u0105 do \npostaci wektorowej.\n&quot;&quot;&quot; \n@tool\ndef context_searcher(query: str):\n    &quot;&quot;&quot;Search the relevant documents&quot;&quot;&quot;\n\n    loader = UnstructuredLoader(datasource)\n    document = loader.load()\n\n    text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=50)\n    split_documents = text_splitter.split_documents(document)\n    filtered_documents = filter_complex_metadata(split_documents)\n\n    vectorstore = Chroma.from_documents(\n        documents=filtered_documents,\n        collection_name=&quot;knowledge-base&quot;,\n        embedding=OpenAIEmbeddings(),\n    )\n\n    retriever = vectorstore.as_retriever()\n    results = retriever.invoke(query)\n\n    return &quot;\\n&quot;.join(&#x5B;doc.page_content for doc in results])\n\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nAI Agent\n--------------------------------------------------------------------------------------------------------------------\n\nDefiniujemy list\u0119 dodatkowych narz\u0119dzi, z kt\u00f3rych bedzie mogl skorzysta\u0107 LLM.\nObecnie do dyspozycji mamy jedno narz\u0119dzie o nazwie `context_searcher`, kt\u00f3re rozszerza wbudowan\u0105 baz\u0119 wiedzy LLM-a. \nW ten spos\u00f3b implementujemy wspominan\u0105 wcze\u015bniej technik\u0119 RAG.\n&quot;&quot;&quot;\ntools = &#x5B;context_searcher]\n\n&quot;&quot;&quot;\nInicjujemy LLM-a i do\u0142\u0105czamy dost\u0119pne narz\u0119dzia. \n&quot;&quot;&quot;\nopenai_api_key = os.getenv(&quot;OPENAI_API_KEY&quot;)\nmodel = ChatOpenAI(model=&quot;gpt-4o-mini&quot;, temperature=0, api_key=openai_api_key).bind_tools(tools)\n\n&quot;&quot;&quot;\nWA\u017bNE: kluczowy w budowaniu agent\u00f3w jest parametr `temperature`, kt\u00f3ry kontroluje poziom losowo\u015bci generowanych \nodpowiedzi. \nPrzy aplikacjach opieraj\u0105cych si\u0119 na w\u0142asnej bazie wiedzy zaleca si\u0119 podej\u015bcie zero-jedynkowe, rozumiane tutaj jako\nnajni\u017cszy udzia\u0142 losowo\u015bci - w przypadku ChatGPT reprezentowany jest on przez warto\u015b\u0107 temperature=0.\n&quot;&quot;&quot;\n<\/pre><\/div>\n\n\n<p>Czas na zdefiniowanie funkcji u\u017cywanych w grafie\/workflow, kt\u00f3re wprowadz\u0105 koncepcje HILP w \u017cycie:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n&quot;&quot;&quot;\nhuman_interaction() -  to najwa\u017cniejsza funkcja, w kontek\u015bcie HILP poniewa\u017c zatrzymuje workflow procesu, w oczekiwaniu \nna interakcje z cz\u0142owiekiem.\nTo co jest istotne to fakt, ze ka\u017cda funkcja u\u017cywana w grafie ma zdefiniowane \u015bcie\u017cki przej\u015bcia do innych etap\u00f3w \nprocesu.\nDefinicj\u0119 mo\u017cliwych krok\u00f3w stanowi tu `Command&#x5B;Literal&#x5B;&quot;human_approved&quot;, &quot;human_rejected&quot;, END]]`, gdzie w zale\u017cno\u015bci \nod decyzji cz\u0142owieka, mo\u017cemy przej\u015b\u0107 do, kt\u00f3regokolwiek z w\u0119z\u0142\u00f3w (&quot;human_approved&quot;, &quot;human_rejected&quot;) lub zako\u0144czy\u0107 \nproces (END). \n&quot;&quot;&quot;\ndef human_interaction(state: MessagesState) -&gt; Command&#x5B;Literal&#x5B;&quot;human_approved&quot;, &quot;human_rejected&quot;, END]]:\n\n  &quot;&quot;&quot;\n  WA\u017bNE: Udost\u0119pniona przez LangGraph funkcja `interrupt()` pozwala, zatrzyma\u0107 proces w celu zadania pytania \n  operatorowi aplikacji.\n  &quot;&quot;&quot;\n  answer = interrupt(\n      {\n          &quot;question&quot;: &quot;Hi human! :) If the answer are correct? Type: `y` or `n`&quot;,\n      }\n  )\n\n  print(&quot;&gt;&gt;&gt; Agent message &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\\n\\n&quot;)\n  print(&quot;Your answer: &quot;, answer, &quot;\\n\\n&quot;)\n\n  &quot;&quot;&quot;\n  W zale\u017cno\u015bci od otrzymanej odpowiedzi przekierowujemy proces za pomoc\u0105 akcji Command do konkretnego w\u0119z\u0142a w grafie\n  &quot;&quot;&quot;\n  if answer == &quot;y&quot;:\n      return Command(goto=&quot;human_approved&quot;)\n  if answer == &quot;n&quot;:\n      return Command(goto=&quot;human_rejected&quot;)\n  else:\n      print(&quot;Unsupported answer. Terminating...&quot;)\n      return Command(goto=END)\n\ndef human_approved(state: MessagesState) -&gt; Command&#x5B;END]:\n  print(&quot;&gt;&gt;&gt; Agent message &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\\n\\n&quot;)\n  print(&quot;\u2705 Do something in approved path.&quot;)\n  return Command(goto=END)\n\ndef human_rejected(state: MessagesState) -&gt; Command&#x5B;END]:\n  print(&quot;&gt;&gt;&gt; Agent message &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\\n\\n&quot;)\n  print(&quot;\u274c Do something in rejected path.&quot;)\n  return Command(goto=END)\n<\/pre><\/div>\n\n\n<p>Na tym etapie mamy ju\u017c funkcje dla w\u0119z\u0142\u00f3w, niezb\u0119dnych do zrealizowania koncepcji HILP i tym samym mo\u017cemy przej\u015b\u0107 do modelowania kompletnego grafu:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nBuild state graph workflow\n--------------------------------------------------------------------------------------------------------------------\n\nWA\u017bNE: W tym miejscu definiujemy kilka funkcji dodatkowych u\u017cywanych tylko przez Agenta AI\n\ncall_model() - inicjuje przes\u0142anie zapytania (prompt-u) do modelu oraz zapisuje odpowiedzi z modelu do pami\u0119ci \naplikacji (pami\u0119ci zdefiniowanej, na etapie konfiguracji - w zmiennej `memory`) \n&quot;&quot;&quot;\ndef call_model(state: MessagesState):\n    messages = state&#x5B;&#039;messages&#039;]\n    response = model.invoke(messages)\n    return {&quot;messages&quot;: &#x5B;response]}\n\n&quot;&quot;&quot;\nPoni\u017csza funkcja to z kolei funkcja steruj\u0105ca (tzw. w\u0119ze\u0142 Conditional edge w LangGraph), kt\u00f3ra po otrzymaniu \ninformacji zwrotnej z LLM-a decyduje o potrzebie u\u017cycia narz\u0119dzia lub interakcji z cz\u0142owiekiem\n&quot;&quot;&quot;\ndef should_continue(state: MessagesState) -&gt; Literal&#x5B;&quot;tools&quot;, &quot;human_interaction&quot;]:\n    messages = state&#x5B;&#039;messages&#039;]\n    last_message = messages&#x5B;-1]\n    &quot;&quot;&quot;\n    Je\u017celi LLM zadecyduje o potrzebie u\u017cycia narz\u0119dzi w\u00f3wczas w aplikacji zwracamy nazw\u0119 w\u0119z\u0142a z dost\u0119pnymi narz\u0119dziami\n    &quot;&quot;&quot;\n    if last_message.tool_calls:\n        return &quot;tools&quot;\n\n    return &quot;human_interaction&quot;\n\n&quot;&quot;&quot;\nInicjujemy graf (workflow)\n&quot;&quot;&quot;\ngraph_builder = StateGraph(MessagesState)\n\n&quot;&quot;&quot;\nDodajemy w\u0119ze\u0142 Agenta\n&quot;&quot;&quot;\ngraph_builder.add_node(&quot;agent&quot;, call_model)\n\n&quot;&quot;&quot;\nDodajemy w\u0119ze\u0142 narz\u0119dzi, kt\u00f3re mog\u0105 by\u0107 u\u017cyte przez LLM-a\n&quot;&quot;&quot;\ngraph_builder.add_node(&quot;tools&quot;, ToolNode(tools))\n\n&quot;&quot;&quot;\nDodajemy w\u0119z\u0142y niezb\u0119dne do realizacji koncepcji HILP\n&quot;&quot;&quot;\ngraph_builder.add_node(&quot;human_interaction&quot;, human_interaction)\ngraph_builder.add_node(&quot;human_approved&quot;, human_approved)\ngraph_builder.add_node(&quot;human_rejected&quot;, human_rejected)\n\n&quot;&quot;&quot;\nOstatecznie konfigurujemy punkt wej\u015bcia\/wyj\u015bcia (START, END) procesu oraz w\u0119ze\u0142 steruj\u0105cy (tzw. conditional edge), \npozwalaj\u0105cy na decyzj\u0119 o kolejnym etapie.\n&quot;&quot;&quot; \ngraph_builder.add_edge(START, &quot;agent&quot;)\ngraph_builder.add_conditional_edges(&quot;agent&quot;, should_continue)\ngraph_builder.add_edge(&quot;tools&quot;, &quot;agent&quot;)\n<\/pre><\/div>\n\n\n<p>Zaprogramowany w ten spos\u00f3b graf b\u0119dzie wygl\u0105da\u0142 tak jak poni\u017cej:<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><img decoding=\"async\" width=\"457\" height=\"432\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/08\/graph-visualisation.png\" alt=\"zaprogramowany graf\" class=\"wp-image-31963\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/08\/graph-visualisation.png 457w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/08\/graph-visualisation-300x284.png 300w\" sizes=\"(max-width: 457px) 100vw, 457px\" \/><figcaption class=\"wp-element-caption\">Ryc. 1 Zaprogramowany graf<\/figcaption><\/figure>\n\n\n\n<p>Na tym etapie nie pozostaje nam ju\u017c nic innego, jak skompilowanie grafu i uruchomienie aplikacji:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nCompile and run\n--------------------------------------------------------------------------------------------------------------------\n\nKompilujemy skonfigurowany wcze\u015bniej graf, przy okazji wskazuj\u0105c modu\u0142 pami\u0119ci do przechowywania jego stanu  \n(`checkpointer=memory`)\n&quot;&quot;&quot;\ngraph_config = {&quot;configurable&quot;: {&quot;thread_id&quot;: uuid.uuid4()}}\ncompiled_graph = graph_builder.compile(checkpointer=memory)\n\n&quot;&quot;&quot;\nDefiniujemy prompt, czyli zapytanie do LLM-a.\nProsz\u0119 zauwa\u017cy\u0107 \u017ce, w zapytaniu wskazujamy r\u00f3wnie\u017c \u017ar\u00f3d\u0142o danych do przeszukania (w tym przypadku stanowi ono nasza \nbaza wiedzy) oraz okre\u015blamy oczekiwany format odpowiedzi (JSON).\n\nCo ciekawe, w pliku bazy wiedzy celowo zaciemnili\u015bmy kontekst wprowadzaj\u0105c do niego kilka nieistotnych informacji \npo to, aby sprawdzi\u0107 jak model poradzi sobie z utrudnieniami w dotarciu do informacji. \n&quot;&quot;&quot;\nprompt = {&quot;messages&quot;: &#x5B;\n        SystemMessage(content=&quot;Provide the temperature for all cities described in `knowledge-base`. Respond in JSON format without any additional text (JSON only without markdown).&quot;)\n]}\n\n&quot;&quot;&quot;\nWy\u015bwietlamy wiadomo\u015bci z ca\u0142ego procesu - do momentu jego zatrzymania w oczekiwaniu na interakcj\u0119 z cz\u0142owiekiem\n&quot;&quot;&quot;\nfor event in compiled_graph.stream(prompt, config=graph_config, stream_mode=&quot;values&quot;):\n    stream_parser(event)\n\n&quot;&quot;&quot;\nOdbieramy odpowied\u017a od cz\u0142owieka\n&quot;&quot;&quot;\nhuman_response_input = input()\n\n&quot;&quot;&quot;\nOstatecznie, kontynuujemy wy\u015bwietlanie wiadomo\u015bci po interakcji z cz\u0142owiekiem\n&quot;&quot;&quot;\nfor event in compiled_graph.stream(Command(resume=human_response_input), config=graph_config, stream_mode=&quot;updates&quot;):\n    stream_parser(event)\n<\/pre><\/div>\n\n\n<p>Kompletny kod aplikacji znajdziesz na ko\u0144cu tego artyku\u0142u.<\/p>\n\n\n\n<p>Teraz przyjrzyjmy si\u0119 natomiast wyj\u015bciu z aplikacji po jej uruchomieniu:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n================================ System Message ================================\nProvide the temperature for all cities described in `knowledge-base`. Respond in JSON format without any additional \ntext (JSON only without markdown).\n================================== Ai Message ==================================\nTool Calls:\n  context_searcher (call_kZ7MnU6UjP2KlHd80P6bdok7)\n Call ID: call_kZ7MnU6UjP2KlHd80P6bdok7\n  Args:\n    query: temperature\n================================= Tool Message =================================\nName: context_searcher\n\nHere are some words to confuse the LLM and create confusion in the context. The average temperature\nconfusion in the context. The average temperature in Bialystok is 27 C.\nMeanwhile, Warsaw can boast an average of 22.5 C.\nHowever, in Gdansk it can be 25 C. And here is some more content to confuse the model again and see\n================================== Ai Message ==================================\nTool Calls:\n  context_searcher (call_L46mEnkzlfjfJu3DCZZwhhMF)\n Call ID: call_L46mEnkzlfjfJu3DCZZwhhMF\n  Args:\n    query: Bialystok temperature\n  context_searcher (call_9TgcqpBqjp4PayXy1mP0DL7w)\n Call ID: call_9TgcqpBqjp4PayXy1mP0DL7w\n  Args:\n    query: Warsaw temperature\n  context_searcher (call_2mWkttSW8tbo8sG25NpXCrYT)\n Call ID: call_2mWkttSW8tbo8sG25NpXCrYT\n  Args:\n    query: Gdansk temperature\n================================= Tool Message =================================\nName: context_searcher\n\nMeanwhile, Warsaw can boast an average of 22.5 C.\nMeanwhile, Warsaw can boast an average of 22.5 C.\nMeanwhile, Warsaw can boast an average of 22.5 C.\nMeanwhile, Warsaw can boast an average of 22.5 C.\n================================== Ai Message ==================================\nTool Calls:\n  context_searcher (call_7h7SIsFlIvXJ7ZamsMN36J6g)\n Call ID: call_7h7SIsFlIvXJ7ZamsMN36J6g\n  Args:\n    query: Bialystok\n  context_searcher (call_KzuJXj6S1MioIbnc7p9KBTH3)\n Call ID: call_KzuJXj6S1MioIbnc7p9KBTH3\n  Args:\n    query: Warsaw\n  context_searcher (call_uYle26b7Dd1rrQgEq8WWP5t3)\n Call ID: call_uYle26b7Dd1rrQgEq8WWP5t3\n  Args:\n    query: Gdansk\n================================= Tool Message =================================\nName: context_searcher\n\nMeanwhile, Warsaw can boast an average of 22.5 C.\nMeanwhile, Warsaw can boast an average of 22.5 C.\nMeanwhile, Warsaw can boast an average of 22.5 C.\nMeanwhile, Warsaw can boast an average of 22.5 C.\n================================== Ai Message ==================================\n{\n  &quot;Bialystok&quot;: &quot;27 C&quot;,\n  &quot;Warsaw&quot;: &quot;22.5 C&quot;,\n  &quot;Gdansk&quot;: &quot;25 C&quot;\n}\n&gt;&gt;&gt; User interaction &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\n{&#039;question&#039;: &#039;Hi human! :) If the answer are correct? Type: `y` or `n`&#039;}\n\ny\n&gt;&gt;&gt; Agent message &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\nYour answer:  y \n\n&gt;&gt;&gt; Agent message &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\n\u2705 Do something in approved path.\n<\/pre><\/div>\n\n\n<h3 class=\"wp-block-heading\"><strong>Co tu si\u0119 dok\u0142adnie wydarzy\u0142o?<\/strong><\/h3>\n\n\n\n<p>Po pierwsze, system u\u017cy\u0142 zdefiniowanego promptu, celem uzyskania informacji o bie\u017c\u0105cej temperaturze w poszczeg\u00f3lnych miastach w Polsce (co warto zaznaczy\u0107 \u2013 LLM zrobi\u0142 to bez konkretnego wskazania tych\u017ce miast).<\/p>\n\n\n\n<p>Dodatkowo, LLM odnalaz\u0142 te informacje, pomimo cz\u0119\u015bciowego zaciemnienia danych i na ko\u0144cu poprosi\u0142 cz\u0142owieka o zatwierdzenie poprawno\u015bci odpowiedzi.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/sii.pl\/oferty-pracy\/\" target=\"_blank\" rel=\"noreferrer noopener\"><img decoding=\"async\" width=\"737\" height=\"170\" src=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/08\/praca-PL-k-5.jpg\" alt=\"oferty pracy\" class=\"wp-image-31966\" srcset=\"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/08\/praca-PL-k-5.jpg 737w, https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/08\/praca-PL-k-5-300x69.jpg 300w\" sizes=\"(max-width: 737px) 100vw, 737px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Podsumowanie<\/strong><\/h2>\n\n\n\n<p>Jak wida\u0107 na powy\u017cszym przyk\u0142adzie, implementacja koncepcji Human-in-the-loop z u\u017cyciem LangGraph jest <strong>kwesti\u0105 relatywnie prost\u0105.<\/strong><\/p>\n\n\n\n<p>Co wa\u017cne, pozwala skutecznie \u0142\u0105czy\u0107 mo\u017cliwo\u015bci LLM-\u00f3w z dodatkowym kontekstem (baz\u0105 wiedzy) oraz z niezb\u0119dn\u0105, ludzk\u0105 kontrol\u0105.<\/p>\n\n\n\n<p>Powy\u017cszy tandem pozwala wznie\u015b\u0107 u\u017cyteczno\u015b\u0107 aplikacji na niespotykany dotychczas poziom.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Kompletny kod aplikacji<\/strong><\/h2>\n\n\n\n<p>Zawarto\u015b\u0107 pliku .<strong>env<\/strong> (zmienne \u015brodowiskowe):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nOPENAI_API_KEY=TWOJ_KLUCZ_OPEN_AI\nANONYMIZED_TELEMETRY=False\n<\/pre><\/div>\n\n\n<p>Zawarto\u015b\u0107 pliku <strong>knowledge-base.txt<\/strong> (baza wiedzy):<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\nHere are some words to confuse the LLM and create confusion in the context. The average temperature in Bialystok is 27 C.\nHowever, in Gdansk it can be 25 C. And here is some more content to confuse the model again and see how it handles with this.\nMeanwhile, Warsaw can boast an average of 22.5 C.\n<\/pre><\/div>\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: python; title: ; notranslate\" title=\"\">\n&quot;&quot;&quot;\nBase Python libraries\n&quot;&quot;&quot;\nimport os\nimport uuid\n\nfrom pathlib import Path\nfrom typing import Literal\nfrom dotenv import load_dotenv\nfrom IPython.display import Image, display\n\n&quot;&quot;&quot;\nRequired LangGraph and LangChain libraries\n&quot;&quot;&quot;\nfrom langchain_core.messages import SystemMessage\nfrom langchain_core.tools import tool\nfrom langchain_openai import ChatOpenAI, OpenAIEmbeddings\n\nfrom langgraph.checkpoint.memory import MemorySaver\nfrom langgraph.graph import StateGraph, MessagesState, START, END\nfrom langgraph.prebuilt import ToolNode\nfrom langgraph.types import Command, interrupt\n\n&quot;&quot;&quot;\nContext search libraries\n&quot;&quot;&quot;\nfrom langchain_community.vectorstores import Chroma\nfrom langchain_community.vectorstores.utils import filter_complex_metadata\nfrom langchain_unstructured import UnstructuredLoader\nfrom langchain_text_splitters import RecursiveCharacterTextSplitter\n\n&quot;&quot;&quot;\n-------------\nConfiguration\n-------------\n\nEnable more details in LLM response (debug logs)\n&quot;&quot;&quot;\n\ndetailed_model_response = False\n\n&quot;&quot;&quot;\nLoad external environment variables contained project specific data (API keys etc.)\n&quot;&quot;&quot;\nload_dotenv(dotenv_path=Path(&quot;.env&quot;))\n\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nData source \/ Knowledge Base configuration\n--------------------------------------------------------------------------------------------------------------------\n&quot;&quot;&quot;\n\ndatasource = &#039;knowledge-base.txt&#039;\n\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nBuild memory\n--------------------------------------------------------------------------------------------------------------------\n&quot;&quot;&quot;\n\nmemory = MemorySaver()\n\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nUtils\n--------------------------------------------------------------------------------------------------------------------\n&quot;&quot;&quot;\n\ndef stream_parser(stream_message):\n  if &quot;messages&quot; in stream_message:\n    stream_message&#x5B;&quot;messages&quot;]&#x5B;-1].pretty_print()\n  if &quot;__interrupt__&quot; in stream_message:\n    print(&quot;&gt;&gt;&gt; User interaction &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\\n\\n&quot;)\n    print(stream_message&#x5B;&quot;__interrupt__&quot;]&#x5B;-1].value)\n  else:\n    if detailed_model_response:\n      print(stream_message)\n\n  print(&quot;\\n&quot;)\n\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nTools\n--------------------------------------------------------------------------------------------------------------------\n\nTool required to search the relevant documents (like prepared knowledge-base)\n&quot;&quot;&quot;\n@tool\ndef context_searcher(query: str):\n  &quot;&quot;&quot;Search the relevant documents&quot;&quot;&quot;\n\n  loader = UnstructuredLoader(datasource)\n  document = loader.load()\n\n  text_splitter = RecursiveCharacterTextSplitter(chunk_size=100, chunk_overlap=50)\n  split_documents = text_splitter.split_documents(document)\n  filtered_documents = filter_complex_metadata(split_documents)\n\n  vectorstore = Chroma.from_documents(\n    documents=filtered_documents,\n    collection_name=&quot;knowledge-base&quot;,\n    embedding=OpenAIEmbeddings(),\n  )\n\n  retriever = vectorstore.as_retriever()\n  results = retriever.invoke(query)\n\n  return &quot;\\n&quot;.join(&#x5B;doc.page_content for doc in results])\n\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nAI Agent\n--------------------------------------------------------------------------------------------------------------------\n\nList of available tools\n&quot;&quot;&quot;\ntools = &#x5B;context_searcher]\n\n&quot;&quot;&quot;\nInitialize the LLM model and attach tools\n&quot;&quot;&quot;\nopenai_api_key = os.getenv(&quot;OPENAI_API_KEY&quot;)\nmodel = ChatOpenAI(model=&quot;gpt-4o-mini&quot;, temperature=0, api_key=openai_api_key).bind_tools(tools)\n\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nFunctions required by Human in the loop concept\n--------------------------------------------------------------------------------------------------------------------\n&quot;&quot;&quot;\n\ndef human_interaction(state: MessagesState) -&gt; Command&#x5B;Literal&#x5B;&quot;human_approved&quot;, &quot;human_rejected&quot;, END]]:\n  &quot;&quot;&quot;note: we not use the state in the current graph example - remember the state contains the LLM context&quot;&quot;&quot;\n  answer = interrupt(\n    {\n      &quot;question&quot;: &quot;Hi human! :) If the answer are correct? Type: `y` or `n`&quot;,\n    }\n  )\n\n  print(&quot;&gt;&gt;&gt; Agent message &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\\n\\n&quot;)\n  print(&quot;Your answer: &quot;, answer, &quot;\\n\\n&quot;)\n\n  if answer == &quot;y&quot;:\n    return Command(goto=&quot;human_approved&quot;)\n  if answer == &quot;n&quot;:\n    return Command(goto=&quot;human_rejected&quot;)\n  else:\n    print(&quot;Unsupported answer. Terminating...&quot;)\n    return Command(goto=END)\n\ndef human_approved(state: MessagesState) -&gt; Command&#x5B;END]:\n  print(&quot;&gt;&gt;&gt; Agent message &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\\n\\n&quot;)\n  print(&quot;\u2705 Do something in approved path.&quot;)\n  return Command(goto=END)\n\ndef human_rejected(state: MessagesState) -&gt; Command&#x5B;END]:\n  print(&quot;&gt;&gt;&gt; Agent message &lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;&lt;\\n\\n&quot;)\n  print(&quot;\u274c Do something in rejected path.&quot;)\n  return Command(goto=END)\n\n\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nBuild state graph workflow\n--------------------------------------------------------------------------------------------------------------------\n\nGet LLM response function\n&quot;&quot;&quot;\ndef call_model(state: MessagesState):\n  messages = state&#x5B;&#039;messages&#039;]\n  response = model.invoke(messages)\n  return {&quot;messages&quot;: &#x5B;response]}\n\n&quot;&quot;&quot;\nConditional-edge function\n&quot;&quot;&quot;\ndef should_continue(state: MessagesState) -&gt; Literal&#x5B;&quot;tools&quot;, &quot;human_interaction&quot;]:\n  messages = state&#x5B;&#039;messages&#039;]\n  last_message = messages&#x5B;-1]\n\n  &quot;&quot;&quot;If the model needs a tool, then call the &quot;tools&quot; node&quot;&quot;&quot;\n  if last_message.tool_calls:\n    return &quot;tools&quot;\n\n  return &quot;human_interaction&quot;\n\n&quot;&quot;&quot;\nCreate a new State graph (workflow)\n&quot;&quot;&quot;\ngraph_builder = StateGraph(MessagesState)\n\n&quot;&quot;&quot;\nAdd an agent node\n&quot;&quot;&quot;\ngraph_builder.add_node(&quot;agent&quot;, call_model)\n\n&quot;&quot;&quot;\nAdd tools node\n&quot;&quot;&quot;\ngraph_builder.add_node(&quot;tools&quot;, ToolNode(tools))\n\n&quot;&quot;&quot;\nAdd human interaction nodes (HITL)\n&quot;&quot;&quot;\ngraph_builder.add_node(&quot;human_interaction&quot;, human_interaction)\ngraph_builder.add_node(&quot;human_approved&quot;, human_approved)\ngraph_builder.add_node(&quot;human_rejected&quot;, human_rejected)\n\n&quot;&quot;&quot;\nConfigure workflow conditions (edges)\n&quot;&quot;&quot;\ngraph_builder.add_edge(START, &quot;agent&quot;)\ngraph_builder.add_conditional_edges(&quot;agent&quot;, should_continue)\ngraph_builder.add_edge(&quot;tools&quot;, &quot;agent&quot;)\n\n&quot;&quot;&quot;\n--------------------------------------------------------------------------------------------------------------------\nCompile and run\n--------------------------------------------------------------------------------------------------------------------\n&quot;&quot;&quot;\n\ngraph_config = {&quot;configurable&quot;: {&quot;thread_id&quot;: uuid.uuid4()}}\ncompiled_graph = graph_builder.compile(checkpointer=memory)\n\n&quot;&quot;&quot;\nStreaming the model output\n&quot;&quot;&quot;\nprompt = {&quot;messages&quot;: &#x5B;\n  SystemMessage(content=&quot;Provide the temperature for all cities described in `knowledge-base`. Respond in JSON format without any additional text (JSON only without markdown).&quot;)\n]}\nfor event in compiled_graph.stream(prompt, config=graph_config, stream_mode=&quot;values&quot;):\n  stream_parser(event)\n\n&quot;&quot;&quot;\nWaiting for human response (if needed)\n&quot;&quot;&quot;\nhuman_response_input = input()\nfor event in compiled_graph.stream(Command(resume=human_response_input), config=graph_config, stream_mode=&quot;updates&quot;):\n  stream_parser(event)\n\n&quot;&quot;&quot;\nVisualize your graph\n&quot;&quot;&quot;\ndisplay(Image(compiled_graph.get_graph().draw_mermaid_png())) # works only in Jupiter notebooks\n<\/pre><\/div>\n\n\n<p><\/p>\n\n\n<div class=\"kk-star-ratings kksr-auto kksr-align-left kksr-valign-bottom\"\n    data-payload='{&quot;align&quot;:&quot;left&quot;,&quot;id&quot;:&quot;31961&quot;,&quot;slug&quot;:&quot;default&quot;,&quot;valign&quot;:&quot;bottom&quot;,&quot;ignore&quot;:&quot;&quot;,&quot;reference&quot;:&quot;auto&quot;,&quot;class&quot;:&quot;&quot;,&quot;count&quot;:&quot;2&quot;,&quot;legendonly&quot;:&quot;&quot;,&quot;readonly&quot;:&quot;&quot;,&quot;score&quot;:&quot;5&quot;,&quot;starsonly&quot;:&quot;&quot;,&quot;best&quot;:&quot;5&quot;,&quot;gap&quot;:&quot;11&quot;,&quot;greet&quot;:&quot;&quot;,&quot;legend&quot;:&quot;5\\\/5 ( votes: 2)&quot;,&quot;size&quot;:&quot;18&quot;,&quot;title&quot;:&quot;Koncepcja Human-in-the-loop \u2013 cz\u0142owiek w procesie decyzyjnym agent\u00f3w AI (na przyk\u0142adzie LangGraph)&quot;,&quot;width&quot;:&quot;139.5&quot;,&quot;_legend&quot;:&quot;{score}\\\/{best} ( {votes}: {count})&quot;,&quot;font_factor&quot;:&quot;1.25&quot;}'>\n            \n<div class=\"kksr-stars\">\n    \n<div class=\"kksr-stars-inactive\">\n            <div class=\"kksr-star\" data-star=\"1\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"2\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"3\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"4\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" data-star=\"5\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n    \n<div class=\"kksr-stars-active\" style=\"width: 139.5px;\">\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n            <div class=\"kksr-star\" style=\"padding-right: 11px\">\n            \n\n<div class=\"kksr-icon\" style=\"width: 18px; height: 18px;\"><\/div>\n        <\/div>\n    <\/div>\n<\/div>\n                \n\n<div class=\"kksr-legend\" style=\"font-size: 14.4px;\">\n            5\/5 ( votes: 2)    <\/div>\n    <\/div>\n","protected":false},"excerpt":{"rendered":"<p>Zanim zaczniemy, celem lepszego zrozumienia wyst\u0119puj\u0105cych w artykule poj\u0119\u0107, zach\u0119cam zapoznanie si\u0119 z kilkoma kluczowymi definicjami: Wprowadzenie do Human-in-the-loop Koncepcja &hellip; <a class=\"continued-btn\" href=\"https:\/\/sii.pl\/blog\/koncepcja-human-in-the-loop-czlowiek-w-procesie-decyzyjnym-agentow-ai-na-przykladzie-langgraph\/\">Continued<\/a><\/p>\n","protected":false},"author":735,"featured_media":32025,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_editorskit_title_hidden":false,"_editorskit_reading_time":0,"_editorskit_is_block_options_detached":false,"_editorskit_block_options_position":"{}","inline_featured_image":false,"footnotes":""},"categories":[1314],"tags":[2864,2862,2427,1512,680,584],"class_list":["post-31961","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-development-na-twardo","tag-langgraph","tag-human-in-the-loop","tag-digital","tag-poradnik","tag-ai","tag-python"],"acf":[],"aioseo_notices":[],"republish_history":[],"featured_media_url":"https:\/\/sii.pl\/blog\/wp-content\/uploads\/2025\/08\/Code-1.jpg","category_names":["Development na twardo"],"_links":{"self":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/31961"}],"collection":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/users\/735"}],"replies":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/comments?post=31961"}],"version-history":[{"count":3,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/31961\/revisions"}],"predecessor-version":[{"id":31977,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/posts\/31961\/revisions\/31977"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media\/32025"}],"wp:attachment":[{"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/media?parent=31961"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/categories?post=31961"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sii.pl\/blog\/wp-json\/wp\/v2\/tags?post=31961"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}