Skip to content

Commit 5e835bf

Browse files
authored
Merge pull request game-by-virtuals#74 from game-by-virtuals/chat_agent
Chat agent
2 parents 5122775 + c26633c commit 5e835bf

File tree

6 files changed

+370
-19
lines changed

6 files changed

+370
-19
lines changed

examples/game/chat_agent.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import os
2+
from typing import Any, Tuple
3+
from game_sdk.game.chat_agent import ChatAgent
4+
from game_sdk.game.custom_types import Argument, Function, FunctionResultStatus
5+
6+
# ACTION SPACE
7+
8+
def generate_picture(prompt: str) -> Tuple[FunctionResultStatus, str, dict[str, Any]]:
9+
print(f"Generated picture with prompt: {prompt}")
10+
return FunctionResultStatus.DONE, "Picture generated and presented to the user", {}
11+
12+
def generate_music(prompt: str) -> Tuple[FunctionResultStatus, str, dict[str, Any]]:
13+
print(f"Generated music with prompt: {prompt}")
14+
return FunctionResultStatus.DONE, "Music generated and presented to the user", {}
15+
16+
17+
def check_crypto_price(currency: str):
18+
prices = {
19+
"bitcoin": 100000,
20+
"ethereum": 20000,
21+
}
22+
result = prices[currency.lower()]
23+
if result is None:
24+
return FunctionResultStatus.FAILED, "The price of the currency is not available", {}
25+
return FunctionResultStatus.DONE, f"The price of {currency} is {result}", {}
26+
27+
28+
action_space = [
29+
Function(
30+
fn_name="generate_picture",
31+
fn_description="Generate a picture",
32+
args=[Argument(name="prompt", description="The prompt for the picture")],
33+
executable=generate_picture,
34+
),
35+
Function(
36+
fn_name="generate_music",
37+
fn_description="Generate a music",
38+
args=[Argument(name="prompt", description="The prompt for the music")],
39+
executable=generate_music,
40+
),
41+
Function(
42+
fn_name="check_crypto_price",
43+
fn_description="Check the price of a crypto currency",
44+
args=[Argument(name="currency", description="The currency to check the price of")],
45+
executable=check_crypto_price,
46+
),
47+
]
48+
49+
api_key = os.environ.get("GAME_API_KEY")
50+
if not api_key:
51+
raise ValueError("GAME_API_KEY is not set")
52+
53+
54+
# CREATE AGENT
55+
agent = ChatAgent(
56+
prompt="You are helpful assistant",
57+
api_key=api_key
58+
)
59+
60+
chat = agent.create_chat(
61+
partner_id="tom",
62+
partner_name="Tom",
63+
action_space=action_space,
64+
)
65+
66+
chat_continue = True
67+
while chat_continue:
68+
69+
user_message = input("Enter a message: ")
70+
71+
response = chat.next(user_message)
72+
73+
if response.function_call:
74+
print(f"Function call: {response.function_call.fn_name}")
75+
76+
if response.message:
77+
print(f"Response: {response.message}")
78+
79+
if response.is_finished:
80+
chat_continue = False
81+
break
82+
83+
print("Chat ended")

src/game_sdk/game/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,65 @@ worker = agent.get_worker("worker_id")
110110
worker.run("Bring me some fruits")
111111
```
112112

113+
### 5. Chat Agents
114+
115+
Chat Agents enable interactive conversations with AI agents that can execute functions. They are simpler to use than full Agents and are ideal for chatbot-like interactions where the agent can perform actions.
116+
117+
```python
118+
# Initialize the chat agent
119+
chat_agent = ChatAgent(
120+
prompt="You are helpful assistant",
121+
api_key="your_api_key"
122+
)
123+
124+
# Define functions
125+
```python
126+
def generate_picture(prompt: str):
127+
# Implementation
128+
return FunctionResultStatus.DONE, "Picture generated", {}
129+
130+
action_space = [
131+
Function(
132+
fn_name="generate_picture",
133+
fn_description="Generate a picture",
134+
args=[Argument(name="prompt", description="The prompt for the picture")],
135+
executable=generate_picture
136+
)
137+
]
138+
139+
# Create a chat session
140+
chat = chat_agent.create_chat(
141+
partner_id="user123",
142+
partner_name="User Name",
143+
action_space=[list_of_functions], # Optional
144+
get_state_fn=lambda: {...} # Optional, allows to push state of the environment to the agent
145+
)
146+
147+
# Run conversation
148+
chat_continue = True
149+
while chat_continue:
150+
user_message = input("Enter a message: ")
151+
response = chat.next(user_message)
152+
...
153+
if response.is_finished:
154+
chat_continue = False
155+
156+
# End chat
157+
chat.end("Optional ending message")
158+
```
159+
160+
### Chat Termination
161+
162+
The chat can be terminated in two ways:
163+
164+
1. **Agent-initiated**: The agent may decide to end the chat on its own when it determines the conversation is complete. In this case, `chat.next()` will return `False`.
165+
166+
2. **Client-initiated**: The client can manually end the chat at any time by calling:
167+
```python
168+
chat.end("Optional farewell message")
169+
```
170+
171+
### Chat Memory
172+
173+
ChatAgent maintains a simple short-term memory by keeping track of recent messages in the conversation. This allows the agent to maintain context and provide coherent responses based on the conversation history. The memory is temporary and limited to the current chat session.
174+

