Implement Interview feature for agent interactions in simulations
- Added a new Interview module to facilitate interactions with agents post-simulation, allowing for single and batch interviews. - Introduced IPC communication mechanism for command and response handling between the Flask backend and simulation scripts. - Updated README.md to include detailed instructions on the new Interview functionality, including API endpoints and usage examples. - Enhanced simulation scripts to support waiting for commands after completion, improving user control over the simulation environment. - Implemented error handling and logging for interview processes, ensuring robust operation and traceability.
This commit is contained in:
@@ -1723,3 +1723,568 @@ def get_simulation_comments(simulation_id: str):
|
||||
"error": str(e),
|
||||
"traceback": traceback.format_exc()
|
||||
}), 500
|
||||
|
||||
|
||||
# ============== Interview 采访接口 ==============
|
||||
|
||||
@simulation_bp.route('/interview', methods=['POST'])
|
||||
def interview_agent():
|
||||
"""
|
||||
采访单个Agent
|
||||
|
||||
注意:此功能需要模拟环境处于运行状态(完成模拟循环后进入等待命令模式)
|
||||
|
||||
请求(JSON):
|
||||
{
|
||||
"simulation_id": "sim_xxxx", // 必填,模拟ID
|
||||
"agent_id": 0, // 必填,Agent ID
|
||||
"prompt": "你对这件事有什么看法?", // 必填,采访问题
|
||||
"platform": "twitter", // 可选,指定平台(twitter/reddit)
|
||||
// 不指定时:双平台模拟同时采访两个平台
|
||||
"timeout": 60 // 可选,超时时间(秒),默认60
|
||||
}
|
||||
|
||||
返回(不指定platform,双平台模式):
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"agent_id": 0,
|
||||
"prompt": "你对这件事有什么看法?",
|
||||
"result": {
|
||||
"agent_id": 0,
|
||||
"prompt": "...",
|
||||
"platforms": {
|
||||
"twitter": {"agent_id": 0, "response": "...", "platform": "twitter"},
|
||||
"reddit": {"agent_id": 0, "response": "...", "platform": "reddit"}
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-12-08T10:00:01"
|
||||
}
|
||||
}
|
||||
|
||||
返回(指定platform):
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"agent_id": 0,
|
||||
"prompt": "你对这件事有什么看法?",
|
||||
"result": {
|
||||
"agent_id": 0,
|
||||
"response": "我认为...",
|
||||
"platform": "twitter",
|
||||
"timestamp": "2025-12-08T10:00:00"
|
||||
},
|
||||
"timestamp": "2025-12-08T10:00:01"
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
|
||||
simulation_id = data.get('simulation_id')
|
||||
agent_id = data.get('agent_id')
|
||||
prompt = data.get('prompt')
|
||||
platform = data.get('platform') # 可选:twitter/reddit/None
|
||||
timeout = data.get('timeout', 60)
|
||||
|
||||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
}), 400
|
||||
|
||||
if agent_id is None:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 agent_id"
|
||||
}), 400
|
||||
|
||||
if not prompt:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 prompt(采访问题)"
|
||||
}), 400
|
||||
|
||||
# 验证platform参数
|
||||
if platform and platform not in ("twitter", "reddit"):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "platform 参数只能是 'twitter' 或 'reddit'"
|
||||
}), 400
|
||||
|
||||
# 检查环境状态
|
||||
if not SimulationRunner.check_env_alive(simulation_id):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "模拟环境未运行或已关闭。请确保模拟已完成并进入等待命令模式。"
|
||||
}), 400
|
||||
|
||||
result = SimulationRunner.interview_agent(
|
||||
simulation_id=simulation_id,
|
||||
agent_id=agent_id,
|
||||
prompt=prompt,
|
||||
platform=platform,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
"success": result.get("success", False),
|
||||
"data": result
|
||||
})
|
||||
|
||||
except ValueError as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}), 400
|
||||
|
||||
except TimeoutError as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"等待Interview响应超时: {str(e)}"
|
||||
}), 504
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Interview失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"traceback": traceback.format_exc()
|
||||
}), 500
|
||||
|
||||
|
||||
@simulation_bp.route('/interview/batch', methods=['POST'])
|
||||
def interview_agents_batch():
|
||||
"""
|
||||
批量采访多个Agent
|
||||
|
||||
注意:此功能需要模拟环境处于运行状态
|
||||
|
||||
请求(JSON):
|
||||
{
|
||||
"simulation_id": "sim_xxxx", // 必填,模拟ID
|
||||
"interviews": [ // 必填,采访列表
|
||||
{
|
||||
"agent_id": 0,
|
||||
"prompt": "你对A有什么看法?",
|
||||
"platform": "twitter" // 可选,指定该Agent的采访平台
|
||||
},
|
||||
{
|
||||
"agent_id": 1,
|
||||
"prompt": "你对B有什么看法?" // 不指定platform则使用默认值
|
||||
}
|
||||
],
|
||||
"platform": "reddit", // 可选,默认平台(被每项的platform覆盖)
|
||||
// 不指定时:双平台模拟每个Agent同时采访两个平台
|
||||
"timeout": 120 // 可选,超时时间(秒),默认120
|
||||
}
|
||||
|
||||
返回:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"interviews_count": 2,
|
||||
"result": {
|
||||
"interviews_count": 4,
|
||||
"results": {
|
||||
"twitter_0": {"agent_id": 0, "response": "...", "platform": "twitter"},
|
||||
"reddit_0": {"agent_id": 0, "response": "...", "platform": "reddit"},
|
||||
"twitter_1": {"agent_id": 1, "response": "...", "platform": "twitter"},
|
||||
"reddit_1": {"agent_id": 1, "response": "...", "platform": "reddit"}
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-12-08T10:00:01"
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
|
||||
simulation_id = data.get('simulation_id')
|
||||
interviews = data.get('interviews')
|
||||
platform = data.get('platform') # 可选:twitter/reddit/None
|
||||
timeout = data.get('timeout', 120)
|
||||
|
||||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
}), 400
|
||||
|
||||
if not interviews or not isinstance(interviews, list):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 interviews(采访列表)"
|
||||
}), 400
|
||||
|
||||
# 验证platform参数
|
||||
if platform and platform not in ("twitter", "reddit"):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "platform 参数只能是 'twitter' 或 'reddit'"
|
||||
}), 400
|
||||
|
||||
# 验证每个采访项
|
||||
for i, interview in enumerate(interviews):
|
||||
if 'agent_id' not in interview:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"采访列表第{i+1}项缺少 agent_id"
|
||||
}), 400
|
||||
if 'prompt' not in interview:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"采访列表第{i+1}项缺少 prompt"
|
||||
}), 400
|
||||
# 验证每项的platform(如果有)
|
||||
item_platform = interview.get('platform')
|
||||
if item_platform and item_platform not in ("twitter", "reddit"):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"采访列表第{i+1}项的platform只能是 'twitter' 或 'reddit'"
|
||||
}), 400
|
||||
|
||||
# 检查环境状态
|
||||
if not SimulationRunner.check_env_alive(simulation_id):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "模拟环境未运行或已关闭。请确保模拟已完成并进入等待命令模式。"
|
||||
}), 400
|
||||
|
||||
result = SimulationRunner.interview_agents_batch(
|
||||
simulation_id=simulation_id,
|
||||
interviews=interviews,
|
||||
platform=platform,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
"success": result.get("success", False),
|
||||
"data": result
|
||||
})
|
||||
|
||||
except ValueError as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}), 400
|
||||
|
||||
except TimeoutError as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"等待批量Interview响应超时: {str(e)}"
|
||||
}), 504
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"批量Interview失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"traceback": traceback.format_exc()
|
||||
}), 500
|
||||
|
||||
|
||||
@simulation_bp.route('/interview/all', methods=['POST'])
|
||||
def interview_all_agents():
|
||||
"""
|
||||
全局采访 - 使用相同问题采访所有Agent
|
||||
|
||||
注意:此功能需要模拟环境处于运行状态
|
||||
|
||||
请求(JSON):
|
||||
{
|
||||
"simulation_id": "sim_xxxx", // 必填,模拟ID
|
||||
"prompt": "你对这件事整体有什么看法?", // 必填,采访问题(所有Agent使用相同问题)
|
||||
"platform": "reddit", // 可选,指定平台(twitter/reddit)
|
||||
// 不指定时:双平台模拟每个Agent同时采访两个平台
|
||||
"timeout": 180 // 可选,超时时间(秒),默认180
|
||||
}
|
||||
|
||||
返回:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"interviews_count": 50,
|
||||
"result": {
|
||||
"interviews_count": 100,
|
||||
"results": {
|
||||
"twitter_0": {"agent_id": 0, "response": "...", "platform": "twitter"},
|
||||
"reddit_0": {"agent_id": 0, "response": "...", "platform": "reddit"},
|
||||
...
|
||||
}
|
||||
},
|
||||
"timestamp": "2025-12-08T10:00:01"
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
|
||||
simulation_id = data.get('simulation_id')
|
||||
prompt = data.get('prompt')
|
||||
platform = data.get('platform') # 可选:twitter/reddit/None
|
||||
timeout = data.get('timeout', 180)
|
||||
|
||||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
}), 400
|
||||
|
||||
if not prompt:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 prompt(采访问题)"
|
||||
}), 400
|
||||
|
||||
# 验证platform参数
|
||||
if platform and platform not in ("twitter", "reddit"):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "platform 参数只能是 'twitter' 或 'reddit'"
|
||||
}), 400
|
||||
|
||||
# 检查环境状态
|
||||
if not SimulationRunner.check_env_alive(simulation_id):
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "模拟环境未运行或已关闭。请确保模拟已完成并进入等待命令模式。"
|
||||
}), 400
|
||||
|
||||
result = SimulationRunner.interview_all_agents(
|
||||
simulation_id=simulation_id,
|
||||
prompt=prompt,
|
||||
platform=platform,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
"success": result.get("success", False),
|
||||
"data": result
|
||||
})
|
||||
|
||||
except ValueError as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}), 400
|
||||
|
||||
except TimeoutError as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": f"等待全局Interview响应超时: {str(e)}"
|
||||
}), 504
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"全局Interview失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"traceback": traceback.format_exc()
|
||||
}), 500
|
||||
|
||||
|
||||
@simulation_bp.route('/interview/history', methods=['POST'])
|
||||
def get_interview_history():
|
||||
"""
|
||||
获取Interview历史记录
|
||||
|
||||
从模拟数据库中读取所有Interview记录
|
||||
|
||||
请求(JSON):
|
||||
{
|
||||
"simulation_id": "sim_xxxx", // 必填,模拟ID
|
||||
"platform": "reddit", // 可选,平台类型(reddit/twitter),默认reddit
|
||||
"agent_id": 0, // 可选,过滤Agent ID
|
||||
"limit": 100 // 可选,返回数量,默认100
|
||||
}
|
||||
|
||||
返回:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"count": 10,
|
||||
"history": [
|
||||
{
|
||||
"agent_id": 0,
|
||||
"response": "我认为...",
|
||||
"prompt": "你对这件事有什么看法?",
|
||||
"timestamp": "2025-12-08T10:00:00",
|
||||
"platform": "reddit"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
|
||||
simulation_id = data.get('simulation_id')
|
||||
platform = data.get('platform', 'reddit')
|
||||
agent_id = data.get('agent_id')
|
||||
limit = data.get('limit', 100)
|
||||
|
||||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
}), 400
|
||||
|
||||
history = SimulationRunner.get_interview_history(
|
||||
simulation_id=simulation_id,
|
||||
platform=platform,
|
||||
agent_id=agent_id,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"data": {
|
||||
"count": len(history),
|
||||
"history": history
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取Interview历史失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"traceback": traceback.format_exc()
|
||||
}), 500
|
||||
|
||||
|
||||
@simulation_bp.route('/env-status', methods=['POST'])
|
||||
def get_env_status():
|
||||
"""
|
||||
获取模拟环境状态
|
||||
|
||||
检查模拟环境是否存活(可以接收Interview命令)
|
||||
|
||||
请求(JSON):
|
||||
{
|
||||
"simulation_id": "sim_xxxx" // 必填,模拟ID
|
||||
}
|
||||
|
||||
返回:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"simulation_id": "sim_xxxx",
|
||||
"env_alive": true,
|
||||
"twitter_available": true,
|
||||
"reddit_available": true,
|
||||
"message": "环境正在运行,可以接收Interview命令"
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
|
||||
simulation_id = data.get('simulation_id')
|
||||
|
||||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
}), 400
|
||||
|
||||
env_alive = SimulationRunner.check_env_alive(simulation_id)
|
||||
|
||||
# 获取更详细的状态信息
|
||||
env_status = SimulationRunner.get_env_status_detail(simulation_id)
|
||||
|
||||
if env_alive:
|
||||
message = "环境正在运行,可以接收Interview命令"
|
||||
else:
|
||||
message = "环境未运行或已关闭"
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"data": {
|
||||
"simulation_id": simulation_id,
|
||||
"env_alive": env_alive,
|
||||
"twitter_available": env_status.get("twitter_available", False),
|
||||
"reddit_available": env_status.get("reddit_available", False),
|
||||
"message": message
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取环境状态失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"traceback": traceback.format_exc()
|
||||
}), 500
|
||||
|
||||
|
||||
@simulation_bp.route('/close-env', methods=['POST'])
|
||||
def close_simulation_env():
|
||||
"""
|
||||
关闭模拟环境
|
||||
|
||||
向模拟发送关闭环境命令,使其优雅退出等待命令模式。
|
||||
|
||||
注意:这不同于 /stop 接口,/stop 会强制终止进程,
|
||||
而此接口会让模拟优雅地关闭环境并退出。
|
||||
|
||||
请求(JSON):
|
||||
{
|
||||
"simulation_id": "sim_xxxx", // 必填,模拟ID
|
||||
"timeout": 30 // 可选,超时时间(秒),默认30
|
||||
}
|
||||
|
||||
返回:
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"message": "环境关闭命令已发送",
|
||||
"result": {...},
|
||||
"timestamp": "2025-12-08T10:00:01"
|
||||
}
|
||||
}
|
||||
"""
|
||||
try:
|
||||
data = request.get_json() or {}
|
||||
|
||||
simulation_id = data.get('simulation_id')
|
||||
timeout = data.get('timeout', 30)
|
||||
|
||||
if not simulation_id:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "请提供 simulation_id"
|
||||
}), 400
|
||||
|
||||
result = SimulationRunner.close_simulation_env(
|
||||
simulation_id=simulation_id,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
# 更新模拟状态
|
||||
manager = SimulationManager()
|
||||
state = manager.get_simulation(simulation_id)
|
||||
if state:
|
||||
state.status = SimulationStatus.COMPLETED
|
||||
manager._save_simulation_state(state)
|
||||
|
||||
return jsonify({
|
||||
"success": result.get("success", False),
|
||||
"data": result
|
||||
})
|
||||
|
||||
except ValueError as e:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}), 400
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"关闭环境失败: {str(e)}")
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"traceback": traceback.format_exc()
|
||||
}), 500
|
||||
|
||||
Reference in New Issue
Block a user