UNPKG

@convo-lang/convo-lang

Version:
263 lines (248 loc) 8.99 kB
import { createJsonRefReplacer, getErrorMessage, pushBehaviorSubjectAry } from '@iyio/common'; import { parseJson5 } from '@iyio/json5'; import { BehaviorSubject } from 'rxjs'; import { Conversation } from '../Conversation.js'; import { convoAnyModelName } from '../convo-lib.js'; export class ConvoModelTester { get testResultsSubject() { return this._testResults; } get testResults() { return this._testResults.value; } constructor({ initConvo, convoOptions = {}, model, skipDefineModel = false, manager, tests, }) { this._testResults = new BehaviorSubject([]); this.createDebugger = (output) => (...values) => { for (const v of values) { try { const type = typeof v; if (type === 'string') { output.push(v); } else if (type === 'object') { try { output.push(JSON.stringify(v, null, 4)); } catch { try { output.push(JSON.stringify(v, createJsonRefReplacer(), 4)); } catch { output.push(v + ''); } } } else { output.push(v + ''); } } catch { output.push('!! Unable to push debug output'); } } }; this.test_sayHi = () => { return this.runTestAsync({ name: 'sayHi', description: 'Test to see if a simple message of hi completes', format: null, expectedRoles: ['assistant'] }, /*convo*/ ` > user hi `); }; this.test_jsonSchema = () => { return this.runTestAsync({ name: 'jsonSchema', description: 'Test JSON mode using a schema', format: 'json', }, /*convo*/ ` > define Car=struct( name: string topSpeedMPH: number color: string ) @json Car > user A fast red car `); }; this.test_jsonArray = () => { return this.runTestAsync({ name: 'jsonArray', description: 'Test JSON mode for returning an array', format: 'json', isJsonArray: true, }, /*convo*/ ` > define Planet=struct( name: string # Distance from sun in miles distanceFromSun: number; ) @json Planet[] > user List the planets in our solar system `); }; this.test_callAddNumbers = () => { return this.runTestAsync({ name: 'callAddNumbers', description: 'Calls a functions to add numbers', callFunction: 'addNumbers', returnValue: 19, }, /*convo*/ ` > addNumbers( a:number b:number ) -> ( return(add(a b)) ) > user Add 7 plus 12 by calling the addNumbers function `); }; this.test_assistantFirst = () => { return this.runTestAsync({ name: 'assistantFirst', description: 'Starts a conversation with an assistant message first', }, /*convo*/ ` > assistant How can I help you? > user What color is the sky on a sunny day? `); }; this.options = { initConvo, convoOptions, model, skipDefineModel, manager, tests, }; } async runAllTestsAsync() { for (const name of this.getTestNames()) { await this['test_' + name](); } } getTestNames() { const names = []; for (const e in this) { if (e.startsWith('test_')) { names.push(e.substring(5)); } } if (this.options.tests) { return names.filter(t => this.options.tests?.includes(t)); } else { return names; } } createConvo(debug, convoOptions) { const convo = new Conversation({ disableAutoFlatten: true, debug, ...this.options.convoOptions, ...convoOptions }); if (!this.options.skipDefineModel && this.options.model !== convoAnyModelName) { convo.append(`> define\n__model=${JSON.stringify(this.options.model)}`); } convo.append(`> define\n__debug=true\n__trackModel=true`); if (this.options.initConvo) { convo.append(this.options.initConvo); } return convo; } async runTestAsync({ name, expectedRoles, returnCount, format, isJsonArray, disablePublishResult, callFunction, functionCall = callFunction ? true : false, returnValue, convoOptions, }, source) { const debugOutput = []; const log = this.createDebugger(debugOutput); log(`## test start ${this.options.model}:${name}`); const convo = this.createConvo(log, convoOptions); const startTime = Date.now(); try { convo.append(source); const r = await convo.completeAsync(); const last = r.message; if (expectedRoles) { for (let i = 0; i < expectedRoles.length; i++) { const role = expectedRoles[i]; const msg = r.messages[i]; if (!msg) { throw new Error(`Returned result does not have a message at index ${i} for checking role`); } if (role === null || role === undefined) { continue; } if (msg.role !== role) { throw new Error(`Expected returned message at index (${i}) to have a role of ${msg.role}`); } } if (expectedRoles.length > r.messages.length) { throw new Error(`Expected returned messages has more messages that expected roles`); } } if (returnCount !== undefined && returnCount !== r.messages.length) { throw new Error(`Expected (${returnCount}) returned messages but found (${r.messages.length})`); } if (format && last?.format !== format) { throw new Error(`Expected returned message to have a format of (${format})`); } if (format === null && last?.format) { throw new Error('Expected returned message to not have a format'); } const jsonValue = last?.format === 'json' ? parseJson5(last.content ?? '') : undefined; if (isJsonArray && !Array.isArray(jsonValue)) { throw new Error(`Expected JSON array`); } if (callFunction && r.lastFnCall?.name !== callFunction) { throw new Error(`Expected (${callFunction}) function to be called`); } if (functionCall && !r.lastFnCall) { throw new Error(`Expected function to be called`); } if (returnValue !== undefined && returnValue !== r.lastFnCall?.returnValue) { throw new Error(`Expected return value was not returned.\nExpected:${JSON.stringify(returnValue)}\nReturned:${JSON.stringify(r.lastFnCall?.returnValue)}`); } const endConvo = convo.convo; log(`## test pass ${this.options.model}:${name}`); const result = { model: this.options.model, testName: name, convo: endConvo, passed: true, debugOutput, durationMs: Date.now() - startTime, }; if (!disablePublishResult) { this.publishResult(result); } return result; } catch (ex) { const endConvo = convo.convo; log(`## test failed ${this.options.model}:${name}`); const errorMessage = getErrorMessage(ex); const result = { model: this.options.model, testName: name, convo: endConvo, passed: false, errorMessage, errorStack: ex?.stack, debugOutput, durationMs: Date.now() - startTime, }; if (!disablePublishResult) { this.publishResult(result); } return result; } } publishResult(result) { pushBehaviorSubjectAry(this._testResults, result); this.options.manager?.publishResult(result); } } //# sourceMappingURL=ConvoModelTester.js.map