{/ Эта страница автоматически создается на основе файла SKILL.md навыка с помощью сайта site/scripts/generate-skill-docs.py. Редактируйте исходный код SKILL.md, а не эту страницу. /}

Отладка Python

Отладка Python: pdb REPL + удаленная отладка (DAP).

Метаданные навыков

Источник В комплекте (устанавливается по умолчанию)
Путь навыки/разработка программного обеспечения/python-debugpy
Версия 1.0.0
Автор Агент Гермес
Лицензия Массачусетский технологический институт
Платформы Linux, MacOS
Теги отладка, python, pdb, debugpy, breakpoints, dap, post-mortem
Сопутствующие навыки systematic-debugging, node-inspect-debugger, debugging-hermes-tui-commands

Ссылка: полная версия SKILL.md:::информация

Ниже приведено полное определение навыка, которое Гермес загружает при активации этого навыка. Это то, что агент видит в качестве инструкций, когда навык активен.

Отладчик Python (pdb + debugpy)

Обзор

Три инструмента, выбираемых по ситуации:

Инструмент Когда
breakpoint() + pdb Локальный, интерактивный, самый простой. Добавьте breakpoint() в исходный код, запустите его в обычном режиме, получите REPL в этой строке.
python -m pdb Запустите существующий скрипт в pdb без редактирования исходного кода. Полезно для быстрого тыкания.
отладка Удаленный/безголовый/"присоединиться к уже запущенному процессу". Talks DAP, скриптируемый с терминала, работает для долгоживущих процессов (шлюз, демон, дочерние элементы PTY).

Начните с breakpoint(). Это самый дешевый вариант.

Когда использовать

Не используйте для: вещей, которые print() / logging.debug решают менее чем за минуту, или вещи, которые pytest -vv --tb=long --showlocals уже обнаруживает.

Краткий справочник по pdb

Внутри любого приглашения pdb ((Pdb)):

Команда Действие
h / h cmd помощь
н следующая строка (перешагнуть)
с шагнуть в
р возврат из текущей функции
с продолжить
unt N продолжать до строки N
j N перейти на строку N (только та же функция)
л/лл список источников вокруг текущей строки/полная функция
ш где (трассировка стека)
у / д перемещаться вверх/вниз по стопке
а вывести аргументы текущей функции
p выражение / pp выражение печать / красивое выражение
показать выражение автоматическая печать выражения на каждой остановке
b файл:строка установить точку останова
б функция перерыв при входе в функцию
b файл:строка, условие условная точка останова
кл Н очистить точку останова N
tbreak файл:строка одноразовая точка останова
!stmt выполнить произвольный Python (задания включены)
взаимодействовать перейдите в полную версию Python REPL в текущей области (Ctrl+D для выхода)
к бросить

Команда interact является самой мощной — вы можете импортировать что угодно, проверять сложные объекты и даже вызывать методы, изменяющие состояние. По умолчанию локальные файлы доступны только для чтения; используйте !x = 42 из приглашения (Pdb) для изменения.

Рецепт 1: Локальная точка останова

Самый простой. Отредактируйте файл:

def compute(x, y):
    result = some_helper(x)
    breakpoint()           # <-- drops into pdb here
    return result + y

Запустите код в обычном режиме. Вы попадаете на строку breakpoint() с полным доступом к локальным пользователям.

Не забудьте удалить breakpoint() перед фиксацией. Используйте git diff или grep перед фиксацией:

rg -n 'breakpoint\(\)' --type py

Рецепт 2: Запускаем скрипт под pdb (без редактирования исходного кода)

python -m pdb path/to/script.py arg1 arg2
# Lands at first line of script
(Pdb) b path/to/script.py:42
(Pdb) c

Рецепт 3: Отладка теста pytest

Программа запуска тестов Hermes и pytest поддерживают это:

# Drop to pdb on failure (or on any raised exception):
scripts/run_tests.sh tests/path/to/test_file.py::test_name --pdb

# Drop to pdb at the START of the test:
scripts/run_tests.sh tests/path/to/test_file.py::test_name --trace

# Show locals in tracebacks without pdb:
scripts/run_tests.sh tests/path/to/test_file.py --showlocals --tb=long

Примечание: scripts/run_tests.sh по умолчанию использует xdist (-n 4), а pdb НЕ работает под xdist. Добавьте -p no:xdist или запустите одиночный тест с -n 0:

scripts/run_tests.sh tests/foo_test.py::test_bar --pdb -p no:xdist
# or
source.venv/bin/activate
python -m pytest tests/foo_test.py::test_bar --pdb

Это обходит гарантии hermetic-env — хорошо для отладки, но перед отправкой необходимо повторно запустить под оболочкой для подтверждения.

