Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversations, consolidated wallets, commands, and swap agent #127

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 26 additions & 6 deletions submodules/moragents_dockers/agents/src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
chat_manager_routes,
key_manager_routes,
wallet_manager_routes,
workflow_manager_routes
workflow_manager_routes,
)

# Constants
Expand All @@ -40,6 +40,8 @@
allow_methods=["*"],
allow_headers=["*"],
)


@app.on_event("startup")
async def startup_event():
await workflow_manager_instance.initialize()
Expand Down Expand Up @@ -119,18 +121,36 @@ def validate_agent_response(response: dict, current_agent: str) -> dict:

@app.post("/chat")
async def chat(chat_request: ChatRequest):
prompt = chat_request.prompt.dict()
chat_manager_instance.add_message(prompt)
"""Send a chat message and get a response"""
logger.info(f"Received chat request for conversation {chat_request.conversation_id}")

# Parse command if present
agent_name, message = agent_manager_instance.parse_command(
chat_request.prompt.dict()["content"]
)

if agent_name:
# If command was used, set that agent as active
agent_manager_instance.set_active_agent(agent_name)
# Update message content without command
chat_request.prompt.dict()["content"] = message

chat_manager_instance.add_message(chat_request.prompt.dict(), chat_request.conversation_id)

try:
delegator.reset_attempted_agents()
active_agent = await get_active_agent_for_chat(prompt)
if not agent_name:
delegator.reset_attempted_agents()
active_agent = await get_active_agent_for_chat(chat_request.prompt.dict())
else:
active_agent = agent_name

logger.info(f"Delegating chat to active agent: {active_agent}")
current_agent, response = delegator.delegate_chat(active_agent, chat_request)

validated_response = validate_agent_response(response, current_agent)
chat_manager_instance.add_response(validated_response, current_agent)
chat_manager_instance.add_response(
validated_response, current_agent, chat_request.conversation_id
)

logger.info(f"Sending response: {validated_response}")
return validated_response
Expand Down
10 changes: 10 additions & 0 deletions submodules/moragents_dockers/agents/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Config:
"description": "Must be used for meta-queries that ask about active Morpheus agents, and also for general, simple questions",
"name": "default",
"human_readable_name": "Default General Purpose",
"command": "morpheus",
"upload_required": False,
},
{
Expand All @@ -30,6 +31,7 @@ class Config:
"description": "Must only be used for image generation tasks. Use when the query explicitly mentions generating or creating an image.",
"name": "imagen",
"human_readable_name": "Image Generator",
"command": "imagen",
"upload_required": False,
},
{
Expand All @@ -38,6 +40,7 @@ class Config:
"description": "Handles transactions on the Base crypto network. Use when the user makes any reference to Base, base, the base network, or Coinbase",
"name": "base",
"human_readable_name": "Base Transaction Manager",
"command": "base",
"upload_required": False,
},
{
Expand All @@ -46,6 +49,7 @@ class Config:
"description": "Crypto-specific. Provides real-time cryptocurrency data such as price, market cap, and fully diluted valuation (FDV).",
"name": "crypto data",
"human_readable_name": "Crypto Data Fetcher",
"command": "crypto",
"upload_required": False,
},
# DISABLED: Pending 1inch protocol fix
Expand All @@ -63,6 +67,7 @@ class Config:
"description": "Generates engaging tweets. Use ONLY when the query explicitly mentions Twitter, tweeting, or the X platform.",
"name": "tweet sizzler",
"human_readable_name": "Tweet / X-Post Generator",
"command": "tweet",
"upload_required": False,
},
{
Expand All @@ -71,6 +76,7 @@ class Config:
"description": "Sets up DCA strategies. Use when the user requests to set up a dollar-cost averaging strategy for crypto purchases or trades.",
"name": "dca",
"human_readable_name": "DCA Strategy Manager",
"command": "dca",
"upload_required": False,
},
{
Expand All @@ -79,6 +85,7 @@ class Config:
"description": "Answers questions about a document. Must be used anytime an upload, a document, Documents, or uploaded document is mentioned",
"name": "rag",
"human_readable_name": "Document Assistant",
"command": "document",
"upload_required": True,
},
# DISABLED:
Expand All @@ -96,6 +103,7 @@ class Config:
"description": "Provides information about user's accrued MOR rewards or tokens. Use when the query is about checking or querying reward balances.",
"name": "mor rewards",
"human_readable_name": "MOR Rewards Tracker",
"command": "rewards",
"upload_required": False,
},
{
Expand All @@ -104,6 +112,7 @@ class Config:
"description": f"Use when the query is about searching the web or asks about a recent / current event (The year is {datetime.datetime.now().year})",
"name": "realtime search",
"human_readable_name": "Real-Time Search",
"command": "search",
"upload_required": False,
},
{
Expand All @@ -112,6 +121,7 @@ class Config:
"description": "Fetches and analyzes cryptocurrency news for potential price impacts.",
"name": "crypto news",
"human_readable_name": "Crypto News Analyst",
"command": "news",
"upload_required": False,
},
]
Expand Down
14 changes: 14 additions & 0 deletions submodules/moragents_dockers/agents/src/models/messages.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import List, Optional
from fastapi import Query
from pydantic import BaseModel


