Skip to content

Commit 8ceac05

Browse files
authored
feat: add JSON schema grammar support (#68)
* feat: add JSON schema grammar support * test: `LlamaJsonSchemaGrammar` * feat: add JSON schema grammar support to the `chat` command * feat: add `promptWithMeta` function to `LlamaChatSession` * docs: document JSON schema grammar support * ci: remove redundant step * chore: eslint config fix
1 parent 8691585 commit 8ceac05

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1912
-83
lines changed

.eslintrc.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"browser": false,
66
"es6": true
77
},
8-
"ignorePatterns": ["/dist", "/llama", "/docs"],
8+
"ignorePatterns": ["/dist", "/llama", "/docs-site"],
99
"extends": [
1010
"eslint:recommended"
1111
],
@@ -40,6 +40,11 @@
4040
"@typescript-eslint/semi": ["warn", "always"],
4141
"@typescript-eslint/no-inferrable-types": ["off"]
4242
}
43+
}, {
44+
"files": ["test/**/**.ts"],
45+
"rules": {
46+
"max-len": ["off"]
47+
}
4348
}],
4449
"plugins": [
4550
"@typescript-eslint",

.github/workflows/build.yml

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,6 @@ jobs:
8989
name: llama.cpp
9090
path: llama/llama.cpp
9191

92-
- uses: actions/setup-python@v4
93-
with:
94-
python-version: "3.10"
95-
9692
- name: Install dependencies on windows
9793
if: startsWith(matrix.config.os, 'windows')
9894
run: |
@@ -204,6 +200,40 @@ jobs:
204200
name: "bins-${{ matrix.config.artifact }}"
205201
path: "llamaBins/*"
206202

203+
standalone-tests:
204+
name: Standalone tests
205+
runs-on: ubuntu-22.04
206+
needs:
207+
- build
208+
steps:
209+
- uses: actions/checkout@v3
210+
- uses: actions/setup-node@v3
211+
with:
212+
node-version: "20"
213+
214+
- name: Download build artifact
215+
uses: actions/download-artifact@v3
216+
with:
217+
name: build
218+
path: dist
219+
220+
- name: Download llama.cpp artifact
221+
uses: actions/download-artifact@v3
222+
with:
223+
name: llama.cpp
224+
path: llama/llama.cpp
225+
226+
- name: Install dependencies on ubuntu
227+
run: |
228+
sudo apt-get update
229+
sudo apt-get install ninja-build cmake
230+
231+
- name: Install modules
232+
run: npm ci
233+
234+
- name: Run standalone tests
235+
run: npm run test:standalone
236+
207237
release:
208238
name: Release
209239
if: github.ref == 'refs/heads/master'

.vitepress/config.ts

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,14 @@ export default defineConfig({
7373
}
7474
},
7575
transformPageData(pageData) {
76-
if (pageData.filePath.startsWith("api/") || pageData.filePath.startsWith("guide/cli/")) {
76+
if (pageData.filePath.startsWith("api/")) {
77+
pageData.frontmatter.editLink = false;
78+
pageData.frontmatter.lastUpdated = false;
79+
pageData.frontmatter ||= {}
80+
pageData.frontmatter.outline = [2, 3];
81+
}
82+
83+
if (pageData.filePath.startsWith("guide/cli/")) {
7784
pageData.frontmatter.editLink = false;
7885
pageData.frontmatter.lastUpdated = false;
7986
}
@@ -181,13 +188,20 @@ function getApiReferenceSidebar(): typeof typedocSidebar {
181188
}
182189

183190
function orderApiReferenceSidebar(sidebar: typeof typedocSidebar): typeof typedocSidebar {
191+
orderClasses(sidebar);
192+
orderTypes(sidebar);
193+
194+
return sidebar;
195+
}
196+
197+
function orderClasses(sidebar: typeof typedocSidebar) {
184198
const baseChatPromptWrapper = "ChatPromptWrapper";
185199
const chatPromptWrapperItems: DefaultTheme.SidebarItem[] = [];
186200

187201
const classes = sidebar.find((item) => item.text === "Classes");
188202

189203
if (classes == null || !(classes.items instanceof Array))
190-
return sidebar;
204+
return;
191205

192206
(classes.items as DefaultTheme.SidebarItem[]).unshift({
193207
text: "Chat wrappers",
@@ -222,7 +236,54 @@ function orderApiReferenceSidebar(sidebar: typeof typedocSidebar): typeof typedo
222236

223237
return aIndex - bIndex;
224238
});
225-
226-
return sidebar;
227239
}
228240

241+
function orderTypes(sidebar: typeof typedocSidebar) {
242+
const types = sidebar.find((item) => item.text === "Types");
243+
244+
if (types == null || !(types.items instanceof Array))
245+
return;
246+
247+
function groupGbnfJsonSchema() {
248+
if (types == null || !(types.items instanceof Array))
249+
return;
250+
251+
const gbnfJsonSchemaItemTitle = "GbnfJsonSchema";
252+
const gbnfItemsPrefix = "GbnfJson";
253+
const gbnfJsonSchemaItems: DefaultTheme.SidebarItem[] = [];
254+
255+
const gbnfJsonSchemaItem = types.items
256+
.find((item) => item.text === gbnfJsonSchemaItemTitle) as DefaultTheme.SidebarItem | null;
257+
258+
if (gbnfJsonSchemaItem == null)
259+
return;
260+
261+
gbnfJsonSchemaItem.collapsed = true;
262+
gbnfJsonSchemaItem.items = gbnfJsonSchemaItems;
263+
264+
for (const item of types.items.slice()) {
265+
if (item.text === gbnfJsonSchemaItemTitle || !item.text.startsWith(gbnfItemsPrefix))
266+
continue;
267+
268+
types.items.splice(types.items.indexOf(item), 1);
269+
gbnfJsonSchemaItems.push(item);
270+
}
271+
}
272+
273+
function moveCollapseItemsToTheEnd() {
274+
if (types == null || !(types.items instanceof Array))
275+
return;
276+
277+
types.items.sort((a, b) => {
278+
if (a.collapsed && !b.collapsed)
279+
return 1;
280+
if (!a.collapsed && b.collapsed)
281+
return -1;
282+
283+
return 0;
284+
});
285+
}
286+
287+
groupGbnfJsonSchema();
288+
moveCollapseItemsToTheEnd();
289+
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
* Chat with a model using a chat wrapper
2323
* Use the CLI to chat with a model without writing any code
2424
* Up-to-date with the latest version of `llama.cpp`. Download and compile the latest release with a single CLI command.
25-
* Force a model to generate output in a parseable format, like JSON
25+
* Force a model to generate output in a parseable format, like JSON, or even force it to follow a specific JSON schema
2626

2727
## [Documentation](https://withcatai.github.io/node-llama-cpp/)
2828
* [Getting started guide](https://withcatai.github.io/node-llama-cpp/guide/)

docs/guide/chat-session.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,3 +212,48 @@ const a2 = await session.prompt(q2, {
212212
console.log("AI: " + a2);
213213
console.log(JSON.parse(a2));
214214
```
215+
216+
## JSON response with schema
217+
To learn more about the JSON schema grammar, see the [grammar guide](./grammar.md#using-a-json-schema-grammar).
218+
```typescript
219+
import {fileURLToPath} from "url";
220+
import path from "path";
221+
import {
222+
LlamaModel, LlamaJsonSchemaGrammar, LlamaContext, LlamaChatSession
223+
} from "node-llama-cpp";
224+
225+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
226+
227+
const model = new LlamaModel({
228+
modelPath: path.join(__dirname, "models", "codellama-13b.Q3_K_M.gguf")
229+
})
230+
const grammar = new LlamaJsonSchemaGrammar({
231+
"type": "object",
232+
"properties": {
233+
"responseMessage": {
234+
"type": "string"
235+
},
236+
"requestPositivityScoreFromOneToTen": {
237+
"type": "number"
238+
}
239+
}
240+
} as const);
241+
const context = new LlamaContext({model});
242+
const session = new LlamaChatSession({context});
243+
244+
245+
const q1 = 'How are you doing?';
246+
console.log("User: " + q1);
247+
248+
const a1 = await session.prompt(q1, {
249+
grammar,
250+
maxTokens: context.getContextSize()
251+
});
252+
console.log("AI: " + a1);
253+
254+
const parsedA1 = grammar.parse(a1);
255+
console.log(
256+
parsedA1.responseMessage,
257+
parsedA1.requestPositivityScoreFromOneToTen
258+
);
259+
```

docs/guide/cli/build.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
---
2+
outline: deep
3+
---
14
# `build` command
25

36
<script setup lang="ts">

docs/guide/cli/chat.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
---
2+
outline: deep
3+
---
14
# `chat` command
25

36
<script setup lang="ts">

docs/guide/cli/clear.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
---
2+
outline: deep
3+
---
14
# `clear` command
25

36
<script setup lang="ts">

docs/guide/cli/cli.data.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1+
import {CommandModule} from "yargs";
12
import {getCommandHtmlDoc} from "../../../.vitepress/utils/getCommandHtmlDoc.js";
23
import {BuildCommand} from "../../../src/cli/commands/BuildCommand.js";
34
import {ChatCommand} from "../../../src/cli/commands/ChatCommand.js";
45
import {DownloadCommand} from "../../../src/cli/commands/DownloadCommand.js";
56
import {ClearCommand} from "../../../src/cli/commands/ClearCommand.js";
6-
import {CommandModule} from "yargs";
77
import {htmlEscape} from "../../../.vitepress/utils/htmlEscape.js";
88
import {cliBinName, npxRunPrefix} from "../../../src/config.js";
99
import {buildHtmlHeading} from "../../../.vitepress/utils/buildHtmlHeading.js";
@@ -28,7 +28,7 @@ export default {
2828
clear: await getCommandHtmlDoc(ClearCommand)
2929
};
3030
}
31-
}
31+
};
3232

3333
function buildIndexTable(commands: [pageLink: string, command: CommandModule<any, any>][], cliName: string = cliBinName) {
3434
let res = "";

docs/guide/cli/download.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
---
2+
outline: deep
3+
---
14
# `download` command
25

36
<script setup lang="ts">

0 commit comments

Comments
 (0)