Skip to content

Commit 172da54

Browse files
authored
revamping caches + logging (#1398)
* 🎨 Refactor cache key handling and enhance JSON output Simplified CacheEntry to remove unused key type and refactored nodes. * clean caches * twoards multi-cache support * cleanup caches * removing outdated views * clean up ui * more cleanup * more logging * missing name in cache * ♻️ Simplify cache file path construction Removed redundant name parameter in cache file path generation. * logging and caching * fix * fix key loopup * specify cache source * upgraded tests
1 parent 7dcbd54 commit 172da54

23 files changed

Lines changed: 560 additions & 702 deletions

packages/core/src/agent.ts

Lines changed: 55 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { MemoryCache } from "./cache"
1+
import { createCache } from "./cache"
22
import {
33
AGENT_MEMORY_CACHE_NAME,
44
AGENT_MEMORY_FLEX_TOKENS,
@@ -8,8 +8,34 @@ import { errorMessage } from "./error"
88
import { GenerationOptions } from "./generation"
99
import { HTMLEscape } from "./html"
1010
import { prettifyMarkdown } from "./markdown"
11-
import { MarkdownTrace, TraceOptions } from "./trace"
12-
import { logVerbose } from "./util"
11+
import { TraceOptions } from "./trace"
12+
import { ellipse } from "./util"
13+
import debug from "debug"
14+
const dbg = debug("agent:memory")
15+
16+
export type AgentMemoryCacheKey = { agent: string; query: string }
17+
export type AgentMemoryCacheValue = AgentMemoryCacheKey & {
18+
answer: string
19+
createdAt: number
20+
}
21+
export type AgentMemoryCache = WorkspaceFileCache<
22+
AgentMemoryCacheKey,
23+
AgentMemoryCacheValue
24+
>
25+
26+
export function agentCreateCache(
27+
options: Pick<GenerationOptions, "userState"> & { lookupOnly?: boolean }
28+
): AgentMemoryCache {
29+
const cache = createCache<AgentMemoryCacheKey, AgentMemoryCacheValue>(
30+
AGENT_MEMORY_CACHE_NAME,
31+
{
32+
type: "memory",
33+
userState: options.userState,
34+
lookupOnly: options.lookupOnly,
35+
}
36+
)
37+
return cache
38+
}
1339

1440
/**
1541
* Queries the agent's memory to retrieve contextual information relevant to a given query.
@@ -26,17 +52,19 @@ import { logVerbose } from "./util"
2652
* @returns Memory answer or undefined if no relevant memories are retrieved.
2753
*/
2854
export async function agentQueryMemory(
55+
cache: AgentMemoryCache,
2956
ctx: ChatGenerationContext,
3057
query: string,
31-
options: Pick<GenerationOptions, "userState"> & Required<TraceOptions>
58+
options: Required<TraceOptions>
3259
) {
3360
if (!query) return undefined
3461

35-
const memories = await loadMemories(options)
62+
const memories = await loadMemories(cache)
3663
if (!memories?.length) return undefined
3764

3865
let memoryAnswer: string | undefined
3966
// always pre-query memory with cheap model
67+
dbg(`query: ${query}`)
4068
const res = await ctx.runPrompt(
4169
async (_) => {
4270
_.$`Return the contextual information useful to answer <QUERY> from the content in <MEMORY>.
@@ -47,18 +75,21 @@ export async function agentQueryMemory(
4775
"system"
4876
)
4977
_.def("QUERY", query)
50-
await defMemory(_)
78+
await defMemory(cache, _)
5179
},
5280
{
5381
model: "memory",
5482
system: [],
5583
flexTokens: AGENT_MEMORY_FLEX_TOKENS,
5684
label: "agent memory query",
85+
cache: "agent_memory",
5786
}
5887
)
5988
if (!res.error)
6089
memoryAnswer = res.text.includes(TOKEN_NO_ANSWER) ? "" : res.text
61-
else logVerbose(`agent memory query error: ${errorMessage(res.error)}`)
90+
else dbg(`error: ${errorMessage(res.error)}`)
91+
92+
dbg(`answer: ${ellipse(memoryAnswer, 128)}`)
6293
return memoryAnswer
6394
}
6495

@@ -73,25 +104,20 @@ export async function agentQueryMemory(
73104
* @param options - Configuration options, including user state and tracing details.
74105
*/
75106
export async function agentAddMemory(
107+
cache: AgentMemoryCache,
76108
agent: string,
77109
query: string,
78110
text: string,
79-
options: Pick<GenerationOptions, "userState"> & Required<TraceOptions>
111+
options: Required<TraceOptions>
80112
) {
81113
const { trace } = options || {}
82-
const cache = MemoryCache.byName<
83-
{ agent: string; query: string },
84-
{
85-
agent: string
86-
query: string
87-
answer: string
88-
}
89-
>(AGENT_MEMORY_CACHE_NAME)
90-
const cacheKey = { agent, query }
91-
const cachedValue = {
114+
const cacheKey: AgentMemoryCacheKey = { agent, query }
115+
const cachedValue: AgentMemoryCacheValue = {
92116
...cacheKey,
93117
answer: text,
118+
createdAt: Date.now(),
94119
}
120+
dbg(`add ${agent}: ${ellipse(query, 80)} -> ${ellipse(text, 128)}`)
95121
await cache.set(cacheKey, cachedValue)
96122
trace.detailsFenced(
97123
`🧠 agent memory: ${HTMLEscape(query)}`,
@@ -100,19 +126,9 @@ export async function agentAddMemory(
100126
)
101127
}
102128

103-
async function loadMemories(options: Pick<GenerationOptions, "userState">) {
104-
const cache = MemoryCache.byName<
105-
{ agent: string; query: string },
106-
{
107-
agent: string
108-
query: string
109-
answer: string
110-
}
111-
>(AGENT_MEMORY_CACHE_NAME, {
112-
lookupOnly: true,
113-
userState: options.userState,
114-
})
129+
async function loadMemories(cache: AgentMemoryCache) {
115130
const memories = await cache?.values()
131+
memories?.sort((l, r) => l.createdAt - r.createdAt)
116132
return memories
117133
}
118134

@@ -131,7 +147,11 @@ export async function traceAgentMemory(
131147
options: Pick<GenerationOptions, "userState"> & Required<TraceOptions>
132148
) {
133149
const { trace } = options || {}
134-
const memories = await loadMemories(options)
150+
const cache = agentCreateCache({
151+
userState: options.userState,
152+
lookupOnly: true,
153+
})
154+
const memories = await loadMemories(cache)
135155
if (memories) {
136156
try {
137157
trace.startDetails("🧠 agent memory")
@@ -150,15 +170,10 @@ export async function traceAgentMemory(
150170
}
151171
}
152172

153-
async function defMemory(ctx: ChatTurnGenerationContext) {
154-
const cache = MemoryCache.byName<
155-
{ agent: string; query: string },
156-
{
157-
agent: string
158-
query: string
159-
answer: string
160-
}
161-
>(AGENT_MEMORY_CACHE_NAME)
173+
async function defMemory(
174+
cache: AgentMemoryCache,
175+
ctx: ChatTurnGenerationContext
176+
) {
162177
const memories = await cache.values()
163178
memories.reverse().forEach(({ agent, query, answer }, index) =>
164179
ctx.def(

packages/core/src/azurecontentsafety.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ import {
88
import { runtimeHost } from "./host"
99
import { CancellationOptions } from "./cancellation"
1010
import { YAMLStringify } from "./yaml"
11-
import { JSONLineCache } from "./cache"
1211
import { AzureCredentialsType } from "./server/messages"
1312
import { trimTrailingSlash } from "./cleaners"
1413
import { chunkString } from "./chunkers"
14+
import { CacheOptions, createCache } from "./cache"
1515

1616
interface AzureContentSafetyRequest {
1717
userPrompt?: string
@@ -28,12 +28,12 @@ interface AzureContentSafetyResponse {
2828
}
2929

3030
class AzureContentSafetyClient implements ContentSafety {
31-
private readonly cache: JSONLineCache<
31+
private readonly cache: WorkspaceFileCache<
3232
{ route: string; body: object; options: object },
3333
object
3434
>
3535
constructor(readonly options?: TraceOptions & CancellationOptions) {
36-
this.cache = JSONLineCache.byName("azurecontentsafety")
36+
this.cache = createCache("azurecontentsafety", { type: "fs" })
3737
}
3838

3939
async detectHarmfulContent(

packages/core/src/cache.test.ts

Lines changed: 78 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,90 @@
1-
import { JSONLineCache, CacheEntry } from "./cache"
21
import { describe, test, beforeEach } from "node:test"
32
import assert from "node:assert/strict"
43
import * as fs from "node:fs/promises"
54
import * as path from "node:path"
65
import { TestHost } from "./testhost"
6+
import { JSONLineCache } from "./jsonlinecache"
7+
import { createCache } from "./cache"
78

89
const tempDir = path.join(".genaiscript", "temp")
910

10-
describe("Cache", () => {
11-
beforeEach(async () => {
12-
TestHost.install()
13-
await fs.mkdir(tempDir, { recursive: true })
14-
})
15-
test("JSONLineCache instance creation with byName", async () => {
16-
const cache = JSONLineCache.byName<string, number>("testCache")
17-
assert.ok(cache instanceof JSONLineCache)
18-
})
19-
test("JSONLineCache set key-value pair", async () => {
20-
const cache = JSONLineCache.byName<string, number>("testCache")
21-
await cache.set("anotherKey", 99)
22-
const value = await cache.get("anotherKey")
23-
assert.strictEqual(value, 99)
24-
})
11+
for (const type of ["memory", "jsonl", "fs"]) {
12+
describe(`cache.${type}`, () => {
13+
beforeEach(async () => {
14+
TestHost.install()
15+
await fs.mkdir(tempDir, { recursive: true })
16+
})
17+
test("instance creation with byName", async () => {
18+
const cache = createCache<string, number>("testCache", {
19+
type: type as any,
20+
})
21+
assert.ok(!!cache)
22+
})
23+
test("set key-value pair", async () => {
24+
const cache = createCache<string, number>("testCache", {
25+
type: type as any,
26+
})
27+
await cache.set("anotherKey", 99)
28+
const value = await cache.get("anotherKey")
29+
assert.strictEqual(value, 99)
30+
})
2531

26-
test("JSONLineCache getKeySHA computation", async () => {
27-
const cache = JSONLineCache.byName<string, number>("testCache")
28-
const sha = await cache.getKeySHA("testKey")
29-
assert.ok(sha)
30-
assert.strictEqual(typeof sha, "string")
31-
})
32+
test("getSha computation", async () => {
33+
const cache = createCache<string, number>("testCache", {
34+
type: type as any,
35+
})
36+
const sha = await cache.getSha("testKey")
37+
assert.ok(sha)
38+
assert.strictEqual(typeof sha, "string")
39+
})
3240

33-
test("keySHA generates SHA256 hash from a key", async () => {
34-
const cache = JSONLineCache.byName<string, number>("testCache")
35-
const sha = await cache.getKeySHA("testKey")
36-
assert.ok(sha)
37-
assert.strictEqual(typeof sha, "string")
38-
})
39-
test("JSONLineCache getOrUpdate retrieves existing value", async () => {
40-
const cache = JSONLineCache.byName<string, number>("testCache")
41-
await cache.set("existingKey", 42)
42-
const value = await cache.getOrUpdate(
43-
"existingKey",
44-
async () => 99,
45-
() => true
46-
)
47-
assert.strictEqual(value.value, 42)
48-
})
41+
test("keySHA generates SHA256 hash from a key", async () => {
42+
const cache = createCache<string, number>("testCache", {
43+
type: type as any,
44+
})
45+
const sha = await cache.getSha("testKey")
46+
assert.ok(sha)
47+
assert.strictEqual(typeof sha, "string")
48+
})
49+
test(`${type} getOrUpdate retrieves existing value`, async () => {
50+
const cache = createCache<string, number>("testCache", {
51+
type: type as any,
52+
})
53+
await cache.set("existingKey", 42)
54+
const value = await cache.getOrUpdate(
55+
"existingKey",
56+
async () => 99,
57+
() => true
58+
)
59+
assert.strictEqual(value.value, 42)
60+
})
61+
62+
test("getOrUpdate updates with new value if key does not exist", async () => {
63+
const cache = createCache<string, number>("testCache", {
64+
type: type as any,
65+
})
66+
const value = await cache.getOrUpdate(
67+
"newKey",
68+
async () => 99,
69+
() => true
70+
)
71+
assert.strictEqual(value.value, 99)
72+
const cachedValue = await cache.get("newKey")
73+
assert.strictEqual(cachedValue, 99)
74+
})
75+
76+
test("values() retrieves all stored values", async () => {
77+
const cache = createCache<string, number>("testCache", {
78+
type: type as any,
79+
})
80+
await cache.set("key1", 10)
81+
await cache.set("key2", 20)
82+
await cache.set("key3", 30)
4983

50-
test("JSONLineCache getOrUpdate updates with new value if key does not exist", async () => {
51-
const cache = JSONLineCache.byName<string, number>("testCache")
52-
const value = await cache.getOrUpdate(
53-
"newKey",
54-
async () => 99,
55-
() => true
56-
)
57-
assert.strictEqual(value.value, 99)
58-
const cachedValue = await cache.get("newKey")
59-
assert.strictEqual(cachedValue, 99)
84+
const values = await cache.values()
85+
assert(values.includes(10))
86+
assert(values.includes(20))
87+
assert(values.includes(30))
88+
})
6089
})
61-
})
90+
}

0 commit comments

Comments
 (0)