Skip to content
Closed
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
68 changes: 29 additions & 39 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"figlet": "^1.7.0",
"find-process": "^1.4.10",
"google-protobuf": "^3.21.4",
"json5": "^2.2.3",
"moment": "^2.30.1",
"opener": "^1.5.2",
"portfinder": "^1.0.36",
Expand Down
80 changes: 77 additions & 3 deletions packages/app/friday/args.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
# -*- coding: utf-8 -*-
import json
import json5
from argparse import ArgumentParser, Namespace
from typing import List, Dict, Any


def json_type(value: str) -> dict:
"""Parse a JSON string into a dictionary."""
if not value or value == "":
return {}
try:
result = json.loads(value)
result = json5.loads(value)
if not isinstance(result, dict):
raise ValueError("JSON must be an object/dictionary")
return result
except json.JSONDecodeError as e:
except ValueError as e:
raise ValueError(f"Invalid JSON string: {e}")


def json_list_type(value: str) -> List[Dict[str, Any]]:
"""Parse a JSON string into a list of dictionaries."""
if not value or value == "":
return []
try:
result = json5.loads(value)
if not isinstance(result, list) or not all(
isinstance(item, dict) for item in result
):
raise ValueError("JSON must be an array/list of objects")
return result
except ValueError as e:
raise ValueError(f"Invalid JSON string: {e}")
Comment thread
hongxicheng marked this conversation as resolved.


Expand Down Expand Up @@ -49,6 +65,58 @@ def get_args() -> Namespace:
type=bool,
required=True,
)
parser.add_argument(
"--longTermMemory",
type=bool,
default=False,
help="Whether to enable long-term memory and embedding support.",
)
parser.add_argument(
"--embeddingProvider",
type=str,
choices=["dashscope", "openai", "gemini", "ollama"],
help="Embedding provider name, e.g. openai/dashscope/gemini/ollama.",
)
parser.add_argument(
"--embeddingModelName",
type=str,
help="Embedding model name, e.g. text-embedding-3-small or text-embedding-v1.",
)
parser.add_argument(
"--embeddingApiKey",
type=str,
help="API key for the embedding provider; falls back to --apiKey if not set.",
)
parser.add_argument(
"--embeddingKwargs",
type=json_type,
default={},
help="A JSON string for extra kwargs passed to the embedding model (e.g. host, dimensions).",
)
parser.add_argument(
"--saveToLocal",
type=bool,
default=False,
help="Whether to save long-term memory to local disk.",
)
parser.add_argument(
"--localStoragePath",
type=str,
default="",
help="Local storage path for long-term memory.",
)
parser.add_argument(
"--vectorStoreProvider",
type=str,
default="qdrant",
help="Vector store provider for long-term memory (e.g. qdrant, chroma, faiss).",
)
parser.add_argument(
"--vectorStoreKwargs",
type=json_type,
default={},
help="A JSON string for extra kwargs passed to the vector store configuration.",
)
parser.add_argument(
"--clientKwargs",
type=json_type,
Expand All @@ -61,5 +129,11 @@ def get_args() -> Namespace:
default={},
help="A JSON string representing a dictionary of keyword arguments to pass to the LLM generate method.",
)
parser.add_argument(
"--mcpServers",
type=json_list_type,
default=[],
help="A JSON string representing a list of MCP server configurations.",
)
args = parser.parse_args()
return args
92 changes: 84 additions & 8 deletions packages/app/friday/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import json5
from agentscope.agent import ReActAgent
from agentscope.memory import InMemoryMemory
from agentscope.memory import InMemoryMemory, Mem0LongTermMemory
from agentscope.message import Msg
from agentscope.session import JSONSession
from agentscope.tool import (
Expand All @@ -25,7 +25,7 @@
studio_post_reply_hook,
)
from args import get_args
from model import get_model, get_formatter
from model import get_model, get_formatter, get_embedding_model
from tool.utils import (
view_agentscope_library,
view_agentscope_readme,
Expand All @@ -35,6 +35,9 @@
from utils.connect import StudioConnect
from utils.constants import FRIDAY_SESSION_ID

from mcp_manager import connect_mcp_servers, close_mcp_connections
from mem0.vector_stores.configs import VectorStoreConfig


async def main():
args = get_args()
Expand Down Expand Up @@ -88,14 +91,68 @@ async def main():
view_agentscope_faq, group_name="agentscope_tools"
)

