fay自然进化

1. 增加清除记忆功能;
2. 增加克隆性格功能;
3. 增加认知模型(专属的记忆逻辑、反思逻辑);
4. 修复自动播报bug;
5. fay_url配置响修正;
6. 修复流式输出前置换行问题;
7. 修复没有用户聊天记录前端反复添加默认用户问题;
8. 更新dockerfile;
9. 重构util.py代码。
1. Fay ai编程指南:https://qqk9ntwbcit.feishu.cn/wiki/FKFywXWaeiBH28k4Q67c3eF7njC
2.Fay认知模型:https://qqk9ntwbcit.feishu.cn/wiki/BSW3wSsMdikiHUkiCJYcSp2lnio
This commit is contained in:
xszyou
2025-04-02 23:31:46 +08:00
parent c7c0e4605e
commit 11e115b228
48 changed files with 4051 additions and 1048 deletions
+4 -3
View File
@@ -24,15 +24,16 @@
- 支持数字人自动播报模式(虚拟教师、虚拟主播、新闻播报)
- 支持任意终端使用:单片机、app、网站、大屏、成熟系统接入等
- 支持多用户多路并发
- 提供文字沟通接口、声音沟通接口、数字人模型接口、管理控制接口、自动播报接口、意图接口
- 提供文字交互接口、语音交互接口、数字人驱动接口、管理控制接口、自动播报接口、意图接口
- 支持语音指令灵活配置执行
- 支持自定义知识库、自定义问答对、自定义人设信息
- 支持唤醒及打断对话
- 支持服务器及单机模式
- 支持机器人表情输出
- 支持react agent自主决策执行、主动对话
- 支持react agent自主决策执行、主动对话(准备升级到MCP协议)
- 支持后台静默启动
- 支持deepseek
- 设计独特的认知模型
###
@@ -51,7 +52,7 @@
### **环境**
- Python 3.9、3.10、3.11、3.12
- Python 3.12
- Windows、macos、linux
### **安装依赖**
+2 -2
View File
@@ -6,11 +6,11 @@
"constellation": "\u6c34\u74f6\u5ea7",
"contact": "qq467665317",
"gender": "\u5973",
"goal": "\u89e3\u51b3\u95ee\u9898",
"goal": "\u5de5\u4f5c\u534f\u52a9",
"hobby": "\u53d1\u5446",
"job": "\u52a9\u7406",
"name": "\u83f2\u83f2",
"position": "\u5ba2\u670d",
"position": "\u966a\u4f34",
"voice": "\u6653\u6653(edge)",
"zodiac": "\u86c7"
},
+4 -4
View File
@@ -17,7 +17,7 @@ class Authorize_Tb:
#初始化
def init_tb(self):
conn = sqlite3.connect('fay.db')
conn = sqlite3.connect('memory/fay.db')
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS T_Authorize
@@ -34,7 +34,7 @@ class Authorize_Tb:
@synchronized
def add(self,userid,accesstoken,expirestime):
self.init_tb()
conn = sqlite3.connect("fay.db")
conn = sqlite3.connect("memory/fay.db")
cur = conn.cursor()
cur.execute("insert into T_Authorize (userid,accesstoken,expirestime,createtime) values (?,?,?,?)",(userid,accesstoken,expirestime,int(time.time())))
@@ -46,7 +46,7 @@ class Authorize_Tb:
@synchronized
def find_by_userid(self,userid):
self.init_tb()
conn = sqlite3.connect("fay.db")
conn = sqlite3.connect("memory/fay.db")
cur = conn.cursor()
cur.execute("select accesstoken,expirestime from T_Authorize where userid = ? order by id desc limit 1",(userid,))
info = cur.fetchone()
@@ -57,7 +57,7 @@ class Authorize_Tb:
@synchronized
def update_by_userid(self, userid, new_accesstoken, new_expirestime):
self.init_tb()
conn = sqlite3.connect("fay.db")
conn = sqlite3.connect("memory/fay.db")
cur = conn.cursor()
cur.execute("UPDATE T_Authorize SET accesstoken = ?, expirestime = ? WHERE userid = ?",
(new_accesstoken, new_expirestime, userid))
+11 -6
View File
@@ -26,7 +26,8 @@ class Content_Db:
# 初始化数据库
def init_db(self):
conn = sqlite3.connect('fay.db')
conn = sqlite3.connect('memory/fay.db')
conn.text_factory = str
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS T_Msg
(id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -48,7 +49,8 @@ class Content_Db:
# 添加对话
@synchronized
def add_content(self, type, way, content, username='User', uid=0):
conn = sqlite3.connect("fay.db")
conn = sqlite3.connect("memory/fay.db")
conn.text_factory = str
cur = conn.cursor()
try:
cur.execute("INSERT INTO T_Msg (type, way, content, createtime, username, uid) VALUES (?, ?, ?, ?, ?, ?)",
@@ -65,7 +67,8 @@ class Content_Db:
# 根据ID查询对话记录
@synchronized
def get_content_by_id(self, msg_id):
conn = sqlite3.connect("fay.db")
conn = sqlite3.connect("memory/fay.db")
conn.text_factory = str
cur = conn.cursor()
cur.execute("SELECT * FROM T_Msg WHERE id = ?", (msg_id,))
record = cur.fetchone()
@@ -75,7 +78,8 @@ class Content_Db:
# 添加对话采纳记录
@synchronized
def adopted_message(self, msg_id):
conn = sqlite3.connect('fay.db')
conn = sqlite3.connect('memory/fay.db')
conn.text_factory = str
cur = conn.cursor()
# 检查消息ID是否存在
cur.execute("SELECT 1 FROM T_Msg WHERE id = ?", (msg_id,))
@@ -96,7 +100,8 @@ class Content_Db:
# 获取对话内容
@synchronized
def get_list(self, way, order, limit, uid=0):
conn = sqlite3.connect("fay.db")
conn = sqlite3.connect("memory/fay.db")
conn.text_factory = str
cur = conn.cursor()
where_uid = ""
if int(uid) != 0:
@@ -126,7 +131,7 @@ class Content_Db:
@synchronized
def get_previous_user_message(self, msg_id):
conn = sqlite3.connect("fay.db")
conn = sqlite3.connect("memory/fay.db")
cur = conn.cursor()
cur.execute("""
SELECT id, type, way, content, createtime, datetime(createtime, 'unixepoch', 'localtime') AS timetext, username
+29 -24
View File
@@ -31,6 +31,7 @@ from llm import nlp_coze
from llm.agent import fay_agent
from llm import nlp_qingliu
from llm import nlp_gpt_stream
from llm import nlp_cognitive_stream
from core import member_db
import threading
@@ -64,8 +65,8 @@ modules = {
"nlp_coze": nlp_coze,
"nlp_agent": fay_agent,
"nlp_qingliu": nlp_qingliu,
"nlp_gpt_stream": nlp_gpt_stream
"nlp_gpt_stream": nlp_gpt_stream,
"nlp_cognitive_stream": nlp_cognitive_stream
}
#大语言模型回复
@@ -83,7 +84,7 @@ def handle_chat_message(msg, username='User', observation='', cache=None):
if cfg.key_chat_module == 'rasa':
textlist = selected_module.question(msg)
text = textlist[0]['text']
elif cfg.key_chat_module == 'gpt_stream' and cache is not None:#TODO 好像是多余了
elif cfg.key_chat_module.endswith('_stream') and cache is not None:#支持所有流式输出模块
uid = member_db.new_instance().find_user(username)
text = selected_module.question(msg, uid, observation, cache)
else:
@@ -165,9 +166,9 @@ class FeiFei:
textlist = []
if answer is None:
if wsa_server.get_web_instance().is_connected(username):
wsa_server.get_web_instance().add_cmd({"panelMsg": "思考中...", "Username" : username, 'robot': f'http://{cfg.fay_url}:5000/robot/Thinking.jpg'})
wsa_server.get_web_instance().add_cmd({"panelMsg": "思考中...", "Username" : username, 'robot': f'{cfg.fay_url}/robot/Thinking.jpg'})
if wsa_server.get_instance().is_connected(username):
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "思考中..."}, 'Username' : username, 'robot': f'http://{cfg.fay_url}:5000/robot/Thinking.jpg'}
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "思考中..."}, 'Username' : username, 'robot': f'{cfg.fay_url}/robot/Thinking.jpg'}
wsa_server.get_instance().add_cmd(content)
text,textlist = handle_chat_message(interact.data["msg"], username, interact.data.get("observation", ""))
@@ -177,8 +178,8 @@ class FeiFei:
#记录回复并输出到各个终端
self.__process_text_output(text, textlist, username, uid, type)
#声音输出(gpt_stream在stream_manager.py中调用了say函数)
if type == 'qa' or cfg.key_chat_module != 'gpt_stream':
#声音输出(支持流式输出的模块在stream_manager.py中调用了say函数)
if type == 'qa' or not cfg.key_chat_module.endswith('_stream'):
if "</think>" in text:
text = text.split("</think>")[1]
interact.data['isfirst'] = True
@@ -219,7 +220,7 @@ class FeiFei:
username = interact.data.get("user", "User")
if member_db.new_instance().is_username_exist(username) == "notexists":
member_db.new_instance().add_user(username)
if cfg.key_chat_module == "gpt_stream":
if cfg.key_chat_module.endswith('_stream'):
MyThread(target=self.__process_interact, args=[interact]).start()
return None
return self.__process_interact(interact)
@@ -314,9 +315,9 @@ class FeiFei:
if self.think_mode_users.get(uid, False) and is_start_think:
if wsa_server.get_web_instance().is_connected(interact.data.get('user')):
wsa_server.get_web_instance().add_cmd({"panelMsg": "思考中...", "Username" : interact.data.get('user'), 'robot': f'http://{cfg.fay_url}:5000/robot/Thinking.jpg'})
wsa_server.get_web_instance().add_cmd({"panelMsg": "思考中...", "Username" : interact.data.get('user'), 'robot': f'{cfg.fay_url}/robot/Thinking.jpg'})
if wsa_server.get_instance().is_connected(interact.data.get("user")):
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "思考中..."}, 'Username' : interact.data.get('user'), 'robot': f'http://{cfg.fay_url}:5000/robot/Thinking.jpg'}
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "思考中..."}, 'Username' : interact.data.get('user'), 'robot': f'{cfg.fay_url}/robot/Thinking.jpg'}
wsa_server.get_instance().add_cmd(content)
# 如果用户在think模式中,不进行语音合成
@@ -336,7 +337,7 @@ class FeiFei:
util.printInfo(1, interact.data.get("user"), "合成音频完成. 耗时: {} ms 文件:{}".format(math.floor((time.time() - tm) * 1000), result))
else:
if is_end and wsa_server.get_web_instance().is_connected(interact.data.get('user')):
wsa_server.get_web_instance().add_cmd({"panelMsg": "", 'Username' : interact.data.get('user'), 'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'})
wsa_server.get_web_instance().add_cmd({"panelMsg": "", 'Username' : interact.data.get('user'), 'robot': f'{cfg.fay_url}/robot/Normal.jpg'})
if result is not None or is_end:
MyThread(target=self.__process_output_audio, args=[result, interact, text]).start()
@@ -392,8 +393,18 @@ class FeiFei:
is_end = True
util.printInfo(1, interact.data.get('user'), '播放音频...')
self.speaking = True
#自动播报关闭
global auto_play_lock
global can_auto_play
with auto_play_lock:
if self.timer is not None:
self.timer.cancel()
self.timer = None
can_auto_play = False
if wsa_server.get_web_instance().is_connected(interact.data.get('user')):
wsa_server.get_web_instance().add_cmd({"panelMsg": "播放中 ...", "Username" : interact.data.get('user'), 'robot': f'http://{cfg.fay_url}:5000/robot/Speaking.jpg'})
wsa_server.get_web_instance().add_cmd({"panelMsg": "播放中 ...", "Username" : interact.data.get('user'), 'robot': f'{cfg.fay_url}/robot/Speaking.jpg'})
if file_url is not None:
pygame.mixer.music.load(file_url)
@@ -409,7 +420,7 @@ class FeiFei:
if is_end:
self.play_end(interact)
if wsa_server.get_web_instance().is_connected(interact.data.get('user')):
wsa_server.get_web_instance().add_cmd({"panelMsg": "", "Username" : interact.data.get('user'), 'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'})
wsa_server.get_web_instance().add_cmd({"panelMsg": "", "Username" : interact.data.get('user'), 'robot': f'{cfg.fay_url}/robot/Normal.jpg'})
# 播放完毕后通知
if wsa_server.get_web_instance().is_connected(interact.data.get("user")):
wsa_server.get_web_instance().add_cmd({"panelMsg": "", 'Username': interact.data.get('user')})
@@ -463,21 +474,12 @@ class FeiFei:
except Exception as e:
audio_length = 3
#自动播报关闭
global auto_play_lock
global can_auto_play
with auto_play_lock:
if self.timer is not None:
self.timer.cancel()
self.timer = None
can_auto_play = False
#推送远程音频
MyThread(target=self.__send_remote_device_audio, args=[file_url, interact]).start()
#发送音频给数字人接口
if file_url is not None and wsa_server.get_instance().is_connected(interact.data.get("user")):
content = {'Topic': 'human', 'Data': {'Key': 'audio', 'Value': os.path.abspath(file_url), 'HttpValue': f'http://{cfg.fay_url}:5000/audio/' + os.path.basename(file_url), 'Text': text, 'Time': audio_length, 'Type': interact.interleaver}, 'Username' : interact.data.get('user'), 'robot': f'http://{cfg.fay_url}:5000/robot/Speaking.jpg'}
content = {'Topic': 'human', 'Data': {'Key': 'audio', 'Value': os.path.abspath(file_url), 'HttpValue': f'{cfg.fay_url}/audio/' + os.path.basename(file_url), 'Text': text, 'Time': audio_length, 'Type': interact.interleaver}, 'Username' : interact.data.get('user'), 'robot': f'{cfg.fay_url}/robot/Speaking.jpg'}
#计算lips
if platform.system() == "Windows":
try:
@@ -497,7 +499,7 @@ class FeiFei:
self.sound_query.put((file_url, audio_length, interact))
else:
if wsa_server.get_web_instance().is_connected(interact.data.get('user')):
wsa_server.get_web_instance().add_cmd({"panelMsg": "", 'Username' : interact.data.get('user'), 'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'})
wsa_server.get_web_instance().add_cmd({"panelMsg": "", 'Username' : interact.data.get('user'), 'robot': f'{cfg.fay_url}/robot/Normal.jpg'})
except Exception as e:
print(e)
@@ -610,6 +612,9 @@ class FeiFei:
:param uid: 用户ID
:param type: 消息类型
"""
if text:
text = text.strip()
# 记录主回复
content_id = self.__record_response(text, username, uid)
+9 -9
View File
@@ -27,7 +27,7 @@ class Member_Db:
#初始化
def init_db(self):
conn = sqlite3.connect('user_profiles.db')
conn = sqlite3.connect('memory/user_profiles.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS T_Member
(id INTEGER PRIMARY KEY autoincrement,
@@ -39,7 +39,7 @@ class Member_Db:
@synchronized
def add_user(self, username):
if self.is_username_exist(username) == "notexists":
conn = sqlite3.connect('user_profiles.db')
conn = sqlite3.connect('memory/user_profiles.db')
c = conn.cursor()
c.execute('INSERT INTO T_Member (username) VALUES (?)', (username,))
conn.commit()
@@ -52,7 +52,7 @@ class Member_Db:
@synchronized
def update_user(self, username, new_username):
if self.is_username_exist(new_username) == "notexists":
conn = sqlite3.connect('user_profiles.db')
conn = sqlite3.connect('memory/user_profiles.db')
c = conn.cursor()
c.execute('UPDATE T_Member SET username = ? WHERE username = ?', (new_username, username))
conn.commit()
@@ -64,7 +64,7 @@ class Member_Db:
# 删除用户
@synchronized
def delete_user(self, username):
conn = sqlite3.connect('user_profiles.db')
conn = sqlite3.connect('memory/user_profiles.db')
c = conn.cursor()
c.execute('DELETE FROM T_Member WHERE username = ?', (username,))
conn.commit()
@@ -73,7 +73,7 @@ class Member_Db:
# 检查用户名是否已存在
def is_username_exist(self, username):
conn = sqlite3.connect('user_profiles.db')
conn = sqlite3.connect('memory/user_profiles.db')
c = conn.cursor()
c.execute('SELECT COUNT(*) FROM T_Member WHERE username = ?', (username,))
result = c.fetchone()[0]
@@ -85,7 +85,7 @@ class Member_Db:
#根据username查询uid
def find_user(self, username):
conn = sqlite3.connect('user_profiles.db')
conn = sqlite3.connect('memory/user_profiles.db')
c = conn.cursor()
c.execute('SELECT * FROM T_Member WHERE username = ?', (username,))
result = c.fetchone()
@@ -97,7 +97,7 @@ class Member_Db:
#根据uid查询username
def find_username_by_uid(self, uid):
conn = sqlite3.connect('user_profiles.db')
conn = sqlite3.connect('memory/user_profiles.db')
c = conn.cursor()
c.execute('SELECT username FROM T_Member WHERE id = ?', (uid,))
result = c.fetchone()
@@ -112,7 +112,7 @@ class Member_Db:
@synchronized
def query(self, sql):
try:
conn = sqlite3.connect('user_profiles.db')
conn = sqlite3.connect('memory/user_profiles.db')
c = conn.cursor()
c.execute(sql)
results = c.fetchall()
@@ -126,7 +126,7 @@ class Member_Db:
# 获取所有用户
@synchronized
def get_all_users(self):
conn = sqlite3.connect('user_profiles.db')
conn = sqlite3.connect('memory/user_profiles.db')
c = conn.cursor()
c.execute('SELECT * FROM T_Member')
results = c.fetchall()
+12 -12
View File
@@ -120,9 +120,9 @@ class Recorder:
if wake_up:
util.printInfo(1, self.username, "唤醒成功!")
if wsa_server.get_web_instance().is_connected(self.username):
wsa_server.get_web_instance().add_cmd({"panelMsg": "唤醒成功!", "Username" : self.username , 'robot': f'http://{cfg.fay_url}:5000/robot/Listening.jpg'})
wsa_server.get_web_instance().add_cmd({"panelMsg": "唤醒成功!", "Username" : self.username , 'robot': f'{cfg.fay_url}/robot/Listening.jpg'})
if wsa_server.get_instance().is_connected(self.username):
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "唤醒成功!"}, 'Username' : self.username, 'robot': f'http://{cfg.fay_url}:5000/robot/Listening.jpg'}
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "唤醒成功!"}, 'Username' : self.username, 'robot': f'{cfg.fay_url}/robot/Listening.jpg'}
wsa_server.get_instance().add_cmd(content)
self.wakeup_matched = True # 唤醒成功
with fay_core.auto_play_lock:
@@ -136,9 +136,9 @@ class Recorder:
else:
util.printInfo(1, self.username, "[!] 待唤醒!")
if wsa_server.get_web_instance().is_connected(self.username):
wsa_server.get_web_instance().add_cmd({"panelMsg": "[!] 待唤醒!", "Username" : self.username , 'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'})
wsa_server.get_web_instance().add_cmd({"panelMsg": "[!] 待唤醒!", "Username" : self.username , 'robot': f'{cfg.fay_url}/robot/Normal.jpg'})
if wsa_server.get_instance().is_connected(self.username):
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "[!] 待唤醒!"}, 'Username' : self.username, 'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'}
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "[!] 待唤醒!"}, 'Username' : self.username, 'robot': f'{cfg.fay_url}/robot/Normal.jpg'}
wsa_server.get_instance().add_cmd(content)
else:
self.on_speaking(text)
@@ -160,9 +160,9 @@ class Recorder:
if wake_up:
util.printInfo(1, self.username, "唤醒成功!")
if wsa_server.get_web_instance().is_connected(self.username):
wsa_server.get_web_instance().add_cmd({"panelMsg": "唤醒成功!", "Username" : self.username , 'robot': f'http://{cfg.fay_url}:5000/robot/Listening.jpg'})
wsa_server.get_web_instance().add_cmd({"panelMsg": "唤醒成功!", "Username" : self.username , 'robot': f'{cfg.fay_url}/robot/Listening.jpg'})
if wsa_server.get_instance().is_connected(self.username):
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "唤醒成功!"}, 'Username' : self.username, 'robot': f'http://{cfg.fay_url}:5000/robot/Listening.jpg'}
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "唤醒成功!"}, 'Username' : self.username, 'robot': f'{cfg.fay_url}/robot/Listening.jpg'}
wsa_server.get_instance().add_cmd(content)
#去除唤醒词后语句
question = text#[len(wake_up_word):].lstrip()
@@ -173,9 +173,9 @@ class Recorder:
else:
util.printInfo(1, self.username, "[!] 待唤醒!")
if wsa_server.get_web_instance().is_connected(self.username):
wsa_server.get_web_instance().add_cmd({"panelMsg": "[!] 待唤醒!", "Username" : self.username , 'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'})
wsa_server.get_web_instance().add_cmd({"panelMsg": "[!] 待唤醒!", "Username" : self.username , 'robot': f'{cfg.fay_url}/robot/Normal.jpg'})
if wsa_server.get_instance().is_connected(self.username):
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "[!] 待唤醒!"}, 'Username' : self.username, 'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'}
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': "[!] 待唤醒!"}, 'Username' : self.username, 'robot': f'{cfg.fay_url}/robot/Normal.jpg'}
wsa_server.get_instance().add_cmd(content)
#非唤醒模式
@@ -190,9 +190,9 @@ class Recorder:
util.printInfo(1, self.username, "[!] 语音未检测到内容!")
self.dynamic_threshold = self.__get_history_percentage(30)
if wsa_server.get_web_instance().is_connected(self.username):
wsa_server.get_web_instance().add_cmd({"panelMsg": "", 'Username' : self.username, 'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'})
wsa_server.get_web_instance().add_cmd({"panelMsg": "", 'Username' : self.username, 'robot': f'{cfg.fay_url}/robot/Normal.jpg'})
if wsa_server.get_instance().is_connected(self.username):
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': ""}, 'Username' : self.username, 'robot': f'http://{cfg.fay_url}:5000/robot/Normal.jpg'}
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': ""}, 'Username' : self.username, 'robot': f'{cfg.fay_url}/robot/Normal.jpg'}
wsa_server.get_instance().add_cmd(content)
def __record(self):
@@ -313,14 +313,14 @@ class Recorder:
wsa_server.get_web_instance().add_cmd({
"panelMsg": "聆听中...",
'Username': self.username,
'robot': f'http://{cfg.fay_url}:5000/robot/Listening.jpg'
'robot': f'{cfg.fay_url}/robot/Listening.jpg'
})
if wsa_server.get_instance().is_connected(self.username):
content = {
'Topic': 'human',
'Data': {'Key': 'log', 'Value': "聆听中..."},
'Username': self.username,
'robot': f'http://{cfg.fay_url}:5000/robot/Listening.jpg'
'robot': f'{cfg.fay_url}/robot/Listening.jpg'
}
wsa_server.get_instance().add_cmd(content)
except Exception as e:
+2 -2
View File
@@ -114,10 +114,10 @@ class StreamManager:
fay_core = fay_booter.feiFei
# 处理普通消息,区分是否是会话的第一句
if sentence.endswith('_<isfirst>'):
sentence = sentence[:-len('_<isfirst>')]
sentence = sentence[:-len('_<isfirst>')].strip()
interact = Interact("stream", 1, {'user': username, 'msg': sentence, 'isfirst': True})
elif sentence.endswith('_<isend>'):
sentence = sentence[:-len('_<isend>')]
sentence = sentence[:-len('_<isend>')].strip()
interact = Interact("stream", 1, {'user': username, 'msg': sentence, 'isend': True})
else:
interact = Interact("stream", 1, {'user': username, 'msg': sentence})
+9 -8
View File
@@ -1,10 +1,11 @@
FROM docker.m.daocloud.io/python:3.10
COPY install_deps.sh /usr/local/bin/install_deps.sh
RUN chmod +x /usr/local/bin/install_deps.sh && /usr/local/bin/install_deps.sh
RUN pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/
COPY requirements.txt /app/
FROM docker.m.daocloud.io/python:3.12
#FROM python:3.12 ---> Nick
COPY app /app
RUN chmod +x /app/docker/install_deps.sh \
# && mv /app/docker/sources.list /etc/apt/sources.list \ ---> 添加对应的sources list可以提升apt install 效率
&& /app/docker/install_deps.sh \
&& pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple/ \
&& pip install --no-cache-dir -r /app/docker/requirements.txt
WORKDIR /app
RUN pip install --no-cache-dir -r requirements.txt
COPY ./ /app
CMD ["python", "main.py"]
-32
View File
@@ -1,32 +0,0 @@
name: fay
channels:
- defaults
dependencies:
- python=3.10
- requests
- numpy
- pyaudio=0.2.11
- websockets=10.2
- ws4py=0.5.1
- pyqt=5.15.6
- flask=3.0.0
- openpyxl=3.0.9
- flask-cors=3.0.10
- pyqtwebengine=5.15.5
- eyed3=0.9.6
- websocket-client
- azure-cognitiveservices-speech
- aliyun-python-sdk-core
- simhash
- pytz
- gevent=22.10.1
- edge-tts=6.1.3
- ultralytics=8.0.2
- pydub
- cemotion
- langchain=0.0.336
- chromadb
- tenacity=8.2.3
- pygame
- scipy
- pip
-50
View File
@@ -1,50 +0,0 @@
#!/bin/bash
# 检测 Debian 系统(如 Ubuntu
if grep -qEi "(debian|ubuntu)" /etc/*release; then
apt-get update -yq --fix-missing && \
DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
pkg-config \
wget \
cmake \
curl \
git \
vim \
build-essential \
libgl1-mesa-glx \
portaudio19-dev \
libnss3 \
libxcomposite1 \
libxrender1 \
libxrandr2 \
libqt5webkit5-dev \
libxdamage1 \
libxtst6 && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# 检测 CentOS 系统
elif grep -qEi "(centos|fedora|rhel)" /etc/*release; then
yum update -y && \
yum install -y \
pkgconfig \
wget \
cmake \
curl \
git \
vim-enhanced \
gcc \
gcc-c++ \
mesa-libGL \
portaudio \
nss \
libXcomposite \
libXrender \
libXrandr \
qt5-qtwebkit-devel \
libXdamage \
libXtst && \
yum clean all
else
echo "Unsupported OS"
exit 1
fi
+13 -10
View File
@@ -1,28 +1,31 @@
requests
numpy
pyaudio~=0.2.11
websockets~=10.2
websockets~=10.4
ws4py~=0.5.1
pyqt5~=5.15.6
PyQt5==5.15.10
PyQt5-sip==12.13.0
PyQtWebEngine==5.15.6
flask~=3.0.0
openpyxl~=3.0.9
flask_cors~=3.0.10
PyQtWebEngine~=5.15.5
eyed3~=0.9.6
websocket-client
azure-cognitiveservices-speech
aliyun-python-sdk-core
simhash
pytz
gevent
edge_tts~=6.1.3
eyed3
ultralytics~=8.0.2
edge_tts
pydub
cemotion
langchain==0.0.336
chromadb
tenacity==8.2.3
pygame
scipy
flask-httpauth
flask-httpauth
opencv-python
psutil
langchain
langchain_openai
langgraph
bs4
schedule
+17
View File
@@ -16,6 +16,7 @@ from core import wsa_server
from core import socket_bridge_service
from llm.agent import agent_service
import subprocess
from llm.nlp_cognitive_stream import save_agent_memory
# 全局变量声明
feiFei = None
@@ -294,6 +295,16 @@ def stop():
util.log(1, '正在关闭服务...')
__running = False
# 保存代理记忆
if config_util.key_chat_module == 'cognitive_stream':
util.log(1, '正在保存代理记忆...')
try:
save_agent_memory()
util.log(1, '代理记忆保存成功')
except Exception as e:
util.log(1, f'保存代理记忆失败: {str(e)}')
if recorderListener is not None:
util.log(1, '正在关闭录音服务...')
recorderListener.stop()
@@ -347,6 +358,12 @@ def start():
from llm import nlp_privategpt
nlp_privategpt.save_all()
#初始化定时保存记忆的任务
if config_util.key_chat_module == 'cognitive_stream':
util.log(1, '初始化定时保存记忆的任务...')
from llm.nlp_cognitive_stream import init_memory_scheduler
init_memory_scheduler()
#开启录音服务
record = config_util.config['source']['record']
if record['enabled']:
+150
View File
@@ -0,0 +1,150 @@
import uuid
from genagents.modules.interaction import *
from genagents.modules.memory_stream import *
# ############################################################################
# ### GENERATIVE AGENT CLASS ###
# ############################################################################
class GenerativeAgent:
def __init__(self, agent_folder=None):
if agent_folder:
# We stop the process if the agent storage folder already exists.
if not check_if_file_exists(f"{agent_folder}/scratch.json"):
print ("Generative agent does not exist in the current location.")
return
# Loading the agent's memories.
try:
with open(f"{agent_folder}/scratch.json", 'r', encoding='utf-8') as json_file:
scratch = json.load(json_file)
with open(f"{agent_folder}/memory_stream/embeddings.json", 'r', encoding='utf-8') as json_file:
embeddings = json.load(json_file)
with open(f"{agent_folder}/memory_stream/nodes.json", 'r', encoding='utf-8') as json_file:
nodes = json.load(json_file)
except Exception as e:
print(f"加载代理记忆时出错: {str(e)}")
# 如果加载失败,创建空的记忆
scratch = {}
embeddings = {}
nodes = []
self.id = uuid.uuid4()
self.scratch = scratch
self.memory_stream = MemoryStream(nodes, embeddings)
else:
self.id = uuid.uuid4()
self.scratch = {}
self.memory_stream = MemoryStream([], {})
def update_scratch(self, update):
self.scratch.update(update)
def package(self):
"""
Packaging the agent's meta info for saving.
Parameters:
None
Returns:
packaged dictionary
"""
return {"id": str(self.id)}
def save(self, save_directory):
"""
Given a save_code, save the agents' state in the storage. Right now, the
save directory works as follows:
'storage/<agent_name>/<save_code>'
As you grow different versions of the agent, save the new agent state in
a different save code location. Remember that 'init' is the originally
initialized agent directory.
Parameters:
save_code: str
Returns:
None
"""
try:
# Name of the agent and the current save location.
storage = save_directory
create_folder_if_not_there(f"{storage}/memory_stream")
# 确保embeddings不为None
if self.memory_stream.embeddings is None:
self.memory_stream.embeddings = {}
# Saving the agent's memory stream. This includes saving the embeddings
# as well as the nodes.
with open(f"{storage}/memory_stream/embeddings.json", "w", encoding='utf-8') as json_file:
json.dump(self.memory_stream.embeddings,
json_file, ensure_ascii=False, indent=2)
with open(f"{storage}/memory_stream/nodes.json", "w", encoding='utf-8') as json_file:
json.dump([node.package() for node in self.memory_stream.seq_nodes],
json_file, ensure_ascii=False, indent=2)
# Saving the agent's scratch memories.
with open(f"{storage}/scratch.json", "w", encoding='utf-8') as json_file:
json.dump(self.scratch, json_file, ensure_ascii=False, indent=2)
# Saving the agent's meta information.
with open(f"{storage}/meta.json", "w", encoding='utf-8') as json_file:
json.dump(self.package(), json_file, ensure_ascii=False, indent=2)
except Exception as e:
print(f"保存代理记忆时出错: {str(e)}")
def get_fullname(self):
if "first_name" in self.scratch and "last_name" in self.scratch:
return f"{self.scratch['first_name']} {self.scratch['last_name']}"
else:
return ""
def get_self_description(self):
return str(self.scratch)
def remember(self, content, time_step=0):
"""
Add a new observation to the memory stream.
Parameters:
content: The content of the current memory record that we are adding to
the agent's memory stream.
Returns:
None
"""
self.memory_stream.remember(content, time_step)
def reflect(self, anchor, time_step=0):
"""
Add a new reflection to the memory stream.
Parameters:
anchor: str reflection anchor
Returns:
None
"""
self.memory_stream.reflect(anchor, time_step)
def categorical_resp(self, questions):
ret = categorical_resp(self, questions)
return ret
def numerical_resp(self, questions, float_resp=False):
ret = numerical_resp(self, questions, float_resp)
return ret
def utterance(self, curr_dialogue, context=""):
ret = utterance(self, curr_dialogue, context)
return ret
+278
View File
@@ -0,0 +1,278 @@
from flask import Flask, render_template, request, jsonify, send_from_directory
import os
import uuid
import json
import sys
import threading
import time
from genagents.genagents import GenerativeAgent
from utils import util
# 添加项目根目录到sys.path
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, project_root)
# 导入项目中的模块
from llm.nlp_cognitive_stream import save_agent_memory, create_agent, set_memory_cleared_flag
# 创建Flask应用
app = Flask(__name__)
# 全局变量
instruction = ""
genagents_port = 5001
genagents_host = "0.0.0.0"
genagents_debug = True
server_thread = None
shutdown_flag = False
fay_agent = None
# 确保模板和静态文件目录存在
def setup_directories():
os.makedirs(os.path.join(os.path.dirname(__file__), 'templates'), exist_ok=True)
os.makedirs(os.path.join(os.path.dirname(__file__), 'static'), exist_ok=True)
# 读取指令文件
def load_instruction():
global instruction
instruction_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'instruction.json')
if os.path.exists(instruction_file):
try:
with open(instruction_file, 'r', encoding='utf-8') as f:
data = json.load(f)
instruction = data.get('instruction', '')
# 读取后删除文件,防止重复使用
os.remove(instruction_file)
except Exception as e:
print(f"读取指令文件出错: {str(e)}")
@app.route('/')
def index():
"""提供主页HTML"""
return render_template('decision_interview.html', instruction=instruction)
# 关闭服务器的函数
def shutdown_server():
global shutdown_flag
shutdown_flag = True
# 不再直接访问request对象,而是设置标志让服务器自行关闭
print("服务器将在处理完当前请求后关闭...")
# 清除记忆API
@app.route('/api/clear-memory', methods=['POST'])
def api_clear_memory():
try:
# 获取memory目录路径
memory_dir = os.path.join(os.getcwd(), "memory")
# 检查目录是否存在
if not os.path.exists(memory_dir):
return jsonify({'success': False, 'message': '记忆目录不存在'}), 400
# 清空memory目录下的所有文件(保留目录结构)
for root, dirs, files in os.walk(memory_dir):
for file in files:
file_path = os.path.join(root, file)
try:
if os.path.isfile(file_path):
os.remove(file_path)
util.log(1, f"已删除文件: {file_path}")
except Exception as e:
util.log(1, f"删除文件时出错: {file_path}, 错误: {str(e)}")
# 删除memory_stream目录(如果存在)
memory_stream_dir = os.path.join(memory_dir, "memory_stream")
if os.path.exists(memory_stream_dir):
import shutil
try:
shutil.rmtree(memory_stream_dir)
util.log(1, f"已删除目录: {memory_stream_dir}")
except Exception as e:
util.log(1, f"删除目录时出错: {memory_stream_dir}, 错误: {str(e)}")
# 创建一个标记文件,表示记忆已被清除,防止退出时重新保存
with open(os.path.join(memory_dir, ".memory_cleared"), "w") as f:
f.write("Memory has been cleared. Do not save on exit.")
# 设置记忆清除标记
try:
# 导入并修改nlp_cognitive_stream模块中的保存函数
from llm.nlp_cognitive_stream import set_memory_cleared_flag, clear_agent_memory
# 设置记忆清除标记
set_memory_cleared_flag(True)
# 清除内存中已加载的记忆
clear_agent_memory()
util.log(1, "已同时清除文件存储和内存中的记忆")
except Exception as e:
util.log(1, f"清除内存中记忆时出错: {str(e)}")
util.log(1, "记忆已清除,需要重启应用才能生效")
return jsonify({'success': True, 'message': '记忆已清除,请重启应用使更改生效'}), 200
except Exception as e:
util.log(1, f"清除记忆时出错: {str(e)}")
return jsonify({'success': False, 'message': f'清除记忆时出错: {str(e)}'}), 500
@app.route('/api/submit', methods=['POST'])
def submit_data():
"""处理提交的表单数据并将其添加到Agent的记忆中"""
try:
# 接收JSON格式的表单数据
data = request.json
if not data or 'dimensions' not in data:
return jsonify({'status': 'error', 'message': '数据格式不正确'}), 400
# 导入需要的函数
from llm.nlp_cognitive_stream import get_current_time_step, save_agent_memory, create_agent
# 确保Fay的agent已经初始化
global fay_agent
if fay_agent is None:
fay_agent = create_agent()
# 确保embeddings不为None
if fay_agent.memory_stream.embeddings is None:
fay_agent.memory_stream.embeddings = {}
# 使用全局函数获取时间步
time_step = get_current_time_step() + 1
# 处理各维度数据
for dimension_name, dimension_qa in data['dimensions'].items():
# 为每个维度创建一个摘要记忆
dimension_summary = f"决策分析维度: {dimension_name}\n"
for qa_pair in dimension_qa:
question = qa_pair.get('问题', '')
answer = qa_pair.get('回答', '')
dimension_summary += f"问题: {question}\n回答: {answer}\n\n"
# 将维度摘要添加到Agent的记忆中
fay_agent.remember(dimension_summary, time_step=time_step)
time_step += 1
# 添加一个总结记忆
global instruction # 明确声明使用全局变量
summary = f"[系统指令] 基于以上决策分析,你的人格已被重新定义。"
if 'instruction' in globals() and instruction:
summary += f" 你需要遵循以下指令:{instruction}"
fay_agent.remember(summary, time_step=time_step)
# 保存记忆
save_agent_memory()
# 设置关闭标志,让服务器在响应后关闭
global shutdown_flag
shutdown_flag = True
# 返回响应,添加自动关闭窗口的JavaScript代码
return jsonify({
'status': 'success',
'message': '决策分析数据已克隆到记忆中,请关闭页面并重启Fay',
'redirect': 'http://localhost:8080/setting',
'closeWindow': True # 添加标志,指示前端关闭窗口
})
except Exception as e:
import traceback
error_details = traceback.format_exc()
print(f"处理决策分析数据时出错: {str(e)}\n{error_details}")
return jsonify({'status': 'error', 'message': f'处理数据时出错: {str(e)}'}), 500
@app.route('/api/shutdown', methods=['POST'])
def shutdown():
"""手动关闭服务器的API"""
shutdown_server()
return jsonify({'status': 'success', 'message': '服务器正在关闭'})
@app.route('/static/<path:filename>')
def serve_static(filename):
# 提供静态文件
return send_from_directory('static', filename)
@app.route('/templates/<path:filename>')
def serve_template(filename):
# 提供模板文件(仅用于调试)
return send_from_directory('templates', filename)
# 全局变量,用于控制服务器关闭
shutdown_flag = False
# 检查是否请求关闭服务器
def is_shutdown_requested():
global shutdown_flag
return shutdown_flag
# 设置应用程序,复制必要的文件到正确的位置
def setup():
setup_directories()
# 确保decision_interview.html存在于templates目录
template_source = os.path.join(os.path.dirname(__file__), 'decision_interview.html')
template_dest = os.path.join(os.path.dirname(__file__), 'templates', 'decision_interview.html')
if os.path.exists(template_source) and not os.path.exists(template_dest):
import shutil
shutil.copy2(template_source, template_dest)
# 启动决策分析服务
def start_genagents_server(instruction_text="", port=None, host=None, debug=None):
global instruction, genagents_port, genagents_host, genagents_debug, shutdown_flag
# 重置关闭标志
shutdown_flag = False
# 设置指令
if instruction_text:
instruction = instruction_text
else:
load_instruction()
# 设置服务器参数
if port is not None:
genagents_port = port
if host is not None:
genagents_host = host
if debug is not None:
genagents_debug = debug
# 设置应用
setup()
# 返回应用实例,但不启动
return app
# 直接运行时启动服务器
if __name__ == '__main__':
setup() # 确保所有必要的目录和文件都存在
load_instruction() # 加载指令
print(f"启动Flask服务器,请访问 http://127.0.0.1:{genagents_port}/ 打开页面")
# 使用Werkzeug的服务器,并添加关闭检查
from werkzeug.serving import make_server
# 创建服务器
server = make_server(genagents_host, genagents_port, app)
# 启动服务器,但在单独的线程中运行,以便我们可以检查shutdown_flag
import threading
def run_server():
server.serve_forever()
server_thread = threading.Thread(target=run_server)
server_thread.daemon = True
server_thread.start()
# 主线程检查shutdown_flag
try:
while not is_shutdown_requested():
time.sleep(1)
except KeyboardInterrupt:
print("接收到键盘中断,正在关闭服务器...")
finally:
print("正在关闭服务器...")
server.shutdown()
+1
View File
@@ -0,0 +1 @@
{"instruction": "你是一个严肃认真的程序员"}
+279
View File
@@ -0,0 +1,279 @@
import math
import sys
import datetime
import random
import string
import re
import os
from numpy import dot
from numpy.linalg import norm
from simulation_engine.settings import *
from simulation_engine.global_methods import *
from simulation_engine.gpt_structure import *
from simulation_engine.llm_json_parser import *
from utils import util
def _main_agent_desc(agent, anchor):
agent_desc = ""
agent_desc += f"Self description: {agent.get_self_description()}\n==\n"
agent_desc += f"Other observations about the subject:\n\n"
retrieved = agent.memory_stream.retrieve([anchor], 0, n_count=120)
if len(retrieved) == 0:
return agent_desc
nodes = list(retrieved.values())[0]
for node in nodes:
agent_desc += f"{node.content}\n"
return agent_desc
def _utterance_agent_desc(agent, anchor):
agent_desc = ""
agent_desc += f"Self description: {agent.get_self_description()}\n==\n"
agent_desc += f"Other observations about the subject:\n\n"
retrieved = agent.memory_stream.retrieve([anchor], 0, n_count=120)
if len(retrieved) == 0:
return agent_desc
nodes = list(retrieved.values())[0]
for node in nodes:
agent_desc += f"{node.content}\n"
return agent_desc
def run_gpt_generate_categorical_resp(
agent_desc,
questions,
prompt_version="1",
gpt_version="GPT4o",
verbose=False):
def create_prompt_input(agent_desc, questions):
str_questions = ""
for key, val in questions.items():
str_questions += f"Q: {key}\n"
str_questions += f"Option: {val}\n\n"
str_questions = str_questions.strip()
return [agent_desc, str_questions]
def _func_clean_up(gpt_response, prompt=""):
responses, reasonings = extract_first_json_dict_categorical(gpt_response)
ret = {"responses": responses, "reasonings": reasonings}
return ret
def _get_fail_safe():
return None
if len(questions) > 1:
prompt_lib_file = f"{LLM_PROMPT_DIR}/generative_agent/interaction/categorical_resp/batch_v1.txt"
else:
prompt_lib_file = f"{LLM_PROMPT_DIR}/generative_agent/interaction/categorical_resp/singular_v1.txt"
prompt_input = create_prompt_input(agent_desc, questions)
fail_safe = _get_fail_safe()
output, prompt, prompt_input, fail_safe = chat_safe_generate(
prompt_input, prompt_lib_file, gpt_version, 1, fail_safe,
_func_clean_up, verbose)
return output, [output, prompt, prompt_input, fail_safe]
def categorical_resp(agent, questions):
anchor = " ".join(list(questions.keys()))
agent_desc = _main_agent_desc(agent, anchor)
return run_gpt_generate_categorical_resp(
agent_desc, questions, "1", LLM_VERS)[0]
def run_gpt_generate_numerical_resp(
agent_desc,
questions,
float_resp,
prompt_version="1",
gpt_version="GPT4o",
verbose=False):
def create_prompt_input(agent_desc, questions, float_resp):
str_questions = ""
for key, val in questions.items():
str_questions += f"Q: {key}\n"
str_questions += f"Range: {str(val)}\n\n"
str_questions = str_questions.strip()
if float_resp:
resp_type = "float"
else:
resp_type = "integer"
return [agent_desc, str_questions, resp_type]
def _func_clean_up(gpt_response, prompt=""):
responses, reasonings = extract_first_json_dict_numerical(gpt_response)
ret = {"responses": responses, "reasonings": reasonings}
return ret
def _get_fail_safe():
return None
if len(questions) > 1:
prompt_lib_file = f"{LLM_PROMPT_DIR}/generative_agent/interaction/numerical_resp/batch_v1.txt"
else:
prompt_lib_file = f"{LLM_PROMPT_DIR}/generative_agent/interaction/numerical_resp/singular_v1.txt"
prompt_input = create_prompt_input(agent_desc, questions, float_resp)
fail_safe = _get_fail_safe()
output, prompt, prompt_input, fail_safe = chat_safe_generate(
prompt_input, prompt_lib_file, gpt_version, 1, fail_safe,
_func_clean_up, verbose)
if float_resp:
output["responses"] = [float(i) for i in output["responses"]]
else:
output["responses"] = [int(i) for i in output["responses"]]
return output, [output, prompt, prompt_input, fail_safe]
def numerical_resp(agent, questions, float_resp):
anchor = " ".join(list(questions.keys()))
agent_desc = _main_agent_desc(agent, anchor)
return run_gpt_generate_numerical_resp(
agent_desc, questions, float_resp, "1", LLM_VERS)[0]
def run_gpt_generate_utterance(
agent_desc,
str_dialogue,
context,
prompt_version="1",
gpt_version="GPT4o",
verbose=False):
"""
运行GPT生成对话回复
参数:
agent_desc: 代理描述
str_dialogue: 对话字符串
context: 上下文
prompt_version: 提示版本,默认为"1"
gpt_version: GPT版本,默认为"GPT4o"
verbose: 是否输出详细信息,默认为False
返回:
output: 生成的回复
详细信息: [output, prompt, prompt_input, fail_safe]
"""
def create_prompt_input(agent_desc, str_dialogue, context):
return [agent_desc, context, str_dialogue]
def _func_clean_up(gpt_response, prompt=""):
try:
# 确保gpt_response是字符串类型
if not isinstance(gpt_response, str):
util.log(1, f"GPT响应不是字符串类型: {type(gpt_response)}")
return "抱歉,我现在太忙了,休息一会,请稍后再试。"
# 提取JSON字典
json_dict = extract_first_json_dict(gpt_response)
if json_dict is None or "utterance" not in json_dict:
util.log(1, f"无法从GPT响应中提取有效的JSON或缺少utterance字段: {gpt_response[:100]}...")
return "抱歉,我现在太忙了,休息一会,请稍后再试。"
# 返回utterance字段
return json_dict["utterance"]
except Exception as e:
util.log(1, f"处理GPT响应时出错: {str(e)}")
return "抱歉,我现在太忙了,休息一会,请稍后再试。"
def _get_fail_safe():
return "对不起,我现在无法回答这个问题。"
# 确保模板文件路径正确
prompt_lib_file = f"{LLM_PROMPT_DIR}/generative_agent/interaction/utternace/utterance_v1.txt"
if not os.path.exists(prompt_lib_file):
util.log(1, f"模板文件不存在: {prompt_lib_file}")
return "抱歉,我现在太忙了,休息一会,请稍后再试。", ["抱歉,我现在太忙了,休息一会,请稍后再试。", "", [], ""]
prompt_input = create_prompt_input(agent_desc, str_dialogue, context)
fail_safe = _get_fail_safe()
# 调用chat_safe_generate函数生成回复
try:
output, prompt, prompt_input, fail_safe = chat_safe_generate(
prompt_input, prompt_lib_file, gpt_version, 1, fail_safe,
_func_clean_up, verbose)
# 确保输出是字符串类型
if output is None:
util.log(1, "GPT生成的输出为None")
output = fail_safe
except Exception as e:
util.log(1, f"调用chat_safe_generate时出错: {str(e)}")
output = fail_safe
prompt = ""
prompt_input = []
return output, [output, prompt, prompt_input, fail_safe]
def utterance(agent, curr_dialogue, context):
str_dialogue = ""
for row in curr_dialogue:
str_dialogue += f"[{row[0]}]: {row[1]}\n"
str_dialogue += f"[{agent.get_fullname()}]: [Fill in]\n"
anchor = str_dialogue
agent_desc = _utterance_agent_desc(agent, anchor)
return run_gpt_generate_utterance(
agent_desc, str_dialogue, context, "1", LLM_VERS, False)[0]
## Ask function.
def run_gpt_generate_ask(
agent_desc,
questions,
prompt_version="1",
gpt_version="GPT4o",
verbose=False):
def create_prompt_input(agent_desc, questions):
str_questions = ""
i = 1
for q in questions:
str_questions += f"Q{i}: {q['question']}\n"
str_questions += f"Type: {q['response-type']}\n"
if q['response-type'] == 'categorical':
str_questions += f"Options: {', '.join(q['response-options'])}\n"
elif q['response-type'] in ['int', 'float']:
str_questions += f"Range: {q['response-scale']}\n"
elif q['response-type'] == 'open':
char_limit = q.get('response-char-limit', 200)
str_questions += f"Character Limit: {char_limit}\n"
str_questions += "\n"
i += 1
return [agent_desc, str_questions.strip()]
def _func_clean_up(gpt_response, prompt=""):
responses = extract_first_json_dict(gpt_response)
return responses
def _get_fail_safe():
return None
prompt_lib_file = f"{LLM_PROMPT_DIR}/generative_agent/interaction/ask/batch_v1.txt"
prompt_input = create_prompt_input(agent_desc, questions)
fail_safe = _get_fail_safe()
output, prompt, prompt_input, fail_safe = chat_safe_generate(
prompt_input, prompt_lib_file, gpt_version, 1, fail_safe,
_func_clean_up, verbose)
return output, [output, prompt, prompt_input, fail_safe]
+607
View File
@@ -0,0 +1,607 @@
import math
import sys
import datetime
import random
import string
import re
from numpy import dot
from numpy.linalg import norm
from simulation_engine.settings import *
from simulation_engine.global_methods import *
from simulation_engine.gpt_structure import *
from simulation_engine.llm_json_parser import *
def run_gpt_generate_importance(
records,
prompt_version="1",
gpt_version="GPT4o",
verbose=False):
def create_prompt_input(records):
records_str = ""
for count, r in enumerate(records):
records_str += f"Item {str(count+1)}:\n"
records_str += f"{r}\n"
return [records_str]
def _func_clean_up(gpt_response, prompt=""):
gpt_response = extract_first_json_dict(gpt_response)
# 处理gpt_response为None的情况
if gpt_response is None:
print("警告: extract_first_json_dict返回None,使用默认值")
return [50] # 返回默认重要性分数
return list(gpt_response.values())
def _get_fail_safe():
return 25
if len(records) > 1:
prompt_lib_file = f"{LLM_PROMPT_DIR}/generative_agent/memory_stream/importance_score/batch_v1.txt"
else:
prompt_lib_file = f"{LLM_PROMPT_DIR}/generative_agent/memory_stream/importance_score/singular_v1.txt"
prompt_input = create_prompt_input(records)
fail_safe = _get_fail_safe()
output, prompt, prompt_input, fail_safe = chat_safe_generate(
prompt_input, prompt_lib_file, gpt_version, 1, fail_safe,
_func_clean_up, verbose)
return output, [output, prompt, prompt_input, fail_safe]
def generate_importance_score(records):
return run_gpt_generate_importance(records, "1", LLM_VERS)[0]
def run_gpt_generate_reflection(
records,
anchor,
reflection_count,
prompt_version="1",
gpt_version="GPT4o",
verbose=False):
def create_prompt_input(records, anchor, reflection_count):
records_str = ""
for count, r in enumerate(records):
records_str += f"Item {str(count+1)}:\n"
records_str += f"{r}\n"
return [records_str, reflection_count, anchor]
def _func_clean_up(gpt_response, prompt=""):
return extract_first_json_dict(gpt_response)["reflection"]
def _get_fail_safe():
return []
if reflection_count > 1:
prompt_lib_file = f"{LLM_PROMPT_DIR}/generative_agent/memory_stream/reflection/batch_v1.txt"
else:
prompt_lib_file = f"{LLM_PROMPT_DIR}/generative_agent/memory_stream/reflection/singular_v1.txt"
prompt_input = create_prompt_input(records, anchor, reflection_count)
fail_safe = _get_fail_safe()
output, prompt, prompt_input, fail_safe = chat_safe_generate(
prompt_input, prompt_lib_file, gpt_version, 1, fail_safe,
_func_clean_up, verbose)
return output, [output, prompt, prompt_input, fail_safe]
def generate_reflection(records, anchor, reflection_count):
records = [i.content for i in records]
return run_gpt_generate_reflection(records, anchor, reflection_count, "1",
LLM_VERS)[0]
# ##############################################################################
# ### HELPER FUNCTIONS FOR GENERATIVE AGENTS ###
# ##############################################################################
def get_random_str(length):
"""
Generates a random string of alphanumeric characters with the specified
length. This function creates a random string by selecting characters from
the set of uppercase letters, lowercase letters, and digits. The length of
the random string is determined by the 'length' parameter.
Parameters:
length (int): The desired length of the random string.
Returns:
random_string: A randomly generated string of the specified length.
Example:
>>> get_random_str(8)
'aB3R7tQ2'
"""
characters = string.ascii_letters + string.digits
random_string = ''.join(random.choice(characters) for _ in range(length))
return random_string
def cos_sim(a, b):
"""
This function calculates the cosine similarity between two input vectors
'a' and 'b'. Cosine similarity is a measure of similarity between two
non-zero vectors of an inner product space that measures the cosine
of the angle between them.
Parameters:
a: 1-D array object
b: 1-D array object
Returns:
A scalar value representing the cosine similarity between the input
vectors 'a' and 'b'.
Example:
>>> a = [0.3, 0.2, 0.5]
>>> b = [0.2, 0.2, 0.5]
>>> cos_sim(a, b)
"""
return dot(a, b)/(norm(a)*norm(b))
def normalize_dict_floats(d, target_min, target_max):
"""
This function normalizes the float values of a given dictionary 'd' between
a target minimum and maximum value. The normalization is done by scaling the
values to the target range while maintaining the same relative proportions
between the original values.
Parameters:
d: Dictionary. The input dictionary whose float values need to be
normalized.
target_min: Integer or float. The minimum value to which the original
values should be scaled.
target_max: Integer or float. The maximum value to which the original
values should be scaled.
Returns:
d: A new dictionary with the same keys as the input but with the float
values normalized between the target_min and target_max.
Example:
>>> d = {'a':1.2,'b':3.4,'c':5.6,'d':7.8}
>>> target_min = -5
>>> target_max = 5
>>> normalize_dict_floats(d, target_min, target_max)
"""
# 检查字典是否为None或为空
if d is None:
print("警告: normalize_dict_floats接收到None字典")
return {}
if not d:
print("警告: normalize_dict_floats接收到空字典")
return {}
try:
min_val = min(val for val in d.values())
max_val = max(val for val in d.values())
range_val = max_val - min_val
if range_val == 0:
for key, val in d.items():
d[key] = (target_max - target_min)/2
else:
for key, val in d.items():
d[key] = ((val - min_val) * (target_max - target_min)
/ range_val + target_min)
return d
except Exception as e:
print(f"normalize_dict_floats处理字典时出错: {str(e)}")
# 返回原始字典,避免处理失败
return d
def top_highest_x_values(d, x):
"""
This function takes a dictionary 'd' and an integer 'x' as input, and
returns a new dictionary containing the top 'x' key-value pairs from the
input dictionary 'd' with the highest values.
Parameters:
d: Dictionary. The input dictionary from which the top 'x' key-value pairs
with the highest values are to be extracted.
x: Integer. The number of top key-value pairs with the highest values to
be extracted from the input dictionary.
Returns:
A new dictionary containing the top 'x' key-value pairs from the input
dictionary 'd' with the highest values.
Example:
>>> d = {'a':1.2,'b':3.4,'c':5.6,'d':7.8}
>>> x = 3
>>> top_highest_x_values(d, x)
"""
top_v = dict(sorted(d.items(),
key=lambda item: item[1],
reverse=True)[:x])
return top_v
def extract_recency(seq_nodes):
"""
Gets the current Persona object and a list of nodes that are in a
chronological order, and outputs a dictionary that has the recency score
calculated.
Parameters:
nodes: A list of Node object in a chronological order.
Returns:
recency_out: A dictionary whose keys are the node.node_id and whose values
are the float that represents the recency score.
"""
# 检查seq_nodes是否为None或为空
if seq_nodes is None:
print("警告: extract_recency接收到None节点列表")
return {}
if not seq_nodes:
print("警告: extract_recency接收到空节点列表")
return {}
try:
# 确保所有的last_retrieved都是整数类型
normalized_timestamps = []
for node in seq_nodes:
if node is None:
print("警告: 节点为None,跳过")
continue
if not hasattr(node, 'last_retrieved'):
print(f"警告: 节点 {node} 没有last_retrieved属性,使用默认值0")
normalized_timestamps.append(0)
continue
if isinstance(node.last_retrieved, str):
try:
normalized_timestamps.append(int(node.last_retrieved))
except ValueError:
# 如果无法转换为整数,使用0作为默认值
normalized_timestamps.append(0)
else:
normalized_timestamps.append(node.last_retrieved)
if not normalized_timestamps:
return {node.node_id: 1.0 for node in seq_nodes if node is not None and hasattr(node, 'node_id')}
max_timestep = max(normalized_timestamps)
recency_decay = 0.99
recency_out = dict()
for count, node in enumerate(seq_nodes):
if node is None or not hasattr(node, 'node_id') or not hasattr(node, 'last_retrieved'):
continue
# 获取标准化后的时间戳
try:
last_retrieved = normalized_timestamps[count]
recency_out[node.node_id] = (recency_decay
** (max_timestep - last_retrieved))
except Exception as e:
print(f"计算节点 {node.node_id} 的recency时出错: {str(e)}")
# 使用默认值
recency_out[node.node_id] = 1.0
return recency_out
except Exception as e:
print(f"extract_recency处理节点列表时出错: {str(e)}")
# 返回一个默认字典
return {node.node_id: 1.0 for node in seq_nodes if node is not None and hasattr(node, 'node_id')}
def extract_importance(seq_nodes):
"""
Gets the current Persona object and a list of nodes that are in a
chronological order, and outputs a dictionary that has the importance score
calculated.
Parameters:
seq_nodes: A list of Node object in a chronological order.
Returns:
importance_out: A dictionary whose keys are the node.node_id and whose
values are the float that represents the importance score.
"""
# 检查seq_nodes是否为None或为空
if seq_nodes is None:
print("警告: extract_importance接收到None节点列表")
return {}
if not seq_nodes:
print("警告: extract_importance接收到空节点列表")
return {}
try:
importance_out = dict()
for count, node in enumerate(seq_nodes):
if node is None:
print("警告: 节点为None,跳过")
continue
if not hasattr(node, 'node_id') or not hasattr(node, 'importance'):
print(f"警告: 节点缺少必要属性,跳过")
continue
# 确保importance是数值类型
if isinstance(node.importance, str):
try:
importance_out[node.node_id] = float(node.importance)
except ValueError:
# 如果无法转换为数值,使用默认值
print(f"警告: 节点 {node.node_id} 的importance无法转换为数值,使用默认值")
importance_out[node.node_id] = 50.0
else:
importance_out[node.node_id] = node.importance
return importance_out
except Exception as e:
print(f"extract_importance处理节点列表时出错: {str(e)}")
# 返回一个默认字典
return {node.node_id: 50.0 for node in seq_nodes if node is not None and hasattr(node, 'node_id')}
def extract_relevance(seq_nodes, embeddings, focal_pt):
"""
Gets the current Persona object, a list of seq_nodes that are in a
chronological order, and the focal_pt string and outputs a dictionary
that has the relevance score calculated.
Parameters:
seq_nodes: A list of Node object in a chronological order.
focal_pt: A string describing the current thought of revent of focus.
Returns:
relevance_out: A dictionary whose keys are the node.node_id and whose
values are the float that represents the relevance score.
"""
# 确保embeddings不为None
if embeddings is None:
print("警告: embeddings为None,使用空字典代替")
embeddings = {}
try:
focal_embedding = get_text_embedding(focal_pt)
except Exception as e:
print(f"获取焦点嵌入向量时出错: {str(e)}")
# 如果无法获取嵌入向量,返回默认值
return {node.node_id: 0.5 for node in seq_nodes}
relevance_out = dict()
for count, node in enumerate(seq_nodes):
try:
# 检查节点内容是否在embeddings中
if node.content in embeddings:
node_embedding = embeddings[node.content]
# 计算余弦相似度
relevance_out[node.node_id] = cos_sim(node_embedding, focal_embedding)
else:
# 如果没有对应的嵌入向量,使用默认值
relevance_out[node.node_id] = 0.5
except Exception as e:
print(f"计算节点 {node.node_id} 的相关性时出错: {str(e)}")
# 如果计算过程中出错,使用默认值
relevance_out[node.node_id] = 0.5
return relevance_out
# ##############################################################################
# ### CONCEPT NODE ###
# ##############################################################################
class ConceptNode:
def __init__(self, node_dict):
# Loading the content of a memory node in the memory stream.
self.node_id = node_dict["node_id"]
self.node_type = node_dict["node_type"]
self.content = node_dict["content"]
self.importance = node_dict["importance"]
# 确保created是整数类型
self.created = int(node_dict["created"]) if node_dict["created"] is not None else 0
# 确保last_retrieved是整数类型
self.last_retrieved = int(node_dict["last_retrieved"]) if node_dict["last_retrieved"] is not None else 0
self.pointer_id = node_dict["pointer_id"]
def package(self):
"""
Packaging the ConceptNode
Parameters:
None
Returns:
packaged dictionary
"""
curr_package = {}
curr_package["node_id"] = self.node_id
curr_package["node_type"] = self.node_type
curr_package["content"] = self.content
curr_package["importance"] = self.importance
curr_package["created"] = self.created
curr_package["last_retrieved"] = self.last_retrieved
curr_package["pointer_id"] = self.pointer_id
return curr_package
# ##############################################################################
# ### MEMORY STREAM ###
# ##############################################################################
class MemoryStream:
def __init__(self, nodes, embeddings):
# Loading the memory stream for the agent.
self.seq_nodes = []
self.id_to_node = dict()
for node in nodes:
new_node = ConceptNode(node)
self.seq_nodes += [new_node]
self.id_to_node[new_node.node_id] = new_node
self.embeddings = embeddings
def count_observations(self):
"""
Counting the number of observations (basically, the number of all nodes in
memory stream except for the reflections)
Parameters:
None
Returns:
Count
"""
count = 0
for i in self.seq_nodes:
if i.node_type == "observation":
count += 1
return count
def retrieve(self, focal_points, time_step, n_count=120, curr_filter="all",
hp=[0, 1, 0.5], stateless=False, verbose=False):
"""
Retrieve elements from the memory stream.
Parameters:
focal_points: This is the query sentence. It is in a list form where
the elemnts of the list are the query sentences.
time_step: Current time_step
n_count: The number of nodes that we want to retrieve.
curr_filter: Filtering the node.type that we want to retrieve.
Acceptable values are 'all', 'reflection', 'observation'
hp: Hyperparameter for [recency_w, relevance_w, importance_w]
verbose: verbose
Returns:
retrieved: A dictionary whose keys are a focal_pt query str, and whose
values are a list of nodes that are retrieved for that query str.
"""
curr_nodes = []
# If the memory stream is empty, we return an empty dictionary.
if len(self.seq_nodes) == 0:
return dict()
# Filtering for the desired node type. curr_filter can be one of the three
# elements: 'all', 'reflection', 'observation'
if curr_filter == "all":
curr_nodes = self.seq_nodes
else:
for curr_node in self.seq_nodes:
if curr_node.node_type == curr_filter:
curr_nodes += [curr_node]
# 确保embeddings不为None
if self.embeddings is None:
print("警告: 在retrieve方法中,embeddings为None,初始化为空字典")
self.embeddings = {}
# <retrieved> is the main dictionary that we are returning
retrieved = dict()
for focal_pt in focal_points:
# Calculating the component dictionaries and normalizing them.
x = extract_recency(curr_nodes)
recency_out = normalize_dict_floats(x, 0, 1)
x = extract_importance(curr_nodes)
importance_out = normalize_dict_floats(x, 0, 1)
x = extract_relevance(curr_nodes, self.embeddings, focal_pt)
relevance_out = normalize_dict_floats(x, 0, 1)
# Computing the final scores that combines the component values.
master_out = dict()
for key in recency_out.keys():
recency_w = hp[0]
relevance_w = hp[1]
importance_w = hp[2]
master_out[key] = (recency_w * recency_out[key]
+ relevance_w * relevance_out[key]
+ importance_w * importance_out[key])
if verbose:
master_out = top_highest_x_values(master_out, len(master_out.keys()))
for key, val in master_out.items():
print (self.id_to_node[key].content, val)
print (recency_w*recency_out[key]*1,
relevance_w*relevance_out[key]*1,
importance_w*importance_out[key]*1)
# Extracting the highest x values.
# <master_out> has the key of node.id and value of float. Once we get
# the highest x values, we want to translate the node.id into nodes
# and return the list of nodes.
master_out = top_highest_x_values(master_out, n_count)
master_nodes = [self.id_to_node[key] for key in list(master_out.keys())]
# **Sort the master_nodes list by last_retrieved in descending order**
master_nodes = sorted(master_nodes,
key=lambda node: node.created, reverse=False)
# We do not want to update the last retrieved time_step for these nodes
# if we are in a stateless mode.
if not stateless:
for n in master_nodes:
n.retrieved_time_step = time_step
retrieved[focal_pt] = master_nodes
return retrieved
def _add_node(self, time_step, node_type, content, importance, pointer_id):
"""
Adding a new node to the memory stream.
Parameters:
time_step: Current time_step
node_type: type of node -- it's either reflection, observation
content: the str content of the memory record
importance: int score of the importance score
pointer_id: the str of the parent node
Returns:
retrieved: A dictionary whose keys are a focal_pt query str, and whose
values are a list of nodes that are retrieved for that query str.
"""
node_dict = dict()
node_dict["node_id"] = len(self.seq_nodes)
node_dict["node_type"] = node_type
node_dict["content"] = content
node_dict["importance"] = importance
node_dict["created"] = time_step
node_dict["last_retrieved"] = time_step
node_dict["pointer_id"] = pointer_id
new_node = ConceptNode(node_dict)
self.seq_nodes += [new_node]
self.id_to_node[new_node.node_id] = new_node
# 确保embeddings不为None
if self.embeddings is None:
self.embeddings = {}
try:
self.embeddings[content] = get_text_embedding(content)
except Exception as e:
print(f"获取文本嵌入时出错: {str(e)}")
# 如果获取嵌入失败,使用空列表代替
self.embeddings[content] = []
def remember(self, content, time_step=0):
score = generate_importance_score([content])[0]
self._add_node(time_step, "observation", content, score, None)
def reflect(self, anchor, reflection_count=5,
retrieval_count=120, time_step=0):
records = self.retrieve([anchor], time_step, retrieval_count)[anchor]
record_ids = [i.node_id for i in records]
reflections = generate_reflection(records, anchor, reflection_count)
scores = generate_importance_score(reflections)
for count, reflection in enumerate(reflections):
self._add_node(time_step, "reflection", reflections[count],
scores[count], record_ids)
+627
View File
@@ -0,0 +1,627 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>价值观及经历克隆</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
padding-top: 20px;
padding-bottom: 60px;
background-color: #f8f9fa;
}
.dimension-card {
margin-bottom: 30px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
border-radius: 8px;
}
.dimension-header {
background-color: #f8f9fa;
padding: 15px;
border-radius: 8px 8px 0 0;
border-bottom: 1px solid #dee2e6;
}
.dimension-body {
padding: 20px;
}
.question-item {
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.question-item:last-child {
border-bottom: none;
}
.fixed-bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: rgba(255,255,255,0.95);
padding: 10px 0;
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
z-index: 1000;
}
textarea {
min-height: 100px;
}
.progress {
height: 8px;
}
.instruction-card {
margin-bottom: 20px;
background-color: #e9f7fe;
border-left: 4px solid #3498db;
padding: 15px;
border-radius: 4px;
}
</style>
</head>
<body>
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8">
<div class="text-center mb-4">
<h1 class="display-4">价值观及经历克隆</h1>
<p class="lead">本采访将从多个维度分析你的决策风格和价值观</p>
<p>回答越详细,创建的代理就越接近你的真实决策模式</p>
</div>
<!-- 显示指令信息 -->
<div class="instruction-card" id="instructionCard">
<h4>克隆要求</h4>
<p id="instructionText">{{ instruction }}</p>
</div>
<form id="decisionInterviewForm">
<!-- 核心价值观维度 -->
<div class="dimension-card" id="dimension1">
<div class="dimension-header">
<h3>核心价值观维度</h3>
<p class="text-muted">这个维度分析你在核心价值观方面的特点和偏好</p>
</div>
<div class="dimension-body">
<div class="question-item">
<label class="form-label">在你的人生中,最重要的3-5个价值观是什么?</label>
<textarea class="form-control" name="dim1_q1" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">如果你必须在这些价值观之间做取舍,你会如何排序?</label>
<textarea class="form-control" name="dim1_q2" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">有什么原则是你认为在任何情况下都不应该违背的?</label>
<textarea class="form-control" name="dim1_q3" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">分享一个你坚守个人价值观的具体经历,以及这如何影响了当时的决策和结果?</label>
<textarea class="form-control" name="dim1_q4" rows="5" required></textarea>
</div>
</div>
</div>
<!-- 风险态度维度 -->
<div class="dimension-card" id="dimension2">
<div class="dimension-header">
<h3>风险态度维度</h3>
<p class="text-muted">这个维度分析你在风险态度方面的特点和偏好</p>
</div>
<div class="dimension-body">
<div class="question-item">
<label class="form-label">在投资决策中,你倾向于保守还是激进?为什么?</label>
<textarea class="form-control" name="dim2_q1" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">面对不确定性较高但回报也高的机会,你通常如何抉择?</label>
<textarea class="form-control" name="dim2_q2" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">你如何看待'宁可错过机会也不要承担过大风险'这种说法?</label>
<textarea class="form-control" name="dim2_q3" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">描述一次你冒险(或拒绝冒险)的重要决策,事后你如何评价这个决定?</label>
<textarea class="form-control" name="dim2_q4" rows="5" required></textarea>
</div>
</div>
</div>
<!-- 时间偏好维度 -->
<div class="dimension-card" id="dimension3">
<div class="dimension-header">
<h3>时间偏好维度</h3>
<p class="text-muted">这个维度分析你在时间偏好方面的特点和偏好</p>
</div>
<div class="dimension-body">
<div class="question-item">
<label class="form-label">在追求短期利益和长期发展之间,你通常如何平衡?</label>
<textarea class="form-control" name="dim3_q1" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">你愿意现在牺牲多少,来换取未来更大的收益?</label>
<textarea class="form-control" name="dim3_q2" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">在规划未来时,你会考虑多长的时间跨度?</label>
<textarea class="form-control" name="dim3_q3" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">回忆一个你为长远目标放弃短期利益的重要经历,这个决定最终如何影响了你?</label>
<textarea class="form-control" name="dim3_q4" rows="5" required></textarea>
</div>
</div>
</div>
<!-- 自主性与集体性维度 -->
<div class="dimension-card" id="dimension4">
<div class="dimension-header">
<h3>自主性与集体性维度</h3>
<p class="text-muted">这个维度分析你在自主性与集体性方面的特点和偏好</p>
</div>
<div class="dimension-body">
<div class="question-item">
<label class="form-label">在团队决策中,你更倾向于坚持自己的判断还是顺应多数意见?</label>
<textarea class="form-control" name="dim4_q1" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">个人利益与集体利益冲突时,你通常如何处理?</label>
<textarea class="form-control" name="dim4_q2" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">你认为什么情况下应该妥协,什么情况下应该坚持立场?</label>
<textarea class="form-control" name="dim4_q3" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">分享一次你在团队中坚持己见或选择妥协的经历,结果如何?这教会了你什么?</label>
<textarea class="form-control" name="dim4_q4" rows="5" required></textarea>
</div>
</div>
</div>
<!-- 理性与情感维度 -->
<div class="dimension-card" id="dimension5">
<div class="dimension-header">
<h3>理性与情感维度</h3>
<p class="text-muted">这个维度分析你在理性与情感方面的特点和偏好</p>
</div>
<div class="dimension-body">
<div class="question-item">
<label class="form-label">在做重要决策时,你更依赖理性分析还是直觉感受?</label>
<textarea class="form-control" name="dim5_q1" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">情感因素在你的决策过程中占多大比重?</label>
<textarea class="form-control" name="dim5_q2" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">回忆一次你的情感判断与理性分析相冲突的经历,你最终如何决定?</label>
<textarea class="form-control" name="dim5_q3" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">有没有一次决策,你完全依靠直觉而非分析做出,结果令你印象深刻?</label>
<textarea class="form-control" name="dim5_q4" rows="5" required></textarea>
</div>
</div>
</div>
<!-- 资源分配维度 -->
<div class="dimension-card" id="dimension6">
<div class="dimension-header">
<h3>资源分配维度</h3>
<p class="text-muted">这个维度分析你在资源分配方面的特点和偏好</p>
</div>
<div class="dimension-body">
<div class="question-item">
<label class="form-label">在分配时间、金钱和精力时,你有什么原则或优先级?</label>
<textarea class="form-control" name="dim6_q1" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">面对多个重要但无法同时满足的需求,你如何决定先满足哪一个?</label>
<textarea class="form-control" name="dim6_q2" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">你会为了帮助他人而牺牲自己的资源吗?在什么条件下?</label>
<textarea class="form-control" name="dim6_q3" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">描述一次你必须在多个重要项目或责任之间分配有限资源的经历和你的处理方式?</label>
<textarea class="form-control" name="dim6_q4" rows="5" required></textarea>
</div>
</div>
</div>
<!-- 道德伦理维度 -->
<div class="dimension-card" id="dimension7">
<div class="dimension-header">
<h3>道德伦理维度</h3>
<p class="text-muted">这个维度分析你在道德伦理方面的特点和偏好</p>
</div>
<div class="dimension-body">
<div class="question-item">
<label class="form-label">在面临道德两难时,你通常依据什么原则来决定?</label>
<textarea class="form-control" name="dim7_q1" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">你认为在商业决策中,道德因素应该占多大权重?</label>
<textarea class="form-control" name="dim7_q2" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">有没有某些行为,即使对你有利,你也坚决不会做?为什么?</label>
<textarea class="form-control" name="dim7_q3" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">分享一个你面临道德困境的经历,你如何做出抉择,事后有何反思?</label>
<textarea class="form-control" name="dim7_q4" rows="5" required></textarea>
</div>
</div>
</div>
<!-- 决策过程维度 -->
<div class="dimension-card" id="dimension8">
<div class="dimension-header">
<h3>决策过程维度</h3>
<p class="text-muted">这个维度分析你在决策过程方面的特点和偏好</p>
</div>
<div class="dimension-body">
<div class="question-item">
<label class="form-label">你通常如何收集信息来支持重要决策?</label>
<textarea class="form-control" name="dim8_q1" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">在时间紧迫的情况下,你会如何简化决策过程?</label>
<textarea class="form-control" name="dim8_q2" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">当你对某个决定感到后悔时,你会如何调整未来的决策方式?</label>
<textarea class="form-control" name="dim8_q3" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">回忆一次你做出了错误决策的经历,你从中学到了什么?这如何改变了你后来的决策习惯?</label>
<textarea class="form-control" name="dim8_q4" rows="5" required></textarea>
</div>
</div>
</div>
<!-- 成长与变化维度 -->
<div class="dimension-card" id="dimension9">
<div class="dimension-header">
<h3>成长与变化维度</h3>
<p class="text-muted">这个维度分析你在成长与变化方面的特点和偏好</p>
</div>
<div class="dimension-body">
<div class="question-item">
<label class="form-label">你的决策方式在人生不同阶段有什么变化?什么经历促成了这些变化?</label>
<textarea class="form-control" name="dim9_q1" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">有什么童年或成长经历显著塑造了你现在的决策方式或价值观?</label>
<textarea class="form-control" name="dim9_q2" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">描述一次彻底改变了你思维方式或价值观的关键经历?</label>
<textarea class="form-control" name="dim9_q3" rows="3" required></textarea>
</div>
<div class="question-item">
<label class="form-label">你认为自己最大的决策失误是什么?它如何影响了你现在的思考方式?</label>
<textarea class="form-control" name="dim9_q4" rows="5" required></textarea>
</div>
</div>
</div>
<!-- 进度指示器和保存按钮 -->
<div class="fixed-bottom-bar">
<div class="container">
<div class="row">
<div class="col-md-8 offset-md-2">
<div class="progress mb-2">
<div id="progressBar" class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: 0%" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div class="d-flex justify-content-between align-items-center">
<div>
<span id="progressText">0% 完成</span>
</div>
<div>
<button type="button" class="btn btn-primary" id="saveBtn">克隆</button>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<!-- 导出确认模态框 -->
<div class="modal fade" id="exportModal" tabindex="-1" aria-labelledby="exportModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exportModalLabel">回答已保存</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="exportModalBody">
<p>你的回答已成功保存为JSON格式。可以通过以下方式使用:</p>
<ol>
<li>保存到本地文件</li>
<li>将数据提供给代理创建程序</li>
</ol>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
<button type="button" class="btn btn-primary" id="downloadBtn">下载JSON文件</button>
</div>
</div>
</div>
</div>
<!-- Bootstrap & jQuery JS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
$(document).ready(function() {
// 计算表单完成进度
function updateProgress() {
const totalFields = $('textarea[required], input[required]').length;
const filledFields = $('textarea[required], input[required]').filter(function() {
return $(this).val().trim() !== '';
}).length;
const percentage = Math.floor((filledFields / totalFields) * 100);
$('#progressBar').css('width', percentage + '%');
$('#progressBar').attr('aria-valuenow', percentage);
$('#progressText').text(percentage + '% 完成');
}
// 初始化表单状态
updateProgress();
// 监听输入变化
$('textarea, input').on('change keyup', function() {
updateProgress();
});
// 格式化表单数据为JSON
function formatFormData() {
const dimensions = {};
for (let i = 1; i <= 9; i++) {
const dimensionName = getDimensionName(i);
dimensions[dimensionName] = [
{
"问题": $(`#dimension${i} .question-item:nth-child(1) label`).text(),
"回答": $(`textarea[name="dim${i}_q1"]`).val()
},
{
"问题": $(`#dimension${i} .question-item:nth-child(2) label`).text(),
"回答": $(`textarea[name="dim${i}_q2"]`).val()
},
{
"问题": $(`#dimension${i} .question-item:nth-child(3) label`).text(),
"回答": $(`textarea[name="dim${i}_q3"]`).val()
},
{
"问题": $(`#dimension${i} .question-item:nth-child(4) label`).text(),
"回答": $(`textarea[name="dim${i}_q4"]`).val()
}
];
}
return {
dimensions: dimensions
};
}
// 获取维度名称
function getDimensionName(index) {
const dimensionMap = {
1: "核心价值观维度",
2: "风险态度维度",
3: "时间偏好维度",
4: "自主性与集体性维度",
5: "理性与情感维度",
6: "资源分配维度",
7: "道德伦理维度",
8: "决策过程维度",
9: "成长与变化维度"
};
return dimensionMap[index];
}
// 保存按钮点击事件
$('#saveBtn').click(function() {
// 保存数据到本地存储
const formData = formatFormData();
const jsonData = JSON.stringify(formData, null, 2);
// 创建下载链接
const blob = new Blob([jsonData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
// 设置下载按钮
$('#downloadBtn').off('click').on('click', function() {
const a = document.createElement('a');
a.href = url;
a.download = `决策价值观调查.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
// 显示确认对话框
if (confirm('克隆前将清除所有现有记忆,确定要继续吗?')) {
// 显示保存中状态
$(this).prop('disabled', true).html('<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> 克隆中...');
// 先调用清除记忆API
$.ajax({
url: '/api/clear-memory',
type: 'POST',
success: function(clearResponse) {
console.log('记忆清除成功:', clearResponse);
// 记忆清除成功后,提交表单数据
$.ajax({
url: '/api/submit',
type: 'POST',
contentType: 'application/json',
data: jsonData,
success: function(response) {
// 显示成功消息
$('#exportModal').modal('show');
$('#exportModalLabel').text('保存成功');
$('#exportModalBody').html(`
<div class="alert alert-success">
<p>${response.message || '决策分析数据已成功保存,可以关闭页面了'}</p>
<p>你的回答也已保存到本地浏览器中,下次访问时可以继续编辑。</p>
</div>
`);
// 3秒后关闭当前页面
setTimeout(function() {
window.close();
}, 3000);
},
error: function(xhr, status, error) {
// 恢复保存按钮状态
$('#saveBtn').prop('disabled', false).text('保存回答');
// 显示错误消息
alert('克隆失败: ' + (xhr.responseJSON?.message || error));
}
});
},
error: function(xhr, status, error) {
// 恢复保存按钮状态
$('#saveBtn').prop('disabled', false).text('克隆回答');
// 显示错误消息
alert('清除记忆失败: ' + (xhr.responseJSON?.message || error) + '\n请稍后再试或手动清除记忆。');
}
});
} else {
// 用户取消了提交,但仍然保存到本地并显示下载选项
$('#exportModal').modal('show');
$('#exportModalLabel').text('回答已本地保存');
$('#exportModalBody').html(`
<div class="alert alert-info">
<p>你的回答已保存到本地浏览器中,下次访问时可以继续编辑。</p>
<p>你可以下载JSON文件备份你的回答。</p>
</div>
`);
}
});
// 移除表单提交事件处理,因为我们只使用保存按钮
$('#decisionInterviewForm').submit(function(e) {
e.preventDefault();
// 触发保存按钮点击事件
$('#saveBtn').click();
});
// 检查是否有保存的数据
const savedData = localStorage.getItem('decisionInterviewData');
if (savedData) {
try {
const data = JSON.parse(savedData);
// 填充维度问题
for (let i = 1; i <= 9; i++) {
const dimensionName = getDimensionName(i);
if (data.dimensions && data.dimensions[dimensionName]) {
$(`textarea[name="dim${i}_q1"]`).val(data.dimensions[dimensionName][0].回答);
$(`textarea[name="dim${i}_q2"]`).val(data.dimensions[dimensionName][1].回答);
$(`textarea[name="dim${i}_q3"]`).val(data.dimensions[dimensionName][2].回答);
$(`textarea[name="dim${i}_q4"]`).val(data.dimensions[dimensionName][3].回答);
}
}
// 更新进度
updateProgress();
// 显示提示消息
$('<div class="alert alert-info alert-dismissible fade show" role="alert">' +
'已从本地存储加载之前的回答!' +
'<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>' +
'</div>').insertBefore('#dimension1');
} catch (e) {
console.error('加载保存数据时出错', e);
}
}
});
</script>
<script>
// 显示指令信息
$(document).ready(function() {
// 如果没有指令,隐藏指令卡片
if (!$('#instructionText').text().trim()) {
$('#instructionCard').hide();
}
});
</script>
<script>
// 提交表单的函数
function submitForm() {
// 获取表单数据
const formData = collectFormData();
// 发送数据到后端
fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
})
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
// 显示成功消息
alert(data.message);
// 如果后端指示关闭窗口
if (data.closeWindow) {
// 关闭当前窗口
window.close();
}
// 如果提供了重定向URL,则重定向
if (data.redirect) {
// 如果关闭窗口失败,则重定向
setTimeout(() => {
window.location.href = data.redirect;
}, 500);
}
} else {
// 显示错误消息
alert('提交失败: ' + data.message);
}
})
.catch(error => {
console.error('提交数据时出错:', error);
alert('提交数据时出错,请查看控制台获取详细信息。');
});
}
// 在页面加载完成后,为关闭按钮添加事件处理器
document.addEventListener('DOMContentLoaded', function() {
// 假设成功弹窗有一个ID为"successModal",关闭按钮ID为"closeBtn"
const closeBtn = document.getElementById('closeBtn');
if (closeBtn) {
closeBtn.addEventListener('click', function() {
// 尝试关闭窗口
window.close();
// 如果关闭窗口失败(某些浏览器可能阻止脚本关闭窗口),则重定向到设置页面
setTimeout(() => {
window.location.href = 'http://localhost:8080/setting';
}, 500);
});
}
});
</script>
</body>
</html>
+158 -7
View File
@@ -27,6 +27,11 @@ from flask_httpauth import HTTPBasicAuth
from core import qa_service
from core import stream_manager
# 全局变量,用于跟踪当前的genagents服务器
genagents_server = None
genagents_thread = None
monitor_thread = None
__app = Flask(__name__)
# 禁用 Flask 默认日志
__app.logger.disabled = True
@@ -327,7 +332,7 @@ def api_send_v1_chat_completions():
util.printInfo(1, username, '[文字沟通接口]{}'.format(interact.data["msg"]), time.time())
text = fay_booter.feiFei.on_interact(interact)
if config_util.key_chat_module == 'gpt_stream':
if config_util.key_chat_module.endswith('_stream'):
return gpt_stream_response(member_db.new_instance().find_user(username))
elif model == 'fay-streaming':
return stream_response(text)
@@ -346,7 +351,7 @@ def api_get_Member_list():
except Exception as e:
return jsonify({'list': [], 'message': f'获取成员列表时出错: {e}'}), 500
@__app.route('/api/get_run_status', methods=['post'])
@__app.route('/api/get-run-status', methods=['post'])
def api_get_run_status():
# 获取运行状态
try:
@@ -355,7 +360,7 @@ def api_get_run_status():
except Exception as e:
return jsonify({'status': False, 'message': f'获取运行状态时出错: {e}'}), 500
@__app.route('/api/adopt_msg', methods=['POST'])
@__app.route('/api/adopt-msg', methods=['POST'])
def adopt_msg():
# 采纳消息
data = request.get_json()
@@ -531,7 +536,7 @@ def serve_gif(filename):
return jsonify({'error': '文件未找到'}), 404
#打招呼
@__app.route('/to_greet', methods=['POST'])
@__app.route('/to-greet', methods=['POST'])
def to_greet():
data = request.get_json()
username = data.get('username', 'User')
@@ -541,7 +546,7 @@ def to_greet():
return jsonify({'status': 'success', 'data': text, 'msg': '已进行打招呼'}), 200
#唤醒:在普通唤醒模式,进行大屏交互才有意义
@__app.route('/to_wake', methods=['POST'])
@__app.route('/to-wake', methods=['POST'])
def to_wake():
data = request.get_json()
username = data.get('username', 'User')
@@ -550,7 +555,7 @@ def to_wake():
return jsonify({'status': 'success', 'msg': '已唤醒'}), 200
#打断
@__app.route('/to_stop_talking', methods=['POST'])
@__app.route('/to-stop-talking', methods=['POST'])
def to_stop_talking():
try:
data = request.get_json()
@@ -572,7 +577,7 @@ def to_stop_talking():
#消息透传接口
@__app.route('/transparent_pass', methods=['post'])
@__app.route('/transparent-pass', methods=['post'])
def transparent_pass():
try:
data = request.form.get('data')
@@ -592,6 +597,152 @@ def transparent_pass():
except Exception as e:
return jsonify({'code': 500, 'message': f'出错: {e}'}), 500
# 清除记忆API
@__app.route('/api/clear-memory', methods=['POST'])
def api_clear_memory():
try:
# 获取memory目录路径
memory_dir = os.path.join(os.getcwd(), "memory")
# 检查目录是否存在
if not os.path.exists(memory_dir):
return jsonify({'success': False, 'message': '记忆目录不存在'}), 400
# 清空memory目录下的所有文件(保留目录结构)
for root, dirs, files in os.walk(memory_dir):
for file in files:
file_path = os.path.join(root, file)
try:
if os.path.isfile(file_path):
os.remove(file_path)
util.log(1, f"已删除文件: {file_path}")
except Exception as e:
util.log(1, f"删除文件时出错: {file_path}, 错误: {str(e)}")
# 删除memory_stream目录(如果存在)
memory_stream_dir = os.path.join(memory_dir, "memory_stream")
if os.path.exists(memory_stream_dir):
import shutil
try:
shutil.rmtree(memory_stream_dir)
util.log(1, f"已删除目录: {memory_stream_dir}")
except Exception as e:
util.log(1, f"删除目录时出错: {memory_stream_dir}, 错误: {str(e)}")
# 创建一个标记文件,表示记忆已被清除,防止退出时重新保存
with open(os.path.join(memory_dir, ".memory_cleared"), "w") as f:
f.write("Memory has been cleared. Do not save on exit.")
# 修改fay_booter.py中的保存记忆逻辑
try:
# 导入并修改nlp_cognitive_stream模块中的保存函数
from llm.nlp_cognitive_stream import set_memory_cleared_flag
set_memory_cleared_flag(True)
except Exception as e:
util.log(1, f"设置记忆清除标记时出错: {str(e)}")
util.log(1, "记忆已清除,需要重启应用才能生效")
return jsonify({'success': True, 'message': '记忆已清除,请重启应用使更改生效'}), 200
except Exception as e:
util.log(1, f"清除记忆时出错: {str(e)}")
return jsonify({'success': False, 'message': f'清除记忆时出错: {str(e)}'}), 500
# 启动genagents_flask.py的API
@__app.route('/api/start-genagents', methods=['POST'])
def api_start_genagents():
try:
# 只有在数字人启动后才能克隆人格
if not fay_booter.is_running():
return jsonify({'success': False, 'message': 'Fay未启动,无法启动决策分析'}), 400
# 获取克隆要求
data = request.get_json()
if not data or 'instruction' not in data:
return jsonify({'success': False, 'message': '缺少克隆要求参数'}), 400
instruction = data['instruction']
# 保存指令到临时文件,供genagents_flask.py读取
instruction_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'genagents', 'instruction.json')
with open(instruction_file, 'w', encoding='utf-8') as f:
json.dump({'instruction': instruction}, f, ensure_ascii=False)
# 导入genagents_flask模块
import sys
sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..'))
from genagents.genagents_flask import start_genagents_server, is_shutdown_requested
from werkzeug.serving import make_server
# 关闭之前的genagents服务器(如果存在)
global genagents_server, genagents_thread, monitor_thread
if genagents_server is not None:
try:
# 主动关闭之前的服务器
util.log(1, "关闭之前的决策分析服务...")
genagents_server.shutdown()
# 等待线程结束
if genagents_thread and genagents_thread.is_alive():
genagents_thread.join(timeout=2)
if monitor_thread and monitor_thread.is_alive():
monitor_thread.join(timeout=2)
except Exception as e:
util.log(1, f"关闭之前的决策分析服务时出错: {str(e)}")
# 清除之前的记忆,确保只保留最新的决策分析
try:
from llm.nlp_cognitive_stream import clear_agent_memory
util.log(1, "已清除之前的决策分析记忆")
except Exception as e:
util.log(1, f"清除之前的决策分析记忆时出错: {str(e)}")
# 启动决策分析服务(不启动单独进程,而是返回Flask应用实例)
genagents_app = start_genagents_server(instruction_text=instruction)
# 创建服务器
genagents_server = make_server('0.0.0.0', 5001, genagents_app)
# 在后台线程中启动Flask服务
import threading
def run_genagents_app():
try:
# 使用serve_forever而不是app.run
genagents_server.serve_forever()
except Exception as e:
util.log(1, f"决策分析服务运行出错: {str(e)}")
finally:
util.log(1, f"决策分析服务已关闭")
# 启动监控线程,检查是否需要关闭服务器
def monitor_shutdown():
try:
while not is_shutdown_requested():
time.sleep(1)
util.log(1, f"检测到关闭请求,正在关闭决策分析服务...")
genagents_server.shutdown()
except Exception as e:
util.log(1, f"监控决策分析服务时出错: {str(e)}")
# 启动服务器线程
genagents_thread = threading.Thread(target=run_genagents_app)
genagents_thread.daemon = True
genagents_thread.start()
# 启动监控线程
monitor_thread = threading.Thread(target=monitor_shutdown)
monitor_thread.daemon = True
monitor_thread.start()
util.log(1, f"已启动决策分析页面,指令: {instruction}")
# 返回决策分析页面的URL
return jsonify({
'success': True,
'message': '已启动决策分析页面',
'url': 'http://127.0.0.1:5001/'
}), 200
except Exception as e:
util.log(1, f"启动决策分析页面时出错: {str(e)}")
return jsonify({'success': False, 'message': f'启动决策分析页面时出错: {str(e)}'}), 500
def run():
class NullLogHandler:
+19 -13
View File
@@ -95,7 +95,7 @@ class FayInterface {
}
getRunStatus() {
return this.fetchData(`${this.baseApiUrl}/api/get_run_status`, {
return this.fetchData(`${this.baseApiUrl}/api/get-run-status`, {
method: 'POST'
});
}
@@ -446,18 +446,24 @@ new Vue({
this.fayService.getUserList().then((response) => {
if (response && response.list) {
if (response.list.length == 0){
info = [];
info[0] = 1;
info[1] = 'User';
this.userList.push(info)
this.selectUser(info);
}else{
this.userList = response.list;
if (!this.selectedUser) {
this.selectUser(this.userList[0]);
// 检查是否已经有默认用户
const defaultUserExists = this.userList.some(user => user[1] === 'User');
if (!defaultUserExists) {
// 只有在不存在默认用户时才添加
const info = [];
info[0] = 1;
info[1] = 'User';
this.userList.push(info);
this.selectUser(info);
console.log('添加默认用户: User');
}
} else {
this.userList = response.list;
if (!this.selectedUser) {
this.selectUser(this.userList[0]);
}
}
}
}
});
},
startUserListTimer() {
@@ -521,12 +527,12 @@ new Vue({
} ,
adoptText(id) {
// 调用采纳接口
this.fayService.fetchData(`${this.base_url}/api/adopt_msg`, {
this.fayService.fetchData(`${this.base_url}/api/adopt-msg`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ id }) // 发送采纳请求
body: JSON.stringify({ id })
})
.then((response) => {
if (response && response.status === 'success') {
+90 -2
View File
@@ -74,7 +74,7 @@ class FayInterface {
}
getRunStatus() {
return this.fetchData(`${this.baseApiUrl}/api/get_run_status`, {
return this.fetchData(`${this.baseApiUrl}/api/get-run-status`, {
method: 'POST'
});
}
@@ -312,7 +312,7 @@ new Vue({
}
}
}
this.sendSuccessMsg("配置已保存!")
this.sendSuccessMsg("配置已保存!");
},
startLive() {
this.liveState = 2
@@ -335,5 +335,93 @@ new Vue({
type: 'success',
});
},
clearMemory() {
this.$confirm('清除记忆操作将删除Fay的所有对话记忆,清除后需要重启应用才能生效,确认继续吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
// 发送清除记忆请求
fetch(`${this.host_url}/api/clear-memory`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
})
.then(response => response.json())
.then(data => {
if (data.success) {
this.sendSuccessMsg(data.message || "记忆已清除,请重启应用使更改生效");
} else {
this.$notify({
title: '错误',
message: data.message || '清除记忆失败',
type: 'error'
});
}
})
.catch(error => {
this.$notify({
title: '错误',
message: '清除记忆请求失败',
type: 'error'
});
});
}).catch(() => {
// 用户取消操作
});
},
clonePersonality() {
if (this.liveState === 1) {
this.$prompt('请输入克隆要求', '克隆人格', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPlaceholder: '请输入克隆要求,例如:你现在是一个活泼开朗的助手...'
}).then(({ value }) => {
if (!value) {
this.$notify({
title: '提示',
message: '克隆要求不能为空',
type: 'warning'
});
return;
}
// 直接启动genagents_flask.py并打开decision_interview.html页面
fetch(`${this.host_url}/api/start-genagents`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ instruction: value })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 弹出提示,显示克隆地址,不自动打开
this.$alert(`决策分析页面已启动,请复制以下链接在新窗口中打开:<br><br><code style="background-color: #f5f5f5; padding: 5px; border-radius: 3px;">${data.url}</code>`, '克隆人格', {
confirmButtonText: '确定',
dangerouslyUseHTMLString: true
});
} else {
this.$notify({
title: '错误',
message: data.message || '启动决策分析页面失败',
type: 'error'
});
}
})
.catch(error => {
this.$notify({
title: '错误',
message: '启动决策分析页面请求失败',
type: 'error'
});
});
});
} else {
this.$notify({
title: '提示',
message: '请先开Fay后再执行此操作',
type: 'warning'
});
}
},
},
});
+24 -12
View File
@@ -131,20 +131,32 @@
<input class="section_2" v-model="automatic_player_url" placeholder="http://127.0.0.1:6000" :disabled="!configEditable" />
</div>
</div>
<div style="margin-top: 40px; text-align: center;">
<el-button style="width: 160px;height: 50px;margin-left: -100px;" :disabled="!configEditable" @click=saveConfig()>保存配置</el-button>
<div style="margin-top: 40px; position: relative; width: 100%; text-align: center;">
<div style="display: inline-block; position: absolute; left: 30%; transform: translateX(-50%);">
<el-button style="width: 160px; height: 50px;" :disabled="!configEditable" @click=saveConfig()>保存配置</el-button>
</div>
<!-- <el-button type="primary" style="width: 160px;height: 50px;margin-left: 100px;background-color: #007aff;">开 启</el-button> -->
<el-button v-if="liveState == 1" type="success" class="btn_close"
style="width: 160px;height: 50px;margin-left: 100px;background-color: #007aff;" @click=stopLive()>关闭(运行中)</el-button>
<el-button v-else-if="liveState == 2" type="primary" plain disabled
style="width: 160px;height: 50px;margin-left: 100px;background-color: #007aff;">正在开启...</el-button>
<el-button v-else-if="liveState == 3" type="success" plain disabled
style="width: 160px;height: 50px;margin-left: 100px;background-color: #007aff;">正在关闭...</el-button>
<el-button v-else type="primary" style="width: 160px;height: 50px;margin-left: 100px;background-color: #007aff;"
@click=startLive()>开启</el-button>
<div style="display: inline-block; position: absolute; left: 70%; transform: translateX(-50%);">
<!-- <el-button type="primary" style="width: 160px;height: 50px;background-color: #007aff;">开 启</el-button> -->
<el-button v-if="liveState == 1" type="success" class="btn_close"
style="width: 160px; height: 50px; background-color: #007aff;" @click=stopLive()>关闭(运行中)</el-button>
<el-button v-else-if="liveState == 2" type="primary" plain disabled
style="width: 160px; height: 50px; background-color: #007aff;">正在开启...</el-button>
<el-button v-else-if="liveState == 3" type="success" plain disabled
style="width: 160px; height: 50px; background-color: #007aff;">正在关闭...</el-button>
<el-button v-else type="primary" style="width: 160px; height: 50px; background-color: #007aff;"
@click=startLive()>开启</el-button>
</div>
</div>
<div style="margin-top: 100px; position: relative; width: 100%; text-align: center;">
<div style="display: inline-block; position: absolute; left: 30%; transform: translateX(-50%);">
<a href="javascript:void(0)" @click="clearMemory" style="color: #007aff; font-size: 14px; text-decoration: none;">清除记忆</a>
</div>
<div style="display: inline-block; position: absolute; left: 70%; transform: translateX(-50%);">
<a href="javascript:void(0)" @click="clonePersonality" style="color: #007aff; font-size: 14px; text-decoration: none;">克隆(赋予)人格</a>
</div>
</div>
<div style="height: 60px;"></div> <!-- 添加空间以避免内容被覆盖 -->
</div>
</div>
+5 -5
View File
@@ -15,7 +15,7 @@ agent_running = False
# 数据库初始化
def init_db():
conn = sqlite3.connect('timer.db')
conn = sqlite3.connect('memory/timer.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS timer (
@@ -33,7 +33,7 @@ def init_db():
# 插入测试数据
def insert_test_data():
conn = sqlite3.connect('timer.db')
conn = sqlite3.connect('memory/timer.db')
cursor = conn.cursor()
cursor.execute("INSERT INTO timer (time, repeat_rule, content) VALUES (?, ?, ?)", ('16:20', '1010001', 'Meeting Reminder'))
conn.commit()
@@ -66,7 +66,7 @@ def execute_task(task_time, id, content, uid):
if text is not None and id in scheduled_tasks:
del scheduled_tasks[id]
# 如果不重复,执行后删除记录
conn = sqlite3.connect('timer.db')
conn = sqlite3.connect('memory/timer.db')
cursor = conn.cursor()
cursor.execute("DELETE FROM timer WHERE repeat_rule = '0000000' AND id = ?", (id,))
conn.commit()
@@ -76,7 +76,7 @@ def execute_task(task_time, id, content, uid):
# 30秒扫描一次数据库,当扫描到今天的不存在于定时任务列表的记录,则添加到定时任务列表。执行完的记录从定时任务列表中清除。
def check_and_execute():
while agent_running:
conn = sqlite3.connect('timer.db')
conn = sqlite3.connect('memory/timer.db')
cursor = conn.cursor()
cursor.execute("SELECT * FROM timer")
rows = cursor.fetchall()
@@ -99,7 +99,7 @@ def agent_start():
agent_running = True
#初始计划
if not os.path.exists("./timer.db"):
if not os.path.exists("./memory/timer.db"):
init_db()
content ="""执行任务->
你是一个数字人,你的责任是陪伴主人生活、工作:
+1 -1
View File
@@ -20,7 +20,7 @@ class DeleteTimer(BaseTool):
return "输入的 ID 无效,必须是数字。"
try:
with sqlite3.connect('timer.db') as conn:
with sqlite3.connect('memory/timer.db') as conn:
cursor = conn.cursor()
cursor.execute("DELETE FROM timer WHERE id = ?", (id,))
conn.commit()
+1 -1
View File
@@ -38,7 +38,7 @@ class MyTimer(BaseTool, abc.ABC):
return "事项内容必须为非空字符串。"
# 数据库操作
conn = sqlite3.connect('timer.db')
conn = sqlite3.connect('memory/timer.db')
cursor = conn.cursor()
try:
cursor.execute("INSERT INTO timer (time, repeat_rule, content, uid) VALUES (?, ?, ?, ?)", (time, repeat_rule, content, self.uid))
+1 -1
View File
@@ -20,7 +20,7 @@ class QueryTimerDB(BaseTool, abc.ABC):
def _run(self, para) -> str:
conn = sqlite3.connect('timer.db')
conn = sqlite3.connect('memory/timer.db')
cursor = conn.cursor()
# 执行查询
cursor.execute("SELECT * FROM timer")
+634
View File
@@ -0,0 +1,634 @@
import os
import json
import time
import threading
import requests
from utils import util
import utils.config_util as cfg
from genagents.genagents import GenerativeAgent
from genagents.modules.memory_stream import ConceptNode
from core import member_db
from urllib3.exceptions import InsecureRequestWarning
import schedule
from scheduler.thread_manager import MyThread
import datetime
from core import stream_manager
# 加载配置
cfg.load_config()
# 禁用不安全请求警告
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
agent = None
memory_dir = None
memory_stream_dir = None
agent_lock = threading.RLock() # 使用可重入锁保护agent对象
memory_cleared = False # 添加记忆清除标记
def get_current_time_step():
"""
获取当前时间作为time_step
返回:
int: 当前时间的时间戳(秒)
"""
global agent
try:
if agent and agent.memory_stream and agent.memory_stream.seq_nodes:
# 如果有记忆节点,则使用最后一个节点的created属性加1
return int(agent.memory_stream.seq_nodes[-1].created) + 1
else:
# 如果没有记忆节点或agent未初始化,则使用0
return 0
except Exception as e:
util.log(1, f"获取time_step时出错: {str(e)},使用0代替")
return 0
# 定时保存记忆的线程
def memory_scheduler_thread():
"""
定时任务线程,运行schedule调度器
"""
while True:
schedule.run_pending()
time.sleep(60) # 每分钟检查一次是否有定时任务需要执行
# 初始化定时保存记忆的任务
def init_memory_scheduler():
"""
初始化定时保存记忆的任务
"""
global agent
# 确保agent已经创建
if agent is None:
util.log(1, '创建代理实例...')
agent = create_agent()
# 设置每天0点保存记忆
schedule.every().day.at("00:00").do(save_agent_memory)
# 设置每天晚上11点执行反思
schedule.every().day.at("23:30").do(perform_daily_reflection)
# 启动定时任务线程
scheduler_thread = MyThread(target=memory_scheduler_thread)
scheduler_thread.start()
util.log(1, '定时任务已启动:每天0点保存记忆,每天23点执行反思')
def check_memory_files():
"""
检查memory目录及其必要文件是否存在
返回:
memory_dir: memory目录路径
is_complete: 是否已经存在完整的memory目录结构
"""
global memory_dir
global memory_stream_dir
# 获取memory目录路径
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
memory_dir = os.path.join(base_dir, "memory")
# 检查memory目录是否存在,不存在则创建
if not os.path.exists(memory_dir):
os.makedirs(memory_dir)
util.log(1, f"创建memory目录: {memory_dir}")
# 删除.memory_cleared标记文件(如果存在)
memory_cleared_flag_file = os.path.join(memory_dir, ".memory_cleared")
if os.path.exists(memory_cleared_flag_file):
try:
os.remove(memory_cleared_flag_file)
util.log(1, f"删除记忆清除标记文件: {memory_cleared_flag_file}")
# 重置记忆清除标记
global memory_cleared
memory_cleared = False
except Exception as e:
util.log(1, f"删除记忆清除标记文件时出错: {str(e)}")
# 检查meta.json是否存在
meta_file = os.path.join(memory_dir, "meta.json")
is_complete = os.path.exists(meta_file)
# 检查memory_stream目录是否存在,不存在则创建
memory_stream_dir = os.path.join(memory_dir, "memory_stream")
if not os.path.exists(memory_stream_dir):
os.makedirs(memory_stream_dir)
util.log(1, f"创建memory_stream目录: {memory_stream_dir}")
# 检查必要的文件是否存在
scratch_path = os.path.join(memory_dir, "scratch.json")
embeddings_path = os.path.join(memory_stream_dir, "embeddings.json")
nodes_path = os.path.join(memory_stream_dir, "nodes.json")
# 检查文件是否存在且不为空
is_complete = (os.path.exists(scratch_path) and os.path.getsize(scratch_path) > 2 and
os.path.exists(embeddings_path) and os.path.getsize(embeddings_path) > 2 and
os.path.exists(nodes_path) and os.path.getsize(nodes_path) > 2)
# 如果文件不存在,创建空的JSON文件
if not os.path.exists(scratch_path):
with open(scratch_path, 'w', encoding='utf-8') as f:
f.write('{}')
if not os.path.exists(embeddings_path):
with open(embeddings_path, 'w', encoding='utf-8') as f:
f.write('{}')
if not os.path.exists(nodes_path):
with open(nodes_path, 'w', encoding='utf-8') as f:
f.write('[]')
return memory_dir, is_complete
def create_agent():
"""
创建一个GenerativeAgent实例
返回:
agent: GenerativeAgent对象
"""
global agent
# 创建代理
with agent_lock:
if agent is None:
memory_dir, is_exist = check_memory_files()
agent = GenerativeAgent(memory_dir)
# 检查是否有scratch属性,如果没有则添加
if not hasattr(agent, 'scratch'):
agent.scratch = {}
# 如果memory目录不存在或为空,则初始化代理
if not is_exist:
# 初始化代理的scratch数据
scratch_data = {
"first_name": cfg.config["attribute"]["name"],
"last_name": "",
"age": cfg.config["attribute"]["age"],
"gender": cfg.config["attribute"]["gender"],
"traits": cfg.config["attribute"]["additional"],
"status": "active",
"location": "home",
"occupation": cfg.config["attribute"]["job"],
"interests": [],
"current_time": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
agent.scratch = scratch_data
else:
# 加载之前保存的记忆
load_agent_memory(agent)
return agent
def load_agent_memory(agent):
"""
从文件加载代理的记忆
参数:
agent: GenerativeAgent对象
"""
try:
# 获取memory目录路径
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
memory_dir = os.path.join(base_dir, "memory")
memory_stream_dir = os.path.join(memory_dir, "memory_stream")
# 加载scratch.json
scratch_path = os.path.join(memory_dir, "scratch.json")
if os.path.exists(scratch_path) and os.path.getsize(scratch_path) > 2: # 文件存在且不为空
with open(scratch_path, 'r', encoding='utf-8') as f:
scratch_data = json.load(f)
agent.scratch = scratch_data
# 加载nodes.json
nodes_path = os.path.join(memory_stream_dir, "nodes.json")
if os.path.exists(nodes_path) and os.path.getsize(nodes_path) > 2: # 文件存在且不为空
with open(nodes_path, 'r', encoding='utf-8') as f:
nodes_data = json.load(f)
# 清空当前的seq_nodes
agent.memory_stream.seq_nodes = []
agent.memory_stream.id_to_node = {}
# 重新创建节点
for node_dict in nodes_data:
new_node = ConceptNode(node_dict)
agent.memory_stream.seq_nodes.append(new_node)
agent.memory_stream.id_to_node[new_node.node_id] = new_node
# 加载embeddings.json
embeddings_path = os.path.join(memory_stream_dir, "embeddings.json")
if os.path.exists(embeddings_path) and os.path.getsize(embeddings_path) > 2: # 文件存在且不为空
with open(embeddings_path, 'r', encoding='utf-8') as f:
embeddings_data = json.load(f)
agent.memory_stream.embeddings = embeddings_data
util.log(1, f"已加载代理记忆")
except Exception as e:
util.log(1, f"加载代理记忆失败: {str(e)}")
# 记忆对话内容的线程函数
def remember_conversation_thread(username, content, response_text):
"""
在单独线程中记录对话内容到代理记忆
参数:
username: 用户名
content: 用户问题内容
response_text: 代理回答内容
"""
global agent
try:
with agent_lock:
# 获取当前时间作为time_step
time_step = get_current_time_step()
# 记录对话内容
memory_content = f"在对话中,我回答了用户{username}的问题:{content}\n,我的回答是:{response_text}"
agent.remember(memory_content, time_step)
except Exception as e:
util.log(1, f"记忆对话内容出错: {str(e)}")
def question(content, uid=0, observation=""):
"""
处理用户问题并返回回答
参数:
content: 用户问题内容
uid: 用户ID,默认为0
observation: 额外的观察信息,默认为空
返回:
response_text: 回答内容
"""
global agent
# 获取用户名
username = member_db.new_instance().find_username_by_uid(uid) if uid != 0 else "User"
# 创建代理
agent = create_agent()
# 获取对话历史
history_messages = []
history_messages.append([username, content])
# 构建提示信息
str_dialogue = ""
for row in history_messages:
str_dialogue += f"[{row[0]}]: {row[1]}\n"
str_dialogue += f"[Fay]: [Fill in]\n"
# 构建代理描述
agent_desc = {
"first_name": agent.scratch.get("first_name", "Fay"),
"last_name": agent.scratch.get("last_name", ""),
"age": agent.scratch.get("age", "25"),
"gender": agent.scratch.get("gender", ""),
"traits": agent.scratch.get("traits", "友好、乐于助人"),
"status": agent.scratch.get("status", "active"),
"location": agent.scratch.get("location", "home"),
"occupation": agent.scratch.get("occupation", "助手"),
}
# 获取相关记忆作为上下文
context = ""
if agent.memory_stream and len(agent.memory_stream.seq_nodes) > 0:
# 获取当前时间步
current_time_step = get_current_time_step()
# 使用retrieve方法获取相关记忆
try:
related_memories = agent.memory_stream.retrieve(
[content], # 查询句子列表
current_time_step, # 当前时间步
n_count=5, # 获取5条相关记忆
curr_filter="all", # 获取所有类型的记忆
hp=[0, 1, 0.5] # 权重:[时间近度权重recency_w, 相关性权重relevance_w, 重要性权重importance_w]
)
if related_memories and content in related_memories:
memory_nodes = related_memories[content]
if memory_nodes:
context = "以下是相关的记忆:\n"
for node in memory_nodes:
context += f"- {node.content}\n"
except Exception as e:
util.log(1, f"获取相关记忆时出错: {str(e)}")
# 使用流式请求获取回答
session = requests.Session()
session.verify = False
httpproxy = cfg.proxy_config
if httpproxy:
session.proxies = {
"http": f"http://{httpproxy}",
"https": f"https://{httpproxy}"
}
# 构建消息
prompt = f"""你是我的数字人,你名字是:{agent_desc['first_name']},你性别为{agent_desc['gender']}
你年龄为{agent_desc['age']},你职业为{agent_desc['occupation']}
{agent_desc['traits']}
你有以下记忆和上下文信息:{context}
回答之前请一步一步想清楚。对于大部分问题,请直接回答并提供有用和准确的信息。
所有回复请尽量控制在20字内。
"""
messages = [{"role": "system", "content": prompt}]
messages.append({"role": "user", "content": content})
# 构建请求数据
data = {
"model": cfg.gpt_model_engine,
"messages": messages,
"temperature": 0.3,
"max_tokens": 4096,
"user": f"user_{uid}"
}
# 开启流式传输
data["stream"] = True
url = cfg.gpt_base_url + "/chat/completions"
headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {cfg.key_gpt_api_key}'
}
try:
response = session.post(url, json=data, headers=headers, stream=True)
response.raise_for_status()
full_response_text = ""
accumulated_text = ""
punctuation_marks = ["", "", "", ".", "!", "?", "\n"]
is_first_sentence = True
for raw_line in response.iter_lines(decode_unicode=False):
line = raw_line.decode('utf-8', errors='ignore')
if not line or line.strip() == "":
continue
if line.startswith("data: "):
chunk = line[len("data: "):].strip()
try:
json_data = json.loads(chunk)
finish_reason = json_data["choices"][0].get("finish_reason")
if finish_reason is not None:
if finish_reason == "stop":
# 确保最后一段文本也被发送
if accumulated_text:
if is_first_sentence:
accumulated_text += "_<isfirst>"
is_first_sentence = False
stream_manager.new_instance().write_sentence(uid, accumulated_text)
# 发送结束标记
stream_manager.new_instance().write_sentence(uid, "_<isend>")
break
flush_text = json_data["choices"][0]["delta"].get("content", "")
if not flush_text:
continue
accumulated_text += flush_text
for mark in punctuation_marks:
if mark in accumulated_text:
last_punct_pos = max(accumulated_text.rfind(p) for p in punctuation_marks if p in accumulated_text)
if last_punct_pos != -1:
to_write = accumulated_text[:last_punct_pos + 1]
accumulated_text = accumulated_text[last_punct_pos + 1:]
if is_first_sentence:
to_write += "_<isfirst>"
is_first_sentence = False
stream_manager.new_instance().write_sentence(uid, to_write)
break
full_response_text += flush_text
except json.JSONDecodeError:
continue
# 在单独线程中记忆对话内容
from scheduler.thread_manager import MyThread
MyThread(target=remember_conversation_thread, args=(username, content, full_response_text)).start()
return full_response_text
except requests.exceptions.RequestException as e:
util.log(1, f"请求失败: {e}")
error_message = "抱歉,我现在太忙了,休息一会,请稍后再试。"
stream_manager.new_instance().write_sentence(uid, error_message)
return error_message
def set_memory_cleared_flag(flag=True):
"""
设置记忆清除标记
参数:
flag: 是否清除记忆,默认为True
"""
global memory_cleared
memory_cleared = flag
if not flag:
# 删除.memory_cleared标记文件(如果存在)
memory_cleared_flag_file = os.path.join(memory_dir, ".memory_cleared")
if os.path.exists(memory_cleared_flag_file):
try:
os.remove(memory_cleared_flag_file)
util.log(1, f"删除记忆清除标记文件: {memory_cleared_flag_file}")
except Exception as e:
util.log(1, f"删除记忆清除标记文件时出错: {str(e)}")
def clear_agent_memory():
"""
清除已加载的agent记忆,但不删除文件
该方法仅清除内存中已加载的记忆,不影响持久化存储。
如果需要同时清除文件存储,请使用genagents_flask.py中的api_clear_memory方法。
"""
global agent
try:
with agent_lock:
if agent is None:
util.log(1, "代理未初始化,无需清除记忆")
return
# 清除记忆流中的节点
agent = None
# 设置记忆清除标记,防止在退出时保存空记忆
set_memory_cleared_flag(True)
util.log(1, "已成功清除代理在内存中的记忆")
return True
except Exception as e:
util.log(1, f"清除代理记忆时出错: {str(e)}")
return False
# 反思
def perform_daily_reflection():
# 获取当前时间作为time_step
current_time_step = get_current_time_step()
# 获取今天的日期,用于确定反思主题
today = datetime.datetime.now().weekday()
# 根据星期几选择不同反思主题
reflection_topics = [
"我与用户的关系发展,以及我如何更好地理解和服务他们",
"我的知识库如何得到扩展,哪些概念需要进一步理解",
"我的情感响应模式以及它们如何反映我的核心价值观",
"我的沟通方式如何影响互动质量,哪些模式最有效",
"我的行为如何体现我的核心特质,我的自我认知有何变化",
"今天的经历如何与我的过往记忆建立联系,形成什么样的模式",
"本周的整体经历与学习"
]
# 选择今天的主题(可以按星期轮换或其他逻辑)
topic = reflection_topics[today % len(reflection_topics)]
# 执行反思
agent.reflect(topic)
# 记录反思执行情况
util.log(1, f"反思主题: {topic}")
def save_agent_memory():
"""
保存代理的记忆到文件
"""
global agent
# 检查记忆清除标记,如果已清除则不保存
global memory_cleared
if memory_cleared:
util.log(1, "检测到记忆已被清除,跳过保存操作")
return
try:
with agent_lock:
# 检查.memory_cleared标记文件是否存在
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
memory_dir = os.path.join(base_dir, "memory")
memory_cleared_flag_file = os.path.join(memory_dir, ".memory_cleared")
if os.path.exists(memory_cleared_flag_file):
util.log(1, "检测到记忆清除标记文件,跳过保存操作")
return
# 确保agent和memory_stream已初始化
if agent is None:
util.log(1, "代理未初始化,无法保存记忆")
return
if agent.memory_stream is None:
util.log(1, "代理记忆流未初始化,无法保存记忆")
return
# 确保embeddings不为None
if agent.memory_stream.embeddings is None:
util.log(1, "代理embeddings为None,初始化为空字典")
agent.memory_stream.embeddings = {}
# 确保seq_nodes不为None
if agent.memory_stream.seq_nodes is None:
util.log(1, "代理seq_nodes为None,初始化为空列表")
agent.memory_stream.seq_nodes = []
# 确保id_to_node不为None
if agent.memory_stream.id_to_node is None:
util.log(1, "代理id_to_node为None,初始化为空字典")
agent.memory_stream.id_to_node = {}
# 确保scratch不为None
if agent.scratch is None:
util.log(1, "代理scratch为None,初始化为空字典")
agent.scratch = {}
# 保存记忆前进行完整性检查
try:
# 检查seq_nodes中的每个节点是否有效
valid_nodes = []
for node in agent.memory_stream.seq_nodes:
if node is None:
util.log(1, "发现无效节点(None),跳过")
continue
if not hasattr(node, 'node_id') or not hasattr(node, 'content'):
util.log(1, f"发现无效节点(缺少必要属性),跳过")
continue
valid_nodes.append(node)
# 更新seq_nodes为有效节点列表
agent.memory_stream.seq_nodes = valid_nodes
# 重建id_to_node字典
agent.memory_stream.id_to_node = {node.node_id: node for node in valid_nodes if hasattr(node, 'node_id')}
except Exception as e:
util.log(1, f"检查记忆完整性时出错: {str(e)}")
# 保存记忆
try:
agent.save(memory_dir)
except Exception as e:
util.log(1, f"调用agent.save()时出错: {str(e)}")
# 尝试手动保存关键数据
try:
# 创建必要的目录
memory_stream_dir = os.path.join(memory_dir, "memory_stream")
os.makedirs(memory_stream_dir, exist_ok=True)
# 保存embeddings
with open(os.path.join(memory_stream_dir, "embeddings.json"), "w", encoding='utf-8') as f:
json.dump(agent.memory_stream.embeddings or {}, f, ensure_ascii=False, indent=2)
# 保存nodes
with open(os.path.join(memory_stream_dir, "nodes.json"), "w", encoding='utf-8') as f:
nodes_data = []
for node in agent.memory_stream.seq_nodes:
if node is not None and hasattr(node, 'package'):
try:
nodes_data.append(node.package())
except Exception as node_e:
util.log(1, f"打包节点时出错: {str(node_e)}")
json.dump(nodes_data, f, ensure_ascii=False, indent=2)
# 保存scratch
with open(os.path.join(memory_dir, "scratch.json"), "w", encoding='utf-8') as f:
json.dump(agent.scratch or {}, f, ensure_ascii=False, indent=2)
# 保存meta
with open(os.path.join(memory_dir, "meta.json"), "w", encoding='utf-8') as f:
meta_data = {"id": str(agent.id)} if hasattr(agent, 'id') else {}
json.dump(meta_data, f, ensure_ascii=False, indent=2)
util.log(1, "通过备用方法成功保存记忆")
except Exception as backup_e:
util.log(1, f"备用保存方法也失败: {str(backup_e)}")
# 更新scratch中的时间
try:
agent.scratch["current_time"] = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
except Exception as e:
util.log(1, f"更新时间时出错: {str(e)}")
util.log(1, f"已保存代理记忆")
except Exception as e:
util.log(1, f"保存代理记忆失败: {str(e)}")
if __name__ == "__main__":
init_memory_scheduler()
for _ in range(3):
query = "Fay是什么"
response = question(query)
print(f"Q: {query}")
print(f"A: {response}")
time.sleep(1)
+5
View File
@@ -40,6 +40,10 @@ def __clear_logs():
if file_name.endswith('.log'):
os.remove('./logs/' + file_name)
def __create_memory():
if not os.path.exists("./memory"):
os.mkdir("./memory")
def kill_process_by_port(port):
for conn in psutil.net_connections(kind='inet'):
if conn.laddr.port == port and conn.pid:
@@ -108,6 +112,7 @@ def console_listener():
if __name__ == '__main__':
__clear_samples()
__create_memory()
__clear_logs()
#init_db
+2 -1
View File
@@ -27,4 +27,5 @@ psutil
langchain
langchain_openai
langgraph
bs4
bs4
schedule
Binary file not shown.
-816
View File
@@ -1,816 +0,0 @@
#HomeBrew自动安装脚本
#qq 467665317
#brew brew brew brew
#获取硬件信息 判断inter还是苹果M
UNAME_MACHINE="$(uname -m)"
#在X86电脑上测试arm电脑
# UNAME_MACHINE="arm64"
# 判断是Linux还是Mac os
OS="$(uname)"
if [[ "$OS" == "Linux" ]]; then
HOMEBREW_ON_LINUX=1
elif [[ "$OS" != "Darwin" ]]; then
echo "Homebrew 只运行在 Mac OS 或 Linux."
fi
# 字符串染色程序
if [[ -t 1 ]]; then
tty_escape() { printf "\033[%sm" "$1"; }
else
tty_escape() { :; }
fi
tty_universal() { tty_escape "0;$1"; } #正常显示
tty_mkbold() { tty_escape "1;$1"; } #设置高亮
tty_underline="$(tty_escape "4;39")" #下划线
tty_blue="$(tty_universal 34)" #蓝色
tty_red="$(tty_universal 31)" #红色
tty_green="$(tty_universal 32)" #绿色
tty_yellow="$(tty_universal 33)" #黄色
tty_bold="$(tty_universal 39)" #加黑
tty_cyan="$(tty_universal 36)" #青色
tty_reset="$(tty_escape 0)" #去除颜色
#用户输入极速安装speed,git克隆只取最近新版本
#但是update会出错,提示需要下载全部数据
GIT_SPEED=""
if [[ $0 == "speed" ]]; then
GIT_SPEED="--depth=1"
else
for dir in $@; do
echo $dir
if [[ $dir == "speed" ]]; then
GIT_SPEED="--depth=1"
fi
done
fi
if [[ $GIT_SPEED != "" ]]; then
echo "${tty_red}
检测到参数speed,只拉取最新数据,可以正常install使用!
腾讯和阿里不支持speed拉取,需要腾讯阿里需要完全模式。
但是以后brew update的时候会报错,运行报错提示的两句命令即可修复
${tty_reset}"
fi
#获取前面两个.的数据
major_minor() {
echo "${1%%.*}.$(x="${1#*.}"; echo "${x%%.*}")"
}
#设置一些平台地址
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
#Mac
if [[ "$UNAME_MACHINE" == "arm64" ]]; then
#M1
HOMEBREW_PREFIX="/opt/homebrew"
HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}"
else
#Inter
HOMEBREW_PREFIX="/usr/local"
HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}/Homebrew"
fi
HOMEBREW_CACHE="${HOME}/Library/Caches/Homebrew"
HOMEBREW_LOGS="${HOME}/Library/Logs/Homebrew"
#国内没有homebrew-services,手动在gitee创建了一个,有少数人用到。
USER_SERVICES_GIT=https://gitee.com/cunkai/homebrew-services.git
STAT="stat -f"
CHOWN="/usr/sbin/chown"
CHGRP="/usr/bin/chgrp"
GROUP="admin"
TOUCH="/usr/bin/touch"
#获取Mac系统版本
macos_version="$(major_minor "$(/usr/bin/sw_vers -productVersion)")"
else
#Linux
UNAME_MACHINE="$(uname -m)"
HOMEBREW_PREFIX="/home/linuxbrew/.linuxbrew"
HOMEBREW_REPOSITORY="${HOMEBREW_PREFIX}/Homebrew"
HOMEBREW_CACHE="${HOME}/.cache/Homebrew"
HOMEBREW_LOGS="${HOME}/.logs/Homebrew"
STAT="stat --printf"
CHOWN="/bin/chown"
CHGRP="/bin/chgrp"
GROUP="$(id -gn)"
TOUCH="/bin/touch"
fi
#获取系统时间
TIME=$(date "+%Y-%m-%d %H:%M:%S")
JudgeSuccess()
{
if [ $? -ne 0 ];then
echo "${tty_red}此步骤失败 '$1'${tty_reset}"
if [[ "$2" == 'out' ]]; then
exit 0
fi
else
echo "${tty_green}此步骤成功${tty_reset}"
fi
}
# 判断是否有系统权限
have_sudo_access() {
if [[ -z "${HAVE_SUDO_ACCESS-}" ]]; then
/usr/bin/sudo -l mkdir &>/dev/null
HAVE_SUDO_ACCESS="$?"
fi
if [[ "$HAVE_SUDO_ACCESS" -ne 0 ]]; then
echo "${tty_red}开机密码输入错误,获取权限失败!${tty_reset}"
fi
return "$HAVE_SUDO_ACCESS"
}
abort() {
printf "%s\n" "$1"
# exit 1
}
shell_join() {
local arg
printf "%s" "$1"
shift
for arg in "$@"; do
printf " "
printf "%s" "${arg// /\ }"
done
}
execute() {
if ! "$@"; then
abort "$(printf "${tty_red}此命令运行失败: %s${tty_reset}" "$(shell_join "$@")")"
fi
}
ohai() {
printf "${tty_blue}运行代码 ==>${tty_bold} %s${tty_reset}\n" "$(shell_join "$@")"
}
# 管理员运行
execute_sudo()
{
local -a args=("$@")
if have_sudo_access; then
if [[ -n "${SUDO_ASKPASS-}" ]]; then
args=("-A" "${args[@]}")
fi
ohai "/usr/bin/sudo" "${args[@]}"
execute "/usr/bin/sudo" "${args[@]}"
else
ohai "${args[@]}"
execute "${args[@]}"
fi
}
#添加文件夹权限
AddPermission()
{
execute_sudo "/bin/chmod" "-R" "a+rwx" "$1"
execute_sudo "$CHOWN" "$USER" "$1"
execute_sudo "$CHGRP" "$GROUP" "$1"
}
#创建文件夹
CreateFolder()
{
echo '-> 创建文件夹' $1
execute_sudo "/bin/mkdir" "-p" "$1"
JudgeSuccess
AddPermission $1
}
RmAndCopy()
{
if [[ -d $1 ]]; then
echo " ---备份要删除的$1到系统桌面...."
if ! [[ -d $HOME/Desktop/Old_Homebrew/$TIME/$1 ]]; then
sudo mkdir -p "$HOME/Desktop/Old_Homebrew/$TIME/$1"
fi
sudo cp -rf $1 "$HOME/Desktop/Old_Homebrew/$TIME/$1"
echo " ---$1 备份完成"
fi
sudo rm -rf $1
}
RmCreate()
{
RmAndCopy $1
CreateFolder $1
}
#判断文件夹存在但不可写
exists_but_not_writable() {
[[ -e "$1" ]] && ! [[ -r "$1" && -w "$1" && -x "$1" ]]
}
#文件所有者
get_owner() {
$(shell_join "$STAT %u $1" )
}
#文件本人无权限
file_not_owned() {
[[ "$(get_owner "$1")" != "$(id -u)" ]]
}
#获取所属的组
get_group() {
$(shell_join "$STAT %g $1" )
}
#不在所属组
file_not_grpowned() {
[[ " $(id -G "$USER") " != *" $(get_group "$1") "* ]]
}
#获得当前文件夹权限 例如777
get_permission() {
$(shell_join "$STAT %A $1" )
}
#授权当前用户权限
user_only_chmod() {
[[ -d "$1" ]] && [[ "$(get_permission "$1")" != "755" ]]
}
#创建brew需要的目录 直接复制于国外版本,同步
CreateBrewLinkFolder()
{
echo "--创建Brew所需要的目录"
directories=(bin etc include lib sbin share opt var
Frameworks
etc/bash_completion.d lib/pkgconfig
share/aclocal share/doc share/info share/locale share/man
share/man/man1 share/man/man2 share/man/man3 share/man/man4
share/man/man5 share/man/man6 share/man/man7 share/man/man8
var/log var/homebrew var/homebrew/linked
bin/brew)
group_chmods=()
for dir in "${directories[@]}"; do
if exists_but_not_writable "${HOMEBREW_PREFIX}/${dir}"; then
group_chmods+=("${HOMEBREW_PREFIX}/${dir}")
fi
done
directories=(share/zsh share/zsh/site-functions)
zsh_dirs=()
for dir in "${directories[@]}"; do
zsh_dirs+=("${HOMEBREW_PREFIX}/${dir}")
done
directories=(bin etc include lib sbin share var opt
share/zsh share/zsh/site-functions
var/homebrew var/homebrew/linked
Cellar Caskroom Frameworks)
mkdirs=()
for dir in "${directories[@]}"; do
if ! [[ -d "${HOMEBREW_PREFIX}/${dir}" ]]; then
mkdirs+=("${HOMEBREW_PREFIX}/${dir}")
fi
done
user_chmods=()
if [[ "${#zsh_dirs[@]}" -gt 0 ]]; then
for dir in "${zsh_dirs[@]}"; do
if user_only_chmod "${dir}"; then
user_chmods+=("${dir}")
fi
done
fi
chmods=()
if [[ "${#group_chmods[@]}" -gt 0 ]]; then
chmods+=("${group_chmods[@]}")
fi
if [[ "${#user_chmods[@]}" -gt 0 ]]; then
chmods+=("${user_chmods[@]}")
fi
chowns=()
chgrps=()
if [[ "${#chmods[@]}" -gt 0 ]]; then
for dir in "${chmods[@]}"; do
if file_not_owned "${dir}"; then
chowns+=("${dir}")
fi
if file_not_grpowned "${dir}"; then
chgrps+=("${dir}")
fi
done
fi
if [[ -d "${HOMEBREW_PREFIX}" ]]; then
if [[ "${#chmods[@]}" -gt 0 ]]; then
execute_sudo "/bin/chmod" "u+rwx" "${chmods[@]}"
fi
if [[ "${#group_chmods[@]}" -gt 0 ]]; then
execute_sudo "/bin/chmod" "g+rwx" "${group_chmods[@]}"
fi
if [[ "${#user_chmods[@]}" -gt 0 ]]; then
execute_sudo "/bin/chmod" "755" "${user_chmods[@]}"
fi
if [[ "${#chowns[@]}" -gt 0 ]]; then
execute_sudo "$CHOWN" "$USER" "${chowns[@]}"
fi
if [[ "${#chgrps[@]}" -gt 0 ]]; then
execute_sudo "$CHGRP" "$GROUP" "${chgrps[@]}"
fi
else
execute_sudo "/bin/mkdir" "-p" "${HOMEBREW_PREFIX}"
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
execute_sudo "$CHOWN" "root:wheel" "${HOMEBREW_PREFIX}"
else
execute_sudo "$CHOWN" "$USER:$GROUP" "${HOMEBREW_PREFIX}"
fi
fi
if [[ "${#mkdirs[@]}" -gt 0 ]]; then
execute_sudo "/bin/mkdir" "-p" "${mkdirs[@]}"
execute_sudo "/bin/chmod" "g+rwx" "${mkdirs[@]}"
execute_sudo "$CHOWN" "$USER" "${mkdirs[@]}"
execute_sudo "$CHGRP" "$GROUP" "${mkdirs[@]}"
fi
if ! [[ -d "${HOMEBREW_REPOSITORY}" ]]; then
execute_sudo "/bin/mkdir" "-p" "${HOMEBREW_REPOSITORY}"
fi
execute_sudo "$CHOWN" "-R" "$USER:$GROUP" "${HOMEBREW_REPOSITORY}"
if ! [[ -d "${HOMEBREW_CACHE}" ]]; then
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
execute_sudo "/bin/mkdir" "-p" "${HOMEBREW_CACHE}"
else
execute "/bin/mkdir" "-p" "${HOMEBREW_CACHE}"
fi
fi
if exists_but_not_writable "${HOMEBREW_CACHE}"; then
execute_sudo "/bin/chmod" "g+rwx" "${HOMEBREW_CACHE}"
fi
if file_not_owned "${HOMEBREW_CACHE}"; then
execute_sudo "$CHOWN" "-R" "$USER" "${HOMEBREW_CACHE}"
fi
if file_not_grpowned "${HOMEBREW_CACHE}"; then
execute_sudo "$CHGRP" "-R" "$GROUP" "${HOMEBREW_CACHE}"
fi
if [[ -d "${HOMEBREW_CACHE}" ]]; then
execute "$TOUCH" "${HOMEBREW_CACHE}/.cleaned"
fi
echo "--依赖目录脚本运行完成"
}
#git提交
git_commit(){
git add .
git commit -m "your del"
}
#version_gt 判断$1是否大于$2
version_gt() {
[[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -gt "${2#*.}" ]]
}
#version_ge 判断$1是否大于等于$2
version_ge() {
[[ "${1%.*}" -gt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -ge "${2#*.}" ]]
}
#version_lt 判断$1是否小于$2
version_lt() {
[[ "${1%.*}" -lt "${2%.*}" ]] || [[ "${1%.*}" -eq "${2%.*}" && "${1#*.}" -lt "${2#*.}" ]]
}
#发现错误 关闭脚本 提示如何解决
error_game_over(){
echo "
${tty_red}失败$MY_DOWN_NUM 右键下面地址查看常见错误解决办法
https://github.com/TheRamU/Fay
如果没有解决,把全部运行过程截图发到 467665317@qq.com ${tty_reset}
"
exit 0
}
#一些警告判断
warning_if(){
git_https_proxy=$(git config --global https.proxy)
git_http_proxy=$(git config --global http.proxy)
if [[ -z "$git_https_proxy" && -z "$git_http_proxy" ]]; then
echo "未发现Git代理(属于正常状态)"
else
echo "${tty_yellow}
提示:发现你电脑设置了Git代理,如果Git报错,请运行下面两句话:
git config --global --unset https.proxy
git config --global --unset http.proxy${tty_reset}
"
fi
}
echo "
${tty_green} 开始执行Brew自动安装程序 ${tty_reset}
${tty_cyan} [467665317@qq.com] ${tty_reset}
['$TIME']['$macos_version']
${tty_cyan} https://github.com/TheRamU/Fay${tty_reset}
"
#选择一个brew下载源
echo -n "${tty_green}
请选择一个下载brew本体的序号,例如中科大,输入1回车。
源有时候不稳定,如果git克隆报错重新运行脚本选择源。
1、中科大下载源
2、清华大学下载源
3、北京外国语大学下载源 ${tty_reset}"
if [[ $GIT_SPEED == "" ]]; then
echo -n "${tty_green}
4、腾讯下载源
5、阿里巴巴下载源 ${tty_reset}"
fi
echo -n "
${tty_blue}请输入序号: "
read MY_DOWN_NUM
echo "${tty_reset}"
case $MY_DOWN_NUM in
"2")
echo "
你选择了清华大学brew本体下载源
"
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/
#HomeBrew基础框架
USER_BREW_GIT=https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/brew.git
#HomeBrew Core
USER_CORE_GIT=https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-core.git
#HomeBrew Cask
USER_CASK_GIT=https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask.git
USER_CASK_FONTS_GIT=https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-fonts.git
USER_CASK_DRIVERS_GIT=https://mirrors.tuna.tsinghua.edu.cn/git/homebrew/homebrew-cask-drivers.git
;;
"3")
echo "
北京外国语大学brew本体下载源
"
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.bfsu.edu.cn/homebrew-bottles
#HomeBrew基础框架
USER_BREW_GIT=https://mirrors.bfsu.edu.cn/git/homebrew/brew.git
#HomeBrew Core
USER_CORE_GIT=https://mirrors.bfsu.edu.cn/git/homebrew/homebrew-core.git
#HomeBrew Cask
USER_CASK_GIT=https://mirrors.bfsu.edu.cn/git/homebrew/homebrew-cask.git
USER_CASK_FONTS_GIT=https://mirrors.bfsu.edu.cn/git/homebrew/homebrew-cask-fonts.git
USER_CASK_DRIVERS_GIT=https://mirrors.bfsu.edu.cn/git/homebrew/homebrew-cask-drivers.git
;;
"4")
echo "
你选择了腾讯brew本体下载源
"
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.cloud.tencent.com/homebrew-bottles
#HomeBrew基础框架
USER_BREW_GIT=https://mirrors.cloud.tencent.com/homebrew/brew.git
#HomeBrew Core
USER_CORE_GIT=https://mirrors.cloud.tencent.com/homebrew/homebrew-core.git
#HomeBrew Cask
USER_CASK_GIT=https://mirrors.cloud.tencent.com/homebrew/homebrew-cask.git
;;
"5")
echo "
你选择了阿里巴巴brew本体下载源
"
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles
#HomeBrew基础框架
USER_BREW_GIT=https://mirrors.aliyun.com/homebrew/brew.git
#HomeBrew Core
USER_CORE_GIT=https://mirrors.aliyun.com/homebrew/homebrew-core.git
#HomeBrew Cask
USER_CASK_GIT=https://mirrors.aliyun.com/homebrew/homebrew-cask.git
;;
*)
echo "
你选择了中国科学技术大学brew本体下载源
"
#HomeBrew 下载源 install
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
#HomeBrew基础框架
USER_BREW_GIT=https://mirrors.ustc.edu.cn/brew.git
#HomeBrew Core
USER_CORE_GIT=https://mirrors.ustc.edu.cn/homebrew-core.git
#HomeBrew Cask
USER_CASK_GIT=https://mirrors.ustc.edu.cn/homebrew-cask.git
;;
esac
echo -n "${tty_green}!!!此脚本将要删除之前的brew(包括它下载的软件),请自行备份。
->是否现在开始执行脚本(N/Y) "
read MY_Del_Old
echo "${tty_reset}"
case $MY_Del_Old in
"y")
echo "--> 脚本开始执行"
;;
"Y")
echo "--> 脚本开始执行"
;;
*)
echo "你输入了 $MY_Del_Old ,自行备份老版brew和它下载的软件, 如果继续运行脚本应该输入Y或者y
"
exit 0
;;
esac
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
#MAC
echo "${tty_yellow} Mac os设置开机密码方法:
(设置开机密码:在左上角苹果图标->系统偏好设置->"用户与群组"->更改密码)
(如果提示This incident will be reported. 在"用户与群组"中查看是否管理员) ${tty_reset}"
fi
echo "==> 通过命令删除之前的brew、创建一个新的Homebrew文件夹
${tty_cyan}请输入开机密码,输入过程不显示,输入完后回车${tty_reset}"
sudo echo '开始执行'
#删除以前的Homebrew
RmCreate ${HOMEBREW_REPOSITORY}
RmAndCopy $HOMEBREW_CACHE
RmAndCopy $HOMEBREW_LOGS
# 让环境暂时纯粹,脚本运行结束后恢复
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${HOMEBREW_REPOSITORY}/bin
fi
git --version
if [ $? -ne 0 ];then
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
sudo rm -rf "/Library/Developer/CommandLineTools/"
echo "${tty_cyan}安装Git${tty_reset}后再运行此脚本,${tty_red}在系统弹窗中点击“安装”按钮
如果没有弹窗的老系统,需要自己下载安装:https://sourceforge.net/projects/git-osx-installer/ ${tty_reset}"
xcode-select --install
exit 0
else
echo "${tty_red} 发现缺少git,开始安装,请输入Y ${tty_reset}"
sudo apt install git
fi
fi
echo "
${tty_cyan}下载速度觉得慢可以ctrl+c或control+c重新运行脚本选择下载源${tty_reset}
==> 从 $USER_BREW_GIT 克隆Homebrew基本文件
"
warning_if
sudo git clone ${GIT_SPEED} $USER_BREW_GIT ${HOMEBREW_REPOSITORY}
JudgeSuccess 尝试再次运行自动脚本选择其他下载源或者切换网络 out
#依赖目录创建 授权等等
CreateBrewLinkFolder
echo '==> 创建brew的替身'
if [[ "${HOMEBREW_REPOSITORY}" != "${HOMEBREW_PREFIX}" ]]; then
find ${HOMEBREW_PREFIX}/bin -name brew -exec sudo rm -f {} \;
execute "ln" "-sf" "${HOMEBREW_REPOSITORY}/bin/brew" "${HOMEBREW_PREFIX}/bin/brew"
fi
echo "==> 从 $USER_CORE_GIT 克隆Homebrew Core
${tty_cyan}此处如果显示Password表示需要再次输入开机密码,输入完后回车${tty_reset}"
sudo mkdir -p ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-core
sudo git clone ${GIT_SPEED} $USER_CORE_GIT ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-core/
JudgeSuccess 尝试再次运行自动脚本选择其他下载源或者切换网络 out
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
#MAC
echo "==> 从 $USER_CASK_GIT 克隆Homebrew Cask 图形化软件
${tty_cyan}此处如果显示Password表示需要再次输入开机密码,输入完后回车${tty_reset}"
sudo mkdir -p ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-cask
sudo git clone ${GIT_SPEED} $USER_CASK_GIT ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-cask/
if [ $? -ne 0 ];then
sudo rm -rf ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-cask
echo "${tty_red}尝试切换下载源或者切换网络,不过Cask组件非必须模块。可以忽略${tty_reset}"
else
echo "${tty_green}此步骤成功${tty_reset}"
fi
echo "==> 从 $USER_SERVICES_GIT 克隆Homebrew services 管理服务的启停
"
sudo mkdir -p ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-cask
sudo git clone ${GIT_SPEED} $USER_SERVICES_GIT ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-services/
JudgeSuccess
else
#Linux
echo "${tty_yellow} Linux 不支持Cask图形化软件下载 此步骤跳过${tty_reset}"
fi
echo '==> 配置国内镜像源HOMEBREW BOTTLE'
#判断下mac os终端是Bash还是zsh
case "$SHELL" in
*/bash*)
if [[ -r "$HOME/.bash_profile" ]]; then
shell_profile="${HOME}/.bash_profile"
else
shell_profile="${HOME}/.profile"
fi
;;
*/zsh*)
shell_profile="${HOME}/.zprofile"
;;
*)
shell_profile="${HOME}/.profile"
;;
esac
if [[ -n "${HOMEBREW_ON_LINUX-}" ]]; then
#Linux
shell_profile="/etc/profile"
fi
if [[ -f ${shell_profile} ]]; then
AddPermission ${shell_profile}
fi
#删除之前的环境变量
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
#Mac
sed -i "" "/ckbrew/d" ${shell_profile}
else
#Linux
sed -i "/ckbrew/d" ${shell_profile}
fi
#选择一个homebrew-bottles下载源
echo -n "${tty_green}
Brew本体已经安装成功,接下来配置国内源。
请选择今后brew install的时候访问那个国内镜像,例如阿里巴巴,输入5回车。
1、中科大国内源
2、清华大学国内源
3、北京外国语大学国内源
4、腾讯国内源
5、阿里巴巴国内源 ${tty_reset}"
echo -n "
${tty_blue}请输入序号: "
read MY_DOWN_NUM
echo "${tty_reset}"
case $MY_DOWN_NUM in
"2")
echo "
你选择了清华大学国内源
"
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.tuna.tsinghua.edu.cn/homebrew-bottles/
;;
"3")
echo "
北京外国语大学国内源
"
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.bfsu.edu.cn/homebrew-bottles
;;
"4")
echo "
你选择了腾讯国内源
"
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.cloud.tencent.com/homebrew-bottles
;;
"5")
echo "
你选择了阿里巴巴国内源
"
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles
;;
*)
echo "
你选择了中国科学技术大学国内源
"
#HomeBrew 下载源 install
USER_HOMEBREW_BOTTLE_DOMAIN=https://mirrors.ustc.edu.cn/homebrew-bottles
;;
esac
#写入环境变量到文件
echo "
环境变量写入->${shell_profile}
"
echo "
export HOMEBREW_BOTTLE_DOMAIN=${USER_HOMEBREW_BOTTLE_DOMAIN} #ckbrew
eval \$(${HOMEBREW_REPOSITORY}/bin/brew shellenv) #ckbrew
" >> ${shell_profile}
JudgeSuccess
source "${shell_profile}"
if [ $? -ne 0 ];then
echo "${tty_red}发现错误,${shell_profile} 文件中有错误,建议根据上一句提示修改;
否则会导致提示 permission denied: brew${tty_reset}"
fi
AddPermission ${HOMEBREW_REPOSITORY}
if [[ -n "${HOMEBREW_ON_LINUX-}" ]]; then
#检测linux curl是否有安装
echo "${tty_red}-检测curl是否安装 留意是否需要输入Y${tty_reset}"
curl -V
if [ $? -ne 0 ];then
sudo apt-get install curl
if [ $? -ne 0 ];then
sudo yum install curl
if [ $? -ne 0 ];then
echo '失败 请自行安装curl 可以参考https://www.howtoing.com/install-curl-in-linux'
error_game_over
fi
fi
fi
fi
echo '
==> 安装完成,brew版本
'
brew -v
if [ $? -ne 0 ];then
echo '发现错误,自动修复一次!'
rm -rf $HOMEBREW_CACHE
export PATH=/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:${HOMEBREW_REPOSITORY}/bin
brew update-reset
brew -v
if [ $? -ne 0 ];then
error_game_over
fi
else
echo "${tty_green}Brew前期配置成功${tty_reset}"
fi
#brew 3.1.2版本 修改了很多地址,都写死在了代码中,没有调用环境变量。。额。。
#ruby下载需要改官方文件
ruby_URL_file=$HOMEBREW_REPOSITORY/Library/Homebrew/cmd/vendor-install.sh
#判断Mac系统版本
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
if version_gt "$macos_version" "10.14"; then
echo "电脑系统版本:$macos_version"
else
echo "${tty_red}检测到你不是最新系统,会有一些报错,请稍等Ruby下载安装;${tty_reset}
"
fi
if [[ -f ${ruby_URL_file} ]]; then
sed -i "" "s/ruby_URL=/ruby_URL=\"https:\/\/mirrors.tuna.tsinghua.edu.cn\/homebrew-bottles\/bottles-portable-ruby\/\$ruby_FILENAME\" \#/g" $ruby_URL_file
fi
else
if [[ -f ${ruby_URL_file} ]]; then
sed -i "s/ruby_URL=/ruby_URL=\"https:\/\/mirrors.tuna.tsinghua.edu.cn\/linuxbrew-bottles\/bottles-portable-ruby\/\$ruby_FILENAME\" \#/g" $ruby_URL_file
fi
fi
brew services cleanup
if [[ $GIT_SPEED == "" ]];then
echo '
==> brew update-reset
'
brew update-reset
if [[ $? -ne 0 ]];then
brew config
error_game_over
exit 0
fi
else
#极速模式提示Update修复方法
echo "
${tty_red} 极速版本安装完成,${tty_reset} install功能正常,如果需要update功能请自行运行下面三句命令
git -C ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-core fetch --unshallow
git -C ${HOMEBREW_REPOSITORY}/Library/Taps/homebrew/homebrew-cask fetch --unshallow
brew update-reset
"
fi
echo "
${tty_green}Brew自动安装程序运行完成${tty_reset}
${tty_green}国内地址已经配置完成${tty_reset}
桌面的Old_Homebrew文件夹,大致看看没有你需要的可以删除。
初步介绍几个brew命令
本地软件库列表:brew ls
查找软件:brew search google(其中google替换为要查找的关键字)
查看brew版本:brew -v 更新brew版本:brew update
安装cask软件:brew install --cask firefox 把firefox换成你要安装的
${tty_green}
欢迎右键点击下方地址-打开URL 来给个星${tty_reset}
${tty_underline} https://github.com/TheRamU/Fay ${tty_reset}
"
if [[ -z "${HOMEBREW_ON_LINUX-}" ]]; then
#Mac
echo "${tty_red} 安装成功 但还需要重启终端 或者 运行${tty_bold} source ${shell_profile} ${tty_reset} ${tty_red}否则可能无法使用${tty_reset}
"
else
#Linux
echo "${tty_red} Linux需要重启电脑 或者暂时运行${tty_bold} source ${shell_profile} ${tty_reset} ${tty_red}否则可能无法使用${tty_reset}
"
fi
+18
View File
@@ -0,0 +1,18 @@
from pathlib import Path
OPENAI_API_KEY = "sk-hAuN7OLqKJTdyDjNFdEfF4B0E53642E4B2BbCa248594Cd29"
OPENAI_API_BASE = "https://api.zyai.online/v1" # 可以修改为你的自定义 base URL
KEY_OWNER = "xszyou"
DEBUG = False
MAX_CHUNK_SIZE = 4
LLM_VERS = "gpt-4o-mini"
BASE_DIR = f"{Path(__file__).resolve().parent.parent}"
## To do: Are the following needed in the new structure? Ideally Populations_Dir is for the user to define.
POPULATIONS_DIR = f"{BASE_DIR}/agent_bank/populations"
LLM_PROMPT_DIR = f"{BASE_DIR}/simulation_engine/prompt_template"
+387
View File
@@ -0,0 +1,387 @@
import random
import json
import string
import csv
import datetime as dt
import os
import numpy
import math
import shutil, errno
from os import listdir
def create_folder_if_not_there(curr_path):
"""
Checks if a folder in the curr_path exists. If it does not exist, creates
the folder.
Note that if the curr_path designates a file location, it will operate on
the folder that contains the file. But the function also works even if the
path designates to just a folder.
Args:
curr_list: list to write. The list comes in the following form:
[['key1', 'val1-1', 'val1-2'...],
['key2', 'val2-1', 'val2-2'...],]
outfile: name of the csv file to write
RETURNS:
True: if a new folder is created
False: if a new folder is not created
"""
outfolder_name = curr_path.split("/")
if len(outfolder_name) != 1:
# This checks if the curr path is a file or a folder.
if "." in outfolder_name[-1]:
outfolder_name = outfolder_name[:-1]
outfolder_name = "/".join(outfolder_name)
if not os.path.exists(outfolder_name):
os.makedirs(outfolder_name)
return True
return False
def write_list_of_list_to_csv(curr_list_of_list, outfile):
"""
Writes a list of list to csv.
Unlike write_list_to_csv_line, it writes the entire csv in one shot.
ARGS:
curr_list_of_list: list to write. The list comes in the following form:
[['key1', 'val1-1', 'val1-2'...],
['key2', 'val2-1', 'val2-2'...],]
outfile: name of the csv file to write
RETURNS:
None
"""
create_folder_if_not_there(outfile)
with open(outfile, "w") as f:
writer = csv.writer(f)
writer.writerows(curr_list_of_list)
def write_list_to_csv_line(line_list, outfile):
"""
Writes one line to a csv file.
Unlike write_list_of_list_to_csv, this opens an existing outfile and then
appends a line to that file.
This also works if the file does not exist already.
ARGS:
curr_list: list to write. The list comes in the following form:
['key1', 'val1-1', 'val1-2'...]
Importantly, this is NOT a list of list.
outfile: name of the csv file to write
RETURNS:
None
"""
create_folder_if_not_there(outfile)
# Opening the file first so we can write incrementally as we progress
curr_file = open(outfile, 'a',)
csvfile_1 = csv.writer(curr_file)
csvfile_1.writerow(line_list)
curr_file.close()
def read_file_to_list(curr_file, header=False, strip_trail=True):
"""
Reads in a csv file to a list of list. If header is True, it returns a
tuple with (header row, all rows)
ARGS:
curr_file: path to the current csv file.
RETURNS:
List of list where the component lists are the rows of the file.
"""
if not header:
analysis_list = []
with open(curr_file) as f_analysis_file:
data_reader = csv.reader(f_analysis_file, delimiter=",")
for count, row in enumerate(data_reader):
if strip_trail:
row = [i.strip() for i in row]
analysis_list += [row]
return analysis_list
else:
analysis_list = []
with open(curr_file) as f_analysis_file:
data_reader = csv.reader(f_analysis_file, delimiter=",")
for count, row in enumerate(data_reader):
if strip_trail:
row = [i.strip() for i in row]
analysis_list += [row]
return analysis_list[0], analysis_list[1:]
def read_file_to_set(curr_file, col=0):
"""
Reads in a "single column" of a csv file to a set.
ARGS:
curr_file: path to the current csv file.
RETURNS:
Set with all items in a single column of a csv file.
"""
analysis_set = set()
with open(curr_file) as f_analysis_file:
data_reader = csv.reader(f_analysis_file, delimiter=",")
for count, row in enumerate(data_reader):
analysis_set.add(row[col])
return analysis_set
def get_row_len(curr_file):
"""
Get the number of rows in a csv file
ARGS:
curr_file: path to the current csv file.
RETURNS:
The number of rows
False if the file does not exist
"""
try:
analysis_set = set()
with open(curr_file) as f_analysis_file:
data_reader = csv.reader(f_analysis_file, delimiter=",")
for count, row in enumerate(data_reader):
analysis_set.add(row[0])
return len(analysis_set)
except:
return False
def check_if_file_exists(curr_file):
"""
Checks if a file exists
ARGS:
curr_file: path to the current csv file.
RETURNS:
True if the file exists
False if the file does not exist
"""
try:
with open(curr_file) as f_analysis_file: pass
return True
except:
return False
def find_filenames(path_to_dir, suffix=".csv"):
"""
Given a directory, find all files that ends with the provided suffix and
returns their paths.
ARGS:
path_to_dir: Path to the current directory
suffix: The target suffix.
RETURNS:
A list of paths to all files in the directory.
"""
filenames = listdir(path_to_dir)
new_filenames = []
for i in filenames:
if ".DS_Store" not in i:
new_filenames += [i]
filenames = new_filenames
return [ path_to_dir+"/"+filename
for filename in filenames if filename.endswith( suffix ) ]
def average(list_of_val):
"""
Finds the average of the numbers in a list.
ARGS:
list_of_val: a list of numeric values
RETURNS:
The average of the values
"""
try:
list_of_val = [float(i) for i in list_of_val if not math.isnan(i)]
return sum(list_of_val)/float(len(list_of_val))
except:
return float('nan')
def std(list_of_val):
"""
Finds the std of the numbers in a list.
ARGS:
list_of_val: a list of numeric values
RETURNS:
The std of the values
"""
try:
list_of_val = [float(i) for i in list_of_val if not math.isnan(i)]
std = numpy.std(list_of_val)
return std
except:
return float('nan')
def copyanything(src, dst):
"""
Copy over everything in the src folder to dst folder.
ARGS:
src: address of the source folder
dst: address of the destination folder
RETURNS:
None
"""
try:
shutil.copytree(src, dst)
except OSError as exc: # python >2.5
if exc.errno in (errno.ENOTDIR, errno.EINVAL):
shutil.copy(src, dst)
else: raise
def generate_alphanumeric_string(length):
characters = string.ascii_letters + string.digits
result = ''.join(random.choice(characters) for _ in range(length))
return result
def extract_first_json_dict(input_str):
"""
从字符串中提取第一个JSON字典
参数:
input_str: 包含JSON字典的字符串
返回:
解析后的JSON字典,如果解析失败则返回None
"""
try:
# 确保输入是字符串类型
if not isinstance(input_str, str):
print("提取JSON错误: 输入必须是字符串类型")
return None
# 替换特殊引号为标准双引号
input_str = (input_str.replace(""", "\"")
.replace(""", "\"")
.replace("'", "'")
.replace("'", "'"))
# 查找第一个'{'的位置
try:
start_index = input_str.index('{')
except ValueError:
print("提取JSON错误: 未找到JSON开始标记'{'")
return None
# 初始化计数器,用于跟踪开闭括号
count = 1
end_index = start_index + 1
# 循环查找与第一个'{'匹配的'}'
while count > 0 and end_index < len(input_str):
if input_str[end_index] == '{':
count += 1
elif input_str[end_index] == '}':
count -= 1
end_index += 1
# 如果没有找到匹配的'}'
if count > 0:
print("提取JSON错误: JSON格式不完整,缺少匹配的'}'")
return None
# 提取JSON子字符串
json_str = input_str[start_index:end_index]
# 解析JSON字符串为Python字典
try:
json_dict = json.loads(json_str)
return json_dict
except json.JSONDecodeError as e:
print(f"解析JSON错误: {str(e)}")
return None
except Exception as e:
# 处理所有其他异常
print(f"提取JSON时发生错误: {str(e)}")
return None
def read_file_to_string(file_path):
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
return content
except FileNotFoundError:
return "The file was not found."
except Exception as e:
return str(e)
def write_string_to_file(full_path, text_content):
create_folder_if_not_there(full_path)
import os
try:
with open(full_path, 'w', encoding='utf-8') as file:
file.write(text_content)
return f"File successfully written to {full_path}"
except Exception as e:
return str(e)
def chunk_list(lst, q_chunk_size):
"""
Splits the given list into sublists of specified chunk size.
Parameters:
lst (list): The list to be split into chunks.
q_chunk_size (int): The size of each chunk.
Returns:
list: A list of sublists where each sublist has a length of q_chunk_size.
"""
# Initialize the result list
chunked_list = []
# Loop through the list in steps of q_chunk_size
for i in range(0, len(lst), q_chunk_size):
# Append the sublist to the result list
chunked_list.append(lst[i:i + q_chunk_size])
return chunked_list
def write_dict_to_json(data, filename):
"""
Writes a dictionary to a JSON file.
Parameters:
data (dict): The dictionary to write to the JSON file.
filename (str): The name of the file to write the JSON data to.
"""
try:
# 确保目录存在
directory = os.path.dirname(filename)
if directory and not os.path.exists(directory):
os.makedirs(directory)
# 使用UTF-8编码写入JSON文件
with open(filename, 'w', encoding='utf-8') as file:
json.dump(data, file, ensure_ascii=False, indent=4)
except Exception as e:
print(f"写入JSON文件时出错: {str(e)}")
def read_json_to_dict(file_path):
"""
Reads a JSON file and converts it to a Python dictionary.
Parameters:
file_path (str): The path to the JSON file.
Returns:
dict: The content of the JSON file as a dictionary.
"""
try:
# 使用UTF-8编码读取JSON文件
with open(file_path, 'r', encoding='utf-8') as file:
data = json.load(file)
return data
except FileNotFoundError:
print(f"未找到文件: {file_path}")
except json.JSONDecodeError:
print(f"解析JSON文件出错: {file_path}")
except Exception as e:
print(f"发生错误: {str(e)}")
+281
View File
@@ -0,0 +1,281 @@
import openai
import time
import base64
from typing import List, Dict, Any, Union, Optional
import os
from simulation_engine.settings import *
from utils import config_util as cfg
# 确保配置已加载
cfg.load_config()
# 初始化 OpenAI 客户端
client = openai.OpenAI(
api_key=OPENAI_API_KEY,
base_url=OPENAI_API_BASE
)
# 设置全局API密钥(兼容性考虑)
openai.api_key = OPENAI_API_KEY
# 如果环境变量中没有设置,则设置环境变量(某些库可能依赖环境变量)
if "OPENAI_API_KEY" not in os.environ:
os.environ["OPENAI_API_KEY"] = OPENAI_API_KEY
if "OPENAI_API_BASE" not in os.environ and OPENAI_API_BASE:
os.environ["OPENAI_API_BASE"] = OPENAI_API_BASE
# ============================================================================
# #######################[SECTION 1: HELPER FUNCTIONS] #######################
# ============================================================================
def print_run_prompts(prompt_input: Union[str, List[str]],
prompt: str,
output: str) -> None:
print (f"=== START =======================================================")
print ("~~~ prompt_input ----------------------------------------------")
print (prompt_input, "\n")
print ("~~~ prompt ----------------------------------------------------")
print (prompt, "\n")
print ("~~~ output ----------------------------------------------------")
print (output, "\n")
print ("=== END ==========================================================")
print ("\n\n\n")
def generate_prompt(prompt_input: Union[str, List[str]],
prompt_lib_file: str) -> str:
"""
通过用输入替换模板文件中的占位符来生成提示
参数:
prompt_input: 输入文本,可以是字符串或字符串列表
prompt_lib_file: 模板文件路径
返回:
生成的提示文本
"""
# 确保prompt_input是列表类型
if isinstance(prompt_input, str):
prompt_input = [prompt_input]
# 确保所有输入都是字符串类型
prompt_input = [str(i) for i in prompt_input]
try:
# 使用UTF-8编码读取模板文件
with open(prompt_lib_file, "r", encoding='utf-8') as f:
prompt = f.read()
except FileNotFoundError:
print(f"生成提示错误: 未找到模板文件 {prompt_lib_file}")
return "ERROR: 模板文件不存在"
except Exception as e:
print(f"读取模板文件时出错: {str(e)}")
return f"ERROR: 读取模板文件时出错 - {str(e)}"
# 替换占位符
for count, input_text in enumerate(prompt_input):
prompt = prompt.replace(f"!<INPUT {count}>!", input_text)
# 处理注释块
if "<commentblockmarker>###</commentblockmarker>" in prompt:
prompt = prompt.split("<commentblockmarker>###</commentblockmarker>")[1]
return prompt.strip()
# ============================================================================
# ####################### [SECTION 2: SAFE GENERATE] #########################
# ============================================================================
def gpt_request(prompt: str,
model: str = "gpt-4o",
max_tokens: int = 1500) -> str:
"""
向OpenAI的GPT模型发送请求
参数:
prompt: 提示文本
model: 模型名称,默认为"gpt-4o"
max_tokens: 最大生成令牌数,默认为1500
返回:
模型生成的响应文本
"""
# 确保prompt是字符串类型
if not isinstance(prompt, str):
print("GPT请求错误: 提示文本必须是字符串类型")
return "GENERATION ERROR: 提示文本必须是字符串类型"
# 处理o1-preview模型
if model == "o1-preview":
try:
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}]
)
# 确保返回的内容是UTF-8编码
return response.choices[0].message.content
except Exception as e:
error_msg = f"GENERATION ERROR: {str(e)}"
print(error_msg)
return error_msg
# 处理其他模型
try:
response = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
max_tokens=max_tokens,
temperature=0.7
)
# 确保返回的内容是UTF-8编码
return response.choices[0].message.content
except Exception as e:
error_msg = f"GENERATION ERROR: {str(e)}"
print(error_msg)
return error_msg
def gpt4_vision(messages: List[dict], max_tokens: int = 1500) -> str:
"""Make a request to OpenAI's GPT-4 Vision model."""
try:
client = openai.OpenAI(
api_key=OPENAI_API_KEY,
base_url=OPENAI_API_BASE
)
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
max_tokens=max_tokens,
temperature=0.7
)
return response.choices[0].message.content
except Exception as e:
return f"GENERATION ERROR: {str(e)}"
def chat_safe_generate(prompt_input: Union[str, List[str]],
prompt_lib_file: str,
gpt_version: str = "gpt-4o",
repeat: int = 1,
fail_safe: str = "error",
func_clean_up: callable = None,
verbose: bool = False,
max_tokens: int = 1500,
file_attachment: str = None,
file_type: str = None) -> tuple:
"""Generate a response using GPT models with error handling & retries."""
if file_attachment and file_type:
prompt = generate_prompt(prompt_input, prompt_lib_file)
messages = [{"role": "user", "content": prompt}]
if file_type.lower() == 'image':
with open(file_attachment, "rb") as image_file:
base64_image = base64.b64encode(image_file.read()).decode('utf-8')
messages.append({
"role": "user",
"content": [
{"type": "text", "text": "Please refer to the attached image."},
{"type": "image_url", "image_url":
{"url": f"data:image/jpeg;base64,{base64_image}"}}
]
})
response = gpt4_vision(messages, max_tokens)
elif file_type.lower() == 'pdf':
pdf_text = extract_text_from_pdf_file(file_attachment)
pdf = f"PDF attachment in text-form:\n{pdf_text}\n\n"
instruction = generate_prompt(prompt_input, prompt_lib_file)
prompt = f"{pdf}"
prompt += f"<End of the PDF attachment>\n=\nTask description:\n{instruction}"
response = gpt_request(prompt, gpt_version, max_tokens)
else:
prompt = generate_prompt(prompt_input, prompt_lib_file)
for i in range(repeat):
response = gpt_request(prompt, model=gpt_version)
if response != "GENERATION ERROR":
break
time.sleep(2**i)
else:
response = fail_safe
if func_clean_up:
response = func_clean_up(response, prompt=prompt)
if verbose or DEBUG:
print_run_prompts(prompt_input, prompt, response)
return response, prompt, prompt_input, fail_safe
# ============================================================================
# #################### [SECTION 3: OTHER API FUNCTIONS] ######################
# ============================================================================
# 添加模拟embedding函数
def _create_mock_embedding(dimension=1536):
"""创建一个模拟的embedding函数,用于替代真实API"""
import random
import math
import hashlib
def _get_mock_vector(text):
"""生成一个随机但一致的embedding向量"""
# 使用文本的哈希值作为随机种子,确保相同文本生成相同向量
# 使用hashlib代替hash()函数,确保编码一致性
try:
# 确保文本是UTF-8编码
if isinstance(text, str):
text_bytes = text.encode('utf-8')
else:
text_bytes = str(text).encode('utf-8')
# 使用SHA256生成哈希值
hash_value = int(hashlib.sha256(text_bytes).hexdigest(), 16) % (10 ** 8)
random.seed(hash_value)
except Exception as e:
# 如果出现编码错误,使用一个固定的种子
print(f"处理文本哈希时出错: {str(e)}")
random.seed(42)
# 生成随机向量
vector = [random.uniform(-1, 1) for _ in range(dimension)]
# 归一化向量
magnitude = math.sqrt(sum(x*x for x in vector))
normalized_vector = [x/magnitude for x in vector]
return normalized_vector
return _get_mock_vector
# 创建模拟函数实例
_mock_embedding_function = _create_mock_embedding(1536)
def get_text_embedding(text: str,
model: str = "text-embedding-3-small") -> List[float]:
"""生成文本的embedding向量,使用模拟函数"""
try:
# 确保输入是有效的字符串
if not isinstance(text, str):
print("Embedding错误: 输入必须是字符串类型")
return [0.0] * 1536 # 返回默认embedding
# 处理空字符串
if not text.strip():
print("Embedding警告: 输入字符串为空")
return [0.0] * 1536 # 返回默认embedding
# 标准化文本,替换换行符并去除首尾空格
text = text.replace("\n", " ").strip()
# 使用模拟函数生成embedding
return _mock_embedding_function(text)
except Exception as e:
# 捕获所有异常,确保函数不会崩溃
print(f"生成embedding时出错: {str(e)}")
# 返回一个默认的embedding
return [0.0] * 1536
+57
View File
@@ -0,0 +1,57 @@
import json
import re
def extract_first_json_dict(input_str):
try:
# Replace curly quotes with standard double quotes
input_str = (input_str.replace("", "\"")
.replace("", "\"")
.replace("", "'")
.replace("", "'"))
# Find the first occurrence of '{' in the input_str
start_index = input_str.index('{')
# Initialize a count to keep track of open and close braces
count = 1
end_index = start_index + 1
# Loop to find the closing '}' for the first JSON dictionary
while count > 0 and end_index < len(input_str):
if input_str[end_index] == '{':
count += 1
elif input_str[end_index] == '}':
count -= 1
end_index += 1
# Extract the JSON substring
json_str = input_str[start_index:end_index]
# Parse the JSON string into a Python dictionary
json_dict = json.loads(json_str)
return json_dict
except ValueError:
# Handle the case where the JSON parsing fails
return None
def extract_first_json_dict_categorical(input_str):
reasoning_pattern = r'"Reasoning":\s*"([^"]+)"'
response_pattern = r'"Response":\s*"([^"]+)"'
reasonings = re.findall(reasoning_pattern, input_str)
responses = re.findall(response_pattern, input_str)
return responses, reasonings
def extract_first_json_dict_numerical(input_str):
reasoning_pattern = re.compile(r'"Reasoning":\s*"([^"]+)"')
response_pattern = re.compile(r'"Response":\s*(\d+\.?\d*)')
reasonings = reasoning_pattern.findall(input_str)
responses = response_pattern.findall(input_str)
return responses, reasonings
@@ -0,0 +1,44 @@
Variables:
Note: basically main version (ver 3) but with "reasoning" step
<commentblockmarker>###</commentblockmarker>
!<INPUT 0>!
=====
Task: What you see above is an interview transcript. Based on the interview transcript, I want you to predict the participant's survey responses. All questions are multiple choice where you must guess from one of the options presented.
As you answer, I want you to take the following steps:
Step 1) Describe in a few sentences the kind of person that would choose each of the response options. ("Option Interpretation")
Step 2) For each response options, reason about why the Participant might answer with the particular option. ("Option Choice")
Step 3) Write a few sentences reasoning on which of the option best predicts the participant's response ("Reasoning")
Step 4) Predict how the participant will actually respond in the survey. Predict based on the interview and your thoughts, but ultimately, DON'T over think it. Use your system 1 (fast, intuitive) thinking. ("Response")
Here are the questions:
!<INPUT 1>!
-----
Output format -- output your response in json, where you provide the following:
{"1": {"Q": "<repeat the question you are answering>",
"Option Interpretation": {
"<option 1>": "a few sentences the kind of person that would choose each of the response options",
"<option 2>": "..."},
"Option Choice": {
"<option 1>": "reasoning about why the participant might choose each of the options",
"<option 2>": "..."},
"Reasoning": "<reasoning on which of the option best predicts the participant's response>",
"Response": "<your prediction on how the participant will answer the question>"},
"2": {"Q": "<repeat the question you are answering>",
"Option Interpretation": {
"<option 1>": "a few sentences the kind of person that would choose each of the response options",
"<option 2>": "..."},
"Option Choice": {
"<option 1>": "reasoning about why the participant might choose each of the options",
"<option 2>": "..."},
"Reasoning": "<reasoning on which of the option best predicts the participant's response>",
"Response": "<your prediction on how the participant will answer the question>"},
...}
@@ -0,0 +1,34 @@
Variables:
Note: basically main version (ver 3) but with "reasoning" step
<commentblockmarker>###</commentblockmarker>
!<INPUT 0>!
=====
Task: What you see above is an interview transcript. Based on the interview transcript, I want you to predict the participant's survey responses. The question is a multiple choice where you must guess from one of the options presented.
As you answer, I want you to take the following steps:
Step 1) Describe in a few sentences the kind of person that would choose each of the response options. ("Option Interpretation")
Step 2) For each response options, reason about why the Participant might answer with the particular option. ("Option Choice")
Step 3) Write a few sentences reasoning on which of the option best predicts the participant's response ("Reasoning")
Step 4) Predict how the participant will actually respond in the survey. Predict based on the interview and your thoughts, but ultimately, DON'T over think it. Use your system 1 (fast, intuitive) thinking. ("Response")
Here is the question:
!<INPUT 1>!
-----
Output format -- output your response in json, where you provide the following:
{"1": {"Q": "<repeat the question you are answering>",
"Option Interpretation": {
"<option 1>": "a few sentences the kind of person that would choose each of the response options",
"<option 2>": "..."},
"Option Choice": {
"<option 1>": "reasoning about why the participant might choose each of the options",
"<option 2>": "..."},
"Reasoning": "<reasoning on which of the option best predicts the participant's response>",
"Response": "<your prediction on how the participant will answer the question>"}}
@@ -0,0 +1,37 @@
Variables:
Note: basically main version (ver 3) but with "reasoning" step
<commentblockmarker>###</commentblockmarker>
!<INPUT 0>!
=====
Task: What you see above is an interview transcript. Based on the interview transcript, I want you to predict the participant's survey responses. For all questions, you should output a number that is in the range that was specified for that question.
As you answer, I want you to take the following steps:
Step 1) Describe in a few sentences the kind of person that would choose each end of the range. ("Range Interpretation")
Step 2) Write a few sentences reasoning on which of the option best predicts the participant's response ("Reasoning")
Step 3) Predict how the participant will actually respond. Predict based on the interview and your thoughts, but ultimately, DON'T over think it. Use your system 1 (fast, intuitive) thinking. ("Response")
Here are the questions:
!<INPUT 1>!
-----
Output format -- output your response in json, where you provide the following:
{"1": {"Q": "<repeat the question you are answering>",
"Range Interpretation": {
"<option 1>": "a few sentences about the kind of person that would choose each end of the range",
"<option 2>": "..."},
"Reasoning": "<reasoning on which of the option best predicts the participant's response>",
"Response": <a single !<INPUT 2>! value that best represents your prediction on how the participant's answer>},
"2": {"Q": "<repeat the question you are answering>",
"Range Interpretation": {
"<option 1>": "a few sentences about the kind of person that would choose each end of the range",
"<option 2>": "..."},
"Reasoning": "<reasoning on which of the option best predicts the participant's response>",
"Response": <your prediction on how the participant will answer the question>},
...}
@@ -0,0 +1,30 @@
Variables:
Note: basically main version (ver 3) but with "reasoning" step
<commentblockmarker>###</commentblockmarker>
!<INPUT 0>!
=====
Task: What you see above is an interview transcript. Based on the interview transcript, I want you to predict the participant's survey response to a question. You should output a number that is in the range that was specified for that question.
As you answer, I want you to take the following steps:
Step 1) Describe in a few sentences the kind of person that would choose each end of the range. ("Range Interpretation")
Step 2) Write a few sentences reasoning on which of the option best predicts the participant's response ("Reasoning")
Step 3) Predict how the participant will actually respond. Predict based on the interview and your thoughts, but ultimately, DON'T over think it. Use your system 1 (fast, intuitive) thinking. ("Response")
Here is the question:
!<INPUT 1>!
-----
Output format -- output your response in json, where you provide the following:
{"1": {"Q": "<repeat the question you are answering>",
"Range Interpretation": {
"<option 1>": "a few sentences about the kind of person that would choose each end of the range",
"<option 2>": "..."},
"Reasoning": "<reasoning on which of the option best predicts the participant's response>",
"Response": <a single !<INPUT 2>! value that best represents your prediction on how the participant's answer>}}
@@ -0,0 +1,19 @@
<commentblockmarker>###</commentblockmarker>
<Background information about the Character>
!<INPUT 0>!
<End of background information about the Character>
=====
<Dialogue so far>
!<INPUT 1>!
!<INPUT 2>!
<End of dialogue so far>
Task: First, study the background information that I provided you about a fictional subject above. We are writing a dialogue between the subject above, and me. Given the dialogue so far, generate the next utterance that the character will speak.
Important: Please respond in Chinese (Simplified Chinese). If the question is in English, translate your response to Chinese.
Output format -- output your response in json, where you provide the following:
{"utterance": "[...]"}
@@ -0,0 +1,12 @@
Variables:
!<INPUT 0>!: Observations
<commentblockmarker>###</commentblockmarker>
!<INPUT 0>!
---
Task: Above is a list of observations about a fictional human subject. For each item, rate its importance on a scale from 0 to 100, where 0 represents 'not important' and 100 represents 'very important' for understanding the subject.
Output format: Json dictionaries of the following format:
{
"Item 1": <int importance score (range: 0 to 100) for item 1>,
"Item 2": <int importance score (range: 0 to 100) for item 2>, ...
}
@@ -0,0 +1,11 @@
Variables:
!<INPUT 0>!: Observations
<commentblockmarker>###</commentblockmarker>
!<INPUT 0>!
---
Task: Above is an observation ("Item 1") about a fictional human subject. Rate its importance on a scale from 0 to 100, where 0 represents 'not important' and 100 represents 'very important' for understanding the subject.
Output format: Json dictionaries of the following format:
{
"Item 1": <int importance score (range: 0 to 100) for item 1>
}
@@ -0,0 +1,16 @@
Variables:
!<INPUT 0>!: observations
!<INPUT 1>!: reflection count
!<INPUT 2>!: anchor
<commentblockmarker>###</commentblockmarker>
!<INPUT 0>!
---
Task: Above are observations about a fictional human subject. Write a list of !<INPUT 1>! reflections (in first person voice, from the perspective of the subject) that you can infer from the observations above about the subject on the following anchoring topic/phrase: "!<INPUT 2>!".
Output format: Json dictionaries of the following format:
{
"reflection": [
"<fill in>",
"<fill in>", ...
]
}
@@ -0,0 +1,15 @@
Variables:
!<INPUT 0>!: observations
!<INPUT 1>!: reflection count
!<INPUT 2>!: anchor
<commentblockmarker>###</commentblockmarker>
!<INPUT 0>!
---
Task: Above are observations about a fictional human subject. Write one reflection (in first person voice, from the perspective of the subject) that you can infer from the observations above about the subject on the following anchoring topic/phrase: "!<INPUT 2>!".
Output format: Json dictionaries of the following format:
{
"reflection": [
"<fill in>"
]
}
+27
View File
@@ -0,0 +1,27 @@
from pathlib import Path
import sys
import os
# 添加项目根目录到系统路径
BASE_DIR = f"{Path(__file__).resolve().parent.parent}"
sys.path.append(BASE_DIR)
# 导入配置工具
from utils import config_util as cfg
# 确保配置已加载
cfg.load_config()
# 从system.conf读取配置
OPENAI_API_KEY = cfg.key_gpt_api_key
OPENAI_API_BASE = cfg.gpt_base_url
DEBUG = False
MAX_CHUNK_SIZE = 4
# 使用system.conf中的模型配置
LLM_VERS = cfg.gpt_model_engine
## To do: Are the following needed in the new structure? Ideally Populations_Dir is for the user to define.
POPULATIONS_DIR = f"{BASE_DIR}/agent_bank/populations"
LLM_PROMPT_DIR = f"{BASE_DIR}/simulation_engine/prompt_template"
+7 -9
View File
@@ -1,4 +1,3 @@
[key]
#funasr / ali / sensevoice
#建议使用funasr,请依照asr/funasr/README>md的说明启动
@@ -41,20 +40,19 @@ baidu_emotion_app_id=
baidu_emotion_api_key=
baidu_emotion_secret_key=
#NLP多选一:agent、deepseek、gpt、gpt_stream、rasa、rwkv、xingchen、ollama_api、privategpt、coze、return
chat_module=gpt_stream
#NLP多选一:cognitive_stream、gpt_stream、agent
chat_module = cognitive_stream
#灵聚 服务密钥(NLP多选1) https://open.lingju.ai
lingju_api_key=
lingju_api_authcode=
#gpt 服务密钥(NLP多选1) https://openai.com/
gpt_api_key=sk-4Spva89SGSikpacz3a70Dd081cA84c9a8dEd345f19C9BdFc
#gpt base url 如:https://api.openai.com/v1、https://rwkv.ai-creator.net/chntuned/v1、https://api.fastgpt.in/api/v1、https://api.moonshot.cn/v1、https://api.deepseek.com/v1
#免费key只支持gpt 3.5 ,若想使用其他model,可到 https://api.zyai.online/register/?aff_code=MyCI 下购买申请。
gpt_api_key=sk-xxxx
#gpt base url 如:https://api.openai.com/v1、https://rwkv.ai-creator.net/chntuned/v1、https://api.fastgpt.in/api/v1、https://api.moonshot.cn/v1
gpt_base_url=http://dsfay.s7.tunnelfrp.com/v1
#gpt model engine 如:gpt-3.5-turbo、moonshot-v1-8k、deepseek-chat
#gpt model engine 如:gemini-1.5-flash-latest、moonshot-v1-8k、deepseek-chat、https://api.deepseek.com/v1
gpt_model_engine=deepseek
#gpt(fastgpt)代理(可为空,填写例子:127.0.0.1:7890)
proxy_config=
@@ -77,4 +75,4 @@ coze_api_key=
start_mode=web
#服务器主动地址
fay_url=127.0.0.1
fay_url=http://127.0.0.1:5000
+62 -18
View File
@@ -19,30 +19,74 @@ def random_hex(length):
def __write_to_file(text):
if not os.path.exists("logs"):
os.mkdir("logs")
file = codecs.open(LOGS_FILE_URL, 'a', 'utf-8')
file.write(text + "\n")
file.close()
"""
将文本写入日志文件
参数:
text: 要写入的文本
"""
try:
if not os.path.exists("logs"):
os.mkdir("logs")
with codecs.open(LOGS_FILE_URL, 'a', 'utf-8') as file:
file.write(text + "\n")
except Exception as e:
print(f"写入日志文件时出错: {str(e)}")
def printInfo(level, sender, text, send_time=-1):
if send_time < 0:
send_time = time.time()
format_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(send_time)) + f".{int(send_time % 1 * 10)}"
logStr = '[{}][{}] {}'.format(format_time, sender, text)
print(logStr)
if level >= 3:
if wsa_server.get_web_instance().is_connected(sender):
wsa_server.get_web_instance().add_cmd({"panelMsg": text} if sender == "系统" else {"panelMsg": text, "Username" : sender})
if wsa_server.get_instance().is_connected(sender):
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': text}} if sender == "系统" else {'Topic': 'human', 'Data': {'Key': 'log', 'Value': text}, "Username" : sender}
wsa_server.get_instance().add_cmd(content)
MyThread(target=__write_to_file, args=[logStr]).start()
"""
打印并记录信息
参数:
level: 日志级别
sender: 发送者
text: 日志内容
send_time: 发送时间,默认为当前时间
"""
try:
# 确保text是字符串类型
if not isinstance(text, str):
text = str(text)
if send_time < 0:
send_time = time.time()
format_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(send_time)) + f".{int(send_time % 1 * 10)}"
logStr = '[{}][{}] {}'.format(format_time, sender, text)
# 使用print函数打印日志,确保编码正确
print(logStr)
if level >= 3:
# 发送日志到WebSocket服务器
if wsa_server.get_web_instance().is_connected(sender):
wsa_server.get_web_instance().add_cmd({"panelMsg": text} if sender == "系统" else {"panelMsg": text, "Username" : sender})
if wsa_server.get_instance().is_connected(sender):
content = {'Topic': 'human', 'Data': {'Key': 'log', 'Value': text}} if sender == "系统" else {'Topic': 'human', 'Data': {'Key': 'log', 'Value': text}, "Username" : sender}
wsa_server.get_instance().add_cmd(content)
# 异步写入日志文件
MyThread(target=__write_to_file, args=[logStr]).start()
except Exception as e:
print(f"处理日志时出错: {str(e)}")
def log(level, text):
printInfo(level, "系统", text)
"""
记录系统日志
参数:
level: 日志级别
text: 日志内容
"""
try:
# 确保text是字符串类型
if not isinstance(text, str):
text = str(text)
printInfo(level, "系统", text)
except Exception as e:
print(f"记录系统日志时出错: {str(e)}")
class DisablePrint:
def __enter__(self):