Liz uses Prisma as its ORM, supporting both SQLite and PostgreSQL databases. The schema defines the structure for storing memories and tweets.
// prisma/schema.prisma
datasource db {
provider = "sqlite" // or "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Memory {
id String @id @default(uuid())
userId String
agentId String
roomId String
content String // Stores JSON as string
type String
generator String // "llm" or "external"
createdAt DateTime @default(now())
@@index([roomId])
@@index([userId, agentId])
@@index([type])
}
model Tweet {
id String @id
text String
userId String
username String
conversationId String?
inReplyToId String?
createdAt DateTime @default(now())
permanentUrl String?
@@index([userId])
@@index([conversationId])
}
The loadMemories middleware retrieves relevant conversation history for each request:
// src/middleware/load-memories.ts
export function createLoadMemoriesMiddleware(
options: LoadMemoriesOptions = {}
): AgentMiddleware {
const { limit = 100 } = options;
return async (req, res, next) => {
const memories = await prisma.memory.findMany({
where: {
userId: req.input.userId,
},
orderBy: {
createdAt: "desc",
},
take: limit,
});
req.memories = memories.map((memory) => ({
id: memory.id,
userId: memory.userId,
agentId: memory.agentId,
roomId: memory.roomId,
type: memory.type,
createdAt: memory.createdAt,
generator: memory.generator,
content: JSON.parse(memory.content),
}));
await next();
};
}
The createMemoryFromInput middleware stores new interactions in the database:
// src/middleware/create-memory.ts
export const createMemoryFromInput: AgentMiddleware = async (
req,
res,
next
) => {
await prisma.memory.create({
data: {
userId: req.input.userId,
agentId: req.input.agentId,
roomId: req.input.roomId,
type: req.input.type,
generator: "external",
content: JSON.stringify(req.input),
},
});
await next();
};
// Creating LLM response memories
await prisma.memory.create({
data: {
userId: req.input.userId,
agentId: req.input.agentId,
roomId: req.input.roomId,
type: "agent",
generator: "llm",
content: JSON.stringify({ text: response }),
},
});
The wrapContext middleware formats memories into a structured context for LLM interactions:
// src/middleware/wrap-context.ts
function formatMemories(memories: Memory[]): string {
return memories
.reverse()
.map((memory) => {
const content = memory.content;
if (memory.generator === "external") {
return `[${memory.createdAt}] User ${memory.userId}: ${content.text}`;
} else if (memory.generator === "llm") {
return `[${memory.createdAt}] You: ${content.text}`;
}
})
.join("\n\n");
}
// Final context structure
<PREVIOUS_CONVERSATION>
${memories}
</PREVIOUS_CONVERSATION>
<AGENT_CONTEXT>
${agentContext}
</AGENT_CONTEXT>
<CURRENT_USER_INPUT>
${currentInput}
</CURRENT_USER_INPUT>