File size: 3,127 Bytes
cc15101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
from agent.core.parser import TOOL_PATTERN
from agent.tools import dispatch

AGENT_SYSTEM = (
    "You are The Abyss, an AI assistant with acess to tools.\n"
    "WHen you need a tool, output EXACTLY this format on its own line:\n"
    "  [SEARCH: your query]\n"
    "  [CALC: expression]\n"
    "  [CODE: python code]\n"
    "  [FILE: read name | write name content | list]\n"
    "After the observation is show, continue your answer naturally.\n"
    "Never fabricate tool results."
)   

MAX_STEPS = 6


def run_agent(messages: list, generate_fn, **gen_kwargs):
    """

    Runs the ReAct loop. Yields (kind, value) tuples:

      ("token", str)       – stream to user (now emitted incrementally)

      ("tool_call", str)   – tool being called

      ("observation", str) – tool result

      ("done", str)        – full assembled response (for DB storage)

    """
    if any(m.get("role") == "system" for m in messages):
        msgs = list(messages)
        msgs[0] = {
            "role": "system",
            "content": AGENT_SYSTEM + "\n\n" + msgs[0]["content"],
        }
    else:
        msgs = [{"role": "system", "content": AGENT_SYSTEM}] + list(messages)

    assembled: list[str] = []

    for _step in range(MAX_STEPS):
        buffer = ""
        flushed = 0          # chars of buffer already streamed as tokens
        used_tool = False

        for token in generate_fn(msgs, **gen_kwargs):
            buffer += token
            match = TOOL_PATTERN.search(buffer)
            if match:
                before = buffer[flushed:match.start()]
                if before:
                    yield ("token", before)
                    assembled.append(before)

                tool_name = match.group(1)
                tool_args = match.group(2)
                call_str = f"[{tool_name}: {tool_args}]"
                yield ("tool_call", call_str)

                result = dispatch(tool_name, tool_args)
                yield ("observation", result)

                msgs = msgs + [
                    {"role": "assistant", "content": buffer[:match.start()] + call_str},
                    {
                        "role": "user",
                        "content": f"[OBSERVATION: {result}]\nContinue your response.",
                    },
                ]
                used_tool = True
                break

            # No tool yet: stream everything that can't start a "[TOOL ...]" call.
            # A call begins with '[', so hold back from the last '[' onward.
            pending = buffer.rfind("[", flushed)
            safe_upto = pending if pending != -1 else len(buffer)
            if safe_upto > flushed:
                chunk = buffer[flushed:safe_upto]
                yield ("token", chunk)
                assembled.append(chunk)
                flushed = safe_upto

        if not used_tool:
            rest = buffer[flushed:]
            if rest:
                yield ("token", rest)
                assembled.append(rest)
            break

    yield ("done", "".join(assembled))