Add JSON Schema
This commit is contained in:
@@ -146,6 +146,21 @@ export const languageEnglish = {
|
||||
customCSS: "Custom CSS for styling. you can also disable/enable it by pressing (Ctrl + .) if something goes wrong.",
|
||||
betaMobileGUI: "If enabled, it will use beta mobile GUI on small (less than 800px) screens. requires refresh.",
|
||||
unrecommended: "This is a unrecommended setting. it is not recommended to use this setting.",
|
||||
jsonSchema: "This is a JSON Schema that will be sent to the AI model if AI model supports JSON Schema.\n\nHowever, since JSON Schema is hard to learn, In RisuAI, you can use subset of TypeScript interface instead of JSON Schema. RisuAI will convert it in runtime." +
|
||||
"For example, if you want to send a JSON like this:\n\n```js\n{\n \"name\": \"RisuAI\", //name must be RisuAI,\n \"age\": 1, //age must be number,\n \"icon\": \"slim\", //icon must be \'slim\' or 'rounded'\n \"thoughts\": [\"Good View!\", \"Lorem\"] //thoughts must be array of strings\n}\n```\n\n" +
|
||||
"You can put this TypeScript interface:\n\n```typescript\ninterface Schema {\n name: string;\n age: number;\n icon: \'slim\'|\'rounded\'\n thoughts: string[]\n}\n```\n\n" +
|
||||
"Name of the interface doesn't matter. for more information, see the typescript documentation. (https://www.typescriptlang.org/docs/handbook/interfaces.html), and to Check what subset of TypeScript is supported, see the below." +
|
||||
"<details><summary>Supported TypeScript Subset</summary>\n\n" +
|
||||
`Supported types are \`boolean\`, \`number\`, \`string\`, \`Array\`. Advanced typing like unit types, intersection types, union types, optional, literal types, and etc. are not supported except for these cases:\n
|
||||
- Array of primitive types: (ex. \`string[]\`, \`Array<boolean>)\`
|
||||
- Unit types between strings: (ex. \`'slim'|'rounded'\`).
|
||||
|
||||
Properties must be one in a line. if there is multiple properties in a line, it will throw an error. Properties and name of the interface must be only in latin characters, in ASCII range. name of the properties must not be surrounded by quotes or double quotes. Nesting inside the interface is not supported. it is not allowed to put \`{\` or \`}\` in the line that properties are defined. If you want to use more advanced types, use JSON Schema instead.
|
||||
` +
|
||||
"</details>"
|
||||
,
|
||||
strictJsonSchema: "If enabled, it will strictly follow the Provided Schema for JSON on some models. if it is disabled, it may ignore the JSON Schema.",
|
||||
extractJson: "If it is not blank, it will extract specific JSON data from the response. for example, if you want to extract `response.text[0]` in response `{\"response\": {\"text\": [\"hello\"]}}`, you can put `response.text.0`.",
|
||||
},
|
||||
setup: {
|
||||
chooseProvider: "Choose AI Provider",
|
||||
@@ -715,4 +730,8 @@ export const languageEnglish = {
|
||||
connectionHost: "You are the host of the room.",
|
||||
connectionGuest: "You are the guest of the room.",
|
||||
otherUserRequesting: "Other user is already requesting. try again later.",
|
||||
jsonSchema: "JSON Schema",
|
||||
enableJsonSchema: "Enable Schema",
|
||||
strictJsonSchema: "Strict Schema",
|
||||
extractJson: "Extract JSON",
|
||||
}
|
||||
@@ -114,6 +114,9 @@
|
||||
<Check bind:check={$DataBase.promptSettings.sendChatAsSystem} name={language.sendChatAsSystem} className="mt-4"/>
|
||||
<Check bind:check={$DataBase.promptSettings.sendName} name={language.sendName} className="mt-4"/>
|
||||
<Check bind:check={$DataBase.promptSettings.utilOverride} name={language.utilOverride} className="mt-4"/>
|
||||
<Check bind:check={$DataBase.jsonSchemaEnabled} name={language.enableJsonSchema} className="mt-4"/>
|
||||
<Check bind:check={$DataBase.strictJsonSchema} name={language.strictJsonSchema} className="mt-4"/>
|
||||
|
||||
{#if $DataBase.showUnrecommended}
|
||||
<Check bind:check={$DataBase.promptSettings.customChainOfThought} name={language.customChainOfThought} className="mt-4">
|
||||
<Help unrecommended key='customChainOfThought' />
|
||||
@@ -125,4 +128,10 @@
|
||||
<TextAreaInput bind:value={$DataBase.customPromptTemplateToggle}/>
|
||||
<span class="text-textcolor mt-4">{language.defaultVariables} <Help key='defaultVariables' /></span>
|
||||
<TextAreaInput bind:value={$DataBase.templateDefaultVariables}/>
|
||||
{#if $DataBase.jsonSchemaEnabled}
|
||||
<span class="text-textcolor mt-4">{language.jsonSchema} <Help key='jsonSchema' /></span>
|
||||
<TextAreaInput bind:value={$DataBase.jsonSchema}/>
|
||||
<span class="text-textcolor mt-4">{language.extractJson} <Help key='extractJson' /></span>
|
||||
<TextInput bind:value={$DataBase.extractJson}/>
|
||||
{/if}
|
||||
{/if}
|
||||
@@ -22,6 +22,7 @@ import {createParser} from 'eventsource-parser'
|
||||
import {Ollama} from 'ollama/dist/browser.mjs'
|
||||
import { applyChatTemplate } from "./templates/chatTemplate";
|
||||
import { OobaParams } from "./prompt";
|
||||
import { extractJSON, getOpenAIJSONSchema } from "./templates/jsonSchema";
|
||||
|
||||
|
||||
|
||||
@@ -485,13 +486,9 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
: (!requestModel) ? 'gpt-3.5-turbo'
|
||||
: requestModel,
|
||||
messages: formatedChat,
|
||||
// temperature: temperature,
|
||||
max_tokens: maxTokens,
|
||||
// presence_penalty: arg.PresensePenalty || (db.PresensePenalty / 100),
|
||||
// frequency_penalty: arg.frequencyPenalty || (db.frequencyPenalty / 100),
|
||||
logit_bias: bias,
|
||||
stream: false,
|
||||
// top_p: db.top_p,
|
||||
|
||||
})
|
||||
|
||||
@@ -503,6 +500,13 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
body.user = getOpenUserString()
|
||||
}
|
||||
|
||||
if(db.jsonSchemaEnabled){
|
||||
body.response_format = {
|
||||
"type": "json_schema",
|
||||
"json_schema": getOpenAIJSONSchema()
|
||||
}
|
||||
}
|
||||
|
||||
if(aiModel === 'openrouter'){
|
||||
if(db.openrouterFallback){
|
||||
body.route = "fallback"
|
||||
@@ -641,6 +645,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
const transtream = new TransformStream<Uint8Array, StreamResponseChunk>( {
|
||||
async transform(chunk, control) {
|
||||
dataUint = Buffer.from(new Uint8Array([...dataUint, ...chunk]))
|
||||
let JSONreaded:{[key:string]:string} = {}
|
||||
try {
|
||||
const datas = dataUint.toString().split('\n')
|
||||
let readed:{[key:string]:string} = {}
|
||||
@@ -649,7 +654,17 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
try {
|
||||
const rawChunk = data.replace("data: ", "")
|
||||
if(rawChunk === "[DONE]"){
|
||||
control.enqueue(readed)
|
||||
if(db.extractJson && db.jsonSchemaEnabled){
|
||||
for(const key in readed){
|
||||
const extracted = extractJSON(readed[key], db.extractJson)
|
||||
JSONreaded[key] = extracted
|
||||
}
|
||||
console.log(JSONreaded)
|
||||
control.enqueue(JSONreaded)
|
||||
}
|
||||
else{
|
||||
control.enqueue(readed)
|
||||
}
|
||||
return
|
||||
}
|
||||
const choices = JSON.parse(rawChunk).choices
|
||||
@@ -674,7 +689,17 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
control.enqueue(readed)
|
||||
if(db.extractJson && db.jsonSchemaEnabled){
|
||||
for(const key in readed){
|
||||
const extracted = extractJSON(readed[key], db.extractJson)
|
||||
JSONreaded[key] = extracted
|
||||
}
|
||||
console.log(JSONreaded)
|
||||
control.enqueue(JSONreaded)
|
||||
}
|
||||
else{
|
||||
control.enqueue(readed)
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
@@ -741,6 +766,21 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
if(res.ok){
|
||||
try {
|
||||
if(multiGen && dat.choices){
|
||||
if(db.extractJson && db.jsonSchemaEnabled){
|
||||
|
||||
const c = dat.choices.map((v:{
|
||||
message:{content:string}
|
||||
}) => {
|
||||
const extracted = extractJSON(v.message.content, db.extractJson)
|
||||
return ["char",extracted]
|
||||
})
|
||||
|
||||
return {
|
||||
type: 'multiline',
|
||||
result: c
|
||||
}
|
||||
|
||||
}
|
||||
return {
|
||||
type: 'multiline',
|
||||
result: dat.choices.map((v) => {
|
||||
@@ -751,11 +791,33 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
}
|
||||
|
||||
if(dat?.choices[0]?.text){
|
||||
if(db.extractJson && db.jsonSchemaEnabled){
|
||||
try {
|
||||
const parsed = JSON.parse(dat.choices[0].text)
|
||||
const extracted = extractJSON(parsed, db.extractJson)
|
||||
return {
|
||||
type: 'success',
|
||||
result: extracted
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return {
|
||||
type: 'success',
|
||||
result: dat.choices[0].text
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'success',
|
||||
result: dat.choices[0].text
|
||||
}
|
||||
}
|
||||
if(db.extractJson && db.jsonSchemaEnabled){
|
||||
return {
|
||||
type: 'success',
|
||||
result: extractJSON(dat.choices[0].message.content, db.extractJson)
|
||||
}
|
||||
}
|
||||
const msg:OpenAIChatFull = (dat.choices[0].message)
|
||||
return {
|
||||
type: 'success',
|
||||
|
||||
@@ -144,7 +144,6 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
const matchAll = isGlobal ? data.matchAll(reg) : [data.match(reg)]
|
||||
data = data.replace(reg, "")
|
||||
for(const matched of matchAll){
|
||||
console.log(matched)
|
||||
if(matched){
|
||||
const inData = matched[0]
|
||||
let out = outScript.replace('@@move_top ', '').replace('@@move_bottom ', '')
|
||||
@@ -163,7 +162,6 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
}
|
||||
return v
|
||||
})
|
||||
console.log(out)
|
||||
if(outScript.startsWith('@@move_top') || pscript.actions.includes('move_top')){
|
||||
data = out + '\n' +data
|
||||
}
|
||||
@@ -257,8 +255,6 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
})
|
||||
}
|
||||
|
||||
console.log(parsedScripts)
|
||||
|
||||
if(orderChanged){
|
||||
parsedScripts.sort((a, b) => b.order - a.order) //sort by order
|
||||
}
|
||||
|
||||
172
src/ts/process/templates/jsonSchema.ts
Normal file
172
src/ts/process/templates/jsonSchema.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { risuChatParser } from "src/ts/parser"
|
||||
import { DataBase } from "src/ts/storage/database"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
export function convertInterfaceToSchema(int:string){
|
||||
if(!int.startsWith('interface ') && !int.startsWith('export interface ')){
|
||||
return JSON.parse(int)
|
||||
}
|
||||
|
||||
int = risuChatParser(int)
|
||||
|
||||
type SchemaProp = {
|
||||
"type": "array"|"string"|"number"|"boolean",
|
||||
"items"?:SchemaProp
|
||||
"enum"?:string[]
|
||||
"const"?:string
|
||||
}
|
||||
|
||||
const lines = int.split('\n')
|
||||
let schema = {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {} as {[key:string]:SchemaProp},
|
||||
"required": [] as string[],
|
||||
}
|
||||
for(let i = 1; i < lines.length; i++){
|
||||
let content = lines[i].trim()
|
||||
if(content === '{'){
|
||||
continue
|
||||
}
|
||||
if(content === '}'){
|
||||
continue
|
||||
}
|
||||
if(content === ''){
|
||||
continue
|
||||
}
|
||||
|
||||
let placeHolders:string[] = []
|
||||
|
||||
content = content
|
||||
.replace(/\\"/gu, '\uE9b4a')
|
||||
.replace(/\\'/gu, '\uE9b4b')
|
||||
.replace(/"(.+?)"/gu, function(match, p1){
|
||||
placeHolders.push(match)
|
||||
return `\uE9b4d${placeHolders.length - 1}`
|
||||
})
|
||||
.replace(/'(.+?)'/gu, function(match, p1){
|
||||
placeHolders.push(`"${p1}"`)
|
||||
return `\uE9b4d${placeHolders.length - 1}`
|
||||
})
|
||||
|
||||
.split('//')[0].trim() //remove comments
|
||||
|
||||
.replace(/((number)|(string)|(boolean))\[\]/gu, 'Array<$1>')
|
||||
|
||||
if(content.endsWith(',') || content.endsWith(';')){
|
||||
content = content.slice(0, -1)
|
||||
}
|
||||
|
||||
let spData = content.replace(/ /g, '').split(':')
|
||||
|
||||
if(spData.length !== 2){
|
||||
throw "SyntaxError Found"
|
||||
}
|
||||
|
||||
let [property,typeData] = spData
|
||||
|
||||
switch(typeData){
|
||||
case 'string':
|
||||
case 'number':
|
||||
case 'boolean':{
|
||||
schema.properties[property] = {
|
||||
type: typeData
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Array<string>':
|
||||
case 'Array<number>':
|
||||
case 'Array<boolean>':{
|
||||
const ogType = typeData.slice(6,-1)
|
||||
|
||||
schema.properties[property] = {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: ogType as 'string'|'number'|'boolean'
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
default:{
|
||||
const types = typeData.split("|")
|
||||
const strings:string[] = []
|
||||
for(const t of types){
|
||||
if(!t.startsWith('\uE9b4d')){
|
||||
throw "Unsupported Type Detected"
|
||||
}
|
||||
const textIndex = t.replace('\uE9b4d','')
|
||||
const text = placeHolders[parseInt(textIndex)]
|
||||
const textParsed = JSON.parse(text.replace(/\uE9b4a/gu, '\\"').replace(/\uE9b4b/gu, "\\'"))
|
||||
strings.push(textParsed)
|
||||
}
|
||||
if(strings.length === 1){
|
||||
schema.properties[property] = {
|
||||
type: 'string',
|
||||
const: strings[0]
|
||||
}
|
||||
}
|
||||
else{
|
||||
schema.properties[property] = {
|
||||
type: 'string',
|
||||
enum: strings
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
schema.required.push(property)
|
||||
|
||||
}
|
||||
return schema
|
||||
}
|
||||
|
||||
export function getOpenAIJSONSchema(){
|
||||
const db = get(DataBase)
|
||||
const schema = {
|
||||
"name": "format",
|
||||
"strict": db.strictJsonSchema,
|
||||
"schema": convertInterfaceToSchema(db.jsonSchema)
|
||||
}
|
||||
return schema
|
||||
}
|
||||
|
||||
export function extractJSON(data:string, format:string){
|
||||
const extract = (data:any, format:string) => {
|
||||
try {
|
||||
if(data === undefined || data === null){
|
||||
return ''
|
||||
}
|
||||
|
||||
const fp = format.split('.')
|
||||
const current = data[fp[0]]
|
||||
|
||||
if(current === undefined){
|
||||
return ''
|
||||
}
|
||||
else if(fp.length === 1){
|
||||
return `${current ?? ''}`
|
||||
}
|
||||
else if(typeof current === 'object'){
|
||||
return extractJSON(current, fp.slice(1).join('.'))
|
||||
}
|
||||
else if(Array.isArray(current)){
|
||||
const index = parseInt(fp[1])
|
||||
return extractJSON(current[index], fp.slice(1).join('.'))
|
||||
}
|
||||
else{
|
||||
return `${current ?? ''}`
|
||||
}
|
||||
} catch (error) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
try {
|
||||
format = risuChatParser(format)
|
||||
data = data.trim()
|
||||
if(data.startsWith('{')){
|
||||
return extract(JSON.parse(data), format)
|
||||
}
|
||||
} catch (error) {}
|
||||
return data
|
||||
}
|
||||
@@ -435,6 +435,7 @@ export function setDatabase(data:Database){
|
||||
data.falModel ??= 'fal-ai/flux/dev'
|
||||
data.falLoraScale ??= 1
|
||||
data.customCSS ??= ''
|
||||
data.strictJsonSchema ??= true
|
||||
changeLanguage(data.language)
|
||||
DataBase.set(data)
|
||||
}
|
||||
@@ -729,6 +730,10 @@ export interface Database{
|
||||
moduleIntergration: string
|
||||
customCSS: string
|
||||
betaMobileGUI:boolean
|
||||
jsonSchemaEnabled:boolean
|
||||
jsonSchema:string
|
||||
strictJsonSchema:boolean
|
||||
extractJson:string
|
||||
}
|
||||
|
||||
export interface customscript{
|
||||
|
||||
Reference in New Issue
Block a user