Знакът на КАГАМИ КАГАМИ
kagami.bg/academy · lesson · machine-readable viewUPDATED 2026-10-03
IDENTITY
module
GX10-04-27 · Multi-agent chatbot with a router and specialist agents
series
GX10 (local AI server class: NVIDIA GB10, e.g. ASUS Ascent GX10 / DGX Spark)
level
Intermediate
duration
about 1 h, plus model downloads (about 27 GB in total)
prerequisites
A GB10-class machine with DGX OS (Ubuntu 24.04, Arm64), shell access (local or SSH), Ollama installed and running on the machine (lesson 04-07), Python 3 with venv, internet for the first downloads
trust_label
UPDATED 2026-10-03 (model names, sizes and package versions checked against ollama.com, PyPI and the Ollama docs) · NOT TESTED on a GB10 machine · code not run end to end · not marked VERIFIED
versions
Gradio 6.29.1 (PyPI, 2026-10-03; chatbot history is a list of role/content messages, the tuple format was removed in Gradio 6) · openai Python package (current release, used only as an HTTP client for Ollama) · Ollama latest release 0.35.1 on GitHub (2026-10-03)
models
llama3.2:3b (2.0 GB) as router and chat agent · qwen2.5-coder:7b (4.7 GB) as code agent · qwen3:32b (20 GB) as math and research agent. Note: qwen3:7b and qwen3:72b do not exist on ollama.com; the qwen3 sizes are 0.6b, 1.7b, 4b, 8b, 14b, 30b, 32b, 235b
language
human view: bg · english edition: /en/academy/gx10/ (same file name)
previous / next
GX10 series index / GX10 series index
PURPOSE

Build a chatbot made of one router and four specialist agents on a local AI server. The router (a small, fast model at temperature 0.1) classifies each user message into code, math, research or chat and answers with JSON; the matching specialist answers with its own system prompt and model. The last 6 messages of the conversation are passed to the specialist. The web UI is Gradio, bound to 127.0.0.1 and reached through an SSH tunnel. Everything runs locally; no external API.

KEY CONCEPTS
COMMANDS / PATHS
CHECKLIST
NEXT MODULE

GX10 series index: kagami.bg/academy/gx10/ · related lessons: 04-07 Open WebUI and Ollama (04-07_Open_WebUI_Ollama.html), 04-01 n8n on GX10 (04-01_n8n_Training_GX10.html) · offer: Quick experiment (kagami.bg/stalbata/)

SOURCES
TAGS
gx10nvidia-gb10multi-agentrouterollamagradiolocal-llmchatbot
ОБНОВЕНО · 03.10.2026

Multi-agent чатбот на GX10 с рутер

Един модел трудно е едновременно най-добрият програмист, математик и събеседник. Тук правим чатбот от няколко агента: малък и бърз модел (рутер) прочита въпроса и го подава на правилния специалист. Всичко върви локално през Ollama, а данните не напускат машината.

⏱ ~1 ч Средно GX10 Рутер · специалисти · история Ollama · Python · Gradio
Ollama (моделите)🔒 локално Python · Gradio🔒 локално
🔄
ОБНОВЕНО · 03.10.2026 — какво
Поправихме модели, които не съществуват. В предишния вариант рутерът беше qwen3:7b, а математикът и изследователят — qwen3:72b. И двата не са в каталога на Ollama (размерите на qwen3 са 0,6b, 1,7b, 4b, 8b, 14b, 30b, 32b и 235b). Сега: рутер и разговор — llama3.2:3b, код — qwen2.5-coder:7b, математика и изследване — qwen3:32b. Поправихме кода за интерфейса. В Gradio 6 форматът с двойки (кортежи) в чатбота е премахнат; историята е списък от съобщения с роля и текст — затова интерфейсът е написан наново. Поправихме историята: „последните 6 съобщения“ означава 6 съобщения (3 размени), а не 6 двойки. Добавихме: JSON режим за рутера и безопасно връщане към разговорния агент, разделяне на ядрото от интерфейса, виртуална среда, затворен порт (интерфейсът слуша само на 127.0.0.1) с достъп през SSH тунел и сметка за паметта. Махнахме двуезичния интерфейсен текст и сложния брандиран Gradio-вид.
⚠️
Какво не сме пускали сами
Нямахме машина от класа GB10 и не пуснахме кода от край до край — затова няма етикети „ТЕСТВАНО“ и „ПРОВЕРЕНО“. Имената на моделите, размерите им и версиите на пакетите са сверени онлайн (03.10.2026). Непроверени са: точното поведение на историята в Gradio 6 при по-сложно съдържание (затова кодът я привежда до обикновен текст), скоростта на моделите на твоята машина и колко често малкият рутер греши на български. Опитай и ни кажи как е минало.

