Add JSON Schema

This commit is contained in:
kwaroran
2024-09-11 06:08:21 +09:00
parent 09b3859006
commit 5b6c3e0d04
6 changed files with 273 additions and 10 deletions

View File

@@ -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',

View File

@@ -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
}

View 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
}