|
3 | 3 | import asyncio |
4 | 4 | import logging |
5 | 5 | import uuid |
| 6 | +import re |
6 | 7 | from typing import List, Optional |
7 | 8 |
|
8 | 9 | import models.messages as messages |
|
33 | 34 | # See localspec/bugs/framework/F1-tool-history-leak.md |
34 | 35 | apply_tool_history_leak_patch() |
35 | 36 |
|
| 37 | +_BARE_IMAGE_URL_RE = re.compile( |
| 38 | + r"(?<![\(\]])" |
| 39 | + r"(?<!\]\()" |
| 40 | + r"(https?://[^\s)]+?" |
| 41 | + r"(?:/api/v4/images/[^\s)]+?|[^\s)]+?\.(?:png|jpe?g|gif|webp)))" |
| 42 | + r"(?=[\s)\]]|$)", |
| 43 | + re.IGNORECASE, |
| 44 | +) |
| 45 | + |
| 46 | + |
| 47 | +def _embed_bare_image_urls(text: str) -> str: |
| 48 | + """Wrap bare image URLs in markdown image syntax so the UI renders them inline. |
| 49 | +
|
| 50 | + Skips URLs already inside ```` or ``[text](url)`` (handled by the |
| 51 | + negative lookbehinds), so it never double-wraps an existing markdown embed. |
| 52 | + """ |
| 53 | + if not text: |
| 54 | + return text |
| 55 | + return _BARE_IMAGE_URL_RE.sub(r"", text) |
| 56 | + |
36 | 57 | class OrchestrationManager: |
37 | 58 | """Manager for handling orchestration logic using agent_framework Magentic workflow.""" |
38 | 59 |
|
@@ -378,6 +399,21 @@ async def run_orchestration(self, user_id: str, input_task) -> None: |
378 | 399 | # accumulated orchestrator streaming chunks. |
379 | 400 | final_text = final_output_ref[0] or "".join(orchestrator_chunks) |
380 | 401 |
|
| 402 | + final_text = _embed_bare_image_urls(final_text) |
| 403 | + |
| 404 | + # Issue 1 diagnostic: confirm the final answer carries a renderable image |
| 405 | + # embed. has_image_markdown tracks TRUE markdown (![]) — the renderable form; |
| 406 | + # has_image_url tracks any image reference, even a bare URL. |
| 407 | + final_source = "executor" if final_output_ref[0] else "chunks" |
| 408 | + has_image_markdown = "![" in final_text |
| 409 | + has_image_url = "/api/v4/images/" in final_text |
| 410 | + self.logger.info( |
| 411 | + "[FINAL-ASSEMBLY] job=%s user=%s source=%s len=%d " |
| 412 | + "has_image_markdown=%s has_image_url=%s", |
| 413 | + job_id, user_id, final_source, len(final_text), |
| 414 | + has_image_markdown, has_image_url, |
| 415 | + ) |
| 416 | + |
381 | 417 | # Log results |
382 | 418 | self.logger.info("\nAgent responses:") |
383 | 419 | self.logger.info( |
@@ -721,15 +757,16 @@ async def _process_event_stream( |
721 | 757 | executor, cb_err, |
722 | 758 | ) |
723 | 759 |
|
724 | | - try: |
725 | | - await streaming_agent_response_callback( |
726 | | - executor, output_data, False, user_id, |
727 | | - ) |
728 | | - except Exception as cb_err: |
729 | | - self.logger.error( |
730 | | - "Error in streaming callback for %s: %s", |
731 | | - executor, cb_err, |
732 | | - ) |
| 760 | + if executor != "magentic_orchestrator": |
| 761 | + try: |
| 762 | + await streaming_agent_response_callback( |
| 763 | + executor, output_data, False, user_id, |
| 764 | + ) |
| 765 | + except Exception as cb_err: |
| 766 | + self.logger.error( |
| 767 | + "Error in streaming callback for %s: %s", |
| 768 | + executor, cb_err, |
| 769 | + ) |
733 | 770 |
|
734 | 771 | # Executor completed |
735 | 772 | elif ( |
|
0 commit comments