src/game_sdk/game/agent.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,7 @@ def step(self):
342342
"is_global": True
343343
}
344344

345+
return action_response, self._session.function_result
345346

346347
def run(self):
347348
self._session = Session()

src/game_sdk/game/api_v2.py

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,7 @@ def create_agent(self, name: str, description: str, goal: str) -> str:
2828
json=payload
2929
)
3030

31-
if response.status_code != 200:
32-
raise ValueError(f"Failed to create agent (status {response.status_code}). Response: {response.text}")
33-
34-
response_json = response.json()
35-
36-
return response_json["data"]["id"]
31+
return self._get_response_body(response)["id"]
3732

3833
def create_workers(self, workers: List) -> str:
3934
"""
@@ -54,12 +49,7 @@ def create_workers(self, workers: List) -> str:
5449
json=payload
5550
)
5651

57-
if response.status_code != 200:
58-
raise ValueError(f"Failed to get token (status {response.status_code}). Response: {response.text}")
59-
60-
response_json = response.json()
61-
62-
return response_json["data"]["id"]
52+
return self._get_response_body(response)["id"]
6353

6454
def set_worker_task(self, agent_id: str, task: str) -> Dict:
6555
"""
@@ -77,12 +67,7 @@ def set_worker_task(self, agent_id: str, task: str) -> Dict:
7767
json=payload
7868
)
7969

80-
if response.status_code != 200:
81-
raise ValueError(f"Failed to set worker task (status {response.status_code}). Response: {response.text}")
82-
83-
response_json = response.json()
84-
85-
return response_json["data"]
70+
return self._get_response_body(response)
8671

8772
def get_worker_action(self, agent_id: str, submission_id: str, data: dict, model_name: str) -> Dict:
8873
"""
@@ -120,4 +105,64 @@ def get_agent_action(self, agent_id: str, data: dict, model_name: str) -> Dict:
120105

121106
response_json = response.json()
122107

108+
return response_json["data"]
109+
110+
def create_chat(self, data: dict) -> str:
111+
response = requests.post(
112+
f"{self.base_url}/conversation",
113+
headers=self.headers,
114+
json={
115+
"data": data
116+
}
117+
)
118+
119+
chat_id = self._get_response_body(response).get("conversation_id")
120+
if not chat_id:
121+
raise Exception("Agent did not return a conversation_id for the chat.")
122+
return chat_id
123+
124+
def update_chat(self, conversation_id: str, data: dict) -> dict:
125+
response = requests.post(
126+
f"{self.base_url}/conversation/{conversation_id}/next",
127+
headers=self.headers,
128+
json={
129+
"data": data
130+
}
131+
)
132+
133+
if response.status_code != 200:
134+
raise ValueError(f"Failed to update conversation (status {response.status_code}). Response: {response.text}")
135+
136+
response_json = response.json()
137+
138+
return response_json["data"]
139+
140+
def report_function(self, conversation_id: str, data: dict) -> dict:
141+
response = requests.post(
142+
f"{self.base_url}/conversation/{conversation_id}/function/result",
143+
headers=self.headers,
144+
json={
145+
"data": data
146+
}
147+
)
148+
149+
return self._get_response_body(response)
150+
151+
def end_chat(self, conversation_id: str, data: dict) -> dict:
152+
response = requests.post(
153+
f"{self.base_url}/conversation/{conversation_id}/end",
154+
headers=self.headers,
155+
json={
156+
"data": data
157+
}
158+
)
159+
160+
return self._get_response_body(response)
161+
162+
def _get_response_body(self, response: requests.Response) -> dict:
163+
if response.status_code != 200:
164+
raise ValueError(f"Failed to get response body (status {response.status_code}). Response: {response.text}")
165+
166+
response_json = response.json()
167+
123168
return response_json["data"]

0 commit comments

Comments
 (0)