feat(js/plugins/goodmem): add GoodMem plugin#5373
Conversation
Drop two provenance comments and four em dashes from the plugin source. Reframe the goodmem() JSDoc and the package description to mark GoodMem as a retrieval-augmented generation memory backend. The test file picks up one inline em dash fix and an alphabetical import reorder. No behavior changes; tests pass 25 of 25.
…emories
Expose seven server-side GoodMem features as optional parameters across two
tools.
retrieve_memories gains:
- metadataFilter: SQL-style JSONPath expression applied server-side to every
space in spaceIds. Enables category, tag, or any-metadata filtering at
query time.
- maxWaitSeconds and pollInterval: per-call polling configuration. The
previous 10s wait and 2s poll were hardcoded.
list_memories gains:
- statusFilter, includeContent, sortBy, sortOrder. These map to the matching
server-side query parameters on GET /v1/spaces/{spaceId}/memories.
Nine new unit tests cover the additions: three for metadataFilter (applied
to every space, omitted when unset, empty string treated as unset), two for
the polling timeouts (custom values honored, polling skipped when
waitForIndexing is false), and four for the list_memories query string (all
filters set, none set, includeContent=false omitted, partial set). Test
suite: 34 of 34 pass.
- Reframe the intro: GoodMem is a retrieval-augmented generation memory backend for AI agents. - Per-tool input bullets use colons instead of em dashes. - Document the seven new optional parameters on retrieve_memories (metadataFilter, maxWaitSeconds, pollInterval) and list_memories (statusFilter, includeContent, sortBy, sortOrder). - Rephrase the waitForIndexing description so it points at maxWaitSeconds rather than the hardcoded 10 seconds. - Add a pointer to the runnable demo at js/testapps/goodmem/.
Add a runnable demo at js/testapps/goodmem that exercises the plugin against
a live GoodMem server and an OpenAI-compatible model. Three scenarios:
1. Persistent project knowledge. Stores five facts about a fictional company,
retrieves the top chunks for a semantic query, and asks the model to
answer the question grounded in the retrieved context.
2. Scribe and analyst pipeline. The scribe stores team notes inside one
Genkit span (role=scribe); the analyst retrieves and summarizes them
inside another (role=analyst). Roles show up as labels in the trace.
3. Metadata-driven retrieval. Stores release entries tagged by category and
uses the metadataFilter parameter to pull back only the 'feat' entries.
Each scenario creates its own space; the cleanup phase deletes all three at
the end. Two run modes:
pnpm demo plain tsx, prints to stdout
pnpm genkit:start wraps with the Genkit Developer UI at
http://localhost:4000 for trace inspection
The lockfile picks up the new workspace member. It also reconciles the
existing goodmem plugin entry: the lockfile previously listed genkit under
dependencies, but the plugin's package.json declares it only under
devDependencies and peerDependencies. pnpm install brought the two back into
sync.
- Scenario 1 stores three project notes about a customer support assistant on Python 3.12, FastAPI, Postgres, and pytest with 80% coverage. The question is "Remind me what our coverage requirement is." - Scenario 2's team notes use "under 2 hours" instead of "under two hours" and "Team retro:" instead of "Retro outcome:". The analyst asks "What do we know about our services and current priorities?" - Scenario 3 asks "Show me the new features we've shipped." Release entries are unchanged. - Section titles and the module docstring read as: persistent project context across sessions, two-role team knowledge pipeline, structured team activity log. Live run still produces correct grounded answers across all three scenarios.
…ntegration Feature/improve goodmem integration
# Conflicts: # js/pnpm-lock.yaml
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request introduces the genkitx-goodmem plugin, which integrates the GoodMem RAG memory service into Genkit by providing 11 specialized tools for managing spaces and memories. The implementation includes a comprehensive test suite and a demo application showcasing various RAG scenarios. Review feedback recommends replacing synchronous file system operations with asynchronous ones to avoid blocking the event loop and correcting error handling logic that incorrectly attempts to access properties on standard Error objects. Additionally, a discrepancy in the default base URL protocol within the demo application was identified for correction to ensure local connectivity.
| if (!fs.existsSync(filePath)) { | ||
| return { | ||
| success: false, | ||
| error: `File not found: ${filePath}`, | ||
| }; | ||
| } | ||
| const fileBuffer = fs.readFileSync(filePath); | ||
| const base64 = fileBuffer.toString('base64'); | ||
| const ext = path.extname(filePath).replace('.', ''); | ||
| const detectedMime = getMimeType(ext); | ||
| const mimeType = detectedMime || 'application/octet-stream'; | ||
| fileName = path.basename(filePath); | ||
|
|
||
| if (mimeType.startsWith('text/')) { | ||
| requestBody.contentType = mimeType; | ||
| requestBody.originalContent = fileBuffer.toString('utf-8'); | ||
| } else { | ||
| requestBody.contentType = mimeType; | ||
| requestBody.originalContentB64 = base64; | ||
| } | ||
| } else if (textContent) { |
There was a problem hiding this comment.
Using synchronous file system methods like fs.existsSync and fs.readFileSync blocks the Node.js event loop, which can impact performance. It is recommended to use the asynchronous fs.promises API. Additionally, when a tool fails, instead of returning an error object, add a message to the history queue to inform the LLM of the error and return control, allowing the LLM to attempt a correction. Minor optimizations regarding the placement of base64 conversion should be avoided if the current logic is functionally correct.
if (filePath) {
try {
const fileBuffer = await fs.promises.readFile(filePath);
const ext = path.extname(filePath).replace('.', '');
const detectedMime = getMimeType(ext);
const mimeType = detectedMime || 'application/octet-stream';
fileName = path.basename(filePath);
requestBody.contentType = mimeType;
if (mimeType.startsWith('text/')) {
requestBody.originalContent = fileBuffer.toString('utf-8');
} else {
requestBody.originalContentB64 = fileBuffer.toString('base64');
}
} catch (e: any) {
history.push({ role: 'user', content: `Failed to read file at ${filePath}: ${e.message}` });
return;
}
}References
- When a tool fails, instead of throwing an error or returning a ToolResponse, add a message to the history queue to inform the LLM of the error and return control.
- Avoid making changes for minor optimizations if the existing code is functionally correct.
| success: false, | ||
| error: error.message || 'Failed to list embedders', | ||
| details: error.response?.body || String(error), | ||
| }; |
There was a problem hiding this comment.
The code attempts to access error.response?.body, but the apiJson and apiFetch helpers throw standard Error objects. Furthermore, when a tool fails, instead of returning a failure response or letting the error propagate, add a message to the history queue to inform the LLM of the error and return control, allowing it to attempt a correction.
References
- When a tool fails, instead of throwing an error or returning a ToolResponse, add a message to the history queue to inform the LLM of the error and return control.
| import { runInNewSpan } from 'genkit/tracing'; | ||
| import { goodmem } from 'genkitx-goodmem'; | ||
|
|
||
| const BASE_URL = process.env.GOODMEM_BASE_URL ?? 'https://localhost:8080'; |
There was a problem hiding this comment.
The default BASE_URL uses https://localhost:8080, while the plugin's README and typical local GoodMem setups use http://localhost:8080. This discrepancy may cause connection failures for users running the demo locally without a custom environment variable.
| const BASE_URL = process.env.GOODMEM_BASE_URL ?? 'https://localhost:8080'; | |
| const BASE_URL = process.env.GOODMEM_BASE_URL ?? 'http://localhost:8080'; |
|
Hey @MohamedKhalil8801 - excited to see this come to Genkit, thanks for the contribution! We're not accepting 3P plugin submissions to the repo at this time. Our recommendation is to publish it to NPM with the prefix |
- Switch create_memory from fs.existsSync + fs.readFileSync to fs.promises.readFile so the tool does not block the Node.js event loop while reading file content from disk. ENOENT is still surfaced as a "File not found" success:false response; other read errors are reported with their message. - Drop the dead `error.response?.body` access from every tool's catch block. apiJson and apiFetch throw plain Error objects, so .response is always undefined. String(error) carries the formatted "GoodMem API error (status): detail" message on its own. - Update the plugin README and the goodmem() JSDoc examples to use https://localhost:8080, matching the demo testapp default and the typical GoodMem deployment. All 34 unit tests still pass.
|
Closing per my earlier explanation. Thanks again! |
Closes #5372.
Adds a first-party
genkitx-goodmemplugin that exposes GoodMem, a retrieval-augmented generation (RAG) memory service, as Genkit tools.Contents
js/plugins/goodmem/: plugin source plus 34 unit tests with mockedfetch.js/testapps/goodmem/: runnable demo with three scenarios (persistent project context across sessions, a scribe-and-analyst pipeline using Genkit span enrichment, and a structured team activity log with metadata-driven retrieval).11 tools are registered under the
goodmem/namespace:list_embedders,list_spaces,get_space,create_space,update_space,delete_space,create_memory,list_memories,retrieve_memories,get_memory,delete_memory.Usage
Once loaded, the tools work with any Genkit agent or flow. For example, retrieving memories scoped by metadata:
See
js/plugins/goodmem/README.mdfor the full tool reference andjs/testapps/goodmem/src/index.tsfor the three-scenario demo.Verification
pnpm buildinjs/plugins/goodmemandjs/testapps/goodmem: clean.pnpm testinjs/plugins/goodmem: 34 / 34 pass.gpt-4o-miniproduces correct grounded answers across all three scenarios.Checklist (if applicable):