Dirk Haupt
commited on
Commit
·
b67dc9d
1
Parent(s):
234ceb7
init hf commit
Browse files- .gitignore +3 -0
- README.md +12 -4
- app.py +50 -0
- quickstart.py +220 -0
- requirements.txt +23 -0
.gitignore
CHANGED
@@ -169,3 +169,6 @@ cython_debug/
|
|
169 |
|
170 |
# PyPI configuration file
|
171 |
.pypirc
|
|
|
|
|
|
|
|
169 |
|
170 |
# PyPI configuration file
|
171 |
.pypirc
|
172 |
+
|
173 |
+
.DS_Store
|
174 |
+
/llm
|
README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1 |
---
|
2 |
-
title: Emotional Game Theory
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: streamlit
|
7 |
sdk_version: 1.41.1
|
8 |
app_file: app.py
|
@@ -12,3 +12,11 @@ short_description: Emotional Game Theory AI
|
|
12 |
---
|
13 |
|
14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
---
|
2 |
+
title: Emotional Game Theory AI
|
3 |
+
emoji: 🎤
|
4 |
+
colorFrom: green
|
5 |
+
colorTo: purple
|
6 |
sdk: streamlit
|
7 |
sdk_version: 1.41.1
|
8 |
app_file: app.py
|
|
|
12 |
---
|
13 |
|
14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
15 |
+
|
16 |
+
# AI Safety Game Theory Experiment
|
17 |
+
|
18 |
+
An experimental application exploring how emotional data impacts AI decision-making in game-theoretic scenarios.
|
19 |
+
|
20 |
+
## Setup
|
21 |
+
|
22 |
+
1. Install dependencies:
|
app.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import asyncio
|
3 |
+
from quickstart import WebSocketHandler, AsyncHumeClient, ChatConnectOptions, MicrophoneInterface
|
4 |
+
import os
|
5 |
+
from dotenv import load_dotenv
|
6 |
+
|
7 |
+
# Page config
|
8 |
+
st.set_page_config(
|
9 |
+
page_title="Hume.ai Voice Chat",
|
10 |
+
page_icon="🎤",
|
11 |
+
layout="centered"
|
12 |
+
)
|
13 |
+
|
14 |
+
st.title("Hume.ai Voice Chat Demo")
|
15 |
+
|
16 |
+
# Load environment variables
|
17 |
+
load_dotenv()
|
18 |
+
|
19 |
+
async def run_chat():
|
20 |
+
# Initialize client and handlers
|
21 |
+
client = AsyncHumeClient(api_key=st.secrets["HUME_API_KEY"])
|
22 |
+
options = ChatConnectOptions(
|
23 |
+
config_id=st.secrets["HUME_CONFIG_ID"],
|
24 |
+
secret_key=st.secrets["HUME_SECRET_KEY"]
|
25 |
+
)
|
26 |
+
|
27 |
+
websocket_handler = WebSocketHandler()
|
28 |
+
|
29 |
+
async with client.empathic_voice.chat.connect_with_callbacks(
|
30 |
+
options=options,
|
31 |
+
on_open=websocket_handler.on_open,
|
32 |
+
on_message=websocket_handler.on_message,
|
33 |
+
on_close=websocket_handler.on_close,
|
34 |
+
on_error=websocket_handler.on_error
|
35 |
+
) as socket:
|
36 |
+
websocket_handler.set_socket(socket)
|
37 |
+
|
38 |
+
# Create microphone interface task
|
39 |
+
microphone_task = asyncio.create_task(
|
40 |
+
MicrophoneInterface.start(
|
41 |
+
socket,
|
42 |
+
allow_user_interrupt=False,
|
43 |
+
byte_stream=websocket_handler.byte_strs
|
44 |
+
)
|
45 |
+
)
|
46 |
+
|
47 |
+
await microphone_task
|
48 |
+
|
49 |
+
if st.button("Start Chat"):
|
50 |
+
asyncio.run(run_chat())
|
quickstart.py
ADDED
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# from https://github.com/HumeAI/hume-api-examples/blob/main/evi-python-example/quickstart.py
|
2 |
+
|
3 |
+
import asyncio
|
4 |
+
import base64
|
5 |
+
import datetime
|
6 |
+
import os
|
7 |
+
from dotenv import load_dotenv
|
8 |
+
from hume.client import AsyncHumeClient
|
9 |
+
from hume.empathic_voice.chat.socket_client import ChatConnectOptions, ChatWebsocketConnection
|
10 |
+
from hume.empathic_voice.chat.types import SubscribeEvent
|
11 |
+
from hume.empathic_voice.types import UserInput
|
12 |
+
from hume.core.api_error import ApiError
|
13 |
+
from hume import MicrophoneInterface, Stream
|
14 |
+
|
15 |
+
class WebSocketHandler:
|
16 |
+
"""Handler for containing the EVI WebSocket and associated socket handling behavior."""
|
17 |
+
|
18 |
+
def __init__(self):
|
19 |
+
"""Construct the WebSocketHandler, initially assigning the socket to None and the byte stream to a new Stream object."""
|
20 |
+
self.socket = None
|
21 |
+
self.byte_strs = Stream.new()
|
22 |
+
|
23 |
+
def set_socket(self, socket: ChatWebsocketConnection):
|
24 |
+
"""Set the socket.
|
25 |
+
|
26 |
+
This method assigns the provided asynchronous WebSocket connection
|
27 |
+
to the instance variable `self.socket`. It is invoked after successfully
|
28 |
+
establishing a connection using the client's connect method.
|
29 |
+
|
30 |
+
Args:
|
31 |
+
socket (ChatWebsocketConnection): EVI asynchronous WebSocket returned by the client's connect method.
|
32 |
+
"""
|
33 |
+
self.socket = socket
|
34 |
+
|
35 |
+
async def on_open(self):
|
36 |
+
"""Logic invoked when the WebSocket connection is opened."""
|
37 |
+
print("WebSocket connection opened.")
|
38 |
+
|
39 |
+
async def on_message(self, message: SubscribeEvent):
|
40 |
+
"""Callback function to handle a WebSocket message event.
|
41 |
+
|
42 |
+
This asynchronous method decodes the message, determines its type, and
|
43 |
+
handles it accordingly. Depending on the type of message, it
|
44 |
+
might log metadata, handle user or assistant messages, process
|
45 |
+
audio data, raise an error if the message type is "error", and more.
|
46 |
+
|
47 |
+
This method interacts with the following message types to demonstrate logging output to the terminal:
|
48 |
+
- [chat_metadata](https://dev.hume.ai/reference/empathic-voice-interface-evi/chat/chat#receive.Chat%20Metadata.type)
|
49 |
+
- [user_message](https://dev.hume.ai/reference/empathic-voice-interface-evi/chat/chat#receive.User%20Message.type)
|
50 |
+
- [assistant_message](https://dev.hume.ai/reference/empathic-voice-interface-evi/chat/chat#receive.Assistant%20Message.type)
|
51 |
+
- [audio_output](https://dev.hume.ai/reference/empathic-voice-interface-evi/chat/chat#receive.Audio%20Output.type)
|
52 |
+
|
53 |
+
Args:
|
54 |
+
data (SubscribeEvent): This represents any type of message that is received through the EVI WebSocket, formatted in JSON. See the full list of messages in the API Reference [here](https://dev.hume.ai/reference/empathic-voice-interface-evi/chat/chat#receive).
|
55 |
+
"""
|
56 |
+
|
57 |
+
# Create an empty dictionary to store expression inference scores
|
58 |
+
scores = {}
|
59 |
+
|
60 |
+
if message.type == "chat_metadata":
|
61 |
+
message_type = message.type.upper()
|
62 |
+
chat_id = message.chat_id
|
63 |
+
chat_group_id = message.chat_group_id
|
64 |
+
text = f"<{message_type}> Chat ID: {chat_id}, Chat Group ID: {chat_group_id}"
|
65 |
+
elif message.type in ["user_message", "assistant_message"]:
|
66 |
+
role = message.message.role.upper()
|
67 |
+
message_text = message.message.content
|
68 |
+
text = f"{role}: {message_text}"
|
69 |
+
if message.from_text is False:
|
70 |
+
scores = dict(message.models.prosody.scores)
|
71 |
+
elif message.type == "audio_output":
|
72 |
+
message_str: str = message.data
|
73 |
+
message_bytes = base64.b64decode(message_str.encode("utf-8"))
|
74 |
+
await self.byte_strs.put(message_bytes)
|
75 |
+
return
|
76 |
+
elif message.type == "error":
|
77 |
+
error_message: str = message.message
|
78 |
+
error_code: str = message.code
|
79 |
+
raise ApiError(f"Error ({error_code}): {error_message}")
|
80 |
+
else:
|
81 |
+
message_type = message.type.upper()
|
82 |
+
text = f"<{message_type}>"
|
83 |
+
|
84 |
+
# Print the formatted message
|
85 |
+
self._print_prompt(text)
|
86 |
+
|
87 |
+
# Extract and print the top 3 emotions inferred from user and assistant expressions
|
88 |
+
if len(scores) > 0:
|
89 |
+
top_3_emotions = self._extract_top_n_emotions(scores, 3)
|
90 |
+
self._print_emotion_scores(top_3_emotions)
|
91 |
+
print("")
|
92 |
+
else:
|
93 |
+
print("")
|
94 |
+
|
95 |
+
async def on_close(self):
|
96 |
+
"""Logic invoked when the WebSocket connection is closed."""
|
97 |
+
print("WebSocket connection closed.")
|
98 |
+
|
99 |
+
async def on_error(self, error):
|
100 |
+
"""Logic invoked when an error occurs in the WebSocket connection.
|
101 |
+
|
102 |
+
See the full list of errors [here](https://dev.hume.ai/docs/resources/errors).
|
103 |
+
|
104 |
+
Args:
|
105 |
+
error (Exception): The error that occurred during the WebSocket communication.
|
106 |
+
"""
|
107 |
+
print(f"Error: {error}")
|
108 |
+
|
109 |
+
def _print_prompt(self, text: str) -> None:
|
110 |
+
"""Print a formatted message with a timestamp.
|
111 |
+
|
112 |
+
Args:
|
113 |
+
text (str): The message text to be printed.
|
114 |
+
"""
|
115 |
+
now = datetime.datetime.now(tz=datetime.timezone.utc)
|
116 |
+
now_str = now.strftime("%H:%M:%S")
|
117 |
+
print(f"[{now_str}] {text}")
|
118 |
+
|
119 |
+
def _extract_top_n_emotions(self, emotion_scores: dict, n: int) -> dict:
|
120 |
+
"""
|
121 |
+
Extract the top N emotions based on confidence scores.
|
122 |
+
|
123 |
+
Args:
|
124 |
+
emotion_scores (dict): A dictionary of emotions and their corresponding confidence scores.
|
125 |
+
n (int): The number of top emotions to extract.
|
126 |
+
|
127 |
+
Returns:
|
128 |
+
dict: A dictionary containing the top N emotions as keys and their raw scores as values.
|
129 |
+
"""
|
130 |
+
# Convert the dictionary into a list of tuples and sort by the score in descending order
|
131 |
+
sorted_emotions = sorted(emotion_scores.items(), key=lambda item: item[1], reverse=True)
|
132 |
+
|
133 |
+
# Extract the top N emotions
|
134 |
+
top_n_emotions = {emotion: score for emotion, score in sorted_emotions[:n]}
|
135 |
+
|
136 |
+
return top_n_emotions
|
137 |
+
|
138 |
+
def _print_emotion_scores(self, emotion_scores: dict) -> None:
|
139 |
+
"""
|
140 |
+
Print the emotions and their scores in a formatted, single-line manner.
|
141 |
+
|
142 |
+
Args:
|
143 |
+
emotion_scores (dict): A dictionary of emotions and their corresponding confidence scores.
|
144 |
+
"""
|
145 |
+
# Format the output string
|
146 |
+
formatted_emotions = ' | '.join([f"{emotion} ({score:.2f})" for emotion, score in emotion_scores.items()])
|
147 |
+
|
148 |
+
# Print the formatted string
|
149 |
+
print(f"|{formatted_emotions}|")
|
150 |
+
|
151 |
+
|
152 |
+
async def sending_handler(socket: ChatWebsocketConnection):
|
153 |
+
"""Handle sending a message over the socket.
|
154 |
+
|
155 |
+
This method waits 3 seconds and sends a UserInput message, which takes a `text` parameter as input.
|
156 |
+
- https://dev.hume.ai/reference/empathic-voice-interface-evi/chat/chat#send.User%20Input.type
|
157 |
+
|
158 |
+
See the full list of messages to send [here](https://dev.hume.ai/reference/empathic-voice-interface-evi/chat/chat#send).
|
159 |
+
|
160 |
+
Args:
|
161 |
+
socket (ChatWebsocketConnection): The WebSocket connection used to send messages.
|
162 |
+
"""
|
163 |
+
# Wait 3 seconds before executing the rest of the method
|
164 |
+
await asyncio.sleep(3)
|
165 |
+
|
166 |
+
# Construct a user input message
|
167 |
+
# user_input_message = UserInput(text="Hello there!")
|
168 |
+
|
169 |
+
# Send the user input as text to the socket
|
170 |
+
# await socket.send_user_input(user_input_message)
|
171 |
+
|
172 |
+
async def main() -> None:
|
173 |
+
# Retrieve any environment variables stored in the .env file
|
174 |
+
load_dotenv()
|
175 |
+
|
176 |
+
# Retrieve the API key, Secret key, and EVI config id from the environment variables
|
177 |
+
HUME_API_KEY = os.getenv("HUME_API_KEY")
|
178 |
+
HUME_SECRET_KEY = os.getenv("HUME_SECRET_KEY")
|
179 |
+
HUME_CONFIG_ID = os.getenv("HUME_CONFIG_ID")
|
180 |
+
|
181 |
+
# Initialize the asynchronous client, authenticating with your API key
|
182 |
+
client = AsyncHumeClient(api_key=HUME_API_KEY)
|
183 |
+
|
184 |
+
# Define options for the WebSocket connection, such as an EVI config id and a secret key for token authentication
|
185 |
+
# See the full list of query parameters here: https://dev.hume.ai/reference/empathic-voice-interface-evi/chat/chat#request.query
|
186 |
+
options = ChatConnectOptions(config_id=HUME_CONFIG_ID, secret_key=HUME_SECRET_KEY)
|
187 |
+
|
188 |
+
# Instantiate the WebSocketHandler
|
189 |
+
websocket_handler = WebSocketHandler()
|
190 |
+
|
191 |
+
# Open the WebSocket connection with the configuration options and the handler's functions
|
192 |
+
async with client.empathic_voice.chat.connect_with_callbacks(
|
193 |
+
options=options,
|
194 |
+
on_open=websocket_handler.on_open,
|
195 |
+
on_message=websocket_handler.on_message,
|
196 |
+
on_close=websocket_handler.on_close,
|
197 |
+
on_error=websocket_handler.on_error
|
198 |
+
) as socket:
|
199 |
+
|
200 |
+
# Set the socket instance in the handler
|
201 |
+
websocket_handler.set_socket(socket)
|
202 |
+
|
203 |
+
# Create an asynchronous task to continuously detect and process input from the microphone, as well as play audio
|
204 |
+
microphone_task = asyncio.create_task(
|
205 |
+
MicrophoneInterface.start(
|
206 |
+
socket,
|
207 |
+
allow_user_interrupt=False,
|
208 |
+
byte_stream=websocket_handler.byte_strs
|
209 |
+
)
|
210 |
+
)
|
211 |
+
|
212 |
+
# Create an asynchronous task to send messages over the WebSocket connection
|
213 |
+
message_sending_task = asyncio.create_task(sending_handler(socket))
|
214 |
+
|
215 |
+
# Schedule the coroutines to occur simultaneously
|
216 |
+
await asyncio.gather(microphone_task, message_sending_task)
|
217 |
+
|
218 |
+
# Execute the main asynchronous function using asyncio's event loop
|
219 |
+
if __name__ == "__main__":
|
220 |
+
asyncio.run(main())
|
requirements.txt
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit
|
2 |
+
python-dotenv
|
3 |
+
hume[microphone]
|
4 |
+
|
5 |
+
aiofiles==24.1.0
|
6 |
+
annotated-types==0.7.0
|
7 |
+
anyio==4.8.0
|
8 |
+
certifi==2025.1.31
|
9 |
+
cffi==1.17.1
|
10 |
+
eval-type-backport==0.2.2
|
11 |
+
h11==0.14.0
|
12 |
+
httpcore==1.0.7
|
13 |
+
httpx==0.28.1
|
14 |
+
idna==3.10
|
15 |
+
pycparser==2.22
|
16 |
+
pydantic==2.10.6
|
17 |
+
pydantic-core==2.27.2
|
18 |
+
pydub==0.25.1
|
19 |
+
simpleaudio==1.0.4
|
20 |
+
sniffio==1.3.1
|
21 |
+
sounddevice==0.4.7
|
22 |
+
typing-extensions==4.12.2
|
23 |
+
websockets==12.0
|