Skip to content

Commit cf34a8f

Browse files
committed
fix(agents): append KB sources in local chats
Apply the shared KB citation post-processing to standalone LocalAGI chat responses so the React agent chat receives the same clickable Sources block as the native executor path. Also fix the run target to use the current cmd/local-ai entrypoint. Assisted-by: Codex:gpt-5
1 parent ec3d92b commit cf34a8f

2 files changed

Lines changed: 76 additions & 2 deletions

File tree

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ osx-signed: build
180180

181181
## Run
182182
run: ## run local-ai
183-
CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOCMD) run ./
183+
CGO_LDFLAGS="$(CGO_LDFLAGS)" $(GOCMD) run ./cmd/local-ai
184184

185185
prepare-test: protogen-go build-mock-backend
186186

core/services/agentpool/agent_pool.go

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,10 +466,11 @@ func (s *AgentPoolService) Chat(name, message string) (string, error) {
466466
s.collectAndCopyMetadata(metadata, chatUserID)
467467
}
468468

469+
content := s.appendLocalAGIKBCitations(response.Response, name, message, response.State)
469470
msg := map[string]any{
470471
"id": messageID + "-agent",
471472
"sender": "agent",
472-
"content": response.Response,
473+
"content": content,
473474
"timestamp": time.Now().Format(time.RFC3339),
474475
}
475476
if len(metadata) > 0 {
@@ -489,6 +490,79 @@ func (s *AgentPoolService) Chat(name, message string) (string, error) {
489490
return messageID, nil
490491
}
491492

493+
func (s *AgentPoolService) appendLocalAGIKBCitations(response, agentKey, message string, states []coreTypes.ActionState) string {
494+
if strings.TrimSpace(response) == "" {
495+
return response
496+
}
497+
498+
userID, collection := splitAgentKey(agentKey)
499+
cfg := s.localAGI.pool.GetConfig(agentKey)
500+
if cfg == nil || !cfg.EnableKnowledgeBase {
501+
return response
502+
}
503+
504+
citations := kbCitationsFromActionStates(states)
505+
if len(citations) == 0 && cfg.KBAutoSearch {
506+
maxResults := cfg.KnowledgeBaseResults
507+
if maxResults <= 0 {
508+
maxResults = 5
509+
}
510+
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
511+
defer cancel()
512+
kbResult := agents.KBAutoSearchPrompt(ctx, s.apiURL, s.apiKey, collection, message, maxResults, userID)
513+
citations = kbResult.Citations
514+
}
515+
516+
return agents.AppendKBCitations(response, collection, userID, citations)
517+
}
518+
519+
func splitAgentKey(agentKey string) (userID, name string) {
520+
if uid, n, ok := strings.Cut(agentKey, ":"); ok {
521+
return uid, n
522+
}
523+
return "", agentKey
524+
}
525+
526+
func kbCitationsFromActionStates(states []coreTypes.ActionState) []agents.KBCitation {
527+
var citations []agents.KBCitation
528+
for _, state := range states {
529+
citations = append(citations, kbCitationsFromMetadata(state.Metadata)...)
530+
}
531+
return citations
532+
}
533+
534+
func kbCitationsFromMetadata(metadata map[string]any) []agents.KBCitation {
535+
if len(metadata) == 0 {
536+
return nil
537+
}
538+
539+
fileName := metadata["file_name"]
540+
source := metadata["source"]
541+
if fileName == nil && source == nil {
542+
return nil
543+
}
544+
545+
citation := agents.KBCitation{
546+
FileName: metadataString(fileName),
547+
EntryKey: metadataString(source),
548+
}
549+
if citation.FileName == "" && citation.EntryKey == "" {
550+
return nil
551+
}
552+
return []agents.KBCitation{citation}
553+
}
554+
555+
func metadataString(value any) string {
556+
switch v := value.(type) {
557+
case string:
558+
return v
559+
case fmt.Stringer:
560+
return v.String()
561+
default:
562+
return ""
563+
}
564+
}
565+
492566
// userOutputsDir returns the per-user outputs directory, creating it if needed.
493567
// If userID is empty, falls back to the shared outputs directory.
494568
func (s *AgentPoolService) userOutputsDir(userID string) string {

0 commit comments

Comments
 (0)