File size: 2,547 Bytes
2f9cbc8
 
f566386
21c7651
f566386
2f9cbc8
 
 
 
 
 
 
 
 
1373c22
f566386
dfdde45
 
758a706
dfdde45
f566386
 
758a706
47b6f03
758a706
 
 
 
 
 
 
 
21c7651
 
 
758a706
f566386
758a706
 
dfdde45
 
 
 
 
 
 
 
758a706
f566386
dfdde45
2f9cbc8
dfdde45
2f9cbc8
 
 
 
 
dfdde45
f566386
dfdde45
 
 
2f9cbc8
dfdde45
 
2f9cbc8
dfdde45
 
 
 
2f9cbc8
 
dfdde45
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
from typing import Literal, List
from pydantic import BaseModel, computed_field, Field

MessageType = Literal["prompt", "info", "agent", "retry", "error", "format", "verbose", "debug", "system"]

message_number = 0


def next_message_number():
    global message_number
    current_message_number = message_number
    message_number += 1
    return current_message_number


class Message(BaseModel):
    """A generic message, these are used to communicate between the game and the players."""

    type: MessageType
    """The type of the message."""
    content: str
    """The content of the message."""

    @property
    def conversation_role(self) -> str:
        """The message type in the format used by the LLM."""

        # Most LLMs expect the "prompt" to come from a "user" and the "response" to come from an "assistant"
        # Since the agents are the ones responding to messages, take on the llm_type of "assistant"
        # This can be counterintuitive since they can be controlled by either human or ai
        # Further, The programmatic messages from the game are always "user"

        if self.type != "system":
            return "system"
        elif self.type != "agent":
            return "user"
        else:
            return "assistant"

    @property
    def requires_response(self) -> bool:
        """Returns True if the message requires a response."""
        return self.type in ["prompt", "retry", "format"]

    def to_openai(self) -> dict[str, str]:
        """Returns the message in an OpenAI API compatible format."""
        return {"role": self.conversation_role, "content": self.content}


class AgentMessage(Message):
    """A message that has been sent to 1 or more agents."""

    agent_ids: List[str]
    """The id/ids of the agent that the message was sent by/to."""
    game_id: str
    """The id of the game the message was sent during."""
    message_number: int = Field(default_factory=next_message_number)
    """The number of the message, indicating the order in which it was sent."""

    @computed_field
    def message_id(self) -> str:
        """Returns the message id in the format used by the LLM."""
        return f"{self.game_id}-{self.message_number}"

    @classmethod
    def from_message(cls, message: Message, agent_ids: List[str], game_id: str) -> "AgentMessage":
        """Creates an AgentMessage from a Message."""
        return cls(
            type=message.type,
            content=message.content,
            agent_ids=agent_ids,
            game_id=game_id
        )