01Какво ще научиш

02Преди да започнеш

💡
Защо паметта не е проблем тук
Машината има 128 GB памет, обща за процесора и видеокартата. Ollama по подразбиране държи един модел в паметта 5 минути след последната заявка и може да държи до три наведнъж, ако се побират (по документацията на Ollama). Нашите три модела са общо около 27 GB, така че се побират без усилие. На по-малка машина смени qwen3:32b с qwen3:8b (5,2 GB).

03Стъпки

  1. Как тече един въпрос

    Системата има един рутер и четирима специалисти. Защо така? Рутерът прави само една евтина работа — решава „чий е този въпрос“. Скъпият голям модел се събужда само когато наистина трябва.

    АгентЗа какво еМодел
    РутерКласифицира въпроса и връща JSONllama3.2:3b
    codeПрограмиране, скриптове, дебъгванеqwen2.5-coder:7b
    mathУравнения, изчисления, стъпка по стъпкаqwen3:32b
    researchОбяснения, сравнения, структурирани отговориqwen3:32b
    chatРазговор, поздрави, дребни задачиllama3.2:3b

    Пътят е: твоето съобщение → рутер → избран специалист → отговор. Ако рутерът върне нещо неразбираемо, въпросът отива при разговорния агент — така системата не се чупи.

  2. Подготви папка и виртуална среда

    Виртуалната среда държи пакетите на проекта настрана от системния Python — не развалят едно друго.

    bash · на машината
    mkdir -p ~/multi-agent && cd ~/multi-agent
    python3 -m venv .venv
    source .venv/bin/activate
    pip install "gradio>=6,<7" openai

    Пакетът openai ползваме само като клиент: Ollama има съвместим с него програмен интерфейс на http://localhost:11434/v1. Gradio е закачен на версия 6 (към 03.10.2026 е 6.29.1), защото кодът по-долу е за нея. ⚠️ Командите не са пускани от нас.

  3. Изтегли моделите

    bash
    ollama pull llama3.2:3b
    ollama pull qwen2.5-coder:7b
    ollama pull qwen3:32b
    ollama list
    МоделРазмер (по ollama.com)Роля
    llama3.2:3b2,0 GBрутер и разговор
    qwen2.5-coder:7b4,7 GBкод
    qwen3:32b20 GBматематика и изследване
    ✅
    Защо рутерът не е qwen3
    Qwen3 е „мислещ“ модел: преди отговора може да напише дълъг блок с разсъждения. За рутера това е излишно забавяне — той трябва да каже една дума. Затова ползваме малкия llama3.2:3b, който отговаря веднага. Ако искаш друг рутер, смени го в една променлива.
  4. Ядрото: файлът multi_agent.py

    Тук е цялата логика — без интерфейс. Така можеш да я пробваш в терминала, а после да сложиш върху нея какъвто искаш екран.

    python · ~/multi-agent/multi_agent.py
    import json
    from openai import OpenAI
    
    # Ollama игнорира ключа, но клиентът изисква някаква стойност
    client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
    
    ROUTER_MODEL = "llama3.2:3b"
    
    AGENTS = {
        "code": {
            "model": "qwen2.5-coder:7b",
            "system": "Ти си опитен програмист. Пишеш чист код с кратки коментари. "
                      "Обясняваш какво прави кодът, показваш пример за употреба "
                      "и споменаваш граничните случаи. Предпочиташ Python и Bash.",
        },
        "math": {
            "model": "qwen3:32b",
            "system": "Ти си математик. Решаваш задачите стъпка по стъпка, "
                      "обясняваш какво правиш и защо, и проверяваш резултата накрая.",
        },
        "research": {
            "model": "qwen3:32b",
            "system": "Ти си изследователски асистент. Даваш точни и пълни отговори, "
                      "подредени със заглавия и точки. Разграничаваш факти от мнения "
                      "и казваш, когато не си сигурен.",
        },
        "chat": {
            "model": "llama3.2:3b",
            "system": "Ти си приятелски асистент. Отговаряш на български, "
                      "кратко, ясно и полезно.",
        },
    }
    
    ROUTER_SYSTEM = """Класифицирай въпроса в ТОЧНО ЕДИН от следните типове:
    - "code": програмиране, скриптове, дебъгване
    - "math": математика, уравнения, изчисления, статистика
    - "research": обяснения, понятия, история, сравнения, анализ
    - "chat": разговор, поздрав, лично мнение, малки задачи
    
    Отговори САМО с JSON: {"agent": "code|math|research|chat"}"""
    
    
    def route(message: str) -> str:
        """Връща името на агента; при всяко съмнение - 'chat'."""
        r = client.chat.completions.create(
            model=ROUTER_MODEL,
            temperature=0.1,
            response_format={"type": "json_object"},
            messages=[
                {"role": "system", "content": ROUTER_SYSTEM},
                {"role": "user", "content": message},
            ],
        )
        try:
            agent = json.loads(r.choices[0].message.content).get("agent", "chat")
        except (json.JSONDecodeError, TypeError, AttributeError):
            agent = "chat"
        return agent if agent in AGENTS else "chat"
    
    
    def ask_agent(name: str, message: str, history: list) -> str:
        """history е списък от {"role": ..., "content": ...}; взимаме последните 6."""
        agent = AGENTS[name]
        messages = [{"role": "system", "content": agent["system"]}]
        messages += history[-6:]
        messages.append({"role": "user", "content": message})
        r = client.chat.completions.create(
            model=agent["model"],
            temperature=0.7,
            messages=messages,
        )
        return r.choices[0].message.content
    
    
    def respond(message: str, history: list):
        """Връща (име на агента, отговор)."""
        name = route(message)
        return name, ask_agent(name, message, history)
    
    
    if __name__ == "__main__":
        history = []
        while True:
            msg = input("Ти: ").strip()
            if msg.lower() in ("exit", "quit"):
                break
            if not msg:
                continue
            name, answer = respond(msg, history)
            history += [
                {"role": "user", "content": msg},
                {"role": "assistant", "content": answer},
            ]
            print(f"\n[{name.upper()}] {answer}\n")
    💡
    Какво се случва в кода
    JSON режимът (response_format) е поддържан от съвместимия интерфейс на Ollama и кара модела да връща валиден JSON. Проверката agent in AGENTS хваща случая, когато рутерът измисли непозната категория. Историята е обикновен списък; „последните 6“ са 6 съобщения, тоест 3 размени. Ниската температура на рутера (0,1) прави класифицирането стабилно, а 0,7 при специалистите оставя място за изразност.
  5. Пробвай в терминала

    bash
    python multi_agent.py

    Напиши „Здравей, как си?“ и после „Напиши Python скрипт за четене на CSV файл“. Пред всеки отговор трябва да видиш името на агента. Първият отговор на всеки модел е по-бавен — моделът се зарежда в паметта. С exit излизаш.

  6. Интерфейс: файлът app.py

    Gradio прави уеб страница с няколко реда. Тук важното е историята: в Gradio 6 тя е списък от речници с role и content. Съдържанието може да е текст или списък от части, затова го привеждаме до обикновен текст, преди да го върнем на модела. Етикетът с името на агента показваме на екрана, но го махаме от историята, за да не обърква специалистите.

    python · ~/multi-agent/app.py
    import re
    import gradio as gr
    from multi_agent import respond
    
    TAG = re.compile(r"^\*\*\[[A-Z]+\]\*\*\n\n")
    
    
    def as_text(content) -> str:
        """Gradio може да даде текст или списък от части; връщаме обикновен текст."""
        if isinstance(content, str):
            return content
        if isinstance(content, list):
            return "".join(
                p.get("text", "") if isinstance(p, dict) else str(p) for p in content
            )
        return str(content)
    
    
    def chat(message, history):
        if not message.strip():
            return "", history
        plain = [
            {"role": h["role"], "content": TAG.sub("", as_text(h["content"]))}
            for h in history
        ]
        name, answer = respond(message, plain)
        history = history + [
            {"role": "user", "content": message},
            {"role": "assistant", "content": f"**[{name.upper()}]**\n\n{answer}"},
        ]
        return "", history
    
    
    with gr.Blocks(title="Multi-agent чатбот") as demo:
        gr.Markdown("# Multi-agent чатбот\nРутерът насочва въпроса към: **code** · **math** · **research** · **chat**")
        chatbot = gr.Chatbot(height=500, label="Разговор")
        msg = gr.Textbox(placeholder="Задай въпрос...", label="Съобщение")
        with gr.Row():
            send_btn = gr.Button("Изпрати", variant="primary")
            clear_btn = gr.Button("Изчисти")
        send_btn.click(chat, [msg, chatbot], [msg, chatbot])
        msg.submit(chat, [msg, chatbot], [msg, chatbot])
        clear_btn.click(lambda: [], None, chatbot)
    
    demo.launch(server_name="127.0.0.1", server_port=7860)
    bash
    python app.py

    Интерфейсът слуша само на 127.0.0.1:7860 — другите в мрежата не го виждат. От своя компютър стигаш до него през SSH тунел:

    bash · на твоя компютър
    ssh -L 7860:localhost:7860 <потребител>@<адрес-на-машината>

    Остави връзката отворена и отвори http://localhost:7860 в браузъра. Ако работиш директно на машината, просто отвори същия адрес.

    ⚠️
    Не слагай 0.0.0.0
    Старият вариант на урока пускаше интерфейса на 0.0.0.0, тоест към цялата мрежа — без парола. Не го правим: всеки в мрежата би могъл да разговаря с твоите модели.
  7. Провери насочването

    Задай тези четири въпроса и виж кой агент отговаря:

    ВъпросОчакван агентМодел
    „Напиши Python скрипт за четене на CSV файл“CODEqwen2.5-coder:7b
    „Реши: 3x² + 5x − 2 = 0“MATHqwen3:32b
    „Обясни какво е архитектурата transformer“RESEARCHqwen3:32b
    „Здравей, как си?“CHATllama3.2:3b

    За уравнението очакваш корени x = 1/3 и x = −2 — така можеш да провериш дали математикът е прав. Малкият рутер може да сгреши, особено при смесени въпроси („напиши код, който решава уравнение“). Тогава поправката е в ROUTER_SYSTEM: добави по един кратък пример за всяка категория.

  8. Капани и подобрения

    • Първо бавно. Зареждането на 20-гигабайтов модел отнема време. Следващите отговори са бързи, докато моделът е в паметта (5 минути след последната заявка). Ако искаш да освободиш паметта веднага, ползвай ollama stop <модел>.
    • Историята струва. Всяко старо съобщение се изпраща пак към модела — повече история значи по-бавен отговор. Затова пазим само последните 6.
    • Рутерът е предложение. Той не знае всичко. Ако е важно, добави бутон „друг агент“ или показвай името на агента (както правим), за да се вижда кой е отговорил.
    • Човек одобрява действията. Този чатбот само отговаря. Щом му дадеш права да праща имейли или да пише в системи, всяко действие с реален ефект трябва да минава през човек.

04Проверка

Тест

1. Защо рутерът е малък модел, а не най-големият?

2. Какво прави кодът, ако рутерът върне JSON с непозната категория?

3. Защо Gradio е пуснат с server_name="127.0.0.1"?

4. В какъв вид е историята на разговора в чатбота на Gradio 6?

05Какво следва

06Източници

  1. Ollama: съвместимост с OpenAI 🔒 локално — адрес /v1, response_format, ключът се игнорира.
  2. Ollama: често задавани въпроси — 5 минути в паметта, ollama stop, няколко модела наведнъж.
  3. llama3.2 · qwen2.5-coder · qwen3 (тагове) 🔒 локално — наличните размери: 2,0 GB, 4,7 GB, 20 GB.
  4. Gradio в PyPI · Gradio: списък с промените — версия 6.29.1; форматът с двойки в чатбота е премахнат.
  5. openai в PyPI — клиентът, който ползваме за връзка с Ollama.
  6. NVIDIA DGX Spark: хардуер — 128 GB обща памет.