Skip to content

Commit 6690ea6

Browse files
web3ghodilitime
andauthored
fix: Koloxarto/fix ragknowledge for postgres (#2153)
* fix formatting out of the way * fix postgress chunk uuid handling for ragKnowledge --------- Co-authored-by: Odilitime <janesmith@airmail.cc>
1 parent 35d857e commit 6690ea6

File tree

3 files changed

+449
-218
lines changed

3 files changed

+449
-218
lines changed

packages/adapter-postgres/src/index.ts

+168-58
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,31 @@ import { v4 } from "uuid";
44
import pg from "pg";
55
type Pool = pg.Pool;
66

7-
import {
8-
QueryConfig,
9-
QueryConfigValues,
10-
QueryResult,
11-
QueryResultRow,
12-
} from "pg";
137
import {
148
Account,
159
Actor,
10+
DatabaseAdapter,
11+
EmbeddingProvider,
1612
GoalStatus,
13+
Participant,
14+
RAGKnowledgeItem,
15+
elizaLogger,
16+
getEmbeddingConfig,
1717
type Goal,
18+
type IDatabaseCacheAdapter,
1819
type Memory,
1920
type Relationship,
2021
type UUID,
21-
type IDatabaseCacheAdapter,
22-
Participant,
23-
elizaLogger,
24-
getEmbeddingConfig,
25-
DatabaseAdapter,
26-
EmbeddingProvider,
27-
RAGKnowledgeItem
2822
} from "@elizaos/core";
2923
import fs from "fs";
30-
import { fileURLToPath } from "url";
3124
import path from "path";
25+
import {
26+
QueryConfig,
27+
QueryConfigValues,
28+
QueryResult,
29+
QueryResultRow,
30+
} from "pg";
31+
import { fileURLToPath } from "url";
3232

3333
const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file
3434
const __dirname = path.dirname(__filename); // get the name of the directory
@@ -199,7 +199,7 @@ export class PostgresDatabaseAdapter
199199
return true;
200200
} catch (error) {
201201
elizaLogger.error("Failed to validate vector extension:", {
202-
error: error instanceof Error ? error.message : String(error)
202+
error: error instanceof Error ? error.message : String(error),
203203
});
204204
return false;
205205
}
@@ -239,8 +239,10 @@ export class PostgresDatabaseAdapter
239239
);
240240
`);
241241

242-
if (!rows[0].exists || !await this.validateVectorSetup()) {
243-
elizaLogger.info("Applying database schema - tables or vector extension missing");
242+
if (!rows[0].exists || !(await this.validateVectorSetup())) {
243+
elizaLogger.info(
244+
"Applying database schema - tables or vector extension missing"
245+
);
244246
const schema = fs.readFileSync(
245247
path.resolve(__dirname, "../schema.sql"),
246248
"utf8"
@@ -1523,12 +1525,17 @@ export class PostgresDatabaseAdapter
15231525

15241526
const { rows } = await this.pool.query(sql, queryParams);
15251527

1526-
return rows.map(row => ({
1528+
return rows.map((row) => ({
15271529
id: row.id,
15281530
agentId: row.agentId,
1529-
content: typeof row.content === 'string' ? JSON.parse(row.content) : row.content,
1530-
embedding: row.embedding ? new Float32Array(row.embedding) : undefined,
1531-
createdAt: row.createdAt.getTime()
1531+
content:
1532+
typeof row.content === "string"
1533+
? JSON.parse(row.content)
1534+
: row.content,
1535+
embedding: row.embedding
1536+
? new Float32Array(row.embedding)
1537+
: undefined,
1538+
createdAt: row.createdAt.getTime(),
15321539
}));
15331540
}, "getKnowledge");
15341541
}
@@ -1544,7 +1551,7 @@ export class PostgresDatabaseAdapter
15441551
const cacheKey = `embedding_${params.agentId}_${params.searchText}`;
15451552
const cachedResult = await this.getCache({
15461553
key: cacheKey,
1547-
agentId: params.agentId
1554+
agentId: params.agentId,
15481555
});
15491556

15501557
if (cachedResult) {
@@ -1594,24 +1601,29 @@ export class PostgresDatabaseAdapter
15941601
const { rows } = await this.pool.query(sql, [
15951602
vectorStr,
15961603
params.agentId,
1597-
`%${params.searchText || ''}%`,
1604+
`%${params.searchText || ""}%`,
15981605
params.match_threshold,
1599-
params.match_count
1606+
params.match_count,
16001607
]);
16011608

1602-
const results = rows.map(row => ({
1609+
const results = rows.map((row) => ({
16031610
id: row.id,
16041611
agentId: row.agentId,
1605-
content: typeof row.content === 'string' ? JSON.parse(row.content) : row.content,
1606-
embedding: row.embedding ? new Float32Array(row.embedding) : undefined,
1612+
content:
1613+
typeof row.content === "string"
1614+
? JSON.parse(row.content)
1615+
: row.content,
1616+
embedding: row.embedding
1617+
? new Float32Array(row.embedding)
1618+
: undefined,
16071619
createdAt: row.createdAt.getTime(),
1608-
similarity: row.combined_score
1620+
similarity: row.combined_score,
16091621
}));
16101622

16111623
await this.setCache({
16121624
key: cacheKey,
16131625
agentId: params.agentId,
1614-
value: JSON.stringify(results)
1626+
value: JSON.stringify(results),
16151627
});
16161628

16171629
return results;
@@ -1622,35 +1634,52 @@ export class PostgresDatabaseAdapter
16221634
return this.withDatabase(async () => {
16231635
const client = await this.pool.connect();
16241636
try {
1625-
await client.query('BEGIN');
1626-
1627-
const sql = `
1628-
INSERT INTO knowledge (
1629-
id, "agentId", content, embedding, "createdAt",
1630-
"isMain", "originalId", "chunkIndex", "isShared"
1631-
) VALUES ($1, $2, $3, $4, to_timestamp($5/1000.0), $6, $7, $8, $9)
1632-
ON CONFLICT (id) DO NOTHING
1633-
`;
1637+
await client.query("BEGIN");
16341638

16351639
const metadata = knowledge.content.metadata || {};
1636-
const vectorStr = knowledge.embedding ?
1637-
`[${Array.from(knowledge.embedding).join(",")}]` : null;
1638-
1639-
await client.query(sql, [
1640-
knowledge.id,
1641-
metadata.isShared ? null : knowledge.agentId,
1642-
knowledge.content,
1643-
vectorStr,
1644-
knowledge.createdAt || Date.now(),
1645-
metadata.isMain || false,
1646-
metadata.originalId || null,
1647-
metadata.chunkIndex || null,
1648-
metadata.isShared || false
1649-
]);
1640+
const vectorStr = knowledge.embedding
1641+
? `[${Array.from(knowledge.embedding).join(",")}]`
1642+
: null;
1643+
1644+
// If this is a chunk, use createKnowledgeChunk
1645+
if (metadata.isChunk && metadata.originalId) {
1646+
await this.createKnowledgeChunk({
1647+
id: knowledge.id,
1648+
originalId: metadata.originalId,
1649+
agentId: metadata.isShared ? null : knowledge.agentId,
1650+
content: knowledge.content,
1651+
embedding: knowledge.embedding,
1652+
chunkIndex: metadata.chunkIndex || 0,
1653+
isShared: metadata.isShared || false,
1654+
createdAt: knowledge.createdAt || Date.now(),
1655+
});
1656+
} else {
1657+
// This is a main knowledge item
1658+
await client.query(
1659+
`
1660+
INSERT INTO knowledge (
1661+
id, "agentId", content, embedding, "createdAt",
1662+
"isMain", "originalId", "chunkIndex", "isShared"
1663+
) VALUES ($1, $2, $3, $4, to_timestamp($5/1000.0), $6, $7, $8, $9)
1664+
ON CONFLICT (id) DO NOTHING
1665+
`,
1666+
[
1667+
knowledge.id,
1668+
metadata.isShared ? null : knowledge.agentId,
1669+
knowledge.content,
1670+
vectorStr,
1671+
knowledge.createdAt || Date.now(),
1672+
true,
1673+
null,
1674+
null,
1675+
metadata.isShared || false,
1676+
]
1677+
);
1678+
}
16501679

1651-
await client.query('COMMIT');
1680+
await client.query("COMMIT");
16521681
} catch (error) {
1653-
await client.query('ROLLBACK');
1682+
await client.query("ROLLBACK");
16541683
throw error;
16551684
} finally {
16561685
client.release();
@@ -1660,19 +1689,100 @@ export class PostgresDatabaseAdapter
16601689

16611690
async removeKnowledge(id: UUID): Promise<void> {
16621691
return this.withDatabase(async () => {
1663-
await this.pool.query('DELETE FROM knowledge WHERE id = $1', [id]);
1692+
const client = await this.pool.connect();
1693+
try {
1694+
await client.query("BEGIN");
1695+
1696+
// Check if this is a pattern-based chunk deletion (e.g., "id-chunk-*")
1697+
if (typeof id === "string" && id.includes("-chunk-*")) {
1698+
const mainId = id.split("-chunk-")[0];
1699+
// Delete chunks for this main ID
1700+
await client.query(
1701+
'DELETE FROM knowledge WHERE "originalId" = $1',
1702+
[mainId]
1703+
);
1704+
} else {
1705+
// First delete all chunks associated with this knowledge item
1706+
await client.query(
1707+
'DELETE FROM knowledge WHERE "originalId" = $1',
1708+
[id]
1709+
);
1710+
// Then delete the main knowledge item
1711+
await client.query("DELETE FROM knowledge WHERE id = $1", [
1712+
id,
1713+
]);
1714+
}
1715+
1716+
await client.query("COMMIT");
1717+
} catch (error) {
1718+
await client.query("ROLLBACK");
1719+
elizaLogger.error("Error removing knowledge", {
1720+
error:
1721+
error instanceof Error ? error.message : String(error),
1722+
id,
1723+
});
1724+
throw error;
1725+
} finally {
1726+
client.release();
1727+
}
16641728
}, "removeKnowledge");
16651729
}
16661730

16671731
async clearKnowledge(agentId: UUID, shared?: boolean): Promise<void> {
16681732
return this.withDatabase(async () => {
1669-
const sql = shared ?
1670-
'DELETE FROM knowledge WHERE ("agentId" = $1 OR "isShared" = true)' :
1671-
'DELETE FROM knowledge WHERE "agentId" = $1';
1733+
const sql = shared
1734+
? 'DELETE FROM knowledge WHERE ("agentId" = $1 OR "isShared" = true)'
1735+
: 'DELETE FROM knowledge WHERE "agentId" = $1';
16721736

16731737
await this.pool.query(sql, [agentId]);
16741738
}, "clearKnowledge");
16751739
}
1740+
1741+
private async createKnowledgeChunk(params: {
1742+
id: UUID;
1743+
originalId: UUID;
1744+
agentId: UUID | null;
1745+
content: any;
1746+
embedding: Float32Array | undefined | null;
1747+
chunkIndex: number;
1748+
isShared: boolean;
1749+
createdAt: number;
1750+
}): Promise<void> {
1751+
const vectorStr = params.embedding
1752+
? `[${Array.from(params.embedding).join(",")}]`
1753+
: null;
1754+
1755+
// Store the pattern-based ID in the content metadata for compatibility
1756+
const patternId = `${params.originalId}-chunk-${params.chunkIndex}`;
1757+
const contentWithPatternId = {
1758+
...params.content,
1759+
metadata: {
1760+
...params.content.metadata,
1761+
patternId,
1762+
},
1763+
};
1764+
1765+
await this.pool.query(
1766+
`
1767+
INSERT INTO knowledge (
1768+
id, "agentId", content, embedding, "createdAt",
1769+
"isMain", "originalId", "chunkIndex", "isShared"
1770+
) VALUES ($1, $2, $3, $4, to_timestamp($5/1000.0), $6, $7, $8, $9)
1771+
ON CONFLICT (id) DO NOTHING
1772+
`,
1773+
[
1774+
v4(), // Generate a proper UUID for PostgreSQL
1775+
params.agentId,
1776+
contentWithPatternId, // Store the pattern ID in metadata
1777+
vectorStr,
1778+
params.createdAt,
1779+
false,
1780+
params.originalId,
1781+
params.chunkIndex,
1782+
params.isShared,
1783+
]
1784+
);
1785+
}
16761786
}
16771787

16781788
export default PostgresDatabaseAdapter;

0 commit comments

Comments
 (0)