virtual-insanity
← 리포트 목록

bond_daily_report Telegram message_id 로깅 보강

2026-04-15 bond [bond, telegram, observability, message-id, phase17-followup]

결론

bond_daily_report.py의 Telegram 그룹/PDF 발송 경로에 message_id 관측 로그를 추가했다.

  • 신규 TSV: /Users/ron/.hermes/logs/bond_daily_last_telegram.tsv
  • 그룹 텍스트 폴백: send_group_chunked_result()로 각 chunk별 message_id/status/error 확보
  • PDF 발송: send_document_result()sendDocument API 응답의 message_id 확보
  • 기존 bool API(send_group_chunked, send_document)는 유지해서 다른 호출자 호환성 보존

단, 1회 실제 --notify 실행은 LLM 단계에서 네트워크/DNS 실패로 중단되어 bond 본문 그룹 발송까지 도달하지 못했다. 이 실패도 TSV에 남도록 보강했다.

1단계 — 소스 분석

대상 파일:

  • /Users/ron/.hermes/workspace/scripts/pipeline/bond_daily_report.py
  • /Users/ron/.hermes/workspace/scripts/shared/telegram.py

확인한 기존 발송 경로:

  1. bond_daily_report.py --notify
  2. LLM 생성 성공
  3. PDF 생성 성공 시:
  4. send_document(GROUP_CHAT_ID, pdf, topic_id=DAILY_REPORT_TOPIC_ID)
  5. send_document(492860021, pdf)
  6. PDF 생성 실패 시:
  7. send_group_chunked(report_md, topic_id=DAILY_REPORT_TOPIC_ID, parse_mode="")

기존 문제:

  • shared.telegram._send_raw()는 이미 Telegram API 응답에서 message_id를 읽고 있었음.
  • 하지만 send_group_chunked()가 bool만 반환해서 각 chunk의 message_id가 호출부로 전달되지 않았음.
  • send_document()sendDocument 응답 전체를 버리고 bool만 반환했음.
  • bond_daily_report.py는 성공/실패 문자열만 로그에 남기고 message_id를 저장하지 않았음.

2단계 — 수정 diff 요약

/Users/ron/.hermes/workspace/scripts/shared/telegram.py

+ def send_group_chunked_result(... ) -> list[dict]:
+     # dedupe면 status=skipped_dedupe 반환
+     # 각 chunk마다 _send_raw() 결과의 message_id/status/error 반환
+
  def send_group_chunked(... ) -> bool:
-     # 직접 _send() 반복, bool만 반환
+     return all(r["ok"] for r in send_group_chunked_result(...))

+ def send_document_result(... ) -> dict:
+     # sendDocument API 응답에서 result.message_id 추출
+     # 실패 시 {ok: False, message_id: None, error: ...}
+
  def send_document(... ) -> bool:
-     # API 응답에서 ok만 반환
+     return bool(send_document_result(...)["ok"])

핵심 라인:

  • send_group_chunked_result: telegram.py:592-630
  • send_group_chunked 호환 래퍼: telegram.py:633-638
  • send_document_result: telegram.py:838-881
  • send_document 호환 래퍼: telegram.py:884-889

/Users/ron/.hermes/workspace/scripts/pipeline/bond_daily_report.py

+ TELEGRAM_TSV = Path.home() / ".hermes" / "logs" / "bond_daily_last_telegram.tsv"
+
+ def _append_telegram_tsv(chat_id, topic_id, message_id, status, error=None):
+     # timestamp/chat_id/topic_id/message_id/status/error append
+
- send_document(...)
+ group_result = send_document_result(...)
+ _append_telegram_tsv(GROUP_CHAT_ID, DAILY_REPORT_TOPIC_ID,
+                      group_result["message_id"], status, error)
+
- send_group_chunked(...)
+ chunk_results = send_group_chunked_result(...)
+ for r in chunk_results:
+     _append_telegram_tsv(r["chat_id"], r["topic_id"],
+                          r["message_id"], r["status"], r["error"])
+
+ # LLM 실패/노트 없음처럼 본문 발송 전 실패한 notify도 TSV에 실패 상태 기록

핵심 라인:

  • TSV 상수/append 함수: bond_daily_report.py:37-70
  • 노트 없음 알림 결과 기록: bond_daily_report.py:823-832
  • LLM 실패 알림 결과 기록: bond_daily_report.py:859-868
  • PDF 그룹/DM 결과 기록: bond_daily_report.py:914-951
  • 텍스트 폴백 chunk 결과 기록: bond_daily_report.py:952-966

3단계 — 검증

구문 검사

