up
This commit is contained in:
@@ -15,7 +15,11 @@ type FunctionToolSchema struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FunctionToolRuntime struct {
|
type FunctionToolRuntime struct {
|
||||||
UserID uint
|
UserID uint
|
||||||
|
UserName string
|
||||||
|
UserEmail string
|
||||||
|
UserType string
|
||||||
|
UserInfo *CurrentUserInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func FunctionToolSchemas(configs []ToolConfig) []FunctionToolSchema {
|
func FunctionToolSchemas(configs []ToolConfig) []FunctionToolSchema {
|
||||||
@@ -29,6 +33,8 @@ func FunctionToolSchemas(configs []ToolConfig) []FunctionToolSchema {
|
|||||||
tools = append(tools, timeFunctionToolSchema())
|
tools = append(tools, timeFunctionToolSchema())
|
||||||
case "ops_ai_assistant_schedule_query", "ops_ai_assistant":
|
case "ops_ai_assistant_schedule_query", "ops_ai_assistant":
|
||||||
tools = append(tools, opsAIAssistantScheduleQuerySchema())
|
tools = append(tools, opsAIAssistantScheduleQuerySchema())
|
||||||
|
case "ops_ai_assistant_current_user":
|
||||||
|
tools = append(tools, opsAIAssistantCurrentUserSchema())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tools
|
return tools
|
||||||
@@ -56,6 +62,8 @@ func ExecuteFunctionTool(ctx context.Context, runtime FunctionToolRuntime, name
|
|||||||
return json.Marshal(result)
|
return json.Marshal(result)
|
||||||
case "ops_ai_assistant_schedule_query", "ops_ai_assistant":
|
case "ops_ai_assistant_schedule_query", "ops_ai_assistant":
|
||||||
return executeOpsAIAssistantScheduleQuery(ctx, runtime, rawArgs)
|
return executeOpsAIAssistantScheduleQuery(ctx, runtime, rawArgs)
|
||||||
|
case "ops_ai_assistant_current_user":
|
||||||
|
return executeOpsAIAssistantCurrentUser(ctx, runtime, rawArgs)
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("unknown tool: %s", name)
|
return nil, fmt.Errorf("unknown tool: %s", name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,29 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type ScheduleQueryArgs struct {
|
type ScheduleQueryArgs struct {
|
||||||
|
Action string `json:"action,omitempty"`
|
||||||
StartDate string `json:"start_date"`
|
StartDate string `json:"start_date"`
|
||||||
EndDate string `json:"end_date"`
|
EndDate string `json:"end_date"`
|
||||||
CalendarID uint `json:"calendar_id,omitempty"`
|
CalendarID uint `json:"calendar_id,omitempty"`
|
||||||
Limit int `json:"limit,omitempty"`
|
Limit int `json:"limit,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CurrentUserArgs struct {
|
||||||
|
Action string `json:"action,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CurrentUserInfo struct {
|
||||||
|
ID uint `json:"id,omitempty"`
|
||||||
|
UserID uint `json:"user_id,omitempty"`
|
||||||
|
FirstName string `json:"first_name,omitempty"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Birthdate string `json:"birthdate,omitempty"`
|
||||||
|
Gender string `json:"gender,omitempty"`
|
||||||
|
AvatarPath string `json:"avatar_path,omitempty"`
|
||||||
|
Region string `json:"region,omitempty"`
|
||||||
|
Language string `json:"language,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type ScheduleCalendar struct {
|
type ScheduleCalendar struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -29,6 +46,7 @@ type ScheduleEvent struct {
|
|||||||
ScheduleType string `json:"schedule_type"`
|
ScheduleType string `json:"schedule_type"`
|
||||||
IsPublic bool `json:"is_public"`
|
IsPublic bool `json:"is_public"`
|
||||||
Remark string `json:"remark,omitempty"`
|
Remark string `json:"remark,omitempty"`
|
||||||
|
AccessNote string `json:"access_note,omitempty"`
|
||||||
CanEdit bool `json:"canEdit"`
|
CanEdit bool `json:"canEdit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,26 +71,90 @@ func RegisterScheduleProvider(provider ScheduleProvider) {
|
|||||||
func opsAIAssistantScheduleQuerySchema() FunctionToolSchema {
|
func opsAIAssistantScheduleQuerySchema() FunctionToolSchema {
|
||||||
return FunctionToolSchema{
|
return FunctionToolSchema{
|
||||||
Name: "ops_ai_assistant_schedule_query",
|
Name: "ops_ai_assistant_schedule_query",
|
||||||
Description: "按明确日期范围查询当前用户可见的 OPS 日历/日程。相对日期需先调用 time 获取 start_date/end_date。",
|
Description: "只读工具:按明确日期范围查询当前用户可见的 OPS 日历/日程。禁止新增、修改、删除数据,禁止批量添加、批量导入或执行任何写操作。相对日期需先调用 time 获取 start_date/end_date。",
|
||||||
Parameters: map[string]interface{}{
|
Parameters: map[string]interface{}{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": map[string]interface{}{
|
"properties": map[string]interface{}{
|
||||||
|
"action": map[string]interface{}{
|
||||||
|
"type": "string",
|
||||||
|
"enum": []string{"query"},
|
||||||
|
"description": "固定为 query。本工具只允许查询,禁止 create/update/delete/batch_add/import 等写操作。",
|
||||||
|
},
|
||||||
"start_date": map[string]interface{}{"type": "string", "description": "开始日期,格式 YYYY-MM-DD。"},
|
"start_date": map[string]interface{}{"type": "string", "description": "开始日期,格式 YYYY-MM-DD。"},
|
||||||
"end_date": map[string]interface{}{"type": "string", "description": "结束日期,格式 YYYY-MM-DD。"},
|
"end_date": map[string]interface{}{"type": "string", "description": "结束日期,格式 YYYY-MM-DD。"},
|
||||||
"calendar_id": map[string]interface{}{"type": "integer", "description": "可选日历 ID;不传则查询全部可见日程。"},
|
"calendar_id": map[string]interface{}{"type": "integer", "description": "可选日历 ID;不传则查询全部可见日程。"},
|
||||||
"limit": map[string]interface{}{"type": "integer", "description": "可选返回上限,默认 100,最大 200。"},
|
"limit": map[string]interface{}{"type": "integer", "description": "可选返回上限,默认 100,最大 200。"},
|
||||||
},
|
},
|
||||||
"required": []string{"start_date", "end_date"},
|
"required": []string{"action", "start_date", "end_date"},
|
||||||
},
|
},
|
||||||
// ScheduleQueryResult is the return type packed as JSON
|
// ScheduleQueryResult is the return type packed as JSON
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func opsAIAssistantCurrentUserSchema() FunctionToolSchema {
|
||||||
|
return FunctionToolSchema{
|
||||||
|
Name: "ops_ai_assistant_current_user",
|
||||||
|
Description: "只读工具:当用户询问“我是谁”“当前登录用户是谁”“我的用户信息”等当前身份问题时调用。先判断是否已登录;已登录则返回当前登录用户信息,未登录则返回 unknown 并提示需要登录才能获取信息。",
|
||||||
|
Parameters: map[string]interface{}{
|
||||||
|
"type": "object",
|
||||||
|
"properties": map[string]interface{}{
|
||||||
|
"action": map[string]interface{}{
|
||||||
|
"type": "string",
|
||||||
|
"enum": []string{"get"},
|
||||||
|
"description": "固定为 get。本工具只读取当前登录用户信息。",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": []string{"action"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeOpsAIAssistantCurrentUser(ctx context.Context, runtime FunctionToolRuntime, rawArgs []byte) ([]byte, error) {
|
||||||
|
var args CurrentUserArgs
|
||||||
|
if len(rawArgs) > 0 {
|
||||||
|
if err := json.Unmarshal(rawArgs, &args); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if args.Action != "" && args.Action != "get" {
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"ok": false,
|
||||||
|
"error": "ops_ai_assistant_current_user 是只读工具,仅允许 get 读取当前用户信息",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if runtime.UserID <= 0 {
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"ok": true,
|
||||||
|
"loggedIn": false,
|
||||||
|
"unknown": true,
|
||||||
|
"message": "不知道你是谁,需要登录才能获取当前用户信息。",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"ok": true,
|
||||||
|
"loggedIn": true,
|
||||||
|
"user": map[string]interface{}{
|
||||||
|
"id": runtime.UserID,
|
||||||
|
"name": runtime.UserName,
|
||||||
|
"email": runtime.UserEmail,
|
||||||
|
"type": runtime.UserType,
|
||||||
|
"info": runtime.UserInfo,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func executeOpsAIAssistantScheduleQuery(ctx context.Context, runtime FunctionToolRuntime, rawArgs []byte) ([]byte, error) {
|
func executeOpsAIAssistantScheduleQuery(ctx context.Context, runtime FunctionToolRuntime, rawArgs []byte) ([]byte, error) {
|
||||||
var args ScheduleQueryArgs
|
var args ScheduleQueryArgs
|
||||||
if err := json.Unmarshal(rawArgs, &args); err != nil {
|
if err := json.Unmarshal(rawArgs, &args); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if args.Action != "" && args.Action != "query" {
|
||||||
|
return json.Marshal(map[string]interface{}{
|
||||||
|
"ok": false,
|
||||||
|
"error": "ops_ai_assistant 是只读工具,仅允许 query 查询操作,禁止新增、修改、删除、批量添加或导入数据",
|
||||||
|
})
|
||||||
|
}
|
||||||
startDate, err := time.Parse("2006-01-02", args.StartDate)
|
startDate, err := time.Parse("2006-01-02", args.StartDate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("invalid start_date: %w", err)
|
return nil, fmt.Errorf("invalid start_date: %w", err)
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ func ConfigAllInit() error {
|
|||||||
MaxTokens: 512,
|
MaxTokens: 512,
|
||||||
Tools: []ConfigsAIChatTool_{
|
Tools: []ConfigsAIChatTool_{
|
||||||
{Name: "time", Enabled: true, Description: "解析当前时间、相对日期和日期范围。"},
|
{Name: "time", Enabled: true, Description: "解析当前时间、相对日期和日期范围。"},
|
||||||
|
{Name: "ops_ai_assistant_current_user", Enabled: true, Description: "返回当前登录用户信息;未登录时提示需要登录才能获取信息。"},
|
||||||
{Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。"},
|
{Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ func handleChat(ctx *gin.Context) {
|
|||||||
toolNames = append(toolNames, tool.Function.Name)
|
toolNames = append(toolNames, tool.Function.Name)
|
||||||
}
|
}
|
||||||
emitTrace("function_tools", "prepare", "success", "已启用 Function Calling 工具", map[string]interface{}{"tools": toolNames})
|
emitTrace("function_tools", "prepare", "success", "已启用 Function Calling 工具", map[string]interface{}{"tools": toolNames})
|
||||||
openaiMsgs = append([]openaiMessage{{Role: "system", Content: "可用工具使用规则:当用户询问本月、今天、本周、下周等相对日期的日程时,先调用 time 获取明确 start_date/end_date,再调用 ops_ai_assistant_schedule_query 查询日程。不要臆造工具结果中不存在的日程。"}}, openaiMsgs...)
|
openaiMsgs = append([]openaiMessage{{Role: "system", Content: "可用工具使用规则:当用户询问“我是谁”“当前登录用户是谁”“我的用户信息”等当前身份问题时,调用 ops_ai_assistant_current_user;工具返回 loggedIn=true 时按工具结果回答当前用户信息,返回 loggedIn=false 时说明不知道并提示需要登录才能获取信息。当用户询问本月、今天、本周、下周等相对日期的日程时,先调用 time 获取明确 start_date/end_date,再调用 ops_ai_assistant_schedule_query 查询日程。不要臆造工具结果中不存在的信息。"}}, openaiMsgs...)
|
||||||
var toolExecuted bool
|
var toolExecuted bool
|
||||||
openaiMsgs, toolExecuted, err = runOpenAIToolLoop(ctx.Request.Context(), profile, openaiMsgs, functionTools, currentUser, tracker, emitTrace)
|
openaiMsgs, toolExecuted, err = runOpenAIToolLoop(ctx.Request.Context(), profile, openaiMsgs, functionTools, currentUser, tracker, emitTrace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -983,6 +983,22 @@ func executeAIFunctionTool(ctx context.Context, name string, rawArgs []byte, cur
|
|||||||
runtime := agents.FunctionToolRuntime{}
|
runtime := agents.FunctionToolRuntime{}
|
||||||
if currentUser != nil {
|
if currentUser != nil {
|
||||||
runtime.UserID = currentUser.ID
|
runtime.UserID = currentUser.ID
|
||||||
|
runtime.UserName = currentUser.Name
|
||||||
|
runtime.UserEmail = currentUser.Email
|
||||||
|
runtime.UserType = currentUser.Type
|
||||||
|
if userInfo := GetUserInfoFromUserID(currentUser.ID); userInfo != nil {
|
||||||
|
runtime.UserInfo = &agents.CurrentUserInfo{
|
||||||
|
ID: userInfo.ID,
|
||||||
|
UserID: userInfo.UserID,
|
||||||
|
FirstName: userInfo.FirstName,
|
||||||
|
Username: userInfo.Username,
|
||||||
|
Birthdate: userInfo.Birthdate.Format("2006-01-02"),
|
||||||
|
Gender: userInfo.Gender,
|
||||||
|
AvatarPath: userInfo.AvatarPath,
|
||||||
|
Region: userInfo.Region,
|
||||||
|
Language: userInfo.Language,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return agents.ExecuteFunctionTool(ctx, runtime, name, rawArgs)
|
return agents.ExecuteFunctionTool(ctx, runtime, name, rawArgs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ func ApiAIChatInit() {
|
|||||||
func ensureBuiltinAIChatTools() error {
|
func ensureBuiltinAIChatTools() error {
|
||||||
builtins := []TabAIChatTool{
|
builtins := []TabAIChatTool{
|
||||||
{Name: "time", Enabled: true, Description: "解析当前时间、相对日期和日期范围。", SortOrder: 0},
|
{Name: "time", Enabled: true, Description: "解析当前时间、相对日期和日期范围。", SortOrder: 0},
|
||||||
|
{Name: "ops_ai_assistant_current_user", Enabled: true, Description: "返回当前登录用户信息;未登录时提示需要登录才能获取信息。", SortOrder: 5},
|
||||||
{Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。", SortOrder: 10},
|
{Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。", SortOrder: 10},
|
||||||
}
|
}
|
||||||
for _, builtin := range builtins {
|
for _, builtin := range builtins {
|
||||||
@@ -204,6 +205,7 @@ func seedAIChatConfigFromYAMLIfEmpty() error {
|
|||||||
if len(tools) == 0 {
|
if len(tools) == 0 {
|
||||||
tools = []models.ConfigsAIChatTool_{
|
tools = []models.ConfigsAIChatTool_{
|
||||||
{Name: "time", Enabled: true, Description: "解析当前时间、相对日期和日期范围。"},
|
{Name: "time", Enabled: true, Description: "解析当前时间、相对日期和日期范围。"},
|
||||||
|
{Name: "ops_ai_assistant_current_user", Enabled: true, Description: "返回当前登录用户信息;未登录时提示需要登录才能获取信息。"},
|
||||||
{Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。"},
|
{Name: "ops_ai_assistant_schedule_query", Enabled: true, Description: "按日期范围查询当前用户可见的 OPS 日历/日程。"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,6 +140,7 @@ type CalendarScheduleEvent struct {
|
|||||||
ScheduleType string `json:"schedule_type"`
|
ScheduleType string `json:"schedule_type"`
|
||||||
IsPublic bool `json:"is_public"`
|
IsPublic bool `json:"is_public"`
|
||||||
Remark string `json:"remark,omitempty"`
|
Remark string `json:"remark,omitempty"`
|
||||||
|
AccessNote string `json:"access_note,omitempty"`
|
||||||
CanEdit bool `json:"canEdit"`
|
CanEdit bool `json:"canEdit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +189,7 @@ func (calendarScheduleProvider) QuerySchedules(ctx context.Context, query agents
|
|||||||
ScheduleType: event.ScheduleType,
|
ScheduleType: event.ScheduleType,
|
||||||
IsPublic: event.IsPublic,
|
IsPublic: event.IsPublic,
|
||||||
Remark: event.Remark,
|
Remark: event.Remark,
|
||||||
|
AccessNote: event.AccessNote,
|
||||||
CanEdit: event.CanEdit,
|
CanEdit: event.CanEdit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -249,9 +251,15 @@ func QueryCalendarSchedulesForAI(query CalendarScheduleQuery) ([]CalendarSchedul
|
|||||||
result := make([]CalendarScheduleEvent, 0, len(events))
|
result := make([]CalendarScheduleEvent, 0, len(events))
|
||||||
for _, event := range events {
|
for _, event := range events {
|
||||||
canEdit := false
|
canEdit := false
|
||||||
|
accessNote := ""
|
||||||
if currentUserID > 0 {
|
if currentUserID > 0 {
|
||||||
calendarCreatorID := calendarCreators[event.CalendarID]
|
calendarCreatorID := calendarCreators[event.CalendarID]
|
||||||
canEdit = event.UserID == currentUserID || calendarCreatorID == currentUserID || slices.Contains(calendarAdmins, currentUserID)
|
canEdit = event.UserID == currentUserID || calendarCreatorID == currentUserID || slices.Contains(calendarAdmins, currentUserID)
|
||||||
|
if !event.IsPublic && event.ScheduleType == "work" {
|
||||||
|
accessNote = "非公开工作日程,仅因当前用户具备相关权限可见"
|
||||||
|
} else if !event.IsPublic {
|
||||||
|
accessNote = "非公开日程,仅因当前用户具备相关权限可见"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result = append(result, CalendarScheduleEvent{
|
result = append(result, CalendarScheduleEvent{
|
||||||
ID: event.ID,
|
ID: event.ID,
|
||||||
@@ -263,6 +271,7 @@ func QueryCalendarSchedulesForAI(query CalendarScheduleQuery) ([]CalendarSchedul
|
|||||||
ScheduleType: event.ScheduleType,
|
ScheduleType: event.ScheduleType,
|
||||||
IsPublic: event.IsPublic,
|
IsPublic: event.IsPublic,
|
||||||
Remark: event.Remark,
|
Remark: event.Remark,
|
||||||
|
AccessNote: accessNote,
|
||||||
CanEdit: canEdit,
|
CanEdit: canEdit,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user