# Get MCP servers configuration and connect
mcp_servers = args.mcpServers if hasattr(args, 'mcpServers') else []
local_mcp_clients = await connect_mcp_servers(mcp_servers, toolkit)

# get model from args
model = get_model(args.llmProvider, args.modelName, args.apiKey, args.clientKwargs, args.generateKwargs)
model = get_model(
args.llmProvider,
args.modelName,
args.apiKey,
args.clientKwargs,
args.generateKwargs,
)
formatter = get_formatter(args.llmProvider)

# Create the ReAct agent
agent = ReActAgent(
name="Friday",
sys_prompt="""You're Friday, a helpful assistant specialized in daily task management and AgentScope framework support.
# Initialize long-term memory if enabled
long_term_memory = None
if args.longTermMemory:
# Create non-streaming model for long-term memory operations
memory_model = get_model(
args.llmProvider,
args.modelName,
args.apiKey,
args.clientKwargs,
args.generateKwargs,
stream=False,
)
embedding_model = get_embedding_model(
embeddingProvider=args.embeddingProvider,
embeddingModelName=args.embeddingModelName,
embeddingApiKey=args.embeddingApiKey,
Comment thread
hongxicheng marked this conversation as resolved.
embedding_kwargs=args.embeddingKwargs,
)

# Prepare vector store config with embedding dimensions
vector_store_config_dict = {}

# Add Qdrant-specific configuration
if args.vectorStoreProvider == "qdrant":
vector_store_config_dict["on_disk"] = args.saveToLocal
vector_store_config_dict["path"] = args.localStoragePath
Comment thread
hongxicheng marked this conversation as resolved.

# Add embedding_model_dims for vector stores that need it (e.g., Qdrant)
if (dimensions := getattr(embedding_model, 'dimensions', None)) is not None:
vector_store_config_dict["embedding_model_dims"] = dimensions

# Merge vectorStoreKwargs into the config
if args.vectorStoreKwargs:
vector_store_config_dict.update(args.vectorStoreKwargs)

long_term_memory = Mem0LongTermMemory(
agent_name="Friday",
user_name="Studio",
model=memory_model,
embedding_model=embedding_model,
vector_store_config=VectorStoreConfig(
provider=args.vectorStoreProvider,
config=vector_store_config_dict,
),
)

# Build system prompt
sys_prompt = """You're Friday, a helpful assistant specialized in daily task management and AgentScope framework support.

# Core Objectives
- Help users manage and complete daily tasks efficiently
Expand Down Expand Up @@ -125,7 +182,21 @@ async def main():
- Never guess or make up implementations

# Available Context
- Current date and time: {current_time}""".format(
- Current date and time: {current_time}"""

# Add long-term memory information if enabled
if args.longTermMemory:
sys_prompt += """

# Long-term Memory
- You have long-term memory enabled, which allows you to remember information across conversations
- Use this capability to provide more personalized and context-aware assistance
- You can reference past interactions and learned preferences to better serve the user"""

# Create the ReAct agent
agent = ReActAgent(
name="Friday",
sys_prompt=sys_prompt.format(
current_time=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
max_turns=20,
),
Expand All @@ -135,6 +206,8 @@ async def main():
memory=InMemoryMemory(),
max_iters=50,
enable_meta_tool=True,
long_term_memory=long_term_memory,
long_term_memory_mode='both'
)

path_dialog_history = get_local_file_path("")
Expand Down Expand Up @@ -164,6 +237,9 @@ async def main():
session_id=FRIDAY_SESSION_ID,
friday=agent
)

# Close local MCP connections
await close_mcp_connections(local_mcp_clients)

if __name__ == '__main__':
asyncio.run(main())
8 changes: 8 additions & 0 deletions packages/app/friday/mcp_manager/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# -*- coding: utf-8 -*-
"""MCP module for Friday."""
from .manager import connect_mcp_servers, close_mcp_connections

__all__ = [
'connect_mcp_servers',
'close_mcp_connections',
]
Loading
Loading