backend: separated agent classes. frontend: chat message context menu (does nothing)
This commit is contained in:
1 parent
3f37f727af
commit
22fe5619d8
5 files changed
+205
-82
No files matched your search
+94
-26
@@ -17,11 +17,45 @@ class AgentField:
|
||||
# Instances are created and destroyed constantly, agents must not depend solely on in-memory data
|
||||
# to be useful and consistent
|
||||
class Agent:
|
||||
# Class variables:
|
||||
# Class variables, must be defined by subclasses of Agent:
|
||||
# - identifier (for kind in creation)
|
||||
# - schema: list[AgentField{name, type: str}] (for configuration)
|
||||
# - tools: list[ToolDescription] (actions the agent class support)
|
||||
identifier = "basic"
|
||||
# tools: list[ToolDescription] (custom actions supported by the class)
|
||||
|
||||
def __init__(self, app, data):
|
||||
self.app = app
|
||||
|
||||
# These variables are set by '_set_basic_info'
|
||||
self.id = None
|
||||
self.name = None
|
||||
|
||||
# Auto populated based on the schema fields
|
||||
agent_class = type(self)
|
||||
self.config = {field.name: data.get(field.name, field.default) for field in getattr(agent_class, 'schema_fields', [])}
|
||||
|
||||
def _set_basic_info(self, id, name):
|
||||
self.id = id
|
||||
self.name = name
|
||||
|
||||
# Receives a single string prompt, and must return a string response
|
||||
async def prompt(self, content):
|
||||
raise Exception("'prompt' is not implemented for this agent class")
|
||||
|
||||
# Receives a chat message object, agent can send messages to the chat in response
|
||||
async def received_message(self, message):
|
||||
raise Exception("'received_message' is not implemented for this agent class")
|
||||
|
||||
# Executes a tool/action in this agent
|
||||
async def execute_action(self, name: str, argument: dict):
|
||||
# TODO: For security, check that the method is in the tool list
|
||||
method = getattr(self, name)
|
||||
result = method(**argument)
|
||||
if inspect.isawaitable(result):
|
||||
result = await result
|
||||
return result
|
||||
|
||||
class ToolUsingAgent:
|
||||
identifier = "tool-using"
|
||||
schema_fields = [
|
||||
AgentField('system_prompt', 'string',
|
||||
"\n".join([
|
||||
@@ -32,13 +66,12 @@ class Agent:
|
||||
])
|
||||
),
|
||||
]
|
||||
|
||||
# tools: list[ToolDescription] (actions the agent class support)
|
||||
tools = []
|
||||
|
||||
def __init__(self, app, agent_id, data):
|
||||
self.app = app
|
||||
self.agent_id = agent_id
|
||||
|
||||
self.config = {field.name: data.get(field.name, field.default) for field in Agent.schema_fields}
|
||||
def __init__(self, app, data):
|
||||
Agent.__init__(self, app, data)
|
||||
|
||||
self.tool_prompt_section = "\n".join([
|
||||
"# Commands",
|
||||
@@ -106,7 +139,6 @@ class Agent:
|
||||
|
||||
return agent_response or full_response
|
||||
|
||||
|
||||
# Receives a single string prompt, and must respond with a string
|
||||
async def prompt(self, content):
|
||||
response = await self._full_prompt(f"User Message", content)
|
||||
@@ -127,7 +159,7 @@ class Agent:
|
||||
|
||||
self.app.send_chat_message(
|
||||
chat_id=message.chat_id,
|
||||
sender=self.agent_id,
|
||||
sender=self.id,
|
||||
content=response,
|
||||
)
|
||||
|
||||
@@ -154,17 +186,58 @@ class Agent:
|
||||
action_result = json.dumps(action_result)
|
||||
output += f"[{action.toolset_name}.{action.tool_name}]:{action_result}\n"
|
||||
return output
|
||||
|
||||
def create_from_data(app, agent_id, kind, data):
|
||||
return Agent(app, agent_id, data)
|
||||
|
||||
async def execute_action(self, name: str, argument: dict):
|
||||
# TODO: For security, check that the method is in the tool list
|
||||
method = getattr(self, name)
|
||||
result = method(**argument)
|
||||
if inspect.isawaitable(result):
|
||||
result = await result
|
||||
return result
|
||||
|
||||
class Llama8bAgent(Agent):
|
||||
identifier = "llama-8b"
|
||||
schema_fields = [
|
||||
AgentField('system_prompt', 'string', default="You are a helpful AI assistant."),
|
||||
]
|
||||
tools = []
|
||||
|
||||
def __init__(self, app, data):
|
||||
Agent.__init__(self, app, data)
|
||||
|
||||
async def prompt(self, content):
|
||||
messages = [
|
||||
LlamaMessage("system", self.config['system_prompt']),
|
||||
LlamaMessage("user", content)
|
||||
]
|
||||
response = await replicate_llama_completion(self.app, messages)
|
||||
return response
|
||||
|
||||
async def received_message(self, message):
|
||||
def sender_name(message):
|
||||
sender = message['sender']
|
||||
is_digit = isinstance(sender, (int, str)) and str(sender).isdigit()
|
||||
is_self = is_digit and str(sender) == str(self.id)
|
||||
return self.name if is_self else sender
|
||||
|
||||
chat_context = self.app.get_chat_context(message.chat_id)
|
||||
chat_summary = "\n".join(f"[{sender_name(message)}]: {message['content']}" for message in chat_context.messages)
|
||||
messages = [
|
||||
LlamaMessage("system", f"{self.config['system_prompt']}\nYou are given the context of the conversation, respond with your next message in the conversation."),
|
||||
LlamaMessage("user", f"Recent Messages:\n{chat_summary}")
|
||||
]
|
||||
|
||||
response = await replicate_llama_completion(self.app, messages)
|
||||
self.app.send_chat_message(
|
||||
chat_id=message.chat_id,
|
||||
sender=self.id,
|
||||
content=response,
|
||||
)
|
||||
|
||||
class BasicAgent(Llama8bAgent):
|
||||
identifier = "basic"
|
||||
schema_fields = Llama8bAgent.schema_fields
|
||||
tools = Llama8bAgent.tools
|
||||
|
||||
class CoderV1Agent(Llama8bAgent):
|
||||
identifier = "coder-v1"
|
||||
schema_fields = [
|
||||
AgentField('system_prompt', 'string', default="You are an expert Software Engineer profficient in multiple programming languages."),
|
||||
]
|
||||
# TODO: add gral functions to the tool list
|
||||
tools = []
|
||||
|
||||
async def gral_message(self, message_history: list, file_map: dict):
|
||||
return await gral.gral_message(self.app, self.config['system_prompt'], message_history, file_map)
|
||||
@@ -173,11 +246,6 @@ class Agent:
|
||||
return await gral.gral_update_file(self.app, self.config['system_prompt'], message_history, filepath, file_content)
|
||||
|
||||
|
||||
class IntroAgent(Agent):
|
||||
def __init__(self, app, agent_id, _data):
|
||||
Agent.__init__(self, app, agent_id)
|
||||
self.system_prompt = "You are a useful and precise AI assistant. Respond concisely."
|
||||
|
||||
class ParsedResponse:
|
||||
def __init__(self):
|
||||
self.response = None
|
||||
|
||||
+13
-3
@@ -10,8 +10,8 @@ from quart import Quart, Response, request, send_from_directory, jsonify, websoc
|
||||
import replicate
|
||||
|
||||
import database as db
|
||||
import agent as agent_mod
|
||||
from server import start_server
|
||||
from agent import Agent
|
||||
|
||||
MESSAGE_PAGE_SIZE = 32
|
||||
|
||||
@@ -34,7 +34,12 @@ class App:
|
||||
raise Exception("Horde only works with a replicate connection. Please provide an API Token.")
|
||||
|
||||
# `Agent` is the kind 'basic', will be class `BasicAgent` in the future
|
||||
self.agent_classes = [Agent]
|
||||
self.agent_classes = [
|
||||
agent_mod.BasicAgent,
|
||||
agent_mod.Llama8bAgent,
|
||||
agent_mod.CoderV1Agent,
|
||||
agent_mod.ToolUsingAgent,
|
||||
]
|
||||
|
||||
def sql_session(self):
|
||||
return Session(self.sql_engine)
|
||||
@@ -255,8 +260,13 @@ class App:
|
||||
|
||||
if not agent:
|
||||
raise Exception(f"Agent not found with id {agent_id}")
|
||||
|
||||
agent_class = next((kind for kind in self.agent_classes if kind.identifier == agent.kind), None)
|
||||
if agent_class is None:
|
||||
raise Exception(f"invalid agent kind: {agent.kind}")
|
||||
|
||||
control = Agent.create_from_data(app, agent_id, agent.kind, agent.data)
|
||||
control = agent_class(app, agent.data)
|
||||
control._set_basic_info(agent_id, agent.name)
|
||||
return control
|
||||
|
||||
def get_agent_config_fields(self, agent_id):
|
||||
|
||||
+3
-1
@@ -29,7 +29,7 @@ class Agent(Base):
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
|
||||
|
||||
handle: Mapped[Optional[str]]
|
||||
agentname: Mapped[Optional[str]]
|
||||
visibility: Mapped[int] = mapped_column(default=0)
|
||||
# Comma separated list of permission symbols
|
||||
permissions: Mapped[str] = mapped_column(default="")
|
||||
@@ -86,4 +86,6 @@ class Message(Base):
|
||||
# Predefined set of message types, interpreted as 'text' when empty.
|
||||
kind: Mapped[str]
|
||||
content: Mapped[str]
|
||||
# Tells whether this message continues in the chat after the session when it was sent.
|
||||
persistent: Mapped[bool] = mapped_column(default=False)
|
||||
|
||||
Reference in new issue
Block a user