/* * Copyright 2025 coze-dev Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package agentrun import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "os" "runtime/debug" "strconv" "strings" "sync" "time" "github.com/cloudwego/eino/schema" "github.com/mohae/deepcopy" messageModel "github.com/coze-dev/coze-studio/backend/api/model/conversation/message" "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/agentrun" "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/message" "github.com/coze-dev/coze-studio/backend/api/model/crossdomain/singleagent" crossagent "github.com/coze-dev/coze-studio/backend/crossdomain/contract/agent" crossmessage "github.com/coze-dev/coze-studio/backend/crossdomain/contract/message" "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/entity" "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal" "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/internal/dal/model" "github.com/coze-dev/coze-studio/backend/domain/conversation/agentrun/repository" msgEntity "github.com/coze-dev/coze-studio/backend/domain/conversation/message/entity" "github.com/coze-dev/coze-studio/backend/pkg/errorx" "github.com/coze-dev/coze-studio/backend/pkg/lang/conv" "github.com/coze-dev/coze-studio/backend/pkg/lang/ptr" "github.com/coze-dev/coze-studio/backend/pkg/logs" "github.com/coze-dev/coze-studio/backend/pkg/safego" "github.com/coze-dev/coze-studio/backend/types/consts" "github.com/coze-dev/coze-studio/backend/types/errno" ) type runImpl struct { Components runProcess *internal.RunProcess runEvent *internal.Event } type runtimeDependence struct { runID int64 agentInfo *singleagent.SingleAgent questionMsgID int64 runMeta *entity.AgentRunMeta startTime time.Time usage *agentrun.Usage } type Components struct { RunRecordRepo repository.RunRecordRepo } func NewService(c *Components) Run { return &runImpl{ Components: *c, runEvent: internal.NewEvent(), runProcess: internal.NewRunProcess(c.RunRecordRepo), } } func (c *runImpl) AgentRun(ctx context.Context, arm *entity.AgentRunMeta) (*schema.StreamReader[*entity.AgentRunResponse], error) { sr, sw := schema.Pipe[*entity.AgentRunResponse](20) defer func() { if pe := recover(); pe != nil { logs.CtxErrorf(ctx, "panic recover: %v\n, [stack]:%v", pe, string(debug.Stack())) return } }() rtDependence := &runtimeDependence{ runMeta: arm, startTime: time.Now(), } safego.Go(ctx, func() { defer sw.Close() _ = c.run(ctx, sw, rtDependence) }) return sr, nil } func (c *runImpl) run(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) (err error) { agentInfo, err := c.handlerAgent(ctx, rtDependence) if err != nil { return } rtDependence.agentInfo = agentInfo history, err := c.handlerHistory(ctx, rtDependence) if err != nil { return } runRecord, err := c.createRunRecord(ctx, sw, rtDependence) if err != nil { return } rtDependence.runID = runRecord.ID defer func() { srRecord := c.buildSendRunRecord(ctx, runRecord, entity.RunStatusCompleted) if err != nil { srRecord.Error = &entity.RunError{ Code: errno.ErrConversationAgentRunError, Msg: err.Error(), } c.runProcess.StepToFailed(ctx, srRecord, sw) return } c.runProcess.StepToComplete(ctx, srRecord, sw, rtDependence.usage) }() input, err := c.handlerInput(ctx, sw, rtDependence) if err != nil { return } rtDependence.questionMsgID = input.ID err = c.handlerStreamExecute(ctx, sw, history, input, rtDependence) return } func (c *runImpl) handlerAgent(ctx context.Context, rtDependence *runtimeDependence) (*singleagent.SingleAgent, error) { agentInfo, err := crossagent.DefaultSVC().ObtainAgentByIdentity(ctx, &singleagent.AgentIdentity{ AgentID: rtDependence.runMeta.AgentID, IsDraft: rtDependence.runMeta.IsDraft, }) if err != nil { return nil, err } return agentInfo, nil } func (c *runImpl) handlerStreamExecute(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], historyMsg []*msgEntity.Message, input *msgEntity.Message, rtDependence *runtimeDependence) (err error) { mainChan := make(chan *entity.AgentRespEvent, 100) ar := &singleagent.AgentRuntime{ AgentVersion: rtDependence.runMeta.Version, SpaceID: rtDependence.runMeta.SpaceID, IsDraft: rtDependence.runMeta.IsDraft, ConnectorID: rtDependence.runMeta.ConnectorID, PreRetrieveTools: rtDependence.runMeta.PreRetrieveTools, } streamer, err := crossagent.DefaultSVC().StreamExecute(ctx, historyMsg, input, ar) if err != nil { return errors.New(errorx.ErrorWithoutStack(err)) } var wg sync.WaitGroup wg.Add(2) safego.Go(ctx, func() { defer wg.Done() c.pull(ctx, mainChan, streamer) }) safego.Go(ctx, func() { defer wg.Done() c.push(ctx, mainChan, sw, rtDependence) }) wg.Wait() return err } func transformEventMap(eventType singleagent.EventType) (message.MessageType, error) { var eType message.MessageType switch eventType { case singleagent.EventTypeOfFuncCall: return message.MessageTypeFunctionCall, nil case singleagent.EventTypeOfKnowledge: return message.MessageTypeKnowledge, nil case singleagent.EventTypeOfToolsMessage: return message.MessageTypeToolResponse, nil case singleagent.EventTypeOfChatModelAnswer: return message.MessageTypeAnswer, nil case singleagent.EventTypeOfToolsAsChatModelStream: return message.MessageTypeToolAsAnswer, nil case singleagent.EventTypeOfToolMidAnswer: return message.MessageTypeToolMidAnswer, nil case singleagent.EventTypeOfSuggest: return message.MessageTypeFlowUp, nil case singleagent.EventTypeOfInterrupt: return message.MessageTypeInterrupt, nil } return eType, errorx.New(errno.ErrReplyUnknowEventType) } func (c *runImpl) buildAgentMessage2Create(ctx context.Context, chunk *entity.AgentRespEvent, messageType message.MessageType, rtDependence *runtimeDependence) *message.Message { arm := rtDependence.runMeta msg := &msgEntity.Message{ ConversationID: arm.ConversationID, RunID: rtDependence.runID, AgentID: arm.AgentID, SectionID: arm.SectionID, UserID: arm.UserID, MessageType: messageType, } buildExt := map[string]string{} timeCost := fmt.Sprintf("%.1f", float64(time.Since(rtDependence.startTime).Milliseconds())/1000.00) switch messageType { case message.MessageTypeQuestion: msg.Role = schema.User msg.ContentType = arm.ContentType for _, content := range arm.Content { if content.Type == message.InputTypeText { msg.Content = content.Text break } } msg.MultiContent = arm.Content buildExt = arm.Ext msg.DisplayContent = arm.DisplayContent case message.MessageTypeAnswer, message.MessageTypeToolAsAnswer: msg.Role = schema.Assistant msg.ContentType = message.ContentTypeText case message.MessageTypeToolResponse: msg.Role = schema.Assistant msg.ContentType = message.ContentTypeText msg.Content = chunk.ToolsMessage[0].Content buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost modelContent := chunk.ToolsMessage[0] mc, err := json.Marshal(modelContent) if err == nil { msg.ModelContent = string(mc) } case message.MessageTypeKnowledge: msg.Role = schema.Assistant msg.ContentType = message.ContentTypeText knowledgeContent := c.buildKnowledge(ctx, chunk) if knowledgeContent != nil { knInfo, err := json.Marshal(knowledgeContent) if err == nil { msg.Content = string(knInfo) } } buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost modelContent := chunk.Knowledge mc, err := json.Marshal(modelContent) if err == nil { msg.ModelContent = string(mc) } case message.MessageTypeFunctionCall: msg.Role = schema.Assistant msg.ContentType = message.ContentTypeText if len(chunk.FuncCall.ToolCalls) > 0 { toolCall := chunk.FuncCall.ToolCalls[0] toolCalling, err := json.Marshal(toolCall) if err == nil { msg.Content = string(toolCalling) } buildExt[string(msgEntity.MessageExtKeyPlugin)] = toolCall.Function.Name buildExt[string(msgEntity.MessageExtKeyToolName)] = toolCall.Function.Name buildExt[string(msgEntity.MessageExtKeyTimeCost)] = timeCost modelContent := chunk.FuncCall mc, err := json.Marshal(modelContent) if err == nil { msg.ModelContent = string(mc) } } case message.MessageTypeFlowUp: msg.Role = schema.Assistant msg.ContentType = message.ContentTypeText msg.Content = chunk.Suggest.Content case message.MessageTypeVerbose: msg.Role = schema.Assistant msg.ContentType = message.ContentTypeText d := &entity.Data{ FinishReason: 0, FinData: "", } dByte, _ := json.Marshal(d) afc := &entity.AnswerFinshContent{ MsgType: entity.MessageSubTypeGenerateFinish, Data: string(dByte), } afcMarshal, _ := json.Marshal(afc) msg.Content = string(afcMarshal) case message.MessageTypeInterrupt: msg.Role = schema.Assistant msg.MessageType = message.MessageTypeVerbose msg.ContentType = message.ContentTypeText afc := &entity.AnswerFinshContent{ MsgType: entity.MessageSubTypeInterrupt, Data: "", } afcMarshal, _ := json.Marshal(afc) msg.Content = string(afcMarshal) // Add ext to save to context_message interruptByte, err := json.Marshal(chunk.Interrupt) if err == nil { buildExt[string(msgEntity.ExtKeyResumeInfo)] = string(interruptByte) } buildExt[string(msgEntity.ExtKeyToolCallsIDs)] = chunk.Interrupt.ToolCallID rc := &messageModel.RequiredAction{ Type: "submit_tool_outputs", SubmitToolOutputs: &messageModel.SubmitToolOutputs{}, } msg.RequiredAction = rc rcExtByte, err := json.Marshal(rc) if err == nil { buildExt[string(msgEntity.ExtKeyRequiresAction)] = string(rcExtByte) } } if messageType != message.MessageTypeQuestion { botStateExt := c.buildBotStateExt(arm) bseString, err := json.Marshal(botStateExt) if err == nil { buildExt[string(msgEntity.MessageExtKeyBotState)] = string(bseString) } } msg.Ext = buildExt return msg } func (c *runImpl) handlerHistory(ctx context.Context, rtDependence *runtimeDependence) ([]*msgEntity.Message, error) { conversationTurns := entity.ConversationTurnsDefault if rtDependence.agentInfo != nil && rtDependence.agentInfo.ModelInfo != nil && rtDependence.agentInfo.ModelInfo.ShortMemoryPolicy != nil && ptr.From(rtDependence.agentInfo.ModelInfo.ShortMemoryPolicy.HistoryRound) > 0 { conversationTurns = ptr.From(rtDependence.agentInfo.ModelInfo.ShortMemoryPolicy.HistoryRound) } runRecordList, err := c.RunRecordRepo.List(ctx, rtDependence.runMeta.ConversationID, rtDependence.runMeta.SectionID, conversationTurns) if err != nil { return nil, err } if len(runRecordList) == 0 { return nil, nil } runIDS := c.getRunID(runRecordList) history, err := crossmessage.DefaultSVC().GetByRunIDs(ctx, rtDependence.runMeta.ConversationID, runIDS) if err != nil { return nil, err } return history, nil } func (c *runImpl) getRunID(rr []*model.RunRecord) []int64 { ids := make([]int64, 0, len(rr)) for _, c := range rr { ids = append(ids, c.ID) } return ids } func (c *runImpl) createRunRecord(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) (*entity.RunRecordMeta, error) { runPoData, err := c.RunRecordRepo.Create(ctx, rtDependence.runMeta) if err != nil { logs.CtxErrorf(ctx, "RunRecordRepo.Create error: %v", err) return nil, err } srRecord := c.buildSendRunRecord(ctx, runPoData, entity.RunStatusCreated) c.runProcess.StepToCreate(ctx, srRecord, sw) err = c.runProcess.StepToInProgress(ctx, srRecord, sw) if err != nil { logs.CtxErrorf(ctx, "runProcess.StepToInProgress error: %v", err) return nil, err } return runPoData, nil } func (c *runImpl) handlerInput(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) (*msgEntity.Message, error) { msgMeta := c.buildAgentMessage2Create(ctx, nil, message.MessageTypeQuestion, rtDependence) cm, err := crossmessage.DefaultSVC().Create(ctx, msgMeta) if err != nil { return nil, err } ackErr := c.handlerAckMessage(ctx, cm, sw) if ackErr != nil { return msgMeta, ackErr } return cm, nil } func (c *runImpl) pull(_ context.Context, mainChan chan *entity.AgentRespEvent, events *schema.StreamReader[*crossagent.AgentEvent]) { defer func() { close(mainChan) }() for { rm, re := events.Recv() if re != nil { errChunk := &entity.AgentRespEvent{ Err: re, } mainChan <- errChunk return } eventType, tErr := transformEventMap(rm.EventType) if tErr != nil { errChunk := &entity.AgentRespEvent{ Err: tErr, } mainChan <- errChunk return } respChunk := &entity.AgentRespEvent{ EventType: eventType, ModelAnswer: rm.ChatModelAnswer, ToolsMessage: rm.ToolsMessage, FuncCall: rm.FuncCall, Knowledge: rm.Knowledge, Suggest: rm.Suggest, Interrupt: rm.Interrupt, ToolMidAnswer: rm.ToolMidAnswer, ToolAsAnswer: rm.ToolAsChatModelAnswer, } mainChan <- respChunk } } func (c *runImpl) push(ctx context.Context, mainChan chan *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) { var err error defer func() { if err != nil { logs.CtxErrorf(ctx, "run.push error: %v", err) c.handlerErr(ctx, err, sw) } }() reasoningContent := bytes.NewBuffer([]byte{}) var firstAnswerMsg *msgEntity.Message var reasoningMsg *msgEntity.Message isSendFinishAnswer := false var preToolResponseMsg *msgEntity.Message toolResponseMsgContent := bytes.NewBuffer([]byte{}) for { chunk, ok := <-mainChan if !ok || chunk == nil { return } logs.CtxInfof(ctx, "hanlder event:%v,err:%v", conv.DebugJsonToStr(chunk), chunk.Err) if chunk.Err != nil { if errors.Is(chunk.Err, io.EOF) { if !isSendFinishAnswer { isSendFinishAnswer = true if firstAnswerMsg != nil && len(reasoningContent.String()) > 0 { c.saveReasoningContent(ctx, firstAnswerMsg, reasoningContent.String()) reasoningContent.Reset() } finishErr := c.handlerFinalAnswerFinish(ctx, sw, rtDependence) if finishErr != nil { err = finishErr return } } return } c.handlerErr(ctx, chunk.Err, sw) return } switch chunk.EventType { case message.MessageTypeFunctionCall: err = c.handlerFunctionCall(ctx, chunk, sw, rtDependence) if err != nil { return } if preToolResponseMsg == nil { var cErr error preToolResponseMsg, cErr = c.PreCreateAnswer(ctx, rtDependence) if cErr != nil { err = cErr return } } case message.MessageTypeToolResponse: err = c.handlerTooResponse(ctx, chunk, sw, rtDependence, preToolResponseMsg, toolResponseMsgContent.String()) if err != nil { return } preToolResponseMsg = nil // reset case message.MessageTypeKnowledge: err = c.handlerKnowledge(ctx, chunk, sw, rtDependence) if err != nil { return } case message.MessageTypeToolMidAnswer: fullMidAnswerContent := bytes.NewBuffer([]byte{}) var usage *msgEntity.UsageExt toolMidAnswerMsg, cErr := c.PreCreateAnswer(ctx, rtDependence) if cErr != nil { err = cErr return } var preMsgIsFinish = false for { streamMsg, receErr := chunk.ToolMidAnswer.Recv() if receErr != nil { if errors.Is(receErr, io.EOF) { break } err = receErr return } if preMsgIsFinish { toolMidAnswerMsg, cErr = c.PreCreateAnswer(ctx, rtDependence) if cErr != nil { err = cErr return } preMsgIsFinish = false } if streamMsg == nil { continue } if firstAnswerMsg == nil && len(streamMsg.Content) > 0 { if reasoningMsg != nil { toolMidAnswerMsg = deepcopy.Copy(reasoningMsg).(*msgEntity.Message) } firstAnswerMsg = deepcopy.Copy(toolMidAnswerMsg).(*msgEntity.Message) } if streamMsg.Extra != nil { if val, ok := streamMsg.Extra["workflow_node_name"]; ok && val != nil { toolMidAnswerMsg.Ext["message_title"] = val.(string) } } sendMidAnswerMsg := c.buildSendMsg(ctx, toolMidAnswerMsg, false, rtDependence) sendMidAnswerMsg.Content = streamMsg.Content toolResponseMsgContent.WriteString(streamMsg.Content) fullMidAnswerContent.WriteString(streamMsg.Content) c.runEvent.SendMsgEvent(entity.RunEventMessageDelta, sendMidAnswerMsg, sw) if streamMsg != nil && streamMsg.ResponseMeta != nil { usage = c.handlerUsage(streamMsg.ResponseMeta) } if streamMsg.Extra["is_finish"] == true { preMsgIsFinish = true sendMidAnswerMsg := c.buildSendMsg(ctx, toolMidAnswerMsg, false, rtDependence) sendMidAnswerMsg.Content = fullMidAnswerContent.String() fullMidAnswerContent.Reset() hfErr := c.handlerAnswer(ctx, sendMidAnswerMsg, sw, usage, rtDependence, toolMidAnswerMsg) if hfErr != nil { err = hfErr return } } } case message.MessageTypeToolAsAnswer: var usage *msgEntity.UsageExt fullContent := bytes.NewBuffer([]byte{}) toolAsAnswerMsg, cErr := c.PreCreateAnswer(ctx, rtDependence) if cErr != nil { err = cErr return } if firstAnswerMsg == nil { firstAnswerMsg = toolAsAnswerMsg } for { streamMsg, receErr := chunk.ToolAsAnswer.Recv() if receErr != nil { if errors.Is(receErr, io.EOF) { answer := c.buildSendMsg(ctx, toolAsAnswerMsg, false, rtDependence) answer.Content = fullContent.String() hfErr := c.handlerAnswer(ctx, answer, sw, usage, rtDependence, toolAsAnswerMsg) if hfErr != nil { err = hfErr return } break } err = receErr return } if streamMsg != nil && streamMsg.ResponseMeta != nil { usage = c.handlerUsage(streamMsg.ResponseMeta) } sendMsg := c.buildSendMsg(ctx, toolAsAnswerMsg, false, rtDependence) fullContent.WriteString(streamMsg.Content) sendMsg.Content = streamMsg.Content c.runEvent.SendMsgEvent(entity.RunEventMessageDelta, sendMsg, sw) } case message.MessageTypeAnswer: fullContent := bytes.NewBuffer([]byte{}) var usage *msgEntity.UsageExt var isToolCalls = false var modelAnswerMsg *msgEntity.Message for { streamMsg, receErr := chunk.ModelAnswer.Recv() if receErr != nil { if errors.Is(receErr, io.EOF) { if isToolCalls { break } if modelAnswerMsg == nil { break } answer := c.buildSendMsg(ctx, modelAnswerMsg, false, rtDependence) answer.Content = fullContent.String() hfErr := c.handlerAnswer(ctx, answer, sw, usage, rtDependence, modelAnswerMsg) if hfErr != nil { err = hfErr return } break } err = receErr return } if streamMsg != nil && len(streamMsg.ToolCalls) > 0 { isToolCalls = true } if streamMsg != nil && streamMsg.ResponseMeta != nil { usage = c.handlerUsage(streamMsg.ResponseMeta) } if streamMsg != nil && len(streamMsg.ReasoningContent) == 0 && len(streamMsg.Content) == 0 { continue } if len(streamMsg.ReasoningContent) > 0 { if reasoningMsg == nil { reasoningMsg, err = c.PreCreateAnswer(ctx, rtDependence) if err != nil { return } } sendReasoningMsg := c.buildSendMsg(ctx, reasoningMsg, false, rtDependence) reasoningContent.WriteString(streamMsg.ReasoningContent) sendReasoningMsg.ReasoningContent = ptr.Of(streamMsg.ReasoningContent) c.runEvent.SendMsgEvent(entity.RunEventMessageDelta, sendReasoningMsg, sw) } if len(streamMsg.Content) > 0 { if modelAnswerMsg == nil { modelAnswerMsg, err = c.PreCreateAnswer(ctx, rtDependence) if err != nil { return } if firstAnswerMsg == nil { if reasoningMsg != nil { modelAnswerMsg.ID = reasoningMsg.ID } firstAnswerMsg = modelAnswerMsg } } sendAnswerMsg := c.buildSendMsg(ctx, modelAnswerMsg, false, rtDependence) fullContent.WriteString(streamMsg.Content) sendAnswerMsg.Content = streamMsg.Content c.runEvent.SendMsgEvent(entity.RunEventMessageDelta, sendAnswerMsg, sw) } } case message.MessageTypeFlowUp: if isSendFinishAnswer { if firstAnswerMsg != nil && len(reasoningContent.String()) > 0 { c.saveReasoningContent(ctx, firstAnswerMsg, reasoningContent.String()) } isSendFinishAnswer = true finishErr := c.handlerFinalAnswerFinish(ctx, sw, rtDependence) if finishErr != nil { err = finishErr return } } err = c.handlerSuggest(ctx, chunk, sw, rtDependence) if err != nil { return } case message.MessageTypeInterrupt: err = c.handlerInterrupt(ctx, chunk, sw, rtDependence, firstAnswerMsg, reasoningContent.String()) if err != nil { return } } } } func (c *runImpl) saveReasoningContent(ctx context.Context, firstAnswerMsg *msgEntity.Message, reasoningContent string) { _, err := crossmessage.DefaultSVC().Edit(ctx, &message.Message{ ID: firstAnswerMsg.ID, ReasoningContent: reasoningContent, }) if err != nil { logs.CtxInfof(ctx, "save reasoning content failed, err: %v", err) } } func (c *runImpl) handlerInterrupt(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence, firstAnswerMsg *msgEntity.Message, reasoningCOntent string) error { interruptData, cType, err := c.parseInterruptData(ctx, chunk.Interrupt) if err != nil { return err } preMsg, err := c.PreCreateAnswer(ctx, rtDependence) if err != nil { return err } deltaAnswer := &entity.ChunkMessageItem{ ID: preMsg.ID, ConversationID: preMsg.ConversationID, SectionID: preMsg.SectionID, RunID: preMsg.RunID, AgentID: preMsg.AgentID, Role: entity.RoleType(preMsg.Role), Content: interruptData, MessageType: preMsg.MessageType, ContentType: cType, ReplyID: preMsg.RunID, Ext: preMsg.Ext, IsFinish: false, } c.runEvent.SendMsgEvent(entity.RunEventMessageDelta, deltaAnswer, sw) finalAnswer := deepcopy.Copy(deltaAnswer).(*entity.ChunkMessageItem) if len(reasoningCOntent) > 0 && firstAnswerMsg == nil { finalAnswer.ReasoningContent = ptr.Of(reasoningCOntent) } err = c.handlerAnswer(ctx, finalAnswer, sw, nil, rtDependence, preMsg) if err != nil { return err } err = c.handlerInterruptVerbose(ctx, chunk, sw, rtDependence) if err != nil { return err } return nil } func (c *runImpl) parseInterruptData(_ context.Context, interruptData *singleagent.InterruptInfo) (string, message.ContentType, error) { type msg struct { Type string `json:"type,omitempty"` ContentType string `json:"content_type"` Content any `json:"content"` // either optionContent or string ID string `json:"id,omitempty"` } defaultContentType := message.ContentTypeText switch interruptData.InterruptType { case singleagent.InterruptEventType_OauthPlugin: data := interruptData.AllToolInterruptData[interruptData.ToolCallID].ToolNeedOAuth.Message return data, defaultContentType, nil case singleagent.InterruptEventType_Question: var iData map[string][]*msg err := json.Unmarshal([]byte(interruptData.AllWfInterruptData[interruptData.ToolCallID].InterruptData), &iData) if err != nil { return "", defaultContentType, err } if len(iData["messages"]) == 0 { return "", defaultContentType, errorx.New(errno.ErrInterruptDataEmpty) } interruptMsg := iData["messages"][0] if interruptMsg.ContentType == "text" { return interruptMsg.Content.(string), defaultContentType, nil } else if interruptMsg.ContentType == "option" || interruptMsg.ContentType == "form_schema" { iMarshalData, err := json.Marshal(interruptMsg) if err != nil { return "", defaultContentType, err } return string(iMarshalData), message.ContentTypeCard, nil } case singleagent.InterruptEventType_InputNode: data := interruptData.AllWfInterruptData[interruptData.ToolCallID].InterruptData return data, message.ContentTypeCard, nil case singleagent.InterruptEventType_WorkflowLLM: toolInterruptEvent := interruptData.AllWfInterruptData[interruptData.ToolCallID].ToolInterruptEvent data := toolInterruptEvent.InterruptData if singleagent.InterruptEventType(toolInterruptEvent.EventType) == singleagent.InterruptEventType_InputNode { return data, message.ContentTypeCard, nil } if singleagent.InterruptEventType(toolInterruptEvent.EventType) == singleagent.InterruptEventType_Question { var iData map[string][]*msg err := json.Unmarshal([]byte(data), &iData) if err != nil { return "", defaultContentType, err } if len(iData["messages"]) == 0 { return "", defaultContentType, errorx.New(errno.ErrInterruptDataEmpty) } interruptMsg := iData["messages"][0] if interruptMsg.ContentType == "text" { return interruptMsg.Content.(string), defaultContentType, nil } else if interruptMsg.ContentType == "option" || interruptMsg.ContentType == "form_schema" { iMarshalData, err := json.Marshal(interruptMsg) if err != nil { return "", defaultContentType, err } return string(iMarshalData), message.ContentTypeCard, nil } } return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType) } return "", defaultContentType, errorx.New(errno.ErrUnknowInterruptType) } func (c *runImpl) handlerUsage(meta *schema.ResponseMeta) *msgEntity.UsageExt { if meta == nil || meta.Usage == nil { return nil } return &msgEntity.UsageExt{ TotalCount: int64(meta.Usage.TotalTokens), InputTokens: int64(meta.Usage.PromptTokens), OutputTokens: int64(meta.Usage.CompletionTokens), } } func (c *runImpl) handlerErr(_ context.Context, err error, sw *schema.StreamWriter[*entity.AgentRunResponse]) { errMsg := errorx.ErrorWithoutStack(err) if strings.ToLower(os.Getenv(consts.RunMode)) != "debug" { var statusErr errorx.StatusError if errors.As(err, &statusErr) { errMsg = statusErr.Msg() } else { errMsg = "Internal Server Error" } } c.runEvent.SendErrEvent(entity.RunEventError, sw, &entity.RunError{ Code: errno.ErrAgentRun, Msg: errMsg, }) } func (c *runImpl) PreCreateAnswer(ctx context.Context, rtDependence *runtimeDependence) (*msgEntity.Message, error) { arm := rtDependence.runMeta msgMeta := &msgEntity.Message{ ConversationID: arm.ConversationID, RunID: rtDependence.runID, AgentID: arm.AgentID, SectionID: arm.SectionID, UserID: arm.UserID, Role: schema.Assistant, MessageType: message.MessageTypeAnswer, ContentType: message.ContentTypeText, Ext: arm.Ext, } if arm.Ext == nil { msgMeta.Ext = map[string]string{} } botStateExt := c.buildBotStateExt(arm) bseString, err := json.Marshal(botStateExt) if err != nil { return nil, err } if _, ok := msgMeta.Ext[string(msgEntity.MessageExtKeyBotState)]; !ok { msgMeta.Ext[string(msgEntity.MessageExtKeyBotState)] = string(bseString) } msgMeta.Ext = arm.Ext return crossmessage.DefaultSVC().PreCreate(ctx, msgMeta) } func (c *runImpl) handlerAnswer(ctx context.Context, msg *entity.ChunkMessageItem, sw *schema.StreamWriter[*entity.AgentRunResponse], usage *msgEntity.UsageExt, rtDependence *runtimeDependence, preAnswerMsg *msgEntity.Message) error { if len(msg.Content) == 0 && len(ptr.From(msg.ReasoningContent)) == 0 { return nil } msg.IsFinish = true if msg.Ext == nil { msg.Ext = map[string]string{} } if usage != nil { msg.Ext[string(msgEntity.MessageExtKeyToken)] = strconv.FormatInt(usage.TotalCount, 10) msg.Ext[string(msgEntity.MessageExtKeyInputTokens)] = strconv.FormatInt(usage.InputTokens, 10) msg.Ext[string(msgEntity.MessageExtKeyOutputTokens)] = strconv.FormatInt(usage.OutputTokens, 10) rtDependence.usage = &agentrun.Usage{ LlmPromptTokens: usage.InputTokens, LlmCompletionTokens: usage.OutputTokens, LlmTotalTokens: usage.TotalCount, } } if _, ok := msg.Ext[string(msgEntity.MessageExtKeyTimeCost)]; !ok { msg.Ext[string(msgEntity.MessageExtKeyTimeCost)] = fmt.Sprintf("%.1f", float64(time.Since(rtDependence.startTime).Milliseconds())/1000.00) } buildModelContent := &schema.Message{ Role: schema.Assistant, Content: msg.Content, } mc, err := json.Marshal(buildModelContent) if err != nil { return err } preAnswerMsg.Content = msg.Content preAnswerMsg.ReasoningContent = ptr.From(msg.ReasoningContent) preAnswerMsg.Ext = msg.Ext preAnswerMsg.ContentType = msg.ContentType preAnswerMsg.ModelContent = string(mc) preAnswerMsg.CreatedAt = 0 preAnswerMsg.UpdatedAt = 0 _, err = crossmessage.DefaultSVC().Create(ctx, preAnswerMsg) if err != nil { return err } c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, msg, sw) return nil } func (c *runImpl) buildBotStateExt(arm *entity.AgentRunMeta) *msgEntity.BotStateExt { agentID := strconv.FormatInt(arm.AgentID, 10) botStateExt := &msgEntity.BotStateExt{ AgentID: agentID, AgentName: arm.Name, Awaiting: agentID, BotID: agentID, } return botStateExt } func (c *runImpl) handlerFunctionCall(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) error { cm := c.buildAgentMessage2Create(ctx, chunk, message.MessageTypeFunctionCall, rtDependence) cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) if err != nil { return err } sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) return nil } func (c *runImpl) handlerAckMessage(_ context.Context, input *msgEntity.Message, sw *schema.StreamWriter[*entity.AgentRunResponse]) error { sendMsg := &entity.ChunkMessageItem{ ID: input.ID, ConversationID: input.ConversationID, SectionID: input.SectionID, AgentID: input.AgentID, Role: entity.RoleType(input.Role), MessageType: message.MessageTypeAck, ReplyID: input.ID, Content: input.Content, ContentType: message.ContentTypeText, IsFinish: true, } c.runEvent.SendMsgEvent(entity.RunEventAck, sendMsg, sw) return nil } func (c *runImpl) handlerTooResponse(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence, preToolResponseMsg *msgEntity.Message, toolResponseMsgContent string) error { cm := c.buildAgentMessage2Create(ctx, chunk, message.MessageTypeToolResponse, rtDependence) var cmData *message.Message var err error if preToolResponseMsg != nil { cm.ID = preToolResponseMsg.ID cm.CreatedAt = preToolResponseMsg.CreatedAt cm.UpdatedAt = preToolResponseMsg.UpdatedAt if len(toolResponseMsgContent) > 0 { cm.Content = toolResponseMsgContent + "\n" + cm.Content } } cmData, err = crossmessage.DefaultSVC().Create(ctx, cm) if err != nil { return err } sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) return nil } func (c *runImpl) handlerSuggest(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) error { cm := c.buildAgentMessage2Create(ctx, chunk, message.MessageTypeFlowUp, rtDependence) cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) if err != nil { return err } sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) return nil } func (c *runImpl) handlerKnowledge(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) error { cm := c.buildAgentMessage2Create(ctx, chunk, message.MessageTypeKnowledge, rtDependence) cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) if err != nil { return err } sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) return nil } func (c *runImpl) buildKnowledge(_ context.Context, chunk *entity.AgentRespEvent) *msgEntity.VerboseInfo { var recallDatas []msgEntity.RecallDataInfo for _, kOne := range chunk.Knowledge { recallDatas = append(recallDatas, msgEntity.RecallDataInfo{ Slice: kOne.Content, Meta: msgEntity.MetaInfo{ Dataset: msgEntity.DatasetInfo{ ID: kOne.MetaData["dataset_id"].(string), Name: kOne.MetaData["dataset_name"].(string), }, Document: msgEntity.DocumentInfo{ ID: kOne.MetaData["document_id"].(string), Name: kOne.MetaData["document_name"].(string), }, }, Score: kOne.Score(), }) } verboseData := &msgEntity.VerboseData{ Chunks: recallDatas, OriReq: "", StatusCode: 0, } data, err := json.Marshal(verboseData) if err != nil { return nil } knowledgeInfo := &msgEntity.VerboseInfo{ MessageType: string(entity.MessageSubTypeKnowledgeCall), Data: string(data), } return knowledgeInfo } func (c *runImpl) handlerFinalAnswerFinish(ctx context.Context, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) error { cm := c.buildAgentMessage2Create(ctx, nil, message.MessageTypeVerbose, rtDependence) cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) if err != nil { return err } sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) return nil } func (c *runImpl) handlerInterruptVerbose(ctx context.Context, chunk *entity.AgentRespEvent, sw *schema.StreamWriter[*entity.AgentRunResponse], rtDependence *runtimeDependence) error { cm := c.buildAgentMessage2Create(ctx, chunk, message.MessageTypeInterrupt, rtDependence) cmData, err := crossmessage.DefaultSVC().Create(ctx, cm) if err != nil { return err } sendMsg := c.buildSendMsg(ctx, cmData, true, rtDependence) c.runEvent.SendMsgEvent(entity.RunEventMessageCompleted, sendMsg, sw) return nil } func (c *runImpl) buildSendMsg(_ context.Context, msg *msgEntity.Message, isFinish bool, rtDependence *runtimeDependence) *entity.ChunkMessageItem { copyMap := make(map[string]string) for k, v := range msg.Ext { copyMap[k] = v } return &entity.ChunkMessageItem{ ID: msg.ID, ConversationID: msg.ConversationID, SectionID: msg.SectionID, AgentID: msg.AgentID, Content: msg.Content, Role: entity.RoleTypeAssistant, ContentType: msg.ContentType, MessageType: msg.MessageType, ReplyID: rtDependence.questionMsgID, Type: msg.MessageType, CreatedAt: msg.CreatedAt, UpdatedAt: msg.UpdatedAt, RunID: rtDependence.runID, Ext: copyMap, IsFinish: isFinish, ReasoningContent: ptr.Of(msg.ReasoningContent), } } func (c *runImpl) buildSendRunRecord(_ context.Context, runRecord *entity.RunRecordMeta, runStatus entity.RunStatus) *entity.ChunkRunItem { return &entity.ChunkRunItem{ ID: runRecord.ID, ConversationID: runRecord.ConversationID, AgentID: runRecord.AgentID, SectionID: runRecord.SectionID, Status: runStatus, CreatedAt: runRecord.CreatedAt, } } func (c *runImpl) Delete(ctx context.Context, runID []int64) error { return c.RunRecordRepo.Delete(ctx, runID) }