|
| 1 | +"""WritingBotRunner — the only module that imports microbots.bot.WritingBot. |
| 2 | +
|
| 3 | +This isolation keeps the core auto_memory framework decoupled from the |
| 4 | +concrete bot implementation. |
| 5 | +""" |
| 6 | + |
| 7 | +from __future__ import annotations |
| 8 | + |
| 9 | +from microbots.auto_memory.data_models import IterationStatus |
| 10 | +from microbots.auto_memory.runners.base import AgentResult, AgentRunner, IterationContext |
| 11 | +from microbots.bot.WritingBot import WritingBot |
| 12 | +from microbots.MicroBot import BotRunResult |
| 13 | +from microbots.tools.tool_definitions.memory_tool import MemoryTool |
| 14 | + |
| 15 | +_TIMEOUT_ERROR_PREFIX = "Timeout of " |
| 16 | + |
| 17 | + |
| 18 | +class WritingBotRunner: |
| 19 | + """Runs a :class:`~microbots.bot.WritingBot.WritingBot` for one iteration. |
| 20 | +
|
| 21 | + Satisfies the :class:`AgentRunner` protocol structurally. |
| 22 | +
|
| 23 | + Parameters |
| 24 | + ---------- |
| 25 | + model : str |
| 26 | + Model identifier forwarded to ``WritingBot`` (e.g. |
| 27 | + ``"azure/gpt-4o"``). |
| 28 | + max_iterations : int, optional |
| 29 | + Maximum number of agent steps per run; forwarded to |
| 30 | + ``WritingBot.run()``. Defaults to 20. |
| 31 | + """ |
| 32 | + |
| 33 | + def __init__(self, model: str, max_iterations: int = 20) -> None: |
| 34 | + """Store the model identifier and iteration cap used for every bot invocation. |
| 35 | +
|
| 36 | + Parameters |
| 37 | + ---------- |
| 38 | + model : str |
| 39 | + Model identifier forwarded to ``WritingBot`` (e.g. ``"azure/gpt-4o"``). |
| 40 | + max_iterations : int, optional |
| 41 | + Maximum number of agent steps per run. Defaults to 20. |
| 42 | + """ |
| 43 | + self._model = model |
| 44 | + self._max_iterations = max_iterations |
| 45 | + |
| 46 | + # ------------------------------------------------------------------ |
| 47 | + # AgentRunner implementation |
| 48 | + |
| 49 | + def run(self, ctx: IterationContext, timeout_s: int) -> AgentResult: |
| 50 | + """Create a fresh :class:`WritingBot`, run *ctx.task*, and return a |
| 51 | + normalised :class:`AgentResult`. |
| 52 | +
|
| 53 | + The bot is configured with: |
| 54 | +
|
| 55 | + * ``folder_to_mount=ctx.memory_dir`` so the container can read and |
| 56 | + write the iteration's memory directory. |
| 57 | + * ``MemoryTool(memory_dir=ctx.memory_dir)`` so the agent can call the |
| 58 | + ``memory`` tool to persist structured notes. |
| 59 | +
|
| 60 | + Parameters |
| 61 | + ---------- |
| 62 | + ctx : IterationContext |
| 63 | + Iteration context carrying the task and memory directory. |
| 64 | + timeout_s : int |
| 65 | + Per-iteration timeout forwarded to ``WritingBot.run()``. |
| 66 | +
|
| 67 | + Returns |
| 68 | + ------- |
| 69 | + AgentResult |
| 70 | + * ``PASSED`` when the bot completes the task successfully. |
| 71 | + * ``TIMEOUT`` when the bot's wall-clock limit is exceeded. |
| 72 | + * ``ERROR`` for all other failures (max-iterations reached, etc.). |
| 73 | + """ |
| 74 | + bot = WritingBot( |
| 75 | + model=self._model, |
| 76 | + folder_to_mount=ctx.memory_dir, |
| 77 | + additional_tools=[MemoryTool(memory_dir=ctx.memory_dir)], |
| 78 | + ) |
| 79 | + |
| 80 | + bot_result = bot.run( |
| 81 | + ctx.task, |
| 82 | + max_iterations=self._max_iterations, |
| 83 | + timeout_in_seconds=timeout_s, |
| 84 | + ) |
| 85 | + |
| 86 | + return self._map_result(bot_result) |
| 87 | + |
| 88 | + # ------------------------------------------------------------------ |
| 89 | + # Internal helpers |
| 90 | + |
| 91 | + @staticmethod |
| 92 | + def _map_result(bot_result: BotRunResult) -> AgentResult: |
| 93 | + """Convert a :class:`~microbots.MicroBot.BotRunResult` to an |
| 94 | + :class:`AgentResult`. |
| 95 | +
|
| 96 | + Parameters |
| 97 | + ---------- |
| 98 | + bot_result : BotRunResult |
| 99 | + Raw result returned by ``WritingBot.run()``. |
| 100 | +
|
| 101 | + Returns |
| 102 | + ------- |
| 103 | + AgentResult |
| 104 | + Normalised result with ``PASSED``, ``TIMEOUT``, or ``ERROR`` status. |
| 105 | +
|
| 106 | + Notes |
| 107 | + ----- |
| 108 | + Mapping rules: |
| 109 | +
|
| 110 | + * ``status=True`` → ``PASSED``; ``output`` carries the bot's final answer. |
| 111 | + * ``status=False``, error contains ``"Timeout"`` → ``TIMEOUT``. |
| 112 | + * ``status=False``, all other errors → ``ERROR``. |
| 113 | + """ |
| 114 | + if bot_result.status: |
| 115 | + return AgentResult( |
| 116 | + status=IterationStatus.PASSED, |
| 117 | + output=bot_result.result, |
| 118 | + error=None, |
| 119 | + ) |
| 120 | + |
| 121 | + error_msg = bot_result.error or "Unknown error" |
| 122 | + |
| 123 | + if error_msg.startswith(_TIMEOUT_ERROR_PREFIX): |
| 124 | + return AgentResult( |
| 125 | + status=IterationStatus.TIMEOUT, |
| 126 | + output=None, |
| 127 | + error=error_msg, |
| 128 | + ) |
| 129 | + |
| 130 | + return AgentResult( |
| 131 | + status=IterationStatus.ERROR, |
| 132 | + output=None, |
| 133 | + error=error_msg, |
| 134 | + ) |
0 commit comments