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,193 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// The reason search exists at all: recall is ranked once, against the question
|
||||
// the user opened with. An agent that works its way from that question to a
|
||||
// different sub-problem is holding memories chosen for a query it has left
|
||||
// behind, and nothing in the turn's budget can fix that.
|
||||
func TestSearchFindsWhatTheOpeningQuestionDidNotMatch(t *testing.T) {
|
||||
svc, _, tenantRepo := newMemoryHarness(t)
|
||||
ctx := enabledCtx(t, tenantRepo, 1, "alice")
|
||||
|
||||
for _, item := range []types.MemoryItem{
|
||||
{Kind: types.MemoryKindFact, Topic: "数据库", Content: "生产数据库用的是 PostgreSQL"},
|
||||
{Kind: types.MemoryKindFact, Topic: "部署", Content: "部署走的是蓝绿发布"},
|
||||
} {
|
||||
_, err := svc.Remember(ctx, item)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// The turn opened with a database question, so that is what recall ranked
|
||||
// against and the deployment memory is nowhere in the prompt.
|
||||
recall := svc.Recall(ctx, "帮我看看数据库连接池的配置")
|
||||
require.Contains(t, recall.Prompt, "PostgreSQL")
|
||||
require.NotContains(t, recall.Prompt, "蓝绿发布")
|
||||
|
||||
// Several iterations later the agent is looking at deployment instead.
|
||||
result := svc.SearchMemory(ctx, "部署方式", 10)
|
||||
require.True(t, result.Available)
|
||||
require.Len(t, result.Items, 1)
|
||||
require.Equal(t, "部署走的是蓝绿发布", result.Items[0].Content)
|
||||
}
|
||||
|
||||
// The other half of the gap: recall admits five situational items no matter
|
||||
// how many matched, because it is paid for on every turn. A search is paid for
|
||||
// only when the model asked for it, so it can afford to answer properly.
|
||||
func TestSearchReachesPastTheFiveItemTurnBudget(t *testing.T) {
|
||||
svc, _, tenantRepo := newMemoryHarness(t)
|
||||
ctx := enabledCtx(t, tenantRepo, 1, "alice")
|
||||
|
||||
for i := 0; i < 8; i++ {
|
||||
_, err := svc.Remember(ctx, types.MemoryItem{
|
||||
Kind: types.MemoryKindFact,
|
||||
Topic: fmt.Sprintf("配置项-%d", i),
|
||||
Content: fmt.Sprintf("网关配置第 %d 项已经调过", i),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
recall := svc.Recall(ctx, "网关配置")
|
||||
require.Len(t, recall.Items, types.MemoryRecallMaxItems)
|
||||
|
||||
result := svc.SearchMemory(ctx, "网关配置", 8)
|
||||
require.True(t, result.Available)
|
||||
require.Len(t, result.Items, 8)
|
||||
}
|
||||
|
||||
// Resident kinds are in the block rather than in situational recall, and the
|
||||
// block has its own rune budget. Search covers them too, or the tool would be
|
||||
// unable to answer "what do you know about my preferences" for anyone whose
|
||||
// block is full.
|
||||
func TestSearchCoversTheResidentKindsToo(t *testing.T) {
|
||||
svc, _, tenantRepo := newMemoryHarness(t)
|
||||
ctx := enabledCtx(t, tenantRepo, 1, "alice")
|
||||
|
||||
_, err := svc.Remember(ctx, types.MemoryItem{
|
||||
Kind: types.MemoryKindPreference, Topic: "代码风格", Content: "代码注释统一用英文",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
result := svc.SearchMemory(ctx, "代码注释", 10)
|
||||
require.True(t, result.Available)
|
||||
require.Len(t, result.Items, 1)
|
||||
require.Equal(t, types.MemoryKindPreference, result.Items[0].Kind)
|
||||
}
|
||||
|
||||
// A statement a later one replaced is exactly what the supersede machinery
|
||||
// exists to keep out of an answer. Reaching it through search would undo that
|
||||
// and hand the model a fact the user has already corrected.
|
||||
func TestSearchDoesNotResurrectReplacedMemories(t *testing.T) {
|
||||
svc, _, tenantRepo := newMemoryHarness(t)
|
||||
ctx := enabledCtx(t, tenantRepo, 1, "alice")
|
||||
|
||||
_, err := svc.Remember(ctx, types.MemoryItem{
|
||||
Kind: types.MemoryKindFact, Topic: "生产数据库", Content: "生产数据库用的是 MySQL",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = svc.Remember(ctx, types.MemoryItem{
|
||||
Kind: types.MemoryKindFact, Topic: "生产数据库", Content: "生产数据库已经迁到 PostgreSQL",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
result := svc.SearchMemory(ctx, "生产数据库", 10)
|
||||
require.True(t, result.Available)
|
||||
require.Len(t, result.Items, 1)
|
||||
require.Contains(t, result.Items[0].Content, "PostgreSQL")
|
||||
}
|
||||
|
||||
// "Switched off" and "nothing stored" have to stay distinguishable all the way
|
||||
// out to the caller. Collapsing them would have the agent tell someone who
|
||||
// disabled memory that it remembers nothing about them.
|
||||
func TestSearchTellsDisabledApartFromEmpty(t *testing.T) {
|
||||
svc, _, tenantRepo := newMemoryHarness(t)
|
||||
ctx := enabledCtx(t, tenantRepo, 1, "alice")
|
||||
_, err := svc.Remember(ctx, types.MemoryItem{
|
||||
Kind: types.MemoryKindFact, Topic: "数据库", Content: "生产数据库用的是 PostgreSQL",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
empty := svc.SearchMemory(ctx, "完全无关的题目", 10)
|
||||
require.True(t, empty.Available, "memory is on, this user simply has no match")
|
||||
require.Empty(t, empty.Items)
|
||||
|
||||
disabled := false
|
||||
off := svc.SearchMemory(types.ApplyAgentMemoryPreference(ctx, &disabled), "数据库", 10)
|
||||
require.False(t, off.Available, "an agent opting out must not be able to search either")
|
||||
require.Empty(t, off.Items)
|
||||
}
|
||||
|
||||
// MemoryAvailable is what lets a caller decide not to offer a memory feature
|
||||
// at all. It has to track every switch the read path honours, or the agent
|
||||
// would keep being handed a tool that can only report that memory is off.
|
||||
func TestMemoryAvailableTracksAllThreeSwitches(t *testing.T) {
|
||||
svc, _, tenantRepo := newMemoryHarness(t)
|
||||
ctx := enabledCtx(t, tenantRepo, 1, "alice")
|
||||
require.True(t, svc.MemoryAvailable(ctx))
|
||||
|
||||
// The user's own toggle in 我的记忆.
|
||||
require.NoError(t, svc.SetEnabled(ctx, false))
|
||||
require.False(t, svc.MemoryAvailable(ctx), "the user opted out")
|
||||
require.NoError(t, svc.SetEnabled(ctx, true))
|
||||
require.True(t, svc.MemoryAvailable(ctx))
|
||||
|
||||
// The agent handling this request.
|
||||
disabled := false
|
||||
require.False(t, svc.MemoryAvailable(types.ApplyAgentMemoryPreference(ctx, &disabled)))
|
||||
|
||||
// The workspace setting.
|
||||
tenantRepo.set(1, &types.MemoryConfig{Enabled: false})
|
||||
require.False(t, svc.MemoryAvailable(ctx), "the workspace switched memory off")
|
||||
}
|
||||
|
||||
// The predicate and the search must never disagree: anything that reports
|
||||
// available has to be searchable, and anything unavailable has to say so
|
||||
// rather than come back looking like an empty store.
|
||||
func TestMemoryAvailableAgreesWithSearch(t *testing.T) {
|
||||
svc, _, tenantRepo := newMemoryHarness(t)
|
||||
ctx := enabledCtx(t, tenantRepo, 1, "alice")
|
||||
_, err := svc.Remember(ctx, types.MemoryItem{
|
||||
Kind: types.MemoryKindFact, Topic: "数据库", Content: "生产数据库用的是 PostgreSQL",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, svc.MemoryAvailable(ctx), svc.SearchMemory(ctx, "数据库", 10).Available)
|
||||
|
||||
require.NoError(t, svc.SetEnabled(ctx, false))
|
||||
require.Equal(t, svc.MemoryAvailable(ctx), svc.SearchMemory(ctx, "数据库", 10).Available)
|
||||
require.False(t, svc.SearchMemory(ctx, "数据库", 10).Available)
|
||||
}
|
||||
|
||||
func TestSearchWithoutPrincipalIsUnavailable(t *testing.T) {
|
||||
svc, _, tenantRepo := newMemoryHarness(t)
|
||||
tenantRepo.set(1, &types.MemoryConfig{Enabled: true})
|
||||
ctx := t.Context()
|
||||
|
||||
result := svc.SearchMemory(ctx, "数据库", 10)
|
||||
require.False(t, result.Available,
|
||||
"a request with no principal has no memory space to search")
|
||||
}
|
||||
|
||||
func TestSearchClampsAnAbsurdLimit(t *testing.T) {
|
||||
svc, _, tenantRepo := newMemoryHarness(t)
|
||||
ctx := enabledCtx(t, tenantRepo, 1, "alice")
|
||||
|
||||
for i := 0; i < types.MemorySearchMaxItems+10; i++ {
|
||||
_, err := svc.Remember(ctx, types.MemoryItem{
|
||||
Kind: types.MemoryKindFact,
|
||||
Topic: fmt.Sprintf("网关-%d", i),
|
||||
Content: fmt.Sprintf("网关配置第 %d 项已经调过", i),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
result := svc.SearchMemory(ctx, "网关配置", 10_000)
|
||||
require.True(t, result.Available)
|
||||
require.LessOrEqual(t, len(result.Items), types.MemorySearchMaxItems)
|
||||
}
|
||||
Reference in New Issue
Block a user