From 3e98e9379c883f94f4d5a99d3fb456d493ce827b Mon Sep 17 00:00:00 2001 From: mlogclub Date: Sun, 19 Apr 2026 11:38:05 +0800 Subject: [PATCH] refactor(runtime): update function signatures to use value receivers for models --- internal/ai/rag/index.go | 7 +- internal/ai/rag/index_document_helpers.go | 6 +- internal/ai/rag/index_faq_helpers.go | 4 +- internal/ai/rag/index_flow.go | 32 ++++---- .../ai/rag/index_retrieve_helpers_test.go | 6 +- internal/ai/rag/index_run_helpers.go | 4 +- internal/ai/runtime/reply_context.go | 21 +++++ .../ai/runtime/reply_interrupt_service.go | 78 +++++++++---------- internal/ai/runtime/reply_trigger_service.go | 44 ++++++----- 9 files changed, 107 insertions(+), 95 deletions(-) create mode 100644 internal/ai/runtime/reply_context.go diff --git a/internal/ai/rag/index.go b/internal/ai/rag/index.go index af67a6e..e65eb4b 100644 --- a/internal/ai/rag/index.go +++ b/internal/ai/rag/index.go @@ -54,7 +54,7 @@ func (s *index) IndexDocumentByID(ctx context.Context, documentID int64) error { return s.IndexDocument(ctx, document) } -func (s *index) IndexDocument(ctx context.Context, document *models.KnowledgeDocument) error { +func (s *index) IndexDocument(ctx context.Context, document models.KnowledgeDocument) error { start := time.Now() if err := s.markDocumentIndexPending(document.ID); err != nil { slog.Error("Failed to mark knowledge document index as pending", "document_id", document.ID, "error", err) @@ -302,10 +302,7 @@ func (s *index) RebuildKnowledgeBaseIndex(ctx context.Context, knowledgeBaseID i return nil } -func buildFAQChunkContent(faq *models.KnowledgeFAQ) string { - if faq == nil { - return "" - } +func buildFAQChunkContent(faq models.KnowledgeFAQ) string { parts := []string{fmt.Sprintf("问题:%s", faq.Question)} var similarQuestions []string if faq.SimilarQuestions != "" { diff --git a/internal/ai/rag/index_document_helpers.go b/internal/ai/rag/index_document_helpers.go index 3d5b2f9..029a0be 100644 --- a/internal/ai/rag/index_document_helpers.go +++ b/internal/ai/rag/index_document_helpers.go @@ -15,7 +15,7 @@ import ( "github.com/mlogclub/simple/common/strs" ) -func (s *index) buildDocumentChunkRequest(document *models.KnowledgeDocument, knowledgeBase *models.KnowledgeBase) *ragchunk.ChunkRequest { +func (s *index) buildDocumentChunkRequest(document models.KnowledgeDocument, knowledgeBase models.KnowledgeBase) *ragchunk.ChunkRequest { return &ragchunk.ChunkRequest{ KnowledgeBaseID: document.KnowledgeBaseID, DocumentID: document.ID, @@ -33,7 +33,7 @@ func (s *index) buildDocumentChunkRequest(document *models.KnowledgeDocument, kn } } -func (s *index) buildDocumentChunks(ctx context.Context, document *models.KnowledgeDocument, knowledgeBase *models.KnowledgeBase) ([]ragchunk.ChunkResult, error) { +func (s *index) buildDocumentChunks(ctx context.Context, document models.KnowledgeDocument, knowledgeBase models.KnowledgeBase) ([]ragchunk.ChunkResult, error) { chunks, err := s.registry.Chunk(ctx, s.buildDocumentChunkRequest(document, knowledgeBase)) if err != nil { return nil, fmt.Errorf("failed to chunk document: %w", err) @@ -54,7 +54,7 @@ func collectExistingVectorIDs(chunks []models.KnowledgeChunk) []string { return ret } -func (s *index) prepareDocumentVectors(ctx context.Context, knowledgeBase *models.KnowledgeBase, document *models.KnowledgeDocument, chunks []ragchunk.ChunkResult) ([]vectordb.Vector, []models.KnowledgeChunk, int, error) { +func (s *index) prepareDocumentVectors(ctx context.Context, knowledgeBase models.KnowledgeBase, document models.KnowledgeDocument, chunks []ragchunk.ChunkResult) ([]vectordb.Vector, []models.KnowledgeChunk, int, error) { vectors := make([]vectordb.Vector, 0, len(chunks)) chunkModels := make([]models.KnowledgeChunk, 0, len(chunks)) dimension := 0 diff --git a/internal/ai/rag/index_faq_helpers.go b/internal/ai/rag/index_faq_helpers.go index 6f2a369..90fe7cd 100644 --- a/internal/ai/rag/index_faq_helpers.go +++ b/internal/ai/rag/index_faq_helpers.go @@ -11,7 +11,7 @@ import ( "cs-agent/internal/pkg/enums" ) -func buildFAQChunkModel(knowledgeBase *models.KnowledgeBase, faq *models.KnowledgeFAQ, content string) (models.KnowledgeChunk, string) { +func buildFAQChunkModel(knowledgeBase models.KnowledgeBase, faq models.KnowledgeFAQ, content string) (models.KnowledgeChunk, string) { chunkID := buildKnowledgeFAQChunkVectorID(knowledgeBase.ID, faq.ID, 0) now := time.Now() return models.KnowledgeChunk{ @@ -32,7 +32,7 @@ func buildFAQChunkModel(knowledgeBase *models.KnowledgeBase, faq *models.Knowled }, chunkID } -func (s *index) prepareFAQVector(ctx context.Context, knowledgeBase *models.KnowledgeBase, faq *models.KnowledgeFAQ, content string) (vectordb.Vector, models.KnowledgeChunk, int, error) { +func (s *index) prepareFAQVector(ctx context.Context, knowledgeBase models.KnowledgeBase, faq models.KnowledgeFAQ, content string) (vectordb.Vector, models.KnowledgeChunk, int, error) { embeddingResult, err := ai.Embedding.GenerateEmbedding(ctx, content) if err != nil { return vectordb.Vector{}, models.KnowledgeChunk{}, 0, fmt.Errorf("failed to generate embedding for faq %d: %w", faq.ID, err) diff --git a/internal/ai/rag/index_flow.go b/internal/ai/rag/index_flow.go index 26b3b20..05641dd 100644 --- a/internal/ai/rag/index_flow.go +++ b/internal/ai/rag/index_flow.go @@ -10,43 +10,37 @@ import ( "github.com/mlogclub/simple/sqls" ) -func (s *index) loadDocumentByID(documentID int64) (*models.KnowledgeDocument, error) { +func (s *index) loadDocumentByID(documentID int64) (models.KnowledgeDocument, error) { document := repositories.KnowledgeDocumentRepository.Get(sqls.DB(), documentID) if document == nil { - return nil, fmt.Errorf("document not found: %d", documentID) + return models.KnowledgeDocument{}, fmt.Errorf("document not found: %d", documentID) } - return document, nil + return *document, nil } -func (s *index) loadFAQByID(faqID int64) (*models.KnowledgeFAQ, error) { +func (s *index) loadFAQByID(faqID int64) (models.KnowledgeFAQ, error) { faq := repositories.KnowledgeFAQRepository.Get(sqls.DB(), faqID) if faq == nil { - return nil, fmt.Errorf("faq not found: %d", faqID) + return models.KnowledgeFAQ{}, fmt.Errorf("faq not found: %d", faqID) } - return faq, nil + return *faq, nil } -func (s *index) loadDocumentKnowledgeBase(document *models.KnowledgeDocument) (*models.KnowledgeBase, error) { - if document == nil { - return nil, fmt.Errorf("document is nil") - } +func (s *index) loadDocumentKnowledgeBase(document models.KnowledgeDocument) (models.KnowledgeBase, error) { knowledgeBase := repositories.KnowledgeBaseRepository.Get(sqls.DB(), document.KnowledgeBaseID) if knowledgeBase == nil { - return nil, fmt.Errorf("knowledge base not found: %d", document.KnowledgeBaseID) + return models.KnowledgeBase{}, fmt.Errorf("knowledge base not found: %d", document.KnowledgeBaseID) } - return knowledgeBase, nil + return *knowledgeBase, nil } -func (s *index) loadFAQKnowledgeBase(faq *models.KnowledgeFAQ) (*models.KnowledgeBase, error) { - if faq == nil { - return nil, fmt.Errorf("faq is nil") - } +func (s *index) loadFAQKnowledgeBase(faq models.KnowledgeFAQ) (models.KnowledgeBase, error) { knowledgeBase := repositories.KnowledgeBaseRepository.Get(sqls.DB(), faq.KnowledgeBaseID) if knowledgeBase == nil { - return nil, fmt.Errorf("knowledge base not found: %d", faq.KnowledgeBaseID) + return models.KnowledgeBase{}, fmt.Errorf("knowledge base not found: %d", faq.KnowledgeBaseID) } if knowledgeBase.KnowledgeType != string(enums.KnowledgeBaseTypeFAQ) { - return nil, fmt.Errorf("knowledge base %d is not faq type", knowledgeBase.ID) + return models.KnowledgeBase{}, fmt.Errorf("knowledge base %d is not faq type", knowledgeBase.ID) } - return knowledgeBase, nil + return *knowledgeBase, nil } diff --git a/internal/ai/rag/index_retrieve_helpers_test.go b/internal/ai/rag/index_retrieve_helpers_test.go index bd2bbe5..4685694 100644 --- a/internal/ai/rag/index_retrieve_helpers_test.go +++ b/internal/ai/rag/index_retrieve_helpers_test.go @@ -8,7 +8,7 @@ import ( ) func TestBuildFAQChunkContent(t *testing.T) { - faq := &models.KnowledgeFAQ{ + faq := models.KnowledgeFAQ{ Question: "如何退款", SimilarQuestions: `["退款怎么申请","申请售后"]`, Answer: "在订单页发起退款。", @@ -27,8 +27,8 @@ func TestBuildFAQChunkContent(t *testing.T) { } func TestBuildFAQChunkModel(t *testing.T) { - knowledgeBase := &models.KnowledgeBase{ID: 11} - faq := &models.KnowledgeFAQ{ID: 22, Question: "如何退款"} + knowledgeBase := models.KnowledgeBase{ID: 11} + faq := models.KnowledgeFAQ{ID: 22, Question: "如何退款"} chunk, chunkID := buildFAQChunkModel(knowledgeBase, faq, "问题:如何退款\n回答:在订单页发起退款。") if chunkID == "" { diff --git a/internal/ai/rag/index_run_helpers.go b/internal/ai/rag/index_run_helpers.go index b2b99a8..6b35b48 100644 --- a/internal/ai/rag/index_run_helpers.go +++ b/internal/ai/rag/index_run_helpers.go @@ -13,7 +13,7 @@ import ( "github.com/mlogclub/simple/sqls" ) -func (s *index) runDocumentIndex(ctx context.Context, document *models.KnowledgeDocument, knowledgeBase *models.KnowledgeBase) ([]vectordb.Vector, int, error) { +func (s *index) runDocumentIndex(ctx context.Context, document models.KnowledgeDocument, knowledgeBase models.KnowledgeBase) ([]vectordb.Vector, int, error) { existingChunks := repositories.KnowledgeChunkRepository.FindByDocumentID(sqls.DB(), document.ID) chunks, err := s.buildDocumentChunks(ctx, document, knowledgeBase) if err != nil { @@ -51,7 +51,7 @@ func (s *index) runDocumentIndex(ctx context.Context, document *models.Knowledge return vectors, len(chunks), nil } -func (s *index) runFAQIndex(ctx context.Context, faq *models.KnowledgeFAQ, knowledgeBase *models.KnowledgeBase) error { +func (s *index) runFAQIndex(ctx context.Context, faq models.KnowledgeFAQ, knowledgeBase models.KnowledgeBase) error { existingChunks := repositories.KnowledgeChunkRepository.FindByFaqID(sqls.DB(), faq.ID) content := buildFAQChunkContent(faq) if content == "" { diff --git a/internal/ai/runtime/reply_context.go b/internal/ai/runtime/reply_context.go new file mode 100644 index 0000000..1b6af43 --- /dev/null +++ b/internal/ai/runtime/reply_context.go @@ -0,0 +1,21 @@ +package runtime + +import ( + applicationruntime "cs-agent/internal/ai/application/runtime" + "cs-agent/internal/models" +) + +type aiReplyContext struct { + Conversation models.Conversation + Message models.Message + AIAgent models.AIAgent + Trace *aiReplyTraceData + SummaryRef **applicationruntime.Summary + PendingInterrupt *models.ConversationInterrupt +} + +func (c aiReplyContext) setSummary(summary *applicationruntime.Summary) { + if c.SummaryRef != nil { + *c.SummaryRef = summary + } +} diff --git a/internal/ai/runtime/reply_interrupt_service.go b/internal/ai/runtime/reply_interrupt_service.go index 48543e8..99073be 100644 --- a/internal/ai/runtime/reply_interrupt_service.go +++ b/internal/ai/runtime/reply_interrupt_service.go @@ -6,7 +6,6 @@ import ( applicationruntime "cs-agent/internal/ai/application/runtime" "cs-agent/internal/ai/runtime/graphs" - "cs-agent/internal/models" svc "cs-agent/internal/services" ) @@ -16,31 +15,30 @@ func newReplyInterruptService() *replyInterruptService { return &replyInterruptService{} } -func (s *replyInterruptService) ResumePendingInterrupt(ctx context.Context, owner *aiReplyService, conversation models.Conversation, message models.Message, aiAgent models.AIAgent, - pendingInterrupt *models.ConversationInterrupt, trace *aiReplyTraceData, summaryRef **applicationruntime.Summary) error { - if pendingInterrupt == nil || owner == nil { +func (s *replyInterruptService) ResumePendingInterrupt(ctx context.Context, owner *aiReplyService, replyCtx aiReplyContext) error { + if replyCtx.PendingInterrupt == nil || owner == nil { return nil } summary, err := owner.executor.ResumePendingInterrupt(ctx, runtimeReplyResumeInput{ - Conversation: conversation, - Message: message, - AIAgent: aiAgent, - PendingInterrupt: pendingInterrupt, - Trace: trace, + Conversation: replyCtx.Conversation, + Message: replyCtx.Message, + AIAgent: replyCtx.AIAgent, + PendingInterrupt: replyCtx.PendingInterrupt, + Trace: replyCtx.Trace, }) - *summaryRef = summary + replyCtx.setSummary(summary) if err != nil { if isCheckpointMissingError(err) { summary = expiredInterruptSummary() - *summaryRef = summary - trace.Status = "interrupt_expired" - trace.FinalAction = "expired" + replyCtx.setSummary(summary) + replyCtx.Trace.Status = "interrupt_expired" + replyCtx.Trace.FinalAction = "expired" replyMessage, expireErr := owner.commit.CommitAIReply(replyCommitInput{ - Conversation: conversation, - Message: message, - AIAgent: aiAgent, + Conversation: replyCtx.Conversation, + Message: replyCtx.Message, + AIAgent: replyCtx.AIAgent, ReplyText: summary.ReplyText, - Trace: trace, + Trace: replyCtx.Trace, ClientPrefix: "ai_interrupt_expired", }) if expireErr != nil { @@ -50,7 +48,7 @@ func (s *replyInterruptService) ResumePendingInterrupt(ctx context.Context, owne if replyMessage != nil { lastResumeMessageID = replyMessage.ID } - if expireMarkErr := svc.ConversationInterruptService.MarkExpired(pendingInterrupt.ID, lastResumeMessageID); expireMarkErr != nil { + if expireMarkErr := svc.ConversationInterruptService.MarkExpired(replyCtx.PendingInterrupt.ID, lastResumeMessageID); expireMarkErr != nil { return expireMarkErr } return nil @@ -58,15 +56,15 @@ func (s *replyInterruptService) ResumePendingInterrupt(ctx context.Context, owne return err } if summary != nil && summary.Interrupted { - return s.HandleInterruptedResume(owner, conversation, message, aiAgent, pendingInterrupt, summary, trace) + return s.HandleInterruptedResume(owner, replyCtx, summary) } if summary != nil && strings.TrimSpace(summary.ReplyText) != "" { replyMessage, err := owner.commit.CommitAIReply(replyCommitInput{ - Conversation: conversation, - Message: message, - AIAgent: aiAgent, + Conversation: replyCtx.Conversation, + Message: replyCtx.Message, + AIAgent: replyCtx.AIAgent, ReplyText: summary.ReplyText, - Trace: trace, + Trace: replyCtx.Trace, ClientPrefix: "ai_resume", }) if err != nil { @@ -77,30 +75,29 @@ func (s *replyInterruptService) ResumePendingInterrupt(ctx context.Context, owne replyMessageID = replyMessage.ID } if graphs.IsCancellationReply(summary.ReplyText) { - return svc.ConversationInterruptService.MarkCancelled(pendingInterrupt.ID, replyMessageID) + return svc.ConversationInterruptService.MarkCancelled(replyCtx.PendingInterrupt.ID, replyMessageID) } - return svc.ConversationInterruptService.MarkResolved(pendingInterrupt.ID, replyMessageID) + return svc.ConversationInterruptService.MarkResolved(replyCtx.PendingInterrupt.ID, replyMessageID) } - return svc.ConversationInterruptService.MarkResolved(pendingInterrupt.ID, 0) + return svc.ConversationInterruptService.MarkResolved(replyCtx.PendingInterrupt.ID, 0) } -func (s *replyInterruptService) HandleInterruptedSummary(owner *aiReplyService, conversation models.Conversation, message models.Message, aiAgent models.AIAgent, - summary *applicationruntime.Summary, trace *aiReplyTraceData) error { +func (s *replyInterruptService) HandleInterruptedSummary(owner *aiReplyService, replyCtx aiReplyContext, summary *applicationruntime.Summary) error { if owner == nil { return nil } - pending := buildConversationInterrupt(conversation, message, aiAgent, summary) + pending := buildConversationInterrupt(replyCtx.Conversation, replyCtx.Message, replyCtx.AIAgent, summary) if err := svc.ConversationInterruptService.CreateOrUpdatePending(pending); err != nil { return err } pending = svc.ConversationInterruptService.GetByCheckPointID(summary.CheckPointID) replyText := resolveInterruptPrompt(summary) replyMessage, err := owner.commit.CommitAIReply(replyCommitInput{ - Conversation: conversation, - Message: message, - AIAgent: aiAgent, + Conversation: replyCtx.Conversation, + Message: replyCtx.Message, + AIAgent: replyCtx.AIAgent, ReplyText: replyText, - Trace: trace, + Trace: replyCtx.Trace, ClientPrefix: "ai_interrupt", }) if err != nil { @@ -112,25 +109,24 @@ func (s *replyInterruptService) HandleInterruptedSummary(owner *aiReplyService, return nil } -func (s *replyInterruptService) HandleInterruptedResume(owner *aiReplyService, conversation models.Conversation, message models.Message, aiAgent models.AIAgent, - pendingInterrupt *models.ConversationInterrupt, summary *applicationruntime.Summary, trace *aiReplyTraceData) error { - if pendingInterrupt == nil || owner == nil { +func (s *replyInterruptService) HandleInterruptedResume(owner *aiReplyService, replyCtx aiReplyContext, summary *applicationruntime.Summary) error { + if replyCtx.PendingInterrupt == nil || owner == nil { return nil } replyText := resolveInterruptPrompt(summary) replyMessage, err := owner.commit.CommitAIReply(replyCommitInput{ - Conversation: conversation, - Message: message, - AIAgent: aiAgent, + Conversation: replyCtx.Conversation, + Message: replyCtx.Message, + AIAgent: replyCtx.AIAgent, ReplyText: replyText, - Trace: trace, + Trace: replyCtx.Trace, ClientPrefix: "ai_interrupt_resume", }) if err != nil { return err } if replyMessage != nil { - return svc.ConversationInterruptService.MarkPendingAgain(pendingInterrupt.ID, firstInterruptID(summary), replyText, replyMessage.ID) + return svc.ConversationInterruptService.MarkPendingAgain(replyCtx.PendingInterrupt.ID, firstInterruptID(summary), replyText, replyMessage.ID) } return nil } diff --git a/internal/ai/runtime/reply_trigger_service.go b/internal/ai/runtime/reply_trigger_service.go index 9c92286..cf57a21 100644 --- a/internal/ai/runtime/reply_trigger_service.go +++ b/internal/ai/runtime/reply_trigger_service.go @@ -46,6 +46,13 @@ func (s *aiReplyService) TriggerReply(ctx context.Context, conversation models.C startedAt := time.Now() trace := &aiReplyTraceData{Status: "started"} var summary *applicationruntime.Summary + replyCtx := aiReplyContext{ + Conversation: conversation, + Message: message, + AIAgent: aiAgent, + Trace: trace, + SummaryRef: &summary, + } if err := ctx.Err(); err != nil { return err } @@ -65,46 +72,43 @@ func (s *aiReplyService) TriggerReply(ctx context.Context, conversation models.C }) }() if pendingInterrupt := svc.ConversationInterruptService.FindLatestPendingByConversationID(conversation.ID); pendingInterrupt != nil { - return s.resumePendingInterrupt(ctx, conversation, message, aiAgent, pendingInterrupt, trace, &summary) + replyCtx.PendingInterrupt = pendingInterrupt + return s.resumePendingInterrupt(ctx, replyCtx) } - return s.executeReply(ctx, conversation, message, aiAgent, trace, &summary) + return s.executeReply(ctx, replyCtx) } -func (s *aiReplyService) resumePendingInterrupt(ctx context.Context, conversation models.Conversation, message models.Message, aiAgent models.AIAgent, - pendingInterrupt *models.ConversationInterrupt, trace *aiReplyTraceData, summaryRef **applicationruntime.Summary) error { - return s.interrupts.ResumePendingInterrupt(ctx, s, conversation, message, aiAgent, pendingInterrupt, trace, summaryRef) +func (s *aiReplyService) resumePendingInterrupt(ctx context.Context, replyCtx aiReplyContext) error { + return s.interrupts.ResumePendingInterrupt(ctx, s, replyCtx) } -func (s *aiReplyService) executeReply(ctx context.Context, conversation models.Conversation, message models.Message, aiAgent models.AIAgent, - trace *aiReplyTraceData, summaryRef **applicationruntime.Summary) error { +func (s *aiReplyService) executeReply(ctx context.Context, replyCtx aiReplyContext) error { summary, err := s.executor.Run(ctx, runtimeReplyRunInput{ - Conversation: conversation, - Message: message, - AIAgent: aiAgent, - Trace: trace, + Conversation: replyCtx.Conversation, + Message: replyCtx.Message, + AIAgent: replyCtx.AIAgent, + Trace: replyCtx.Trace, }) - if summaryRef != nil { - *summaryRef = summary - } + replyCtx.setSummary(summary) if err != nil { return err } if summary != nil && summary.Interrupted { - return s.interrupts.HandleInterruptedSummary(s, conversation, message, aiAgent, summary, trace) + return s.interrupts.HandleInterruptedSummary(s, replyCtx, summary) } if summary != nil && strings.TrimSpace(summary.ReplyText) != "" { replyMessage, err := s.commit.CommitAIReply(replyCommitInput{ - Conversation: conversation, - Message: message, - AIAgent: aiAgent, + Conversation: replyCtx.Conversation, + Message: replyCtx.Message, + AIAgent: replyCtx.AIAgent, ReplyText: summary.ReplyText, - Trace: trace, + Trace: replyCtx.Trace, ClientPrefix: "ai_reply", }) if err != nil { return err } - trace.ReplySent = replyMessage != nil + replyCtx.Trace.ReplySent = replyMessage != nil } return nil }