Skip to content

Commit

Permalink
Action schema parse cache fixes (#451)
Browse files Browse the repository at this point in the history
- Need to handle if there isn't existing cache file to add to when
writing to cache.
- Fix second round trip for parsed schema, where we need to write it out
before resolve reference.
- Disable action semantic map if there is no embedding model keys.

Also:
- Add basic startup and shutdown dispatcher test, (with no keys).
  • Loading branch information
curtisman authored Dec 3, 2024
1 parent df70413 commit b9d39b7
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 51 deletions.
4 changes: 3 additions & 1 deletion ts/packages/agents/player/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ import {
} from "./search.js";
import { toTrackObjectFull } from "./spotifyUtils.js";

const debugSpotify = registerDebug("typeagent:spotify");

const debugSpotifyError = registerDebug("typeagent:spotify:error");

function createWarningActionResult(message: string) {
Expand Down Expand Up @@ -341,6 +343,7 @@ export async function getClientContext(
await createTokenProvider(profileStorage),
);
await service.init();
debugSpotify("Service initialized");
const userdata = await getUserProfile(service);
service.storeUser({
id: userdata?.id,
Expand All @@ -355,7 +358,6 @@ export async function getClientContext(
devices.devices[0];
deviceId = activeDevice.id ?? undefined;
}

return {
deviceId,
service,
Expand Down
18 changes: 8 additions & 10 deletions ts/packages/agents/player/src/userData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { SpotifyService } from "./service.js";
import registerDebug from "debug";
import { Storage } from "@typeagent/agent-sdk";

const debugSpotify = registerDebug("typeagent:spotify");
const debugData = registerDebug("typeagent:spotify:data");

export interface MusicItemInfo {
id: string;
Expand Down Expand Up @@ -214,7 +214,7 @@ async function updateUserData(
userData: SpotifyUserData,
) {
try {
debugSpotify("Updating user data");
debugData("Updating user data");
const [
favoriteTracks,
favoriteAlbums,
Expand Down Expand Up @@ -287,7 +287,7 @@ async function updateUserData(
];
}

if (debugSpotify.enabled) {
if (debugData.enabled) {
const messages: string[] = [
[
"".padEnd(22),
Expand All @@ -312,13 +312,13 @@ async function updateUserData(
.padStart(length),
);
messages.unshift("");
debugSpotify(messages.join("\n"));
debugData(messages.join("\n"));
}

userData.lastUpdated = Date.now();
await saveUserData(storage, userData);
} catch (e) {
debugSpotify("Failed to update user data", e);
debugData("Failed to update user data", e);
}
}

Expand All @@ -332,12 +332,10 @@ export async function initializeUserData(
profileStorage: Storage,
service: SpotifyService,
) {
debugData("Loading saved user data");
const data = await loadUserData(profileStorage);
const result: UserData = {
data,
};
// print sizes of each map to console
console.log(
const result: UserData = { data };
debugData(
`Tracks: ${data.tracks.size}, Artists: ${data.artists.size}, Albums: ${data.albums.size}`,
);
// Update once a day
Expand Down
2 changes: 1 addition & 1 deletion ts/packages/dispatcher/src/agent/agentProcess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
import { Action, JSONAction } from "agent-cache";
import registerDebug from "debug";

const debug = registerDebug("typeagent:agentProcess");
const debug = registerDebug("typeagent:dispatcher:agentProcess");

const modulePath = process.argv[2];
const module = await import(modulePath);
Expand Down
2 changes: 1 addition & 1 deletion ts/packages/dispatcher/src/dispatcher/dispatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ async function getTemplateCompletion(
export type DispatcherOptions = InitializeCommandHandlerContextOptions;
export async function createDispatcher(
hostName: string,
options: DispatcherOptions,
options?: DispatcherOptions,
): Promise<Dispatcher> {
const context = await initializeCommandHandlerContext(hostName, options);
return {
Expand Down
38 changes: 26 additions & 12 deletions ts/packages/dispatcher/src/handlers/common/appAgentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ import { ActionSchemaFile } from "action-schema";
import path from "path";
import { callEnsureError } from "../../utils/exceptions.js";

const debug = registerDebug("typeagent:agents");
const debugError = registerDebug("typeagent:agents:error");
const debug = registerDebug("typeagent:dispatcher:agents");
const debugError = registerDebug("typeagent:dispatcher:agents:error");

type AppAgentRecord = {
name: string;
Expand Down Expand Up @@ -141,7 +141,7 @@ export class AppAgentManager implements ActionConfigProvider {
private readonly injectedSchemaForActionName = new Map<string, string>();
private readonly emojis: Record<string, string> = {};
private readonly transientAgents: Record<string, boolean | undefined> = {};
private readonly actionSementicMap = new ActionSchemaSementicMap();
private readonly actionSementicMap?: ActionSchemaSementicMap;
private readonly actionSchemaFileCache;

public constructor(sessionDirPath: string | undefined) {
Expand All @@ -150,6 +150,14 @@ export class AppAgentManager implements ActionConfigProvider {
? path.join(sessionDirPath, "actionSchemaFileCache.json")
: undefined,
);

try {
this.actionSementicMap = new ActionSchemaSementicMap();
} catch (e) {
if (process.env.NODE_ENV !== "test") {
console.log("Failed to create action semantic map", e);
}
}
}
public getAppAgentNames(): string[] {
return Array.from(this.agents.keys());
Expand Down Expand Up @@ -218,7 +226,7 @@ export class AppAgentManager implements ActionConfigProvider {
request: string,
maxMatches: number = 1,
) {
return this.actionSementicMap.nearestNeighbors(
return this.actionSementicMap?.nearestNeighbors(
request,
this,
maxMatches,
Expand Down Expand Up @@ -246,13 +254,17 @@ export class AppAgentManager implements ActionConfigProvider {

const actionSchemaFile =
this.actionSchemaFileCache.getActionSchemaFile(config);
semanticMapP.push(
this.actionSementicMap.addActionSchemaFile(
config,
actionSchemaFile,
actionEmbeddingCache,
),
);

if (this.actionSementicMap) {
semanticMapP.push(
this.actionSementicMap.addActionSchemaFile(
config,
actionSchemaFile,
actionEmbeddingCache,
),
);
}

if (config.transient) {
this.transientAgents[name] = false;
}
Expand Down Expand Up @@ -281,7 +293,7 @@ export class AppAgentManager implements ActionConfigProvider {
}

public getActionEmbeddings() {
return this.actionSementicMap.embeddings();
return this.actionSementicMap?.embeddings();
}
public tryGetActionConfig(mayBeSchemaName: string) {
return this.actionConfigs.get(mayBeSchemaName);
Expand Down Expand Up @@ -419,6 +431,7 @@ export class AppAgentManager implements ActionConfigProvider {
record.name,
enableCommands,
]);
debug(`Command enabled ${record.name}`);
} catch (e: any) {
failedCommands.push([
record.name,
Expand All @@ -429,6 +442,7 @@ export class AppAgentManager implements ActionConfigProvider {
})(),
);
} else {
debug(`Command disabled ${record.name}`);
record.commands = false;
changedCommands.push([record.name, enableCommands]);
await this.checkCloseSessionContext(record);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,11 @@ async function addAppAgentProvidres(
}
if (embeddingCachePath) {
try {
await writeEmbeddingCache(
embeddingCachePath,
context.agents.getActionEmbeddings(),
);
debug("Action Schema Embedding cache saved");
const embeddings = context.agents.getActionEmbeddings();
if (embeddings) {
await writeEmbeddingCache(embeddingCachePath, embeddings);
debug("Action Schema Embedding cache saved");
}
} catch {
// Ignore error
}
Expand All @@ -286,6 +286,7 @@ export async function initializeCommandHandlerContext(
session.setConfig(options);
}
const sessionDirPath = session.getSessionDirPath();
debug(`Session directory: ${sessionDirPath}`);
const conversationManager = sessionDirPath
? await Conversation.createConversationManager(
{},
Expand Down Expand Up @@ -339,6 +340,7 @@ export async function initializeCommandHandlerContext(
);

await setAppAgentStates(context, options);
debug("Context initialized");
return context;
}

Expand Down
23 changes: 12 additions & 11 deletions ts/packages/dispatcher/src/handlers/requestCommandHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,16 +536,18 @@ async function pickInitialSchema(
// Use embedding to determine the most likely action schema and use the schema name for that.
const result =
await systemContext.agents.semanticSearchActionSchema(request);
debugSementicSearch(
`Semantic search result: ${result
.map(
(r) =>
`${r.item.actionSchemaFile.schemaName}.${r.item.definition.name} (${r.score})`,
)
.join("\n")}`,
);
if (result.length > 0) {
schemaName = result[0].item.actionSchemaFile.schemaName;
if (result) {
debugSementicSearch(
`Semantic search result: ${result
.map(
(r) =>
`${r.item.actionSchemaFile.schemaName}.${r.item.definition.name} (${r.score})`,
)
.join("\n")}`,
);
if (result.length > 0) {
schemaName = result[0].item.actionSchemaFile.schemaName;
}
}
}

Expand Down Expand Up @@ -989,7 +991,6 @@ export class RequestCommandHandler implements CommandHandler {
? await matchRequest(request, context, history)
: undefined;

systemContext.agents.semanticSearchActionSchema(request);
const translationResult =
match === undefined // undefined means not found
? await translateRequest(
Expand Down
34 changes: 24 additions & 10 deletions ts/packages/dispatcher/src/translation/actionSchemaFileCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,14 @@ export class ActionSchemaFileCache {
public constructor(private readonly cacheFilePath?: string) {
if (cacheFilePath !== undefined) {
try {
const data = fs.readFileSync(cacheFilePath, "utf-8");
const entries = JSON.parse(data) as [string, CacheEntry][];
for (const [key, entry] of entries) {
this.prevSaved.set(key, entry);
const entries = this.loadExistingCache();
if (entries) {
for (const [key, entry] of entries) {
this.prevSaved.set(key, entry);
}
// We will rewrite it.
fs.unlinkSync(cacheFilePath);
}
// We will rewrite it.
fs.unlinkSync(cacheFilePath);
} catch (e) {
debugError(`Failed to load parsed schema cache: ${e}`);
}
Expand All @@ -65,11 +66,12 @@ export class ActionSchemaFileCache {
this.prevSaved.delete(key);
if (lastCached.hash === hash) {
debug(`Cached parsed schema used: ${actionConfig.schemaName}`);
// Add and save the cache first before convert it back (which will modify the data)
this.addToCache(key, hash, lastCached.actionSchemaFile);
const cached = fromJSONActionSchemaFile(
lastCached.actionSchemaFile,
);
this.actionSchemaFiles.set(key, cached);
this.addToCache(key, hash, lastCached.actionSchemaFile);
return cached;
}
debugError(
Expand Down Expand Up @@ -100,18 +102,30 @@ export class ActionSchemaFileCache {
}

try {
const data = fs.readFileSync(this.cacheFilePath, "utf-8");
const entries = JSON.parse(data) as [string, CacheEntry][];
const entries = this.loadExistingCache() ?? [];
entries.push([key, { hash, actionSchemaFile }]);
fs.writeFileSync(
this.cacheFilePath,
JSON.stringify(entries),
"utf-8",
);
} catch {
} catch (e: any) {
// ignore error
debugError(
`Failed to write parsed schema cache: ${this.cacheFilePath}: ${e.message}`,
);
}
}

private loadExistingCache() {
try {
if (this.cacheFilePath) {
const data = fs.readFileSync(this.cacheFilePath, "utf-8");
return JSON.parse(data) as [string, CacheEntry][];
}
} catch {}
return undefined;
}
}

const globalCache = new ActionSchemaFileCache();
Expand Down
12 changes: 12 additions & 0 deletions ts/packages/dispatcher/test/basic.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { createDispatcher } from "../src/dispatcher/dispatcher.js";

describe("basic", () => {
it("startup and shutdown", async () => {
// Placeholder for test
const dispatcher = await createDispatcher("test", {});
await dispatcher.close();
});
});

0 comments on commit b9d39b7

Please sign in to comment.