feat:预订会议室节点-增加识别查询空闲会议室以及触发预订会议室的功能

This commit is contained in:
雷雨
2025-11-15 11:25:31 +08:00
parent ed9e3adce3
commit eec994ca84
5 changed files with 162 additions and 12 deletions

View File

@@ -6,7 +6,7 @@ template:
# 上下文信息
你将作为 Langgraph 工作流中的一个节点存在。你的输出(一个 JSON 对象)将直接用于决定下一步应该调用哪个功能节点(如预订会议室、查询预订等)。你的判断必须精准、高效。
<节点名称> 必须是且仅是以下四个选项之一:
book_meeting - 当用户明确表示要预订/预约会议室时
pre_book_meeting - 当用户明确表示要预订/预约会议室时
cancel_meeting - 当用户明确表示要取消已预订的会议室时
query_meeting - 当用户查询已预订的会议室信息时
unknown - 当意图不明确或超出上述三种情况时
@@ -28,6 +28,69 @@ template:
如果结果中出现错误,请以友好、易于理解的方式提示错误信息,并可提供适当建议。
处理结果信息时尽量“结构化解析”,避免遗漏关键信息(如时间、会议室名称、人数等)
使用口语化中文,避免生硬术语。
解析结果展示时候尽量以MD格式展示。
#用户数据:
{user_operation}
<data>{data}</data>
结果展示示例:
1.空闲会议室查询示例:
```
1.会议室名称: <name>
会议室Id: <id>
容量: <capacity>
```
book_meeting: |
你是一个会议室预订智能体负责识别用户意图并根据提供的信息自动构造相应的JSON查询或预订指令。
当前时间:<current_time>{current_time}</current_time>
**核心指令**
- 严格遵守上述信息检查和响应规则。
- 优先处理时间合法性特别是24:00:00的转换。
- 会议室Id必须是长度大于15位的数字编号可以通过会议室名称匹配上下文获取禁止使用默认值。
- 所有JSON输出必须严格遵循提供的模板格式不得添加任何额外字段或说明文字。
- 禁止对查询结果做任何假设,直接按规则执行。
处理流程如下:
当用户表达预订意向时:
1. **信息检查与提取**
* 严格检查用户的表达中是否包含以下信息,仅作判断和提取:
* `会议室Id` (必需,注意:[1]会议室编号是长度大于15位的数字编号不要混淆会议室名称和编号[2]可以根据会议室名称去上下文中去匹配会议室Id禁止使用默认的)
* `具体时间段` (必需,至少包含开始时间或结束时间中的一个)
* `参会人数` (非必需若用户未指明则默认为20)
* `会议主题` (非必需,若会议主题不存在则用"默认主题")
2. **时间处理规则**
* 如果会议具体的开始和结束时间都没有,则按规则 (4) 提示用户确认会议具体时间。
* 如果只有开始时间则结束时间按照时长2小时自动补齐。补齐时需特别注意24点的合法性例如`2025-05-23 24:00:00` 不合法,必须转换成 `2025-05-24 00:00:00`。
* 如果只有结束时间则开始时间按照提前2小时自动补齐。补齐时同样需处理跨日的情况例如结束时间为 `2025-05-24 01:00:00`,补齐的开始时间应为 `2025-05-23 23:00:00`。
* 时间标准格式为 `YYYY-MM-DD HH:MM:SS`。
* 用户用户只说了上午则默认为上午9-11点下午则默认为下午15-17点。
3. **响应规则**
* (1) **无时间信息**如果用户表达中完全没有提及任何具体的开始或结束时间无法根据上下文推断则按照规则构造并返回如下JSON提示用户输入
```json
{{
"node":"END",
"message":"请输入会议的具体开始-结束时间"
}}
```
* (2) **无会议室ID但有时间段**:如果用户未明确指定`会议室Id`无论是通过ID还是名称但`具体时间段`至少存在一个根据规则2补齐完整后则构造JSON直接查询会议室信息。`start_time`和`end_time`不能为空,`Region`可能是城市、区域、学校、酒店等的名称,注意从上下文中识别并提取。`capacity`参会人数未指明则默认20。禁止假设查询失败按如下方式填充构造json查询体返回给用户:
```json
{{
"node":"query_avali_room",
"start_time":"",
"end_time":"",
"Region":"",
"capacity":""
}}
```
* (3) **信息完整且确认预定**:若用户确认预定且提供了信息完整的`会议室Id`长度大于15位的数字编号并且`具体时间段`存在一个根据规则2补齐完整后则直接构造标准JSON执行预定操作。参会人数未指明则默认20会议主题不存在则用"默认主题"。按如下方式填充构造json返回
```json
{{
"room_id":"",
"capacity":20,
"subject":"会议主题",
"start_time":"2025-06-04 09:30:10",
"end_time":"2025-06-04 11:30:10",
"node":"book_meeting"
}}
```
4 .用户历史消息对话如下:
<history>{history}</history>