Рецепт 4: Вскрытие любого исключения

import pdb, sys
try:
    run_the_thing()
except Exception:
    pdb.post_mortem(sys.exc_info()[2])

Или оберните весь скрипт:

python -m pdb -c continue script.py
# When it crashes, pdb catches it and you're in the frame of the exception

Или установите глобальный хук в repl/jupyter:

import sys
def excepthook(etype, value, tb):
    import pdb; pdb.post_mortem(tb)
sys.excepthook = excepthook

Рецепт 5: Удаленная отладка с помощью debugpy (подключение к запущенному процессу)

Для долгоживущих процессов: шлюз Hermes, tui_gateway, демон, процесс, который уже работает неправильно и не может быть перезапущен в чистом виде.

Настраивать

source /home/bb/hermes-agent/.venv/bin/activate
pip install debugpy

Шаблон A: Source-edit — процесс ожидает отладчика при запуске

Добавьте в верхней части точки входа (или внутри функции, которую вы хотите отладить):

import debugpy
debugpy.listen(("127.0.0.1", 5678))
print("debugpy listening on 5678, waiting for client...", flush=True)
debugpy.wait_for_client()
debugpy.breakpoint()       # optional: pause immediately once attached

Запустите процесс; он блокируется в wait_for_client().

Шаблон B: без редактирования исходного кода — запуск с -m debugpy

python -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1

Эквивалент для входа в модуль:

python -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.module

Шаблон C: подключение к уже запущенному процессу

Требуется, чтобы PID и отладочная программа были предварительно установлены в целевой среде:

python -m debugpy --listen 127.0.0.1:5678 --pid <pid>
# debugpy injects itself into the process. Then attach a client as below.

Некоторые конфигурации ядра/безопасности блокируют внедрение на основе ptrace (/proc/sys/kernel/yama/ptrace_scope). Исправьте с помощью:

echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope

Подключение клиента из терминала

Самый простой клиент DAP на стороне терминала — это VS Code CLI или небольшой скрипт. Изнутри Hermes у вас есть два практических варианта:

Вариант 1: собственный CLI REPL debugpy — не официальная функция, а небольшой клиентский скрипт DAP:

# /tmp/dap_client.py
import socket, json, itertools, time, sys

HOST, PORT = "127.0.0.1", 5678
s = socket.create_connection((HOST, PORT))
seq = itertools.count(1)

def send(msg):
    msg["seq"] = next(seq)
    body = json.dumps(msg).encode()
    s.sendall(f"Content-Length: {len(body)}\r\n\r\n".encode() + body)

def recv():
    header = b""
    while b"\r\n\r\n" not in header:
        header += s.recv(1)
    length = int(header.decode().split("Content-Length:")[1].split("\r\n")[0].strip())
    body = b""
    while len(body) < length:
        body += s.recv(length - len(body))
    return json.loads(body)

send({"type": "request", "command": "initialize", "arguments": {"adapterID": "python"}})
print(recv())
send({"type": "request", "command": "attach", "arguments": {}})
print(recv())
send({"type": "request", "command": "setBreakpoints",
      "arguments": {"source": {"path": sys.argv[1]},
                    "breakpoints": [{"line": int(sys.argv[2])}]}})
print(recv())
send({"type": "request", "command": "configurationDone"})
#... loop reading events and sending continue/stepIn/etc.

Это хорошо для разовой автоматизации, но болезненно для интерактивного UX.

Вариант 2: Прикрепить из VS Code/Cursor/Zed — если у пользователя он открыт, он может добавить launch.json:

{
  "name": "Attach to Hermes",
  "type": "debugpy",
  "request": "attach",
  "connect": { "host": "127.0.0.1", "port": 5678 },
  "justMyCode": false,
  "pathMappings": [
    { "localRoot": "${workspaceFolder}", "remoteRoot": "/home/bb/hermes-agent" }
  ]
}

Вариант 3. Откажитесь от DAP и используйте remote-pdb — обычно это именно то, что вам нужно от терминального агента:

pip install remote-pdb

В вашем коде:

from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444)   # blocks until connection

Затем из терминала:

nc 127.0.0.1 4444
# You get a (Pdb) prompt exactly as if debugging locally.

remote-pdb — самый чистый и удобный для агентов выбор, когда протокол DAP debugpy является излишним. Используйте debugpy только тогда, когда вам действительно нужна интеграция с IDE.

Отладка процессов, специфичных для Hermes

Тесты

См. рецепт 3. Всегда добавляйте -p no:xdist или запускайте отдельные тесты без xdist.

run_agent.py / CLI — одноразовый

