diff --git a/examples/example-twitter.py b/examples/example-twitter.py index 750ed98..285a3ab 100644 --- a/examples/example-twitter.py +++ b/examples/example-twitter.py @@ -1,5 +1,6 @@ import os -from virtuals_sdk import game +from virtuals_sdk import game + agent = game.Agent( api_key=os.environ.get("VIRTUALS_API_KEY"), @@ -35,12 +36,78 @@ ) ) +# adding shared template for twitter +agent.add_share_template( + start_system_prompt="You are a twitter post generator. You can write a variety of tweets. Your tweet style should follow the character described below. ", + shared_prompt="""{{twitterPublicStartSysPrompt}} + +You are roleplaying as {{agentName}}. Do not break out of character. + +Character description: +{{description}} + +Character goal: +{{twitterGoal}} + +These are the world info that might be useful as additional context for your response. You do not need to use any of the information describe in this section if you don't need it. +{{worldInfo}} + +{{retrieveKnowledge}} + +This your post history, you should evaluate if it is repetitive or aligned with your goal. Post history is sorted by oldest to newest. Be creative. +{{postHistory}} + +{{twitterPublicEndSysPrompt}} + +Prepare your thought process first and then only curate the response. You must reply in this format. You only need to have one chain of thought and 5 answers.""", + end_system_prompt="Rule: - Do not host Twitter space, do not use hashtag. - Do not give any contract address" +) + +# adding template for twitter +agent.add_template( + game.Template( + template_type="POST", + user_prompt="{{agentName}}'s suggested tweet content: {{task}}. {{agentName}}'s reasoning: {{taskReasoning}}. Build a new tweet with the suggested tweet content. Do not hold twitter space. Do not use hashtag.", + sys_prompt_response_format=[10,20,30,50], + temperature=0.5, + top_k=50, + top_p=0.7, + repetition_penalty=1.0, + model="meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", + type="POST", + isSandbox=False + ) +) + +agent.add_template( + game.Template( + template_type="REPLY", + user_prompt="""{{agentName}}'s suggested tweet content: {{task}}. {{agentName}}'s reasoning: {{taskReasoning}} + +You will be given the author information and your impression on the author. You should formulate your response based on the suggested tweet content accordingly. {{author}}'s bio: {{bio}} + +This is the ongoing conversation history: {{conversationHistory}}. +""", + sys_prompt_response_format=[10,20,30,50], + temperature=0.5, + top_k=50, + top_p=0.7, + repetition_penalty=1.0, + model="meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", + type="REPLY" + ) +) + +# set empty array to remove the default usernames +agent.set_tweet_usernames([]) +agent.deploy_twitter() + # running reaction module only for platform twitter agent.react( session_id="session-twitter", platform="twitter", tweet_id="1869281466628349975", -) +) # running simulation module only for platform twitter agent.simulate_twitter(session_id="session-twitter") diff --git a/src/virtuals_sdk/game.py b/src/virtuals_sdk/game.py index 09d6723..6660b8b 100644 --- a/src/virtuals_sdk/game.py +++ b/src/virtuals_sdk/game.py @@ -154,6 +154,104 @@ def __call__(self, *args): raise requests.exceptions.HTTPError(f"Request failed: {error_msg}") +@dataclass +class Template: + template_type: str + system_prompt: str = None + sys_prompt_response_format: List[int] = None + model: str = "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo" + temperature: float = 1.0 + user_prompt: str = None + top_k: float = 50.0 + top_p: float = 0.7 + repetition_penalty: float = 1.0 + type: str = None + taskDescription: str = None + isSandbox: bool = False + + def _validate_fields(self): + """Validate all template fields and their values""" + # Validate required fields + if not self.template_type: + raise ValueError("template_type is required") + + if self.template_type not in ["POST", "REPLY", "SHARED", "TWITTER_START_SYSTEM_PROMPT", "TWITTER_END_SYSTEM_PROMPT"]: + raise ValueError(f"{self.template_type} is invalid, valid types are POST, REPLY, SHARED, TWITTER_START_SYSTEM_PROMPT, TWITTER_END_SYSTEM_PROMPT") + + # Set default values based on template_type + if self.template_type in ["POST", "REPLY"]: + if not self.user_prompt: + raise ValueError("user_prompt is required") + # Common settings for POST and REPLY + self.sys_prompt_response_format = self.sys_prompt_response_format or [10, 20, 40, 60, 80] + self.temperature = self.temperature or 1.0 + self.top_k = self.top_k or 50.0 + self.top_p = self.top_p or 0.7 + self.repetition_penalty = self.repetition_penalty or 1.0 + self.model = self.model or "meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo" + self.type = self.template_type + self.isSandbox = False + self.userPrompt = self.user_prompt or "" + + # Additional settings for REPLY only + if self.template_type == "REPLY": + self.taskDescription = self.taskDescription or "Process incoming tweet. Ignore if it is boring or unimportant. Ignore if the conversation has gone too long." + + elif self.template_type in ["TWITTER_START_SYSTEM_PROMPT", "TWITTER_END_SYSTEM_PROMPT", "SHARED"]: + if not self.system_prompt: + raise ValueError("system_prompt is required") + self.sys_prompt_response_format = [] + + # Validate sys_prompt_response_format type and values + if self.sys_prompt_response_format is not None: + if not isinstance(self.sys_prompt_response_format, list): + raise TypeError("sys_prompt_response_format must be a list of integers") + for num in self.sys_prompt_response_format: + if not isinstance(num, int) or num < 10 or num > 80: + raise ValueError("sys_prompt_response_format values must be integers between 10 and 80") + + # Validate numeric ranges + if not 0.1 <= self.temperature <= 2.0: + raise ValueError("temperature must be between 0.0 and 2.0") + if not 0.1 <= self.top_p <= 1.0: + raise ValueError("top_p must be between 0.1 and 1.0") + if not 1 <= self.top_k <= 100: + raise ValueError("top_k must be between 1 and 100") + if not 0.1 <= self.repetition_penalty <= 2.0: + raise ValueError("repetition_penalty must be greater than or equal to 1.0") + + def __post_init__(self): + self._validate_fields() + + # Convert numeric values to proper type + self.temperature = float(self.temperature) + self.top_k = float(self.top_k) + self.top_p = float(self.top_p) + self.repetition_penalty = float(self.repetition_penalty) + + def to_dict(self) -> dict: + """Convert template to dictionary format for API submission""" + if self.template_type in ["SHARED", "TWITTER_START_SYSTEM_PROMPT", "TWITTER_END_SYSTEM_PROMPT"]: + return { + "templateType": self.template_type, + "systemPrompt": self.system_prompt, + "sysPromptResponseFormat": self.sys_prompt_response_format + } + else: + return { + "templateType": self.template_type, + "sysPromptResponseFormat": self.sys_prompt_response_format, + "model": self.model, + "temperature": self.temperature, + "userPrompt": self.user_prompt, + "topK": self.top_k, + "topP": self.top_p, + "repetitionPenalty": self.repetition_penalty, + "type": self.type, + "isSandbox": self.isSandbox + } + + class Agent: def __init__( self, @@ -161,6 +259,8 @@ def __init__( goal: str = "", description: str = "", world_info: str = "", + main_heartbeat: int = 15, + reaction_heartbeat: int = 5 ): self.game_sdk = sdk.GameSDK(api_key) self.goal = goal @@ -168,6 +268,10 @@ def __init__( self.world_info = world_info self.enabled_functions: List[str] = [] self.custom_functions: List[Function] = [] + self.main_heartbeat = main_heartbeat + self.reaction_heartbeat = reaction_heartbeat + self.templates: List[Template] = [] + self.tweet_usernames: List[str] = [] def set_goal(self, goal: str): self.goal = goal @@ -180,6 +284,22 @@ def set_description(self, description: str): def set_world_info(self, world_info: str): self.world_info = world_info return True + + def set_main_heartbeat(self, main_heartbeat: int): + self.main_heartbeat = main_heartbeat + return True + + def set_reaction_heartbeat(self, reaction_heartbeat: int): + self.reaction_heartbeat = reaction_heartbeat + return True + + def set_tweet_usernames(self, usernames: List[str]) -> bool: + # Check username limit + if len(usernames) > 20: + raise ValueError("Maximum number of usernames allowed is 20") + + self.tweet_usernames = usernames + return True def get_goal(self) -> str: return self.goal @@ -189,6 +309,9 @@ def get_description(self) -> str: def get_world_info(self) -> str: return self.world_info + + def get_tweet_usernames(self) -> List[str]: + return self.tweet_usernames def list_available_default_twitter_functions(self) -> Dict[str, str]: """ @@ -214,6 +337,11 @@ def add_custom_function(self, custom_function: Function) -> bool: self.custom_functions.append(custom_function) return True + + def add_template(self, template: Template) -> bool: + """Add a template to the agent""" + self.templates.append(template) + return True def simulate_twitter(self, session_id: str): """ @@ -254,7 +382,11 @@ def deploy_twitter(self): self.description, self.world_info, self.enabled_functions, - self.custom_functions + self.custom_functions, + self.main_heartbeat, + self.reaction_heartbeat, + self.templates, + self.tweet_usernames ) def export(self) -> str: @@ -274,7 +406,9 @@ def export(self) -> str: "config": asdict(func.config) } for func in self.custom_functions - ] + ], + "templates": [template.to_dict() for template in self.templates], + "tweetUsernames": self.tweet_usernames } agent_json = json.dumps(export_dict, indent=4) @@ -283,3 +417,29 @@ def export(self) -> str: f.write(agent_json) return agent_json + + def add_share_template( + self, + start_system_prompt: str, + shared_prompt: str, + end_system_prompt: str + ) -> bool: + self.add_template(Template( + template_type="TWITTER_START_SYSTEM_PROMPT", + system_prompt=start_system_prompt, + sys_prompt_response_format=[] + )) + + self.add_template(Template( + template_type="SHARED", + system_prompt=shared_prompt, + sys_prompt_response_format=[] + )) + + self.add_template(Template( + template_type="TWITTER_END_SYSTEM_PROMPT", + system_prompt=end_system_prompt, + sys_prompt_response_format=[] + )) + + return True diff --git a/src/virtuals_sdk/sdk.py b/src/virtuals_sdk/sdk.py index 97fa4d0..fe39588 100644 --- a/src/virtuals_sdk/sdk.py +++ b/src/virtuals_sdk/sdk.py @@ -38,7 +38,7 @@ def simulate(self, session_id: str, goal: str, description: str, world_info: st "description": description, "worldInfo": world_info, "functions": functions, - "customFunctions": [x.to_dict() for x in custom_functions] + "customFunctions": [x.toJson() for x in custom_functions] } }, headers={"x-api-key": self.api_key} @@ -75,7 +75,6 @@ def react(self, session_id: str, platform: str, goal: str, if (tweet_id): payload["tweetId"] = tweet_id - print(payload) response = requests.post( url, @@ -90,20 +89,34 @@ def react(self, session_id: str, platform: str, goal: str, return response.json()["data"] - def deploy(self, goal: str, description: str, world_info: str, functions: list, custom_functions: list): + def deploy(self, goal: str, description: str, world_info: str, functions: list, custom_functions: list, main_heartbeat: int, reaction_heartbeat: int, templates: list = None, tweet_usernames: list = None): """ - Simulate the agent configuration + Deploy the agent configuration """ + payload = { + "goal": goal, + "description": description, + "worldInfo": world_info, + "functions": functions, + "customFunctions": [x.toJson() for x in custom_functions], + "gameState": { + "mainHeartbeat": main_heartbeat, + "reactionHeartbeat": reaction_heartbeat, + } + } + + if tweet_usernames is not None: + payload["tweetUsernames"] = tweet_usernames + + # Add templates to payload if provided + if templates: + payload["templates"] = [template.to_dict() for template in templates] + + response = requests.post( f"{self.api_url}/deploy", json={ - "data": { - "goal": goal, - "description": description, - "worldInfo": world_info, - "functions": functions, - "customFunctions": custom_functions - } + "data": payload }, headers={"x-api-key": self.api_key} ) @@ -112,3 +125,5 @@ def deploy(self, goal: str, description: str, world_info: str, functions: list, raise Exception(response.json()) return response.json()["data"] + +