Skip to content

Commit

Permalink
✨ allow export opera events and memories
Browse files Browse the repository at this point in the history
  • Loading branch information
yanyongyu committed Apr 22, 2024
1 parent 3209274 commit 45f289d
Show file tree
Hide file tree
Showing 16 changed files with 341 additions and 168 deletions.
51 changes: 31 additions & 20 deletions operagents/agent/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from types import TracebackType
from typing import TYPE_CHECKING
from typing_extensions import Self
from dataclasses import field, dataclass

from operagents import backend
from operagents.log import logger
Expand All @@ -24,27 +23,34 @@
from operagents.backend import Backend, Message, PropMessage


@dataclass(eq=False)
class Agent:
name: str
"""The name of the agent."""
# style: str
backend: "Backend"
"""The backend to use for generating text."""

system_template: TemplateConfig = field(kw_only=True)
"""The system template to use for generating text."""
user_template: TemplateConfig = field(kw_only=True)
"""The user template to use for generating text."""

session_summary_system_template: TemplateConfig = field(kw_only=True)
"""The scene summary system template to use for generating summary."""
session_summary_user_template: TemplateConfig = field(kw_only=True)
"""The scene summary user template to use for generating summary."""

_memory: AgentMemory | None = field(default=None, init=False)
def __init__(
self,
name: str,
backend: "Backend",
*,
system_template: TemplateConfig,
user_template: TemplateConfig,
session_summary_system_template: TemplateConfig,
session_summary_user_template: TemplateConfig,
):
self.name: str = name
"""The name of the agent."""
self.backend: "Backend" = backend
"""The backend to use for generating text."""

self.system_template = system_template
"""The system template to use for generating text."""
self.user_template = user_template
"""The user template to use for generating text."""

self.session_summary_system_template = session_summary_system_template
"""The scene summary system template to use for generating summary."""
self.session_summary_user_template = session_summary_user_template
"""The scene summary user template to use for generating summary."""

self._memory: AgentMemory | None = None

def __post_init__(self):
self.logger = logger.bind(agent=self)
self.system_renderer = get_template_renderer(self.system_template)
self.user_renderer = get_template_renderer(self.user_template)
Expand All @@ -55,6 +61,11 @@ def __post_init__(self):
self.session_summary_user_template
)

def __repr__(self) -> str:
return (
f"{self.__class__.__name__}(name={self.name!r}, backend={self.backend!r})"
)

@classmethod
def from_config(cls, name: str, config: AgentConfig) -> Self:
"""Create an agent from a configuration."""
Expand Down
65 changes: 44 additions & 21 deletions operagents/agent/memory.py
Original file line number Diff line number Diff line change
@@ -1,37 +1,44 @@
from uuid import UUID
from dataclasses import dataclass
from typing_extensions import TypeVar
from typing import TYPE_CHECKING, Any, Generic, Literal, Annotated, TypeAlias

from pydantic import Field, BaseModel
from pydantic import Field, BaseModel, ConfigDict, field_serializer

from operagents.prop import Prop
from operagents.scene import Scene
from operagents.character import Character
from operagents.exception import SceneFinished
from operagents.utils import (
any_serializer,
prop_serializer,
scene_serializer,
character_serializer,
)

if TYPE_CHECKING:
from operagents.prop import Prop
from operagents.scene import Scene
from operagents.timeline import Timeline
from operagents.character import Character


P = TypeVar("P", bound=BaseModel, default=BaseModel)


@dataclass(eq=False, kw_only=True)
class AgentEventObserve:
class AgentEventObserve(BaseModel):
"""Other agent acts observed by an agent.
a.k.a. Agent short term memory.
"""

model_config = ConfigDict(arbitrary_types_allowed=True)

type_: Literal["observe"] = "observe"
session_id: UUID
scene: "Scene"
scene: Scene
content: str

_serialize_scene = field_serializer("scene")(scene_serializer)