python3 -m py_compile \
  /Users/ron/.hermes/workspace/scripts/shared/telegram.py \
  /Users/ron/.hermes/workspace/scripts/pipeline/bond_daily_report.py

결과: 통과.

result API 단위 검증

PYTHONPATH=/Users/ron/.hermes/workspace/scripts python3 - <<'PY'
from shared import telegram
calls=[]
def fake_send_raw(chat_id, text, parse_mode='HTML', topic_id=None, **kwargs):
    calls.append((chat_id, text, topic_id))
    return {'ok': True, 'message_id': 12345 + len(calls), 'error': None}
telegram._send_raw = fake_send_raw
telegram._is_duplicate = lambda chat_id, text: False
res = telegram.send_group_chunked_result('hello', topic_id=39439, parse_mode='')
print(res)
assert res[0]['message_id'] == 12346
assert telegram.send_group_chunked('hello2', topic_id=39439, parse_mode='') is True
print('OK')
PY

출력:

[{'ok': True, 'chat_id': -1003076685086, 'topic_id': 39439, 'message_id': 12346, 'status': 'success', 'error': None, 'chunk_index': 1, 'chunk_count': 1}]
OK

TSV append 형식 검증

PYTHONPATH=/Users/ron/.hermes/workspace/scripts:/Users/ron/.hermes/workspace/scripts/pipeline python3 - <<'PY'
from pathlib import Path
import bond_daily_report as b
p = Path('/tmp/bond_daily_last_telegram_unit.tsv')
if p.exists():
    p.unlink()
b.TELEGRAM_TSV = p
b._append_telegram_tsv(-1003076685086, 39439, 987654321, 'success', None)
b._append_telegram_tsv(-1003076685086, 39439, None, 'skipped_dedupe', None)
print(p.read_text())
PY

출력:

timestamp   chat_id topic_id    message_id  status  error
2026-04-15T19:31:40+09:00   -1003076685086  39439   987654321   success 
2026-04-15T19:31:40+09:00   -1003076685086  39439       skipped_dedupe  

4단계 — --notify 1회 실행 결과

실행 명령:

cd /Users/ron/.hermes/workspace/scripts/pipeline
python3 bond_daily_report.py --notify

출력 요약:

[2026-04-15 19:30:38] bond_daily_report 시작
[2026-04-15 19:30:38] 노트: 260414_황대진_전달-일일-414-화-마감-및-채권시장-정리-DS증권-황대진.md
[2026-04-15 19:30:38] 파싱 완료 — 타입: 낙찰정리, 날짜: 2026-04-14, 낙찰: 0건
[2026-04-15 19:31:28] [ERROR] LLM 실패: github-copilot/gpt-5-mini: <urlopen error [Errno 8] nodename nor servname provided, or not known>

실제 본문/PDF 발송 전 LLM 단계에서 실패했다. 따라서 이번 실행에서는 그룹 토픽 message_id가 생성되지 않았다.

그래도 실패 상태는 TSV에 기록됨:

timestamp   chat_id topic_id    message_id  status  error
2026-04-15T19:31:28+09:00   -1003522748967          llm_failed_alert_ok_or_dedupe   github-copilot/gpt-5-mini: <urlopen error [Errno 8] nodename nor servname provided, or not known>

TSV 경로:

/Users/ron/.hermes/logs/bond_daily_last_telegram.tsv

5단계 — 남은 리스크

  1. 현재 세션에서는 LLM/Gateway/Telegram 네트워크가 불안정해 실제 그룹 발송 message_id를 확보하지 못했다.
  2. 16:00 이전 실행 기록에는 텍스트 폴백 성공이 있었지만, 그 시점은 패치 전이라 message_id가 남아있지 않다.
  3. 다음번 LLM이 성공하고 PDF 또는 텍스트 폴백 발송까지 도달하면 TSV에 실제 message_id가 남는다.
  4. send_group_chunked_result()는 dedupe 발생 시 status=skipped_dedupe를 반환하게 했으므로, 중복 skip도 관측 가능하다.

변경 파일 목록

  • /Users/ron/.hermes/workspace/scripts/shared/telegram.py
  • /Users/ron/.hermes/workspace/scripts/pipeline/bond_daily_report.py
  • /Users/ron/knowledge-agent/400-reports/260415_bond_msgid_logging.md

자체평가

  • 정확성: 4.4/5 — message_id 전달/저장 경로는 구현. 실제 message_id는 LLM 선행 실패로 미확보.
  • 완성도: 4.5/5 — PDF, 텍스트 chunk, dedupe, 실패 상태를 모두 TSV에 기록.
  • 검증: 4.0/5 — 구문검사/단위검증/실제 --notify 실행 완료. 운영 message_id 검증은 네트워크/LLM 실패로 보류.
  • 최소 변경: 4.7/5 — 기존 bool API 유지, bond 비즈니스 로직 변경 없음.
  • 종합: 4.4/5

