mirror of
https://github.com/vegu-ai/talemate.git
synced 2025-12-16 11:47:48 +01:00
* some prompt cleanup * prompt tweaks * prompt tweaks * prompt tweaks * set 0.31.0 * relock * rag queries add brief analysis * brief analysis before building rag questions * rag improvements * prompt tweaks * address circular import issues * set 0.30.1 * docs * numpy to 2 * docs * prompt tweaks * prompt tweak * some template cleanup * prompt viewer increase height * fix codemirror highlighting not working * adjust details height * update default * change to log debug * allow response to scale to max height * template cleanup * prompt tweaks * first progress for installing modules to scene * package install logic * package install polish * package install polish * package install polish and fixes * refactor initial world state update and expose setting for it * add `num` property to ModuleProperty to control order of widgets * dynamic storyline package info * fix issue where deactivating player character would cause inconsistencies in the creative tools menui * cruft * add openrouter support * ollama support * refactor how model choices are loaded, so that can be done per client instance as opposed to just per client type * set num_ctx * remove debug messages * ollama tweaks * toggle for whether or not default character gets added to blank talemate scenes * narrator prompt tweaks and template cleanup * cleanup * prompt tweaks and template cleanup * prompt tweaks * fix instructor embeddings * add additional error handling to prevent broken world state templates from breaking the world editor side menu * fix openrouter breaking startup if not configured * remove debug message * promp tweaks * fix example dialogue generation no longer working * prompt tweaks and better showing of dialogue examples in conversation instructions * prompt tweak * add initial startup message * prompt tweaks * fix asset error * move complex acting instructions into the task block * fix content input socket on DynamicInstructions node * log.error with traceback over log.exception since that has a tendency to explode and hang everything * fix usse with number nodes where they would try to execute even if the incoming wire wasnt active * fix issue with editor revision events missing template_vars * DynamicInstruction node should only run if both header and content can resolve * removed remaining references to 90s adventure game writing style * prompt tweaks * support embeddings via client apis (koboldcpp) * fix label on client-api embeddings * fix issue where adding / removing an embedding preset would not be reflected immediately in the memory agent config * remove debug output * prompt tweaks * prompt tweaks * autocomplete passes message object * validate group names to be filename valid * embedded winsows env installs and up to poetry2 * version config * get-pip * relock * pin runpod * no longer needed * remove rapidfuzz dependency * nodejs directly into embedded_node without a versioned middleman dir - also remove defunct local-tts install script * fix update script * update script error handling * update.bat error handling * adjust wording * support loading jinja2 templates node modules in templates/modules * update google model list * client t/s and business indicator - also switch all clients to async streaming * formatting * support coercion for anthropic / google switch to the new google genai sdk upgrade websockets * more coercion fixes * gracefully handle keyboard interrupt * EmitSystemMessage node * allow visual prompt generation without image generation * relock * chromadb to v1 * fix error handling * fix issue where adding client model list would be empty * supress pip install warnings * allow overriding of base urls * remove key from log * add fade effect * tweak request info ux * further clarification of endpoint override api key * world state manager: fix issue that caused max changes setting to disappear from character progress config * fix issue with google safety settings off causing generation failures * update to base url should always reset the client * getattr * support v3 chara card version and attempt to future proof * client based embeddings improvements * more fixes for client based embeddings * use client icon * history management tools progress * history memory ids fixed and added validation * regenerate summary fixes * more history regeneration fixes * fix layered history gen and prompt twweaks * allow regeneration of individual layered history entries * return list of LayeredArchiveEntry * reorg for less code dupelication * new scene message renderer based on marked * add inspect functionality to history viewer * message if no history entries yet * allow adding of history entries manually * allow deletion of history * summarization unslop improvements * fix make charcter real action from worldstate listing * allow overriding length in all context generation isntructioon dialogs * fix issue where extract_list could fail with an unhandled error if the llm response didnt contain a list * update whats'new * fix issues with the new history management tools * fix check * Switch dependency handling to UV (#202) * Migrate from Poetry to uv package manager (#200) * migrate from poetry to uv package manager * Update all installation and startup scripts for uv migration * Fix pyproject.toml for uv - allow direct references for hatchling * Fix PR feedback: Restore removed functionality - Restored embedded Python/Node.js functionality in install.bat and update.bat - Restored environment variable exposure in docker-compose.yml (CUDA_AVAILABLE, port configs) - Fixed GitHub Actions branches (main, prep-* instead of main, dev) - Restored fail-fast: false and cache configuration in test.yml These changes preserve all the functionality that should not be removed during the migration from Poetry to uv. --------- Co-authored-by: Ztripez von Matérn <ztripez@bobby.se> * remove uv.lock from .gitignore * add lock file * fix install issues * warn if unable to remove legacy poetry virt env dir * uv needs to be explicitly installed into the .venv so its available * third time's the charm? * fix windows install scripts * add .venv guard to update.bat * call :die * fix docker venv install * node 21 * fix cuda install * start.bat calls install if needed * sync start-local to other startup scripts * no need to activate venv --------- Co-authored-by: Ztripez <reg@otherland.nu> Co-authored-by: Ztripez von Matérn <ztripez@bobby.se> * ignore hfhub symlink warnings * add openrouter and ollama mentions * update windows install documentation * docs * docs * fix issue with memory agent fingerprint * removing a client that supports embeddings will also remove any embedding functions it created * on invalid embeddings reset to default * docs * typo * formatting * docs * docs * install package * adjust topic * add more obvious way to exit creative mode * when importing character cards immediately persist a usable save after the restoration save --------- Co-authored-by: Ztripez <reg@otherland.nu> Co-authored-by: Ztripez von Matérn <ztripez@bobby.se>
670 lines
21 KiB
Python
670 lines
21 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import dataclasses
|
|
from inspect import signature
|
|
import re
|
|
from abc import ABC
|
|
from functools import wraps
|
|
from typing import TYPE_CHECKING, Callable, List, Optional, Union
|
|
import uuid
|
|
import pydantic
|
|
import structlog
|
|
from blinker import signal
|
|
|
|
import talemate.emit.async_signals
|
|
import talemate.instance as instance
|
|
import talemate.util as util
|
|
from talemate.agents.context import ActiveAgent, active_agent
|
|
from talemate.emit import emit
|
|
from talemate.events import GameLoopStartEvent
|
|
from talemate.context import active_scene
|
|
import talemate.config as config
|
|
from talemate.client.context import (
|
|
ClientContext,
|
|
set_client_context_attribute,
|
|
)
|
|
|
|
__all__ = [
|
|
"Agent",
|
|
"AgentAction",
|
|
"AgentActionConditional",
|
|
"AgentActionConfig",
|
|
"AgentDetail",
|
|
"AgentEmission",
|
|
"AgentTemplateEmission",
|
|
"set_processing",
|
|
"store_context_state",
|
|
]
|
|
|
|
log = structlog.get_logger("talemate.agents.base")
|
|
|
|
class AgentActionConditional(pydantic.BaseModel):
|
|
attribute: str
|
|
value: int | float | str | bool | list[int | float | str | bool] | None = None
|
|
|
|
|
|
class AgentActionNote(pydantic.BaseModel):
|
|
type: str
|
|
text: str
|
|
|
|
class AgentActionConfig(pydantic.BaseModel):
|
|
type: str
|
|
label: str
|
|
description: str = ""
|
|
value: Union[int, float, str, bool, list, None] = None
|
|
default_value: Union[int, float, str, bool] = None
|
|
max: Union[int, float, None] = None
|
|
min: Union[int, float, None] = None
|
|
step: Union[int, float, None] = None
|
|
scope: str = "global"
|
|
choices: Union[list[dict[str, str]], None] = None
|
|
note: Union[str, None] = None
|
|
expensive: bool = False
|
|
quick_toggle: bool = False
|
|
condition: Union[AgentActionConditional, None] = None
|
|
title: Union[str, None] = None
|
|
value_migration: Union[Callable, None] = pydantic.Field(default=None, exclude=True)
|
|
|
|
note_on_value: dict[str, AgentActionNote] = pydantic.Field(default_factory=dict)
|
|
|
|
class Config:
|
|
arbitrary_types_allowed = True
|
|
|
|
|
|
class AgentAction(pydantic.BaseModel):
|
|
enabled: bool = True
|
|
label: str
|
|
description: str = ""
|
|
warning: str = ""
|
|
config: Union[dict[str, AgentActionConfig], None] = None
|
|
condition: Union[AgentActionConditional, None] = None
|
|
container: bool = False
|
|
icon: Union[str, None] = None
|
|
can_be_disabled: bool = False
|
|
quick_toggle: bool = False
|
|
experimental: bool = False
|
|
|
|
class AgentDetail(pydantic.BaseModel):
|
|
value: Union[str, None] = None
|
|
description: Union[str, None] = None
|
|
icon: Union[str, None] = None
|
|
color: str = "grey"
|
|
|
|
class DynamicInstruction(pydantic.BaseModel):
|
|
title: str
|
|
content: str
|
|
|
|
def __str__(self) -> str:
|
|
return "\n".join(
|
|
[
|
|
f"<|SECTION:{self.title}|>",
|
|
self.content,
|
|
"<|CLOSE_SECTION|>"
|
|
]
|
|
)
|
|
|
|
|
|
def args_and_kwargs_to_dict(fn, args: list, kwargs: dict, filter:list[str] = None) -> dict:
|
|
"""
|
|
Takes a list of arguments and a dict of keyword arguments and returns
|
|
a dict mapping parameter names to their values.
|
|
|
|
Args:
|
|
fn: The function whose parameters we want to map
|
|
args: List of positional arguments
|
|
kwargs: Dictionary of keyword arguments
|
|
filter: List of parameter names to include in the result, if None all parameters are included
|
|
|
|
Returns:
|
|
Dict mapping parameter names to their values
|
|
"""
|
|
sig = signature(fn)
|
|
bound_args = sig.bind(*args, **kwargs)
|
|
bound_args.apply_defaults()
|
|
rv = dict(bound_args.arguments)
|
|
rv.pop("self", None)
|
|
|
|
if filter:
|
|
for key in list(rv.keys()):
|
|
if key not in filter:
|
|
rv.pop(key)
|
|
|
|
return rv
|
|
|
|
|
|
class store_context_state:
|
|
"""
|
|
Flag to store a function's arguments in the agent's context state.
|
|
|
|
Any arguments passed to the function will be stored in the agent's context
|
|
|
|
If no arguments are passed, all arguments will be stored.
|
|
|
|
Keyword arguments can be passed to store additional values in the context state.
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
|
|
def __call__(self, fn):
|
|
fn.store_context_state = self.args
|
|
fn.store_context_state_kwargs = self.kwargs
|
|
return fn
|
|
|
|
def set_processing(fn):
|
|
"""
|
|
decorator that emits the agent status as processing while the function
|
|
is running.
|
|
|
|
Done via a try - final block to ensure the status is reset even if
|
|
the function fails.
|
|
"""
|
|
|
|
@wraps(fn)
|
|
async def wrapper(self, *args, **kwargs):
|
|
with ClientContext():
|
|
scene = active_scene.get()
|
|
|
|
if scene:
|
|
scene.continue_actions()
|
|
|
|
if getattr(scene, "config", None):
|
|
set_client_context_attribute("app_config_system_prompts", scene.config.get("system_prompts", {}))
|
|
|
|
with ActiveAgent(self, fn, args, kwargs) as active_agent_context:
|
|
try:
|
|
await self.emit_status(processing=True)
|
|
|
|
# Now pass the complete args list
|
|
if getattr(fn, "store_context_state", None) is not None:
|
|
all_args = args_and_kwargs_to_dict(
|
|
fn, [self] + list(args), kwargs, getattr(fn, "store_context_state", [])
|
|
)
|
|
if getattr(fn, "store_context_state_kwargs", None) is not None:
|
|
all_args.update(getattr(fn, "store_context_state_kwargs", {}))
|
|
|
|
all_args[f"fn_{fn.__name__}"] = True
|
|
|
|
active_agent_context.state_params = all_args
|
|
|
|
self.set_context_states(**all_args)
|
|
|
|
return await fn(self, *args, **kwargs)
|
|
finally:
|
|
try:
|
|
await self.emit_status(processing=False)
|
|
except RuntimeError as exc:
|
|
# not sure why this happens
|
|
# some concurrency error?
|
|
log.error("error emitting agent status", exc=exc)
|
|
|
|
return wrapper
|
|
|
|
|
|
class Agent(ABC):
|
|
"""
|
|
Base agent class, defines a role
|
|
"""
|
|
|
|
agent_type = "agent"
|
|
verbose_name = None
|
|
set_processing = set_processing
|
|
requires_llm_client = True
|
|
auto_break_repetition = False
|
|
websocket_handler = None
|
|
essential = True
|
|
ready_check_error = None
|
|
|
|
@classmethod
|
|
def init_actions(cls, actions: dict[str, AgentAction] | None = None) -> dict[str, AgentAction]:
|
|
if actions is None:
|
|
actions = {}
|
|
|
|
return actions
|
|
|
|
@property
|
|
def agent_details(self):
|
|
if hasattr(self, "client"):
|
|
if self.client:
|
|
return self.client.name
|
|
return None
|
|
|
|
@property
|
|
def verbose_name(self):
|
|
return self.agent_type.capitalize()
|
|
|
|
@property
|
|
def ready(self):
|
|
if not getattr(self.client, "enabled", True):
|
|
return False
|
|
|
|
if self.client and self.client.current_status in ["error", "warning"]:
|
|
return False
|
|
|
|
return self.client is not None
|
|
|
|
@property
|
|
def status(self):
|
|
if not self.enabled:
|
|
return "disabled"
|
|
|
|
if not self.ready:
|
|
return "uninitialized"
|
|
|
|
if getattr(self, "processing", 0) > 0:
|
|
return "busy"
|
|
|
|
if getattr(self, "processing_bg", 0) > 0:
|
|
return "busy_bg"
|
|
|
|
return "idle"
|
|
|
|
@property
|
|
def enabled(self):
|
|
# by default, agents are enabled, an agent class that
|
|
# is disableable should override this property
|
|
return True
|
|
|
|
@property
|
|
def disable(self):
|
|
# by default, agents are enabled, an agent class that
|
|
# is disableable should override this property to
|
|
# disable the agent
|
|
pass
|
|
|
|
@property
|
|
def has_toggle(self):
|
|
# by default, agents do not have toggles to enable / disable
|
|
# an agent class that is disableable should override this property
|
|
return False
|
|
|
|
@property
|
|
def experimental(self):
|
|
# by default, agents are not experimental, an agent class that
|
|
# is experimental should override this property
|
|
return False
|
|
|
|
@classmethod
|
|
def config_options(cls, agent=None):
|
|
config_options = {
|
|
"client": [name for name, _ in instance.client_instances()],
|
|
"enabled": agent.enabled if agent else True,
|
|
"has_toggle": agent.has_toggle if agent else False,
|
|
"experimental": agent.experimental if agent else False,
|
|
"requires_llm_client": cls.requires_llm_client,
|
|
}
|
|
actions = getattr(agent, "actions", None)
|
|
|
|
if actions:
|
|
config_options["actions"] = {k: v.model_dump() for k, v in actions.items()}
|
|
else:
|
|
config_options["actions"] = {}
|
|
|
|
return config_options
|
|
|
|
@property
|
|
def meta(self):
|
|
return {
|
|
"essential": self.essential,
|
|
}
|
|
|
|
@property
|
|
def sanitized_action_config(self):
|
|
if not getattr(self, "actions", None):
|
|
return {}
|
|
|
|
return {k: v.model_dump() for k, v in self.actions.items()}
|
|
|
|
# scene state
|
|
|
|
|
|
def context_fingerpint(self, extra: list[str] = []) -> str | None:
|
|
active_agent_context = active_agent.get()
|
|
|
|
if not active_agent_context:
|
|
return None
|
|
|
|
if self.scene.history:
|
|
fingerprint = f"{self.scene.history[-1].fingerprint}-{active_agent_context.first.fingerprint}"
|
|
else:
|
|
fingerprint = f"START-{active_agent_context.first.fingerprint}"
|
|
|
|
for extra_key in extra:
|
|
fingerprint += f"-{hash(extra_key)}"
|
|
|
|
return fingerprint
|
|
|
|
|
|
def get_scene_state(self, key:str, default=None):
|
|
agent_state = self.scene.agent_state.get(self.agent_type, {})
|
|
return agent_state.get(key, default)
|
|
|
|
def set_scene_states(self, **kwargs):
|
|
agent_state = self.scene.agent_state.get(self.agent_type, {})
|
|
for key, value in kwargs.items():
|
|
agent_state[key] = value
|
|
self.scene.agent_state[self.agent_type] = agent_state
|
|
|
|
def dump_scene_state(self):
|
|
return self.scene.agent_state.get(self.agent_type, {})
|
|
|
|
# active agent context state
|
|
|
|
def get_context_state(self, key:str, default=None):
|
|
key = f"{self.agent_type}__{key}"
|
|
try:
|
|
return active_agent.get().state.get(key, default)
|
|
except AttributeError:
|
|
log.warning("get_context_state error", agent=self.agent_type, key=key)
|
|
return default
|
|
|
|
def set_context_states(self, **kwargs):
|
|
try:
|
|
|
|
items = {f"{self.agent_type}__{k}": v for k, v in kwargs.items()}
|
|
active_agent.get().state.update(items)
|
|
log.debug("set_context_states", agent=self.agent_type, state=active_agent.get().state)
|
|
except AttributeError:
|
|
log.error("set_context_states error", agent=self.agent_type, kwargs=kwargs)
|
|
|
|
def dump_context_state(self):
|
|
try:
|
|
return active_agent.get().state
|
|
except AttributeError:
|
|
return {}
|
|
|
|
###
|
|
|
|
async def _handle_ready_check(self, fut: asyncio.Future):
|
|
callback_failure = getattr(self, "on_ready_check_failure", None)
|
|
if fut.cancelled():
|
|
if callback_failure:
|
|
await callback_failure()
|
|
return
|
|
|
|
if fut.exception():
|
|
exc = fut.exception()
|
|
self.ready_check_error = exc
|
|
log.error("agent ready check error", agent=self.agent_type, exc=exc)
|
|
if callback_failure:
|
|
await callback_failure(exc)
|
|
return
|
|
|
|
callback = getattr(self, "on_ready_check_success", None)
|
|
if callback:
|
|
await callback()
|
|
|
|
async def setup_check(self):
|
|
return False
|
|
|
|
async def ready_check(self, task: asyncio.Task = None):
|
|
self.ready_check_error = None
|
|
if task:
|
|
task.add_done_callback(
|
|
lambda fut: asyncio.create_task(self._handle_ready_check(fut))
|
|
)
|
|
return
|
|
return True
|
|
|
|
async def apply_config(self, *args, **kwargs):
|
|
if self.has_toggle and "enabled" in kwargs:
|
|
self.is_enabled = kwargs.get("enabled", False)
|
|
|
|
if not getattr(self, "actions", None):
|
|
return
|
|
|
|
for action_key, action in self.actions.items():
|
|
if not kwargs.get("actions"):
|
|
continue
|
|
|
|
action.enabled = (
|
|
kwargs.get("actions", {}).get(action_key, {}).get("enabled", False)
|
|
)
|
|
|
|
if not action.config:
|
|
continue
|
|
|
|
for config_key, config in action.config.items():
|
|
try:
|
|
config.value = (
|
|
kwargs.get("actions", {})
|
|
.get(action_key, {})
|
|
.get("config", {})
|
|
.get(config_key, {})
|
|
.get("value", config.value)
|
|
)
|
|
if config.value_migration and callable(config.value_migration):
|
|
config.value = config.value_migration(config.value)
|
|
except AttributeError:
|
|
pass
|
|
|
|
|
|
async def save_config(self, app_config: config.Config | None = None):
|
|
"""
|
|
Saves the agent config to the config file.
|
|
|
|
If no config object is provided, the config is loaded from the config file.
|
|
"""
|
|
|
|
if not app_config:
|
|
app_config:config.Config = config.load_config(as_model=True)
|
|
|
|
app_config.agents[self.agent_type] = config.Agent(
|
|
name=self.agent_type,
|
|
client=self.client.name if self.client else None,
|
|
enabled=self.enabled,
|
|
actions={action_key: config.AgentAction(
|
|
enabled=action.enabled,
|
|
config={config_key: config.AgentActionConfig(value=config_obj.value) for config_key, config_obj in action.config.items()}
|
|
) for action_key, action in self.actions.items()}
|
|
)
|
|
log.debug("saving agent config", agent=self.agent_type, config=app_config.agents[self.agent_type])
|
|
config.save_config(app_config)
|
|
|
|
async def on_game_loop_start(self, event: GameLoopStartEvent):
|
|
"""
|
|
Finds all ActionConfigs that have a scope of "scene" and resets them to their default values
|
|
"""
|
|
|
|
if not getattr(self, "actions", None):
|
|
return
|
|
|
|
for _, action in self.actions.items():
|
|
if not action.config:
|
|
continue
|
|
|
|
for _, config in action.config.items():
|
|
if config.scope == "scene":
|
|
# if default_value is None, just use the `type` of the current
|
|
# value
|
|
if config.default_value is None:
|
|
default_value = type(config.value)()
|
|
else:
|
|
default_value = config.default_value
|
|
|
|
log.debug(
|
|
"resetting config", config=config, default_value=default_value
|
|
)
|
|
config.value = default_value
|
|
|
|
await self.emit_status()
|
|
|
|
async def emit_status(self, processing: bool = None):
|
|
# should keep a count of processing requests, and when the
|
|
# number is 0 status is "idle", if the number is greater than 0
|
|
# status is "busy"
|
|
#
|
|
# increase / decrease based on value of `processing`
|
|
|
|
if getattr(self, "processing", None) is None:
|
|
self.processing = 0
|
|
|
|
if processing is False:
|
|
self.processing -= 1
|
|
self.processing = max(0, self.processing)
|
|
elif processing is True:
|
|
self.processing += 1
|
|
|
|
emit(
|
|
"agent_status",
|
|
message=self.verbose_name or "",
|
|
id=self.agent_type,
|
|
status=self.status,
|
|
details=self.agent_details,
|
|
meta=self.meta,
|
|
data=self.config_options(agent=self),
|
|
)
|
|
|
|
await asyncio.sleep(0.01)
|
|
|
|
async def _handle_background_processing(self, fut: asyncio.Future, error_handler = None):
|
|
try:
|
|
if fut.cancelled():
|
|
return
|
|
|
|
if fut.exception():
|
|
log.error(
|
|
"background processing error",
|
|
agent=self.agent_type,
|
|
exc=fut.exception(),
|
|
)
|
|
|
|
if error_handler:
|
|
await error_handler(fut.exception())
|
|
|
|
await self.emit_status()
|
|
return
|
|
|
|
log.info("background processing done", agent=self.agent_type)
|
|
finally:
|
|
self.processing_bg -= 1
|
|
await self.emit_status()
|
|
|
|
async def set_background_processing(self, task: asyncio.Task, error_handler = None):
|
|
log.info("set_background_processing", agent=self.agent_type)
|
|
if not hasattr(self, "processing_bg"):
|
|
self.processing_bg = 0
|
|
|
|
self.processing_bg += 1
|
|
|
|
await self.emit_status()
|
|
task.add_done_callback(
|
|
lambda fut: asyncio.create_task(self._handle_background_processing(fut, error_handler))
|
|
)
|
|
|
|
def connect(self, scene):
|
|
self.scene = scene
|
|
talemate.emit.async_signals.get("game_loop_start").connect(
|
|
self.on_game_loop_start
|
|
)
|
|
|
|
def clean_result(self, result):
|
|
if "#" in result:
|
|
result = result.split("#")[0]
|
|
|
|
# Removes partial sentence at the end
|
|
result = re.sub(r"[^\.\?\!]+(\n|$)", "", result)
|
|
result = result.strip()
|
|
|
|
if ":" in result:
|
|
result = result.split(":")[1].strip()
|
|
|
|
return result
|
|
|
|
async def get_history_memory_context(
|
|
self,
|
|
memory_history_context_max: int,
|
|
memory_context_max: int,
|
|
exclude: list = [],
|
|
exclude_fn: Callable = None,
|
|
):
|
|
current_memory_context = []
|
|
memory_helper = self.scene.get_helper("memory")
|
|
if memory_helper:
|
|
history_messages = "\n".join(
|
|
self.scene.recent_history(memory_history_context_max)
|
|
)
|
|
memory_tokens = 0
|
|
for memory in await memory_helper.agent.get(history_messages):
|
|
if memory in exclude:
|
|
continue
|
|
|
|
if exclude_fn:
|
|
for split in memory.split("\n"):
|
|
if exclude_fn(split):
|
|
continue
|
|
|
|
memory_tokens += util.count_tokens(memory)
|
|
|
|
if memory_tokens > memory_context_max:
|
|
break
|
|
|
|
current_memory_context.append(memory)
|
|
return current_memory_context
|
|
|
|
# LLM client related methods. These are called during or after the client
|
|
# sends the prompt to the API.
|
|
|
|
def inject_prompt_paramters(
|
|
self, prompt_param: dict, kind: str, agent_function_name: str
|
|
):
|
|
"""
|
|
Injects prompt parameters before the client sends off the prompt
|
|
Override as needed.
|
|
"""
|
|
pass
|
|
|
|
def allow_repetition_break(
|
|
self, kind: str, agent_function_name: str, auto: bool = False
|
|
):
|
|
"""
|
|
Returns True if repetition breaking is allowed, False otherwise.
|
|
"""
|
|
return False
|
|
|
|
|
|
@set_processing
|
|
async def delegate(self, fn: Callable, *args, **kwargs):
|
|
"""
|
|
Wraps a function as an agent action, allowing it to be called
|
|
by the agent.
|
|
"""
|
|
return await fn(*args, **kwargs)
|
|
|
|
async def emit_message(self, header:str, message:str | list[dict], meta: dict = None, **data):
|
|
if not data:
|
|
data = {}
|
|
|
|
if not meta:
|
|
meta = {}
|
|
|
|
if "uuid" not in data:
|
|
data["uuid"] = str(uuid.uuid4())
|
|
|
|
if "agent" not in data:
|
|
data["agent"] = self.agent_type
|
|
|
|
data["header"] = header
|
|
emit(
|
|
"agent_message",
|
|
message=message,
|
|
data=data,
|
|
meta=meta,
|
|
websocket_passthrough=True,
|
|
)
|
|
|
|
@dataclasses.dataclass
|
|
class AgentEmission:
|
|
agent: Agent
|
|
|
|
@dataclasses.dataclass
|
|
class AgentTemplateEmission(AgentEmission):
|
|
template_vars: dict = dataclasses.field(default_factory=dict)
|
|
response: str = None
|
|
dynamic_instructions: list[DynamicInstruction] = dataclasses.field(default_factory=list)
|
|
|
|
@dataclasses.dataclass
|
|
class RagBuildSubInstructionEmission(AgentEmission):
|
|
sub_instruction: str | None = None
|