|
33 | 33 | VISION_MODELS, |
34 | 34 | WEB2API_URL, |
35 | 35 | ) |
| 36 | +from formatting import md_to_telegram_html |
36 | 37 | from pointing import draw_points_on_image, has_points, parse_points, strip_points |
37 | 38 |
|
38 | 39 | logging.basicConfig( |
|
53 | 54 | # Typing indicator |
54 | 55 | # --------------------------------------------------------------------------- |
55 | 56 |
|
| 57 | +async def send_formatted(msg, text: str) -> None: |
| 58 | + """Send a message with markdown→HTML conversion, falling back to plain text.""" |
| 59 | + formatted = md_to_telegram_html(text) |
| 60 | + chunks = [formatted[i:i + 4096] for i in range(0, len(formatted), 4096)] |
| 61 | + for chunk in chunks: |
| 62 | + try: |
| 63 | + await msg.reply_text(chunk, parse_mode=ParseMode.HTML, disable_web_page_preview=True) |
| 64 | + except Exception: |
| 65 | + # If HTML parsing fails, send as plain text |
| 66 | + plain = text[chunks.index(chunk) * 4096:(chunks.index(chunk) + 1) * 4096] if len(chunks) > 1 else text |
| 67 | + await msg.reply_text(plain[:4096]) |
| 68 | + |
| 69 | + |
56 | 70 | @asynccontextmanager |
57 | 71 | async def keep_typing(chat): |
58 | 72 | """Send typing indicator every 4 seconds until the block exits.""" |
@@ -153,6 +167,15 @@ async def query_model( |
153 | 167 |
|
154 | 168 | fields = items[0].get("fields", {}) |
155 | 169 | answer = fields.get("response") or fields.get("answer") or fields.get("text") or str(fields) |
| 170 | + |
| 171 | + # OLMo sometimes generates fake follow-up conversations — truncate at first |
| 172 | + # occurrence of a role marker that indicates hallucinated multi-turn output. |
| 173 | + for marker in ("\nuser\n", "\nassistant\n", "\n<function_calls>"): |
| 174 | + idx = answer.find(marker) |
| 175 | + if idx > 0: |
| 176 | + answer = answer[:idx].rstrip() |
| 177 | + break |
| 178 | + |
156 | 179 | return answer |
157 | 180 |
|
158 | 181 |
|
@@ -362,11 +385,7 @@ async def handle_media(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No |
362 | 385 | pointed_path = None |
363 | 386 |
|
364 | 387 | if not pointed_path: |
365 | | - if len(answer) <= 4096: |
366 | | - await msg.reply_text(answer) |
367 | | - else: |
368 | | - for i in range(0, len(answer), 4096): |
369 | | - await msg.reply_text(answer[i:i + 4096]) |
| 388 | + await send_formatted(msg, answer) |
370 | 389 |
|
371 | 390 | except httpx.ReadTimeout: |
372 | 391 | await msg.reply_text("⏳ Request timed out. Vision analysis can be slow — try again.") |
@@ -408,11 +427,7 @@ async def cmd_search(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None |
408 | 427 | if len(user_history[uid]) > MAX_HISTORY * 2: |
409 | 428 | user_history[uid] = user_history[uid][-(MAX_HISTORY * 2):] |
410 | 429 |
|
411 | | - if len(answer) <= 4096: |
412 | | - await update.message.reply_text(answer) |
413 | | - else: |
414 | | - for i in range(0, len(answer), 4096): |
415 | | - await update.message.reply_text(answer[i:i + 4096]) |
| 430 | + await send_formatted(update.message, answer) |
416 | 431 |
|
417 | 432 | except httpx.HTTPStatusError as e: |
418 | 433 | logger.error("HTTP error: %s", e) |
@@ -449,14 +464,7 @@ async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> |
449 | 464 | if len(user_history[uid]) > MAX_HISTORY * 2: |
450 | 465 | user_history[uid] = user_history[uid][-(MAX_HISTORY * 2):] |
451 | 466 |
|
452 | | - # Telegram has a 4096 char limit |
453 | | - if len(answer) <= 4096: |
454 | | - await update.message.reply_text(answer) |
455 | | - else: |
456 | | - # Split into chunks |
457 | | - for i in range(0, len(answer), 4096): |
458 | | - chunk = answer[i:i + 4096] |
459 | | - await update.message.reply_text(chunk) |
| 467 | + await send_formatted(update.message, answer) |
460 | 468 |
|
461 | 469 | except httpx.HTTPStatusError as e: |
462 | 470 | logger.error("HTTP error: %s", e) |
|
0 commit comments