Qwen-Agent, часть 1: путеводитель по фреймворку и его документации
Как устроен Qwen-Agent — фреймворк агентов от команды Qwen: карта кода и документации, перевод ключевых разделов (агенты, инструменты, управление контекстом, RAG, MCP) и как читать его код как пример агентного фреймворка.
Что это и зачем читать
Заголовок раздела «Что это и зачем читать»Qwen-Agent — фреймворк для приложений на основе LLM, построенный вокруг возможностей Qwen: следование инструкциям, вызов инструментов, планирование и память. По словам авторов, он работает бэкендом чат-сервиса Qwen Chat.
Для курса у него две ценности:
- Готовый инструмент. Агент с вызовом функций, MCP, интерпретатором кода и RAG по своим PDF — за 20 строк кода, поверх вашего локального vLLM.
- Учебный образец. Код компактный и читаемый. Главный цикл агента — около 40 строк в
qwen_agent/agents/fncall_agent.py; это ровно тот цикл, что вы писали руками в уроке 8.3.1. Документация Qwen называет Qwen-Agent «эталонной реализацией» вызова функций для Qwen3 (см. наш пересказ).
Карта репозитория
Заголовок раздела «Карта репозитория»| Папка / файл | Что там | Урок курса |
|---|---|---|
qwen_agent/agent.py | базовый класс Agent: run(), _call_llm(), _call_tool() | 8.3.1 |
qwen_agent/agents/ | готовые агенты: Assistant, FnCallAgent, ReActChat, GroupChat, Router, TIRMathAgent, агенты для документов | 8.3.1, 8.3.3 |
qwen_agent/llm/ | клиенты моделей: DashScope, OpenAI-совместимый (oai.py), transformers, OpenVINO | 8.1.1 |
qwen_agent/llm/fncall_prompts/ | шаблоны вызова функций: nous (по умолчанию, для Qwen3) и старый qwen | 8.3.2 |
qwen_agent/tools/ | инструменты: code_interpreter, web_search, doc_parser, retrieval, image_gen, mcp_manager и др. | 8.3.2 |
qwen_agent/memory/ | память агента: работа с файлами и поиском по ним | 8.2.1 |
qwen_agent/gui/ | веб-интерфейс на Gradio 5: WebUI(agent).run() | — |
qwen_agent/settings.py | константы, например MAX_LLM_CALL_PER_RUN = 20 | — |
examples/ | ≈35 примеров: от function_calling.py до мультиагентных шахмат | все уроки 8.3 |
qwen-agent-docs/ | исходники сайта документации (руководство и бенчмарк DeepPlanning) | — |
Карта документации
Заголовок раздела «Карта документации»| Раздел | О чём | Важность |
|---|---|---|
| Overview | пример агента с MCP-серверами времени и загрузки страниц + веб-интерфейс | прочитать |
| Get Started → Features | список возможностей (переведён ниже кратко) | прочитать |
| Get Started → Install, Quickstart, Configuration | установка, первые шаги, все параметры llm_cfg и инструментов | справочник |
| Core Modules → Agent | агенты и как писать свои | главное |
| Core Modules → LLM, Schema | интерфейс моделей, формат сообщений Message, ContentItem | справочник |
| Core Modules → Tool | инструменты и как писать свои | главное |
| Core Modules → MCP | подключение MCP-серверов | важно |
| Core Modules → RAG, Context | поиск по документам и обрезка контекста | важно |
| Benchmarks → DeepPlanning | бенчмарк агентного планирования (январь 2026) | по желанию |
Возможности (кратко, по разделу Features)
Заголовок раздела «Возможности (кратко, по разделу Features)»- Единый интерфейс агентов: базовый класс
Agentи готовые реализации (Assistant,FnCallAgent). - Вызов инструментов: параллельный, многошаговый и многоходовый, с автоматическим разбором и выполнением.
- RAG по документам длиной от миллиона токенов: гибрид поиска и агентного разбиения вопроса.
- Встроенные инструменты:
code_interpreter(выполнение Python),web_searchиweb_extractor,image_search,image_zoom_in_tool(увеличить часть картинки по рамке). - MCP: подключение внешних серверов (GitHub, файловая система, SQLite…).
- Свои инструменты через декоратор
@register_toolи классBaseTool. - Модели: Qwen3, Qwen3-VL, Qwen3-Omni, Qwen3-Coder, QwQ, Qwen2.5 — через облако DashScope или локальные OpenAI-совместимые серверы (vLLM, SGLang).
- Свой парсер вызовов: если сервер не умеет разбирать вызовы функций, Qwen-Agent сделает это сам; умеет и пользоваться парсером сервера.
- Управление контекстом: длинная история автоматически ужимается под лимит модели.
- Веб-интерфейс одной строкой, стриминг ответа по токенам, модульность: модель, инструменты, память и стратегию планирования можно менять независимо.
Агенты (перевод раздела Agent Introduction)
Заголовок раздела «Агенты (перевод раздела Agent Introduction)»1. Как пользоваться агентом
Заголовок раздела «1. Как пользоваться агентом»Класс Agent — интерфейс верхнего уровня в Qwen-Agent: объект агента объединяет вызовы инструментов и модели.
Агент получает список сообщений и возвращает генератор, выдающий списки сообщений — то есть поток
выходных сообщений.
У разных классов агентов разные сценарии работы. В папке agents есть несколько базовых подклассов. Например,
ArticleAgent возвращает сообщение со статьёй, BasicDocQA — с результатами ответа на вопрос по документу.
У таких агентов довольно жёсткий шаблон ответа, и они подходят для узких задач.
1.1. Класс Assistant
Заголовок раздела «1.1. Класс Assistant»Универсальный агент — класс Assistant; созданный напрямую, он справляется с большинством задач одного агента.
Он умеет:
- играть роль (по системному сообщению);
- сам планировать и вызывать инструменты;
- RAG: принимает документы и разбирает их встроенной стратегией поиска.
В примере ниже мы создаём агента, указав модель, список инструментов и инструкцию роли, и общаемся с ним. По ответам видно, что на просьбу «милый котик» агент, следуя инструкции, сам спланировал и выполнил нужные инструменты: нарисовал кота, скачал картинку и отразил её.
import osfrom qwen_agent.agents import Assistant
llm_cfg = {'model': 'qwen-max'} # модель из облака DashScope (нужен ключ DASHSCOPE_API_KEY)tools = ['image_gen', 'amap_weather'] # встроенные инструменты Qwen-Agentsystem = ('По запросу пользователя сначала нарисуй картинку, затем сам запусти код, чтобы её скачать, ' 'и выбери из данного документа операцию для обработки изображения')
bot = Assistant(llm=llm_cfg, system_message=system, function_list=tools, files=[os.path.abspath('doc.pdf')])
messages = [{'role': 'user', 'content': 'a cute cat'}]for response in bot.run(messages=messages): print('bot response:', response)Результат в оригинале — список сообщений: вызов my_image_gen → ответ функции с адресом картинки → текст ассистента
→ вызов code_interpreter с кодом на PIL, который скачивает и отражает картинку → «Finished execution.» →
итоговый ответ. Обратите внимание на роли: вызов — сообщение assistant с полем function_call,
результат — сообщение с ролью function.
В папке examples есть и другие сценарии одного агента на основе Assistant.
1.2. Класс GroupChat
Заголовок раздела «1.2. Класс GroupChat»Есть и универсальный мультиагентный класс GroupChat. Он управляет списком агентов и сам определяет очерёдность
их реплик:
- получив внешний ввод, сам координирует, кто из агентов говорит, и по очереди возвращает их ответы;
- человек в контуре (человек в контуречеловек в контуре (подтверждение человеком)EN human-in-the-loop): пользователь тоже описан как агент, и группа может при необходимости спросить его мнение;
- пользователь может прервать групповой чат в любой момент.
В examples есть Gradio-демо group_chat_demo.py для создания групповых чатов и пример игры в гомоку
group_chat_chess.py.
2. Как написать своего агента
Заголовок раздела «2. Как написать своего агента»Класс агента — это сценарий обработки сообщений, поэтому свои агенты пишутся гибко. Из базового класса Agent
видно: в подклассе достаточно реализовать одну функцию
def _run(self, messages: List[Message], lang: str = 'en', **kwargs) -> Iterator[List[Message]]: ...Она принимает список сообщений и возвращает итератор по спискам сообщений. Внутри можно вызывать модель и
инструменты функциями _call_llm(...) и _call_tool(...), а также вкладывать других агентов — например,
вызвать Assistant.run(...), чтобы воспользоваться его планированием.
2.1. Вложенные агенты
Заголовок раздела «2.1. Вложенные агенты»Пример оригинала — агент «рассказ по картинке» (VisualStorytelling): получает адрес изображения и пишет сочинение.
Внутри два вложенных агента: image_agent на мультимодальной модели Qwen-VL описывает картинку, writing_agent
пишет текст по описанию, опираясь на базу знаний. Сценарий в _run: дописать к последнему сообщению просьбу
«подробно опиши все детали изображения» → прогнать через image_agent, отдавая его ответы наружу по мере появления →
добавить сообщение «начинай писать сочинение по содержанию картинки» → прогнать через writing_agent.
Документация оговаривает: это лишь один способ; можно было обойтись одним агентом. Преимущество вложенности в том, что у каждого агента свои промпт, инструменты и модель — и на каждом этапе можно добиться лучшего результата.
2.2. Без вложенности
Заголовок раздела «2.2. Без вложенности»Второй пример — минимальный агент DocQA: склеивает переданные справочные материалы во встроенный промпт,
кладёт его в системное сообщение и вызывает модель:
import copyfrom typing import Iterator, Listfrom qwen_agent import Agentfrom qwen_agent.llm.schema import CONTENT, ROLE, SYSTEM, Message
PROMPT = """Внимательно изучи справочные материалы ниже и дай чёткий, хорошо организованный ответ на вопрос пользователя.# Справочные материалы:{ref_doc}"""
class DocQA(Agent): def _run(self, messages: List[Message], knowledge: str = '', lang: str = 'en', **kwargs) -> Iterator[List[Message]]: messages = copy.deepcopy(messages) # не портим историю вызывающего system_prompt = PROMPT.format(ref_doc=knowledge) if messages and messages[0][ROLE] == SYSTEM: # системное сообщение уже есть — дописываем messages[0][CONTENT] += system_prompt else: # нет — вставляем первым messages.insert(0, Message(SYSTEM, system_prompt)) return self._call_llm(messages=messages) # генератор ответов модели(В оригинале промпт задан в двух вариантах — на китайском и английском — и выбирается по lang.)
Как сочетать _call_llm и _call_tool, смотрите в реализациях ReActChat и Assistant.
Как устроен главный цикл (наш разбор кода)
Заголовок раздела «Как устроен главный цикл (наш разбор кода)»Вот что делает FnCallAgent._run — родитель Assistant:
flowchart TD
A["messages"] --> B["_call_llm(messages, functions)"]
B --> C{"есть function_call?"}
C -- да --> D["_call_tool(name, args)"]
D --> E["добавить сообщение role=function"]
E --> F{"вызовов модели меньше 20?"}
F -- да --> B
C -- нет --> G["вернуть ответ"]
F -- нет --> GТри детали, которые стоит заметить в коде: лимит MAX_LLM_CALL_PER_RUN (по умолчанию 20, задаётся переменной
окружения QWEN_AGENT_MAX_LLM_CALL_PER_RUN) защищает от бесконечного цикла; каждый промежуточный результат сразу
отдаётся наружу через yield — отсюда стриминг; _call_tool переводит любой результат инструмента в строку,
потому что модель читает только текст.
Инструменты (перевод раздела Tool Introduction, сокращённо)
Заголовок раздела «Инструменты (перевод раздела Tool Introduction, сокращённо)»1. Как вызывать инструменты
Заголовок раздела «1. Как вызывать инструменты»Напрямую. У всех инструментов единый интерфейс .call(params): передаёте параметры — получаете результат
типа str, list или dict.
from qwen_agent.tools import ImageGen
tool = ImageGen()res = tool.call(params={'prompt': 'a cute cat'})print(res)Из агента. Агент вызывает инструменты функцией _call_tool(...); каждый экземпляр агента может вызывать
только те инструменты, которые ему передали при создании через параметр function_list. Передать можно имя
инструмента, его конфигурацию или сам объект: 'code_interpreter', {'name': 'code_interpreter', 'timeout': 10}
или CodeInterpreter(). Чтобы результат было удобно подать модели, _call_tool(...) приводит любой результат
к строке.
2. Как написать свой инструмент
Заголовок раздела «2. Как написать свой инструмент»В Qwen-Agent есть механизм регистрации инструментов. Чтобы зарегистрировать свой:
- задайте имя, описание и параметры. Строка из
@register_tool('my_image_gen')автоматически становится атрибутом.nameкласса и служит уникальным идентификатором инструмента; - реализуйте функцию
call(...).
import jsonimport urllib.parseimport json5from qwen_agent.tools.base import BaseTool, register_tool
@register_tool('my_image_gen')class MyImageGen(BaseTool): # описание читает модель — от него зависит, когда она решит вызвать инструмент description = 'Сервис ИИ-рисования: по текстовому описанию возвращает адрес нарисованной картинки.' parameters = { # JSON Schema аргументов 'type': 'object', 'properties': { 'prompt': {'type': 'string', 'description': 'Подробное описание желаемой картинки на английском языке'}, }, 'required': ['prompt'], }
def call(self, params: str, **kwargs) -> str: prompt = json5.loads(params)['prompt'] # params — строка с JSON от модели; json5 прощает мелкие ошибки prompt = urllib.parse.quote(prompt) return json.dumps({'image_url': f'https://image.pollinations.ai/prompt/{prompt}'}, ensure_ascii=False)После регистрации инструментом пользуются, как описано выше. Можно и не регистрировать: объявить класс с атрибутом
name и передать агенту объект. Но тогда нельзя передавать инструмент по имени или конфигурации.
Управление контекстом (перевод раздела Context Management)
Заголовок раздела «Управление контекстом (перевод раздела Context Management)»Логика управления контекстом в Qwen-Agent динамически обрезает входные сообщения, сохраняя разумную структуру диалога, чтобы общее число токенов не превышало максимальную длину контекста модели.
- Механизм срабатывает автоматически при вызове
agent.run(...)илиllm.call(...). - Контекст меняется только тогда, когда его длина достигает
max_input_tokens.
Порядок действий:
- Ш1. Начиная с самого старого хода диалога: если даже после удаления хода целиком контекст всё ещё длиннее лимита — удалить этот ход целиком. Иначе перейти к Ш2 и обрабатывать отдельные ходы.
- Ш2. Начиная с самого старого ответа инструмента (кроме последнего шага), «сворачивать» (сжимать или пересказывать) ответы инструментов. Если длина стала допустимой — остановиться, иначе к Ш3.
- Ш3. Начиная с самого старого шага вызова инструмента, удалять шаг целиком (кроме вопроса пользователя и итогового ответа). Если длина в пределах — остановиться, иначе к Ш4.
- Ш4. Обработать последний шаг, постепенно сворачивая его ответ инструмента. Если хватило — остановиться, иначе к Ш5.
- Ш5. Обрезать вопрос пользователя или итоговый ответ этого хода.
Стратегия в первую очередь выбрасывает старые воспоминания и сведения об окружении, постепенно уменьшая контекст, чтобы агент мог работать как бы с «бесконечным контекстом». Авторы признают, что запоминание при этом далеко от идеала, и обещают более продвинутый модуль памяти. Сравните с подходами к памяти агента в уроке 8.3.3.
RAG (перевод раздела, сокращённо)
Заголовок раздела «RAG (перевод раздела, сокращённо)»Встроенный RAG ищет нужные фрагменты в переданных документах, чтобы модель опиралась в ответе на них.
- Разбор документов. Форматы:
.pdf,.docx,.pptx,.txt,.csv,.tsv,.xlsx,.xls,.html. Документ режется на фрагменты поparser_page_sizeтокенов (по умолчанию 500); разбором занимается инструментDocParser. - Поиск по ключевым словам BM25BM25EN BM25 (Okapi BM25)Функция ранжирования по ключевым словам — TF-IDF с насыщением по частоте (k1) и нормировкой на длину документа (b). (библиотека
rank_bm25): запрос пользователя (или сгенерированные ключевые слова) сравнивается с фрагментами. Стратегии задаются вrag_searchers(keyword_search,front_page_searchили их сочетания). Найденное обрезается доmax_ref_token(по умолчанию 20 000 токенов). - Генерация ключевых слов (по желанию). Модель разбивает вопрос на части и выдаёт ключевые слова на китайском
и английском (стратегия по умолчанию
SplitQueryThenGenKeyword). Без модели ищут по исходному вопросу.
В агенте Assistant RAG включается сам, если в сообщении есть файл:
from qwen_agent.agents import Assistantfrom qwen_agent.llm.schema import Message, ContentItem
agent = Assistant(llm=llm_cfg)for rsp in agent.run(messages=[Message(role='user', content=[ ContentItem(text='Какой срок гарантии на товар?'), ContentItem(file='manual.pdf')])]): passprint(rsp[-1]['content'])Нужны дополнительные пакеты: pip install "qwen-agent[rag]". Итог документации: лёгкий, но действенный поиск на
фрагментах и BM25, без векторной базы. Почему авторы обходятся без эмбеддингов и как это сравнить с векторным
поиском — тема урока 8.2.1.
MCP (кратко)
Заголовок раздела «MCP (кратко)»Раздел MCP пошагово описывает: установить qwen-agent[mcp]; поставить Node.js, uv 0.4.18+, Git и (для примера)
SQLite; описать серверы блоком mcpServers (как в Claude Desktop — команда и аргументы запуска каждого сервера);
передать этот словарь агенту прямо в function_list рядом с обычными инструментами. Пример конфигурации — в
README. Что такое MCP — урок 8.3.2.
Какие примеры запустить
Заголовок раздела «Какие примеры запустить»| Пример | Что показывает |
|---|---|
examples/function_calling.py | вызов функций на уровне модели, без агента |
examples/function_calling_in_parallel.py | параллельные вызовы |
examples/assistant_qwen3.py | агент на Qwen3 с MCP и встроенными инструментами |
examples/assistant_mcp_sqlite_bot.py | агент с SQLite через MCP |
examples/assistant_rag.py, parallel_doc_qa.py | вопросы по очень длинным документам (быстрый и «дорогой» варианты) |
examples/tir_math.py | решение задач с вызовом Python (Tool-Integrated Reasoning) |
examples/group_chat_demo.py, multi_agent_router.py | мультиагентность |
Что сделать руками
Заголовок раздела «Что сделать руками»- Поднимите
vllm serve Qwen/Qwen3-8Bи запустите примерAssistantс инструментомcode_interpreter(нужен Docker): попросите «построй график y = x² от −3 до 3». - Напишите свой инструмент
@register_tool('currency')и проверьте, что агент вызывает его сам. - Прочитайте
qwen_agent/agents/fncall_agent.pyи сравните со своим циклом из урока 8.3.1: что у них общего, чего не хватает вашему? - Задайте маленький
max_input_tokens(например, 2000) и посмотрите, как агент ужимает длинную историю.
Словарик
Заголовок раздела «Словарик»| English | Русский |
|---|---|
| agent | агент |
| tool / function | инструмент / функция |
| multi-agent group chat | групповой чат агентов |
| context management | управление контекстом |
| code interpreter | интерпретатор кода |
| keyword retrieval (BM25) | поиск по ключевым словам |
| human-in-the-loop | человек в контуре |