feat: add support for request ID tracking across services
- Implemented request ID handling in various services and handlers to improve traceability of requests. - Added new AuthOptions endpoint to expose WxWork and OIDC configuration options. - Updated message and event logging to include request ID for better debugging. - Enhanced login form to dynamically show available authentication options based on server configuration. - Introduced utility functions for normalizing and ensuring valid request IDs. - Updated tests to verify request ID functionality in message sending and event logging.
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"cs-agent/internal/ai/runtime/tooling"
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/tracex"
|
||||
"cs-agent/internal/services"
|
||||
|
||||
componenttool "github.com/cloudwego/eino/components/tool"
|
||||
@@ -51,7 +52,8 @@ func (g *HandoffGraph) Run(ctx context.Context, argumentsInJSON string) (string,
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
handled, err := services.ConversationService.TryOffHoursHandoffByAI(g.conversation.ID, g.aiAgent, reason)
|
||||
requestID := tracex.RequestIDFromContext(ctx)
|
||||
handled, err := services.ConversationService.TryOffHoursHandoffByAIWithRequestID(g.conversation.ID, g.aiAgent, reason, requestID)
|
||||
if err != nil || handled {
|
||||
if handled && err == nil {
|
||||
return tooling.MarshalToolResult(tooling.ToolResult{
|
||||
@@ -91,7 +93,7 @@ func (g *HandoffGraph) Run(ctx context.Context, argumentsInJSON string) (string,
|
||||
}
|
||||
switch parseHandoffDecision(resumeText) {
|
||||
case ConfirmationDecisionConfirm:
|
||||
if err := services.ConversationService.HandoffByAI(g.conversation.ID, g.aiAgent, state.Reason); err != nil {
|
||||
if err := services.ConversationService.HandoffByAIWithRequestID(g.conversation.ID, g.aiAgent, state.Reason, tracex.RequestIDFromContext(ctx)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// ConversationService sends the customer-visible handoff notice according to the dispatch decision.
|
||||
|
||||
@@ -36,7 +36,7 @@ func (s *replyCommitService) SendAIReply(input replyCommitInput) (*models.Messag
|
||||
return nil, nil
|
||||
}
|
||||
commitStartedAt := time.Now()
|
||||
replyMessage, err := svc.MessageService.SendAIMessage(
|
||||
replyMessage, err := svc.MessageService.SendAIMessageWithRequestID(
|
||||
input.Conversation.ID,
|
||||
input.AIAgent.ID,
|
||||
fmt.Sprintf("%s_%d", strings.TrimSpace(input.ClientPrefix), input.Message.ID),
|
||||
@@ -44,6 +44,7 @@ func (s *replyCommitService) SendAIReply(input replyCommitInput) (*models.Messag
|
||||
replyText,
|
||||
"",
|
||||
s.buildAIPrincipal(input.AIAgent),
|
||||
input.Message.RequestID,
|
||||
)
|
||||
if input.Trace != nil {
|
||||
input.Trace.CommitMs = time.Since(commitStartedAt).Milliseconds()
|
||||
|
||||
@@ -41,6 +41,7 @@ func (s *replyRunLogService) Write(input replyRunLogInput) {
|
||||
logItem := &models.AgentRunLog{
|
||||
ConversationID: input.Conversation.ID,
|
||||
MessageID: input.Message.ID,
|
||||
RequestID: input.Message.RequestID,
|
||||
AIAgentID: input.AIAgent.ID,
|
||||
AIConfigID: input.AIAgent.AIConfigID,
|
||||
UserMessage: strings.TrimSpace(input.Question),
|
||||
@@ -66,6 +67,7 @@ func (s *replyRunLogService) Write(input replyRunLogInput) {
|
||||
}
|
||||
if err := svc.AgentRunLogService.Create(logItem); err != nil {
|
||||
slog.Warn("create agent run log failed",
|
||||
"requestId", input.Message.RequestID,
|
||||
"message_id", input.Message.ID,
|
||||
"conversation_id", logItem.ConversationID,
|
||||
"ai_agent_id", input.AIAgent.ID,
|
||||
@@ -245,6 +247,9 @@ func extractGraphToolTrace(summary *applicationruntime.Summary) string {
|
||||
}
|
||||
|
||||
func firstToolSearchTargetToolCode(summary *applicationruntime.Summary) string {
|
||||
if summary == nil {
|
||||
return ""
|
||||
}
|
||||
trace := parseRuntimeTraceData(summary.TraceData)
|
||||
for _, item := range trace.ToolSearch.Items {
|
||||
toolCode := strings.TrimSpace(item.TargetToolCode)
|
||||
@@ -262,6 +267,9 @@ func firstToolSearchTargetToolCode(summary *applicationruntime.Summary) string {
|
||||
}
|
||||
|
||||
func firstGraphToolCode(summary *applicationruntime.Summary) string {
|
||||
if summary == nil {
|
||||
return ""
|
||||
}
|
||||
trace := parseRuntimeTraceData(summary.TraceData)
|
||||
for _, item := range trace.GraphTools.Items {
|
||||
toolCode := strings.TrimSpace(item.ToolCode)
|
||||
@@ -273,6 +281,9 @@ func firstGraphToolCode(summary *applicationruntime.Summary) string {
|
||||
}
|
||||
|
||||
func extractHandoffReason(summary *applicationruntime.Summary) string {
|
||||
if summary == nil {
|
||||
return ""
|
||||
}
|
||||
trace := parseRuntimeTraceData(summary.TraceData)
|
||||
for _, item := range trace.GraphTools.Items {
|
||||
if strings.TrimSpace(item.ToolCode) != toolx.GraphHandoffConversation.Code {
|
||||
@@ -291,6 +302,9 @@ func extractHandoffReason(summary *applicationruntime.Summary) string {
|
||||
}
|
||||
|
||||
func graphPlanReason(summary *applicationruntime.Summary) string {
|
||||
if summary == nil {
|
||||
return ""
|
||||
}
|
||||
trace := parseRuntimeTraceData(summary.TraceData)
|
||||
for _, item := range trace.GraphTools.Items {
|
||||
toolCode := strings.TrimSpace(item.ToolCode)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package runtime
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/enums"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/mlogclub/simple/sqls"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
)
|
||||
|
||||
func TestReplyRunLogStoresRequestID(t *testing.T) {
|
||||
dbName := "reply_runlog_trace_test_" + strings.NewReplacer("/", "_").Replace(t.Name())
|
||||
db, err := gorm.Open(sqlite.Open("file:"+dbName+"?mode=memory&cache=shared"), &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: "t_",
|
||||
SingularTable: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("open sqlite db: %v", err)
|
||||
}
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
t.Fatalf("get sqlite db: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if err := sqlDB.Close(); err != nil {
|
||||
t.Fatalf("close sqlite db: %v", err)
|
||||
}
|
||||
})
|
||||
if err := db.AutoMigrate(&models.AgentRunLog{}); err != nil {
|
||||
t.Fatalf("auto migrate: %v", err)
|
||||
}
|
||||
sqls.SetDB(db)
|
||||
|
||||
newReplyRunLogService().Write(replyRunLogInput{
|
||||
StartedAt: time.Now(),
|
||||
Message: models.Message{ID: 22, RequestID: "trace-123", SenderType: enums.IMSenderTypeCustomer, Content: "hello"},
|
||||
Conversation: models.Conversation{ID: 11},
|
||||
AIAgent: models.AIAgent{ID: 33, AIConfigID: 44},
|
||||
Question: "hello",
|
||||
})
|
||||
|
||||
var item models.AgentRunLog
|
||||
if err := db.First(&item).Error; err != nil {
|
||||
t.Fatalf("find run log: %v", err)
|
||||
}
|
||||
if item.RequestID != "trace-123" {
|
||||
t.Fatalf("RequestID=%q want %q", item.RequestID, "trace-123")
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
applicationruntime "cs-agent/internal/ai/application/runtime"
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/enums"
|
||||
"cs-agent/internal/pkg/tracex"
|
||||
svc "cs-agent/internal/services"
|
||||
)
|
||||
|
||||
@@ -30,10 +31,11 @@ func (s *aiReplyService) TriggerReplyAsync(conversation models.Conversation, mes
|
||||
}
|
||||
startedAt := time.Now()
|
||||
timeout := s.resolveReplyTimeout(*aiAgent)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
ctx, cancel := context.WithTimeout(tracex.ContextWithRequestID(context.Background(), message.RequestID), timeout)
|
||||
defer cancel()
|
||||
if err := s.TriggerReply(ctx, conversation, message, *aiAgent); err != nil {
|
||||
slog.Error("failed to trigger ai reply",
|
||||
"requestId", message.RequestID,
|
||||
"message_id", message.ID,
|
||||
"timeout_ms", timeout.Milliseconds(),
|
||||
"elapsed_ms", time.Since(startedAt).Milliseconds(),
|
||||
|
||||
Reference in New Issue
Block a user