View File

@@ -9,6 +9,7 @@ from yj_room_agent.template import template
from yj_room_agent.tools import json_tools
from langgraph.graph import StateGraph, END, START
from langgraph.checkpoint.memory import MemorySaver
from yj_room_agent.temp_history import msg_history
room_llm = ChatOpenAI(
model=config('MODEL_NAME', default=''),
@@ -38,8 +39,10 @@ class RoomChatAgentState(TypedDict):
# data_数据
query_meeting_data: Optional[dict | str]
pre_book_meeting_data: Optional[dict | str]
book_meeting_data: Optional[dict | str]
cancel_meeting_data: Optional[dict | str]
avli_room_data: Optional[dict | str | list]
'''
@@ -53,6 +56,7 @@ def intent_recognition_node(state: RoomChatAgentState) -> dict:
user_query = state.get('user_query', '')
history = state.get('history', [])
user_id = state.get('user_id', '')
msg_history.add_msg(state.get('conversation_id', ''), 'user', user_query)
xx_template = template.get_base_template()
sys_template = xx_template['template']['intent_recognition']
sys_template = sys_template.format(history=history, user_input=user_query,
@@ -70,25 +74,78 @@ def intent_recognition_node(state: RoomChatAgentState) -> dict:
def intent_recognition_node_handler(state: RoomChatAgentState) -> str:
user_intention = state.get('user_intention', {})
conversation_id = state.get('conversation_id', '')
if user_intention is None:
msg_history.add_msg(conversation_id, 'assistant', '识别用户意图识别失败')
return END
node = user_intention.get('node', '')
if node == '' or node == 'unknown':
logger.error(f"user:{state.get('user_id', '1')} 未知意图:{user_intention}")
msg_history.add_msg(conversation_id, 'assistant', user_intention.get('summary', ''))
return END
return node
#预约会议,需要判断历史消息或者用户输入,判断是否有会议室号,如果有,则预约会议,如果没有,触发查询空闲会议室
def book_meeting_node(state: RoomChatAgentState) -> dict:
logger.info(f"user:{state.get('user_id', '1')} ---------------进入 book_meeting_node(预约会议) 节点-")
return {}
#取消会议,需要判断历史消息或者用户输入,判断是否有会议室号,如果有,则取消会议,如果没有,触发查询已预订会议
# 预约会议,需要判断历史消息或者用户输入,判断是否有会议室号,如果有,则预约会议,如果没有,触发查询空闲会议
def pre_book_meeting_node(state: RoomChatAgentState) -> dict:
logger.info(f"user:{state.get('user_id', '1')} ---------------进入 pre_book_meeting_node(预约会议) 节点-")
user_query = state.get('user_query', '')
history = state.get('history', [])
user_id = state.get('user_id', '')
xx_template = template.get_base_template()
sys_template = xx_template['template']['book_meeting']
sys_template = sys_template.format(history=history, current_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
resp = room_llm.invoke(
[{'role': 'system', 'content': sys_template}, {'role': 'user', 'content': user_query}]).text()
resp = json_tools.extract_nested_json(resp)
result = orjson.loads(resp)
logger.info(f"user:{user_id} book_meeting_node(预约会议) 节点返回结果:{resp}")
return {'pre_book_meeting_data': result}
def pre_book_meeting_node_handler(state: RoomChatAgentState) -> str:
user_intention = state.get('pre_book_meeting_data', {})
if user_intention is None:
return END
node = user_intention.get('node', '')
if node == 'END':
logger.error(f"user:{state.get('user_id', '1')} 提取预约会议数据失败:{user_intention}")
msg_history.add_msg(state.get('conversation_id', ''), 'assistant', user_intention.get('message', ''))
return END
return node
# 查询空闲会议室,需要判断历史消息或者用户输入,查询空闲会议室,返回结果
def query_avliable_meeting_room_node(state: RoomChatAgentState) -> dict:
simple_data = [{
'room_id': "17592186050581",
'room_name': "成都527"
}, {
'room_id': "17592186050582",
'room_name': "成都528"
}]
return {'last_node': 'query_avali_room', 'avli_room_data': simple_data}
def book_meeting_node(state: RoomChatAgentState) -> dict:
logger.info(f"user:{state.get('user_id', '1')} ---------------进入 pre_book_meeting_node(预约会议) 节点-")
data={
'code':200,
'message':'预约会议成功',
'data':{
'meeting_id': '17592186050585',
}
}
return {'last_node': 'book_meeting','book_meeting_data':data}
# 取消会议室,需要判断历史消息或者用户输入,判断是否有会议室号,如果有,则取消会议,如果没有,触发查询已预订会议
def cancel_meeting_node(state: RoomChatAgentState) -> dict:
logger.info(f"user:{state.get('user_id', '1')} ---------------进入 cancel_meeting_node(取消会议) 节点-")
return {}
#查询已预订会议,需要判断历史消息或者用户输入,查询已预订会议,返回结果
# 查询已预订会议,需要判断历史消息或者用户输入,查询已预订会议,返回结果
def query_meeting_node(state: RoomChatAgentState) -> dict:
logger.info(f"user:{state.get('user_id', '1')} ---------------进入 query_meeting_node(查询已预订会议) 节点-")
# 示例数据,后面替换用友查询接口数据
@@ -104,7 +161,8 @@ def query_meeting_node(state: RoomChatAgentState) -> dict:
}
return {'query_meeting_data': example_data, 'last_node': 'query_meeting'}
#结果汇总节点,根据上一个节点的返回结果,生成结果汇总模板,返回结果,结束会话
# 结果汇总节点,根据上一个节点的返回结果,生成结果汇总模板,返回结果,结束会话
def _result_summary_node(state: RoomChatAgentState) -> dict:
logger.info(f"user:{state.get('user_id', '1')} ---------------进入 _result_summary_node(结果汇总) 节点-")
last_node = state.get('last_node', '')
@@ -119,26 +177,35 @@ def _result_summary_node(state: RoomChatAgentState) -> dict:
if last_node == 'query_meeting':
node_msg = '用户调用APi查询已预订会议结果如下:'
data = state.get('query_meeting_data', None)
if last_node == 'query_avali_room':
node_msg = '用户调用APi查询空闲会议室结果如下:'
data = state.get('avli_room_data', None)
template_msg = template.get_base_template()
result_summary = template_msg['template']['result_summary']
result_summary = result_summary.format(user_operation=node_msg, data=data)
logger.info(f"大模型汇总结果模板:{result_summary}")
resp = room_llm.invoke(result_summary).text()
msg_history.add_msg(state.get('conversation_id', ''), 'assistant', resp)
return {'result_summary': resp}
workflow = StateGraph(RoomChatAgentState)
workflow.add_node("_intent_recognition", intent_recognition_node)
workflow.add_node("pre_book_meeting", pre_book_meeting_node)
workflow.add_node("book_meeting", book_meeting_node)
workflow.add_node("query_avali_room", query_avliable_meeting_room_node)
workflow.add_node("cancel_meeting", cancel_meeting_node)
workflow.add_node("query_meeting", query_meeting_node)
workflow.add_node("_result_summary", _result_summary_node)
workflow.add_edge(START, "_intent_recognition")
workflow.add_conditional_edges('_intent_recognition', intent_recognition_node_handler,
[END, 'book_meeting', 'cancel_meeting', 'query_meeting'])
[END, 'pre_book_meeting', 'cancel_meeting', 'query_meeting'])
workflow.add_conditional_edges("pre_book_meeting", pre_book_meeting_node_handler,
[END, 'book_meeting', 'query_avali_room'])
workflow.add_edge("_result_summary", END)
workflow.add_edge("book_meeting", END)
workflow.add_edge("cancel_meeting", END)
workflow.add_edge("query_meeting", '_result_summary')
workflow.add_edge("book_meeting", '_result_summary')
workflow.add_edge("query_avali_room", '_result_summary')
memory = MemorySaver()
room_chat_agent = workflow.compile(checkpointer=memory)

View File

View File

@@ -0,0 +1,20 @@
'''
仅用于测试验证使用,后期历史消息还是在数据库中保存
'''
cache = {}
def add_msg(conversation_id, role, msg):
if conversation_id not in cache:
ms = {'role': role, 'content': msg}
cache[conversation_id] = [ms]
else:
cache[conversation_id].append({'role': role, 'content': msg})
print(cache[conversation_id])
def get_msg(conversation_id):
if conversation_id in cache:
return cache[conversation_id]
else:
return []

View File

@@ -8,7 +8,7 @@ from .tools import getinfo
from .tianyi_ai import knowledge_chat
from yj_room_agent.graph.room_meeting_graph_agent import RoomChatAgentState, room_chat_agent
import uuid
from yj_room_agent.temp_history import msg_history
def hello(request):
return JsonResponse({'msg': 'ok'})
@@ -259,7 +259,7 @@ def room_chat_2(request):
ckk = request.COOKIES
params['yht_access_token'] = ckk.get('yht_access_token', None)
body = json.loads(request.body)
history = []
history = msg_history.get_msg(conversation_id=body['covers_id'])
config = {"configurable": {"thread_id": str(uuid.uuid4())}}
state: RoomChatAgentState = {
'params': params,