Expand All @@ -10,3 +12,15 @@ class ChatRequest(BaseModel):
prompt: ChatMessage
chain_id: str
wallet_address: str
conversation_id: str = Query(default="default")


class ChatMessage(BaseModel):
role: str
content: str
agentName: Optional[str] = None


class Conversation(BaseModel):
messages: List[ChatMessage]
has_uploaded_file: bool = False
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,18 @@ async def set_selected_agents(request: Request) -> JSONResponse:
logger.info(f"Newly selected agents: {agent_manager_instance.get_selected_agents()}")

return JSONResponse(content={"status": "success", "agents": agent_names})


@router.get("/commands")
async def get_agent_commands() -> JSONResponse:
"""Get the list of available agent commands"""
available_agents = agent_manager_instance.get_available_agents()
commands = [
{
"command": agent["command"],
"description": agent["description"],
"name": agent["human_readable_name"],
}
for agent in available_agents
]
return JSONResponse(content={"commands": commands})
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from fastapi import APIRouter
from fastapi import APIRouter, Query
from src.stores import chat_manager_instance

logger = logging.getLogger(__name__)
Expand All @@ -8,15 +8,39 @@


@router.get("/messages")
async def get_messages():
"""Get all chat messages"""
logger.info("Received get_messages request")
return {"messages": chat_manager_instance.get_messages()}
async def get_messages(conversation_id: str = Query(default="default")):
"""Get all chat messages for a conversation"""
logger.info(f"Received get_messages request for conversation {conversation_id}")
return {"messages": chat_manager_instance.get_messages(conversation_id)}


@router.get("/clear")
async def clear_messages():
"""Clear chat message history"""
logger.info("Clearing message history")
chat_manager_instance.clear_messages()
async def clear_messages(conversation_id: str = Query(default="default")):
"""Clear chat message history for a conversation"""
logger.info(f"Clearing message history for conversation {conversation_id}")
chat_manager_instance.clear_messages(conversation_id)
return {"response": "successfully cleared message history"}


@router.get("/conversations")
async def get_conversations():
"""Get all conversation IDs"""
logger.info("Getting all conversation IDs")
return {"conversation_ids": chat_manager_instance.get_all_conversation_ids()}


@router.post("/conversations")
async def create_conversation():
"""Create a new conversation"""
new_id = f"conversation_{len(chat_manager_instance.get_all_conversation_ids())}"
conversation = chat_manager_instance.create_conversation(new_id)
logger.info(f"Created new conversation with ID: {new_id}")
return {"conversation_id": new_id, "conversation": conversation}


@router.delete("/conversations/{conversation_id}")
async def delete_conversation(conversation_id: str):
"""Delete a conversation"""
logger.info(f"Deleting conversation {conversation_id}")
chat_manager_instance.delete_conversation(conversation_id)
return {"response": f"successfully deleted conversation {conversation_id}"}
37 changes: 36 additions & 1 deletion submodules/moragents_dockers/agents/src/stores/agent_manager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import importlib
import logging

from typing import Any, Dict, List, Optional
from typing import Any, Dict, List, Optional, Tuple
from langchain_ollama import ChatOllama
from langchain_community.embeddings import OllamaEmbeddings

Expand Down Expand Up @@ -165,6 +165,41 @@ def get_agent(self, agent_name: str) -> Optional[Any]:
"""
return self.agents.get(agent_name)

def get_agent_by_command(self, command: str) -> Optional[str]:
"""
Get agent name by command.

Args:
command (str): Command to look up

Returns:
Optional[str]: Agent name if found, None otherwise
"""
for agent in self.config["agents"]:
if agent["command"] == command:
return agent["name"]
return None

def parse_command(self, message: str) -> Tuple[Optional[str], str]:
"""
Parse a message for commands.

Args:
message (str): Message to parse

Returns:
Tuple[Optional[str], str]: Tuple of (agent_name, message_without_command)
"""
if not message.startswith("/"):
return None, message

parts = message[1:].split(maxsplit=1)
command = parts[0]
remaining_message = parts[1] if len(parts) > 1 else ""

agent_name = self.get_agent_by_command(command)
return agent_name, remaining_message


# Create an instance to act as a singleton store
agent_manager_instance = AgentManager(Config.AGENTS_CONFIG)
Loading
Loading