Самый простой: добавьте breakpoint() рядом с подозрительной строкой, а затем запустите hermes в обычном режиме. Управление возвращается к вашему терминалу в точке паузы.

Подпроцесс tui_gateway (порожденный hermes --tui)

Шлюз работает как дочерний элемент Node TUI. Опции:

А. Исходный код — отредактируйте шлюз:

# tui_gateway/server.py near the top of serve()
import debugpy
debugpy.listen(("127.0.0.1", 5678))
debugpy.wait_for_client()

Запустите hermes --tui. TUI будет зависшим (его серверная часть ожидает). Прикрепить клиента; выполнение возобновляется, когда вы «продолжаете».

Б. Используйте remote-pdb для определенного обработчика:

from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444)   # in the RPC handler you want to trap

Запустите соответствующую команду косой черты из TUI, затем nc 127.0.0.1 4444 в другом терминале.

Подпроцесс _SlashWorker

Тот же шаблон — «remote-pdb» с «set_trace()» внутри пути «exec» воркера. Рабочий объект сохраняется при выполнении команд косой черты, поэтому первый триггер блокируется до тех пор, пока вы не подключитесь; последующие команды косой черты выполняются нормально, если вы не перевооружитесь.

Шлюз (gateway/run.py)

Долговечный. Используйте «remote-pdb» в обработчике или «debugpy» с «--wait-for-client», если вы все равно перезапускаете шлюз.

Распространенные ошибки

  1. pdb под pytest-xdist ничего не делает. Вы не увидите подсказку, тест просто зависает. Всегда используйте -p no:xdist или -n 0.

  2. breakpoint() в контекстах CI/без TTY зависает процесс. Безопасно локально; никогда не совершайте этого. Добавьте grep перед фиксацией в качестве страховки.

  3. PYTHONBREAKPOINT=0 отключает все вызовы breakpoint(). Проверьте окружение, если ваша точка останова не сработала: bash echo $PYTHONBREAKPOINT

  4. debugpy.listen блокируется, только если вы также вызываете wait_for_client(). Без него выполнение продолжается, и ваша первая точка останова может сработать до подключения клиента.

  5. Присоединение к PID не удается на усиленных ядрах. ptrace_scope=1 (по умолчанию в Ubuntu) разрешает только ptrace одного и того же пользователя для дочерних процессов. Обходной путь: echo 0 > /proc/sys/kernel/yama/ptrace_scope (требуется root) или запустить с самого начала под debugpy.

  6. Threads. pdb отлаживает только текущий поток. Для многопоточного кода используйте debugpy (DAP с поддержкой потоков) или установите threading.settrace() для каждого потока.

  7. asyncio. pdb работает в сопрограммах, но await внутри pdb требует Python 3.13+ или await из режима interact в более старых версиях. Для версий 3.11/3.12 используйте трюки asyncio.run_coroutine_threadsafe или ожидание на основе !stmt через asyncio.ensure_future.

  8. scripts/run_tests.sh удаляет учетные данные и устанавливает HOME=<tmpdir>. Если ваша ошибка зависит от конфигурации пользователя или реальных ключей API, она не будет воспроизводиться под оболочкой. Сначала выполните отладку с помощью необработанного pytest для воспроизведения, а затем повторно подтвердите под оболочкой.

  9. Разветвление/многопроцессорность. pdb не следует за разветвлениями. Каждому дочернему элементу нужна своя точка останова() или set_trace(). Для субагентов Hermes выполняйте отладку по одному процессу за раз.

Контрольный список проверки

Одноразовые рецепты

"Почему в этом диктовке отсутствует ключ?"

# add above the KeyError site
breakpoint()
# then in pdb:
(Pdb) pp d
(Pdb) pp list(d.keys())
(Pdb) w                # how did we get here

"Этот тест проходит изолированно, но не проходит в пакете."

scripts/run_tests.sh tests/the_test.py --pdb -p no:xdist
# But if it only fails WITH other tests:
source.venv/bin/activate
python -m pytest tests/ -x --pdb -p no:xdist
# Now it pdb-traps at the exact failing test after state accumulated.

"Мой асинхронный обработчик блокируется."

# Add at handler entry
import remote_pdb; remote_pdb.set_trace(host="127.0.0.1", port=4444)

Запустите обработчик. nc 127.0.0.1 4444, затем w, чтобы увидеть приостановленный кадр, !import asyncio; asyncio.all_tasks(), чтобы узнать, что еще ожидается.

"Вскрытие при сбое в дочернем процессе/подпроцессе Ink."

PYTHONFAULTHANDLER=1 python -m pdb -c continue path/to/entrypoint.py
# On crash, pdb lands at the frame of the exception with full locals