@dataclass(eq=False, kw_only=True)
class AgentEventSessionSummary:
class AgentEventSessionSummary(BaseModel):
"""Summary of observed events for one whole scene session.
a.k.a. Agent long term memory.
Expand All @@ -41,37 +48,53 @@ class AgentEventSessionSummary:
No more `observe` or `act` events will be added after the summary.
"""

model_config = ConfigDict(arbitrary_types_allowed=True)

type_: Literal["session_summary"] = "session_summary"
session_id: UUID
scene: "Scene"
scene: Scene
content: str

_serialize_scene = field_serializer("scene")(scene_serializer)


@dataclass(eq=False, kw_only=True)
class AgentEventAct:
class AgentEventAct(BaseModel):
"""Agent self acts."""

model_config = ConfigDict(arbitrary_types_allowed=True)

type_: Literal["act"] = "act"
session_id: UUID
scene: "Scene"
character: "Character"
scene: Scene
character: Character
content: str

_serialize_scene = field_serializer("scene")(scene_serializer)
_serialize_character = field_serializer("character")(character_serializer)


@dataclass(eq=False, kw_only=True)
class AgentEventUseProp(Generic[P]):
class AgentEventUseProp(BaseModel, Generic[P]):
"""Agent use prop."""

model_config = ConfigDict(arbitrary_types_allowed=True)

type_: Literal["use_prop"] = "use_prop"
session_id: UUID
scene: "Scene"
character: "Character"
scene: Scene
character: Character
usage_id: str
prop: "Prop[P]"
prop: Prop[P]
prop_raw_params: str
prop_params: P | None
prop_params: BaseModel | None
prop_result: Any

_serialize_scene = field_serializer("scene")(scene_serializer)
_serialize_character = field_serializer("character")(character_serializer)
_serialize_prop = field_serializer("prop")(prop_serializer)
_serialize_prop_result = field_serializer("prop_result", mode="wrap")(
any_serializer
)


