WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
package chatpipeline
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/event"
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
||||
"github.com/hibiken/asynq"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// stubMemoryService returns a fixed recall so the test can assert what the
|
||||
// pipeline does with it, rather than re-testing the memory service.
|
||||
type stubMemoryService struct {
|
||||
interfaces.MemoryService
|
||||
|
||||
recall interfaces.MemoryRecall
|
||||
lastQuery string
|
||||
recallCall int
|
||||
}
|
||||
|
||||
func (s *stubMemoryService) Recall(_ context.Context, query string) interfaces.MemoryRecall {
|
||||
s.recallCall++
|
||||
s.lastQuery = query
|
||||
return s.recall
|
||||
}
|
||||
|
||||
func (s *stubMemoryService) ScheduleExtraction(context.Context, string, string, string) {}
|
||||
|
||||
func (s *stubMemoryService) Handle(context.Context, *asynq.Task) error { return nil }
|
||||
|
||||
func newMemoryRecallPlugin(memoryService interfaces.MemoryService) *PluginMemoryRecall {
|
||||
return NewPluginMemoryRecall(NewEventManager(), memoryService)
|
||||
}
|
||||
|
||||
// TestMemoryReachesTheMessagesSentToTheModel is the assertion that matters:
|
||||
// recalling memory is pointless if it never lands in the request. It walks the
|
||||
// recall stage and then the same message assembly the completion plugins use.
|
||||
func TestMemoryReachesTheMessagesSentToTheModel(t *testing.T) {
|
||||
memoryService := &stubMemoryService{
|
||||
recall: interfaces.MemoryRecall{
|
||||
Prompt: types.WrapMemoryForPrompt("Preferences:\n- 回答请直接给结论", ""),
|
||||
Items: []*types.MemoryItem{
|
||||
{ID: "m1", Kind: types.MemoryKindPreference, Content: "回答请直接给结论"},
|
||||
},
|
||||
},
|
||||
}
|
||||
plugin := newMemoryRecallPlugin(memoryService)
|
||||
|
||||
chatManage := &types.ChatManage{}
|
||||
chatManage.Query = "帮我看看这个报错"
|
||||
chatManage.UserContent = "帮我看看这个报错"
|
||||
chatManage.SummaryConfig.Prompt = "你是一个助手。"
|
||||
|
||||
nextCalled := false
|
||||
err := plugin.OnEvent(t.Context(), types.MEMORY_RECALL, chatManage, func() *PluginError {
|
||||
nextCalled = true
|
||||
return nil
|
||||
})
|
||||
require.Nil(t, err)
|
||||
require.True(t, nextCalled, "the recall stage must never stop the pipeline")
|
||||
require.Equal(t, "帮我看看这个报错", memoryService.lastQuery)
|
||||
|
||||
messages := prepareMessagesWithHistory(chatManage)
|
||||
require.NotEmpty(t, messages)
|
||||
require.Equal(t, "system", messages[0].Role)
|
||||
require.Contains(t, messages[0].Content, "回答请直接给结论",
|
||||
"the recalled memory must be present in the system message")
|
||||
require.Contains(t, messages[0].Content, "<user_memory>")
|
||||
require.True(t, strings.HasPrefix(messages[0].Content, "你是一个助手。"),
|
||||
"memory must be appended after the configured prompt, not replace it")
|
||||
}
|
||||
|
||||
func TestMemoryIsAbsentWhenNothingRecalled(t *testing.T) {
|
||||
plugin := newMemoryRecallPlugin(&stubMemoryService{})
|
||||
|
||||
chatManage := &types.ChatManage{}
|
||||
chatManage.Query = "随便问点什么"
|
||||
chatManage.SummaryConfig.Prompt = "你是一个助手。"
|
||||
|
||||
require.Nil(t, plugin.OnEvent(t.Context(), types.MEMORY_RECALL, chatManage, func() *PluginError { return nil }))
|
||||
require.Empty(t, chatManage.MemoryPrompt)
|
||||
require.Empty(t, chatManage.UsedMemories)
|
||||
|
||||
messages := prepareMessagesWithHistory(chatManage)
|
||||
require.NotContains(t, messages[0].Content, "<user_memory>")
|
||||
}
|
||||
|
||||
func TestMemoryRecallEmitsWhatTheAnswerSaw(t *testing.T) {
|
||||
memoryService := &stubMemoryService{
|
||||
recall: interfaces.MemoryRecall{
|
||||
Prompt: types.WrapMemoryForPrompt("About the user:\n- 在做医疗影像", ""),
|
||||
Items: []*types.MemoryItem{
|
||||
{ID: "m1", Kind: types.MemoryKindProfile, Content: "在做医疗影像"},
|
||||
},
|
||||
},
|
||||
}
|
||||
plugin := newMemoryRecallPlugin(memoryService)
|
||||
|
||||
bus := event.NewEventBus()
|
||||
var received types.UsedMemories
|
||||
bus.On(event.EventMemoryRecalled, func(_ context.Context, evt event.Event) error {
|
||||
data, ok := evt.Data.(event.MemoryRecalledData)
|
||||
require.True(t, ok)
|
||||
received, ok = data.Memories.(types.UsedMemories)
|
||||
require.True(t, ok)
|
||||
return nil
|
||||
})
|
||||
|
||||
chatManage := &types.ChatManage{}
|
||||
chatManage.Query = "继续上次的事"
|
||||
chatManage.EventBus = bus.AsEventBusInterface()
|
||||
|
||||
require.Nil(t, plugin.OnEvent(t.Context(), types.MEMORY_RECALL, chatManage, func() *PluginError { return nil }))
|
||||
|
||||
// The chat UI promises "these are the memories this answer saw", so the
|
||||
// streamed list has to be the same one that was injected.
|
||||
require.Len(t, received, 1)
|
||||
require.Equal(t, "m1", received[0].ID)
|
||||
require.Equal(t, "在做医疗影像", received[0].Content)
|
||||
require.Equal(t, received, chatManage.UsedMemories)
|
||||
}
|
||||
|
||||
func TestMemoryRecallToleratesNoService(t *testing.T) {
|
||||
plugin := newMemoryRecallPlugin(nil)
|
||||
chatManage := &types.ChatManage{}
|
||||
chatManage.SummaryConfig.Prompt = "你是一个助手。"
|
||||
require.Nil(t, plugin.OnEvent(t.Context(), types.MEMORY_RECALL, chatManage, func() *PluginError { return nil }))
|
||||
require.Empty(t, chatManage.MemoryPrompt)
|
||||
}
|
||||
|
||||
func TestMemoryRecallStageIsRegisteredInThePipeline(t *testing.T) {
|
||||
// A stage nobody runs is the failure mode this whole feature has had
|
||||
// before, so assert the plugin declares the event the assembler adds.
|
||||
plugin := newMemoryRecallPlugin(&stubMemoryService{})
|
||||
require.Equal(t, []types.EventType{types.MEMORY_RECALL}, plugin.ActivationEvents())
|
||||
}
|
||||
Reference in New Issue
Block a user