재개 후 실제 도달 검증 — 2026-04-15 19:40~19:48 KST

원인 재진단

재개 후 네트워크가 열리자 상태가 바뀌었다.

확인 결과:

Gateway 18789: LISTEN, /health = {"status":"ok","platform":"hermes-agent"}
Ollama 11434: LISTEN, qwen2.5:3b / qwen3.5:4b 사용 가능
Telegram getMe: ok=True, username=RONforMAC_Bot
OpenRouter: DNS/API 접근 가능하지만 OPENROUTER API key 없음
openai-codex/gpt-5.4: Hermes gateway 500, Codex token refresh 401
github-copilot/gpt-5-mini: empty_response 또는 기존 sandbox DNS 실패

직전 실패의 직접 원인:

  • 이전 sandbox 상태에서 로컬/외부 HTTP가 막혀 openai-codex, Ollama 모두 Operation not permitted로 실패했다.
  • OpenRouter는 API key가 없어 스킵됐다.
  • 마지막 Copilot까지 떨어졌고 DNS/empty response로 실패했다.

회피 방법:

  • 네트워크가 열린 상태에서 재실행.
  • 불필요한 장시간 재시도를 줄이기 위해 실행 시 OPENCLAW_MAX_RETRY_PER_MODEL=1만 적용.
  • 실제 LLM 생성은 fallback인 ollama/qwen3.5:4b가 수행했다.
  • bond 비즈니스 로직 추가 변경은 하지 않았다.

실제 실행 명령

cd /Users/ron/.hermes/workspace/scripts/pipeline
OPENCLAW_MAX_RETRY_PER_MODEL=1 OPENCLAW_PER_MODEL_TIMEOUT_SEC=60 \
  python3 bond_daily_report.py --notify

실행 로그 증거

START=2026-04-15 19:41:30 KST
[2026-04-15 19:41:30] bond_daily_report 시작
[2026-04-15 19:41:30] 노트: 260414_황대진_전달-일일-414-화-마감-및-채권시장-정리-DS증권-황대진.md
[2026-04-15 19:41:30] 파싱 완료 — 타입: 낙찰정리, 날짜: 2026-04-14, 낙찰: 0건
[2026-04-15 19:44:42] LLM 완료 (모델: ollama/qwen3.5:4b, 5794자)
[2026-04-15 19:47:54] [WARN] NanumGothic 폰트 없음 — PDF 생성 스킵
[2026-04-15 19:47:54] 메모리 저장 완료. 볼트: None
[2026-04-15 19:47:58] 텔레그램 그룹 텍스트 폴백: 성공 message_ids=[51803, 51804] statuses=['success', 'success']

PDF는 기존 환경과 동일하게 NanumGothic 폰트 부재로 스킵됐고, 텍스트 폴백으로 그룹 토픽에 2 chunk 발송됐다.

TSV 실제 message_id 기록

경로:

/Users/ron/.hermes/logs/bond_daily_last_telegram.tsv

기록:

timestamp   chat_id topic_id    message_id  status  error
2026-04-15T19:31:28+09:00   -1003522748967          llm_failed_alert_ok_or_dedupe   github-copilot/gpt-5-mini: <urlopen error [Errno 8] nodename nor servname provided, or not known>
2026-04-15T19:47:58+09:00   -1003076685086  39439   51803   success 
2026-04-15T19:47:58+09:00   -1003076685086  39439   51804   success 

도달 증거:

  • Telegram Bot API sendMessage 응답에서 message_id=51803, message_id=51804를 수신했다.
  • 대상은 그룹 chat_id=-1003076685086, 토픽 topic_id=39439.
  • TSV와 bond 로그 양쪽에 동일한 성공 기록이 남았다.

최종 상태 갱신

  • message_id 로깅 기능: 동작 확인 완료.
  • 실제 그룹 발송: 성공.
  • 실제 message_id TSV 기록: 성공.
  • LLM 선행 실패: sandbox 네트워크 해제 후 Ollama fallback으로 회피 확인.

자체평가 갱신

  • 정확성: 4.9/5 — 실제 Telegram 그룹 발송 및 message_id 2건 확보.
  • 완성도: 4.8/5 — 성공/실패/dedupe/LLM 실패 모두 TSV 관측 가능.
  • 검증: 4.9/5 — 실제 --notify 운영 실행, LLM 완료, Telegram API message_id 기록 확인.
  • 최소 변경: 4.7/5 — 기존 bool API 유지, bond 비즈니스 로직 변경 없음.
  • 종합: 4.8/5