AgentEvent: TypeAlias = Annotated[
AgentEventObserve | AgentEventSessionSummary | AgentEventAct | AgentEventUseProp,
Expand Down
32 changes: 22 additions & 10 deletions operagents/character/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from typing import TYPE_CHECKING
from typing_extensions import Self
from dataclasses import field, dataclass

from operagents import prop
from operagents.config import CharacterConfig
Expand All @@ -12,16 +11,29 @@
from operagents.timeline.event import TimelineEventSessionAct


@dataclass(eq=False)
class Character:
name: str
"""The name of the character."""
description: str | None
"""The description of the character."""
agent_name: str
"""The name of the agent that acts as the character."""
props: list["Prop"] = field(default_factory=list, kw_only=True)
"""The props the character has."""
def __init__(
self,
name: str,
description: str | None,
agent_name: str,
props: list["Prop"] | None = None,
):
self.name: str = name
"""The name of the character."""
self.description: str | None = description
"""The description of the character."""
self.agent_name: str = agent_name
"""The name of the agent that acts as the character."""
self.props: list["Prop"] = props or []
"""The props the character has."""

def __repr__(self) -> str:
return (
f"{self.__class__.__name__}("
f"name={self.name!r}, agent_name={self.agent_name!r}, props={self.props!r}"
")"
)

@classmethod
def from_config(cls, name: str, config: CharacterConfig) -> Self:
Expand Down
4 changes: 2 additions & 2 deletions operagents/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import sys
import json
import asyncio
import argparse
from pathlib import Path
Expand All @@ -10,6 +9,7 @@

from operagents.opera import Opera
from operagents.version import VERSION
from operagents.utils import save_opera_state
from operagents.config import OperagentsConfig
from operagents.log import logger, setup_logging

Expand Down Expand Up @@ -46,7 +46,7 @@ async def handle_run(
result = await opera.run()

if export is not None:
Path(export).write_text(json.dumps(result, indent=2), encoding="utf-8")
save_opera_state(result, Path(export))


run = subcommands.add_parser(
Expand Down
31 changes: 23 additions & 8 deletions operagents/director/model.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from typing import TYPE_CHECKING
from dataclasses import field, dataclass
from typing_extensions import Self, override

from operagents import backend
Expand All @@ -16,21 +15,37 @@
from operagents.backend import Backend, Message


@dataclass
class ModelDirector(Director):
type_ = "model"

backend: "Backend" = field()
def __init__(
self,
backend: "Backend",
*,
system_template: TemplateConfig,
user_template: TemplateConfig,
allowed_scenes: list[str] | None = None,
finish_flag: str | None = None,
):
self.backend: "Backend" = backend

system_template: TemplateConfig = field(kw_only=True)
user_template: TemplateConfig = field(kw_only=True)
allowed_scenes: list[str] | None = field(default=None, kw_only=True)
finish_flag: str | None = field(default=None, kw_only=True)
self.system_template = system_template
self.user_template = user_template

self.allowed_scenes: list[str] | None = allowed_scenes
self.finish_flag: str | None = finish_flag

def __post_init__(self):
self.system_renderer = get_template_renderer(self.system_template)
self.user_renderer = get_template_renderer(self.user_template)

def __repr__(self) -> str:
return (
f"{self.__class__.__name__}("
f"backend={self.backend}, allowed_scenes={self.allowed_scenes!r}, "
f"finish_flag={self.finish_flag!r}"
")"
)

@classmethod
@override
def from_config( # pyright: ignore[reportIncompatibleMethodOverride]
Expand Down
37 changes: 26 additions & 11 deletions operagents/flow/model.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
from typing import TYPE_CHECKING
from dataclasses import field, dataclass
from typing_extensions import Self, override

from operagents import backend
from operagents.log import logger
from operagents.timeline import Timeline
from operagents.character import Character
from operagents.exception import FlowError
from operagents.utils import get_template_renderer
from operagents.config import TemplateConfig, ModelFlowConfig
Expand All @@ -18,22 +15,40 @@
from operagents.backend import Backend, Message


@dataclass
class ModelFlow(Flow):
type_ = "model"

backend: "Backend" = field()
def __init__(
self,
backend: "Backend",
*,
system_template: TemplateConfig,
user_template: TemplateConfig,
allowed_characters: list[str] | None = None,
begin_character: str | None = None,
fallback_character: str | None = None,
):
self.backend: "Backend" = backend

system_template: TemplateConfig = field(kw_only=True)
user_template: TemplateConfig = field(kw_only=True)
allowed_characters: list[str] | None = field(default=None, kw_only=True)
begin_character: str | None = field(default=None, kw_only=True)
fallback_character: str | None = field(default=None, kw_only=True)
self.system_template = system_template
self.user_template = user_template

self.allowed_characters: list[str] | None = allowed_characters
self.begin_character: str | None = begin_character
self.fallback_character: str | None = fallback_character

def __post_init__(self):
self.system_renderer = get_template_renderer(self.system_template)
self.user_renderer = get_template_renderer(self.user_template)

def __repr__(self) -> str:
return (
f"{self.__class__.__name__}("
f"backend={self.backend}, allowed_characters={self.allowed_characters}, "
f"begin_character={self.begin_character!r}, "
f"fallback_character={self.fallback_character!r}"
")"
)

@classmethod
@override
def from_config( # pyright: ignore[reportIncompatibleMethodOverride]
Expand Down
8 changes: 5 additions & 3 deletions operagents/flow/order.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from typing import TYPE_CHECKING
from dataclasses import dataclass
from typing_extensions import Self, override

from operagents.config import OrderFlowConfig
Expand All @@ -11,11 +10,14 @@
from operagents.character import Character


@dataclass
class OrderFlow(Flow):
type_ = "order"

order: list[str] | None = None
def __init__(self, order: list[str] | None = None):
self.order: list[str] | None = order

def __repr__(self) -> str:
return f"{self.__class__.__name__}(order={self.order})"

@classmethod
@override
Expand Down
Loading

0 comments on commit 45f289d

Please sign in to comment.