ctod 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,11 +21,11 @@
21
21
 
22
22
  ## 摘要
23
23
 
24
- 本工具是利用聊天機器人能夠讀懂自然語言的特性,將我們的需求與資料透過口語化的方式交付給他處理,並要求回應可序列化格式,例如:JSON。
24
+ 本工具是利用聊天機器人能夠讀懂自然語言的特性,將我們的需求與資料透過口語化的方式交付給他處理,並要求回應 JSON。
25
25
 
26
- 在對話過程中,本工具採用 [yup](https://github.com/jquense/yup) 來驗證請求與回復資料是否符合預期,以確保一致性,只要保持這個互動模式,就可以利用在 API 串接或是自動化系統上。
26
+ 在對話過程中,採用 [yup](https://github.com/jquense/yup) 來驗證請求與回復資料是否符合預期,以確保一致性,只要保持這個互動模式,就可以利用在 API 串接或是自動化系統上。
27
27
 
28
- 我們還附帶支援 `OpenAI` 的相關服務。
28
+ 我們還附帶支援 `OpenAI` 與 `Llama3` 的相關服務。
29
29
 
30
30
  ## 安裝
31
31
 
@@ -49,51 +49,51 @@ yarn add ctod
49
49
 
50
50
  ```ts
51
51
  import { ChatBroker, OpenAI, templates } from 'ctod'
52
-
52
+
53
53
  const broker = new ChatBroker({
54
54
  /** 驗證輸入資料 */
55
- input: yup => {
55
+ input(yup) {
56
56
  return {
57
- indexs: yup.array(yup.string()).required(),
57
+ indexs: yup.array(yup.string().required()).required(),
58
58
  question: yup.string().required()
59
59
  }
60
60
  },
61
61
  /** 驗證輸出資料 */
62
- output: yup => {
62
+ output(yup) {
63
+ const item = yup.object({
64
+ name: yup.string().description('索引名稱').required(),
65
+ score: yup.number().description('評比分數').required()
66
+ }).required()
63
67
  return {
64
- indexs: yup.array(yup.object({
65
- name: yup.string().required(),
66
- score: yup.number().required()
67
- })).required()
68
+ indexs: yup.array(item).description('由高到低排序的索引').required()
68
69
  }
69
70
  },
70
71
  /** 初始化系統,通常來植入或掛鉤生命週期 */
71
- install: () => {},
72
+ install({ attach }) {
73
+ attach('start', async({ setPreMessages }) => {
74
+ setPreMessages([
75
+ {
76
+ role: 'system',
77
+ content: '你現在是一位擅長分類索引的藥師'
78
+ }
79
+ ])
80
+ })
81
+ },
72
82
  /** 定義發送請求的接口 */
73
- request: async(messages) => {
74
- const openai = new OpenAI(API_KEY)
75
- const chat = openai.createChat()
76
- const { text } = await chat.talk(messages)
77
- return text
78
- }
83
+ request: OpenAI.createChatRequestWithJsonSchema({
84
+ apiKey: API_KEY,
85
+ config: {
86
+ model: 'gpt-4o-mini'
87
+ }
88
+ }),
79
89
  /** 組裝與定義我們要向機器人發出的請求 */
80
90
  question: async({ indexs, question }) => {
81
- return templates.requireJsonResponse([
91
+ return [
82
92
  '我有以下索引',
83
93
  `${JSON.stringify(indexs)}`,
84
94
  `請幫我解析"${question}"可能是哪個索引`,
85
95
  '且相關性由高到低排序並給予分數,分數由 0 ~ 1'
86
- ], {
87
- indexs: {
88
- desc: '由高到低排序的索引',
89
- example: [
90
- {
91
- name: '索引名稱',
92
- score: '評比分數,數字顯示'
93
- }
94
- ]
95
- }
96
- })
96
+ ]
97
97
  }
98
98
  })
99
99
 
@@ -210,3 +210,12 @@ const broker = new ChatBroker({
210
210
 
211
211
  1. 支援 llama3.cpp server service
212
212
  2. 新增 yup to json scheme。
213
+
214
+ ### 0.5.x
215
+
216
+ 移除了 JSON Schema Info 的支援,而是透過 [yup-to-json-schema](https://github.com/sodaru/yup-to-json-schema) 進行生成資料格式。
217
+
218
+ 由於 `yup-to-json-schema` 的延伸套件要使用 `yup.string().description()` 方法需要進行全域註冊,在此我們提供了 `bindYupToJsonSchemaToYup` 這個方法,讓使用者可以自行決定是否要進行註冊。
219
+
220
+ 1. 可以在 question 中回應 array,會透過 join 進行合併。
221
+ 2. 可以省略 install 參數了。
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.ctod=t():e.ctod=t()}(this||("undefined"!=typeof window?window:global),(()=>(()=>{"use strict";var e={37:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ChatBroker=void 0;const r=n(15),a=n(470),o=n(87);t.ChatBroker=class{__hookType;log;hook=new a.Hook;params;plugins={};installed=!1;translator;event=new a.Event;constructor(e){this.log=new a.Log(e.name??"no name"),this.params=e,this.translator=new o.Translator({...e,parsers:[r.TextParser.JsonMessage()]})}_install(){if(!1===this.installed){this.installed=!0;const e={log:this.log,attach:this.hook.attach.bind(this.hook),attachAfter:this.hook.attachAfter.bind(this.hook),translator:this.translator};if(this.params.install(e),this.params.plugins){this.plugins="function"==typeof this.params.plugins?this.params.plugins():this.params.plugins;for(let t in this.plugins)this.plugins[t].instance._params.onInstall({...e,params:this.plugins[t].params,receive:this.plugins[t].receive})}}}async cancel(e){e?this.event.emit("cancel",{requestId:e}):this.event.emit("cancelAll",{})}requestWithId(e){this._install();let t=a.flow.createUuid(),n=null,r=!1,o=!1,s=[this.event.on("cancel",(({requestId:e})=>{e===t&&l()})),this.event.on("cancelAll",(()=>{l()}))],i=()=>s.forEach((e=>e.off())),l=()=>{!1===r&&(o&&n&&n(),r=!0,i())},c=e=>{n=e},u=async()=>{let s=this.translator.getValidate(),i=null,l={},u=await this.translator.compile(e,{schema:s}),p=[{role:"user",content:u.prompt}];for(let e in this.plugins)l[e]={send:n=>this.plugins[e].send({id:t,data:n})};return await this.hook.notify("start",{id:t,data:e,schema:s,plugins:l,messages:p,setPreMessages:e=>{p=[...e,{role:"user",content:u.prompt}]},changeMessages:e=>{p=e}}),await a.flow.asyncWhile((async({count:a,doBreak:l})=>{if(a>=10)return l();let u="",d="",h=!1,f=p.filter((e=>"user"===e.role)).slice(-1)[0]?.content||"";try{await this.hook.notify("talkBefore",{id:t,data:e,messages:p,lastUserMessage:f});const m=this.params.request(p,{count:a,schema:s,onCancel:c,isRetry:h});if(r)n&&n();else try{o=!0,u=await m,d=u}finally{o=!1}!1===r&&(await this.hook.notify("talkAfter",{id:t,data:e,response:u,messages:p,parseText:d,lastUserMessage:f,changeParseText:e=>{d=e}}),i=(await this.translator.parse(d)).output,await this.hook.notify("succeeded",{id:t,output:i})),await this.hook.notify("done",{id:t}),l()}catch(e){if(!e.isParserError)throw await this.hook.notify("done",{id:t}),e;if(await this.hook.notify("parseFailed",{id:t,error:e.error,count:a,response:u,messages:p,lastUserMessage:f,parserFails:e.parserFails,retry:()=>{h=!0},changeMessages:e=>{p=e}}),!1===h)throw await this.hook.notify("done",{id:t}),e}})),i};return{id:t,request:(async()=>{try{return await u()}finally{i()}})()}}async request(e){const{request:t}=this.requestWithId(e);return await t}}},15:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.TextParser=void 0;const a=r(n(959));class o{params;static JsonMessage(){return new o({name:"JsonMessage",handler:async e=>{try{return JSON.parse(e)}catch(t){const n=/{(?:[^{}]|(?:{[^{}]*}))*}/,r=e.match(n)?.[0]||"";return a.default.parse(r)}}})}constructor(e){this.params=e}get name(){return this.params.name}async read(e){return await this.params.handler(e)}}t.TextParser=o},241:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ChatBrokerPlugin=void 0;const r=n(470);t.ChatBrokerPlugin=class{_event=new r.Event;_params;constructor(e){this._params=e}use(e){return{instance:this,params:e,send:e=>{this._event.emit("receive",e)},receive:e=>{this._event.on("receive",e)},__receiveData:null}}}},87:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Translator=void 0;const r=n(293);t.Translator=class{params;constructor(e){this.params=e}get __schemeType(){return null}get __outputType(){return null}async compile(e,t){const n=(0,r.validate)(e,this.params.input);return{scheme:n,prompt:await this.params.question(n,t)}}getValidate(){return{input:this.params.input,output:this.params.output}}async parse(e){let t,n="",a=[];for(let r of this.params.parsers)try{t=await r.read(e),n=r.name}catch(e){t=void 0,a.push({name:r.name,error:e})}try{return{output:(0,r.validate)(t,this.params.output),parserName:n,parserFails:a}}catch(e){throw{isParserError:!0,error:e,parserFails:a}}}}},620:function(e,t,n){var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var a=Object.getOwnPropertyDescriptor(t,n);a&&!("get"in a?!t.__esModule:a.writable||a.configurable)||(a={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,a)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),a=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&r(t,e,n);return a(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.ctod=t.Translator=t.ChatBrokerPlugin=t.ChatBroker=t.TextParser=t.Llama3Cpp=t.OpenAI=t.validateToJsonSchema=t.templates=t.plugins=void 0,t.plugins=o(n(218)),t.templates=o(n(298));var s=n(293);Object.defineProperty(t,"validateToJsonSchema",{enumerable:!0,get:function(){return s.validateToJsonSchema}});var i=n(616);Object.defineProperty(t,"OpenAI",{enumerable:!0,get:function(){return i.OpenAI}});var l=n(643);Object.defineProperty(t,"Llama3Cpp",{enumerable:!0,get:function(){return l.Llama3Cpp}});var c=n(15);Object.defineProperty(t,"TextParser",{enumerable:!0,get:function(){return c.TextParser}});var u=n(37);Object.defineProperty(t,"ChatBroker",{enumerable:!0,get:function(){return u.ChatBroker}});var p=n(241);Object.defineProperty(t,"ChatBrokerPlugin",{enumerable:!0,get:function(){return p.ChatBrokerPlugin}});var d=n(87);Object.defineProperty(t,"Translator",{enumerable:!0,get:function(){return d.Translator}});const h=o(n(218)),f=o(n(298)),m=n(616),g=n(643),y=n(87),_=n(15),v=n(37),b=n(241),w=n(293);t.ctod={OpenAI:m.OpenAI,Llama3Cpp:g.Llama3Cpp,plugins:h,templates:f,ChatBroker:v.ChatBroker,Translator:y.Translator,TextParser:_.TextParser,ChatBrokerPlugin:b.ChatBrokerPlugin,validateToJsonSchema:w.validateToJsonSchema},e.exports=t.ctod,e.exports.ctod=t.ctod,t.default=t.ctod},218:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.RolePlugin=t.LimiterPluginGlobState=t.LimiterPlugin=t.RetryPlugin=t.PrintLogPlugin=void 0;const a=r(n(894)),o=r(n(829)),s=r(n(626)),i=r(n(1));t.PrintLogPlugin=o.default,t.RetryPlugin=a.default,t.LimiterPlugin=s.default.plugin,t.LimiterPluginGlobState=s.default,t.RolePlugin=i.default},626:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(241),a=n(470),o={limit:3,interval:6e4},s={event:new a.Event,schedule:null,waitTimes:[],waitQueue:[]};t.default={event:s.event,config:o,closeSchedule:()=>{s.schedule&&(s.schedule.close(),s.schedule=null)},plugin:new r.ChatBrokerPlugin({name:"limiter",params:()=>({}),receiveData:()=>({}),onInstall({attach:e}){null==s.schedule&&(s.schedule=new a.Schedule,s.schedule.add("calc queue",1e3,(async()=>{const e=Date.now();if(s.waitTimes=s.waitTimes.filter((t=>e-t<o.interval)),s.waitTimes.length!==o.limit){let e=s.waitQueue.shift();e&&(s.waitTimes.push(Date.now()),s.event.emit("run",{id:e}))}else s.waitTimes[0]&&s.event.emit("waitTimeChange",{waitTime:Math.floor(60-(e-s.waitTimes[0])/1e3)})})),s.schedule.play()),e("talkBefore",(async()=>{const e=a.flow.createUuid();return s.waitQueue.push(e),new Promise((t=>{s.event.on("run",(({id:n},{off:r})=>{n===e&&(r(),t())}))}))}))}})}},829:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(241);t.default=new r.ChatBrokerPlugin({name:"print-log",params:e=>({detail:e.boolean().required().default(!1)}),receiveData:()=>({}),onInstall({params:e,log:t,attach:n}){n("talkBefore",(async({lastUserMessage:n,messages:r})=>{t.print("Send:",{color:"green"}),e.detail?t.print("\n"+JSON.stringify(r,null,4)):t.print("\n"+n)})),n("talkAfter",(async({parseText:e})=>{t.print("Receive:",{color:"cyan"}),t.print("\n"+e)})),n("succeeded",(async({output:e})=>{t.print("Output:",{color:"yellow"});try{t.print("\n"+JSON.stringify(e,null,4))}catch(n){t.print("\n"+e)}}))}})},894:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(241);t.default=new r.ChatBrokerPlugin({name:"retry",params:e=>({retry:e.number().required().default(1),printWarn:e.boolean().required().default(!0)}),receiveData:()=>({}),onInstall({log:e,attach:t,params:n}){t("parseFailed",(async({count:t,retry:r,messages:a,changeMessages:o})=>{t<=n.retry&&(n.printWarn&&e.print(`Is Failed, Retry ${t} times.`),o(a),r())}))}})},1:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(241);t.default=new r.ChatBrokerPlugin({name:"role",params:e=>({role:e.string().required()}),receiveData:()=>({}),onInstall({attach:e,params:t}){e("start",(async({messages:e,changeMessages:n})=>{n([{role:"user",content:`你現在是${t.role}。`},{role:"assistant",content:`沒問題,我現在是${t.role},有什麼可以幫你的嗎?`},...e])}))}})},358:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Llama3CppCompletion=void 0;const r=n(470),a=n(511);class o{core;streamAbortControllers=[];constructor(e){this.core=e}createAbortController(){const e=new AbortController,t=r.flow.createUuid();return this.streamAbortControllers.push({id:t,controller:e}),{signal:e.signal,controllerId:t}}removeAbortController(e){this.streamAbortControllers=this.streamAbortControllers.filter((t=>t.id!==e))}async stream(e){const{signal:t,controllerId:n}=this.createAbortController(),r=()=>{this.removeAbortController(n),e.onEnd()};fetch(`${this.core.config.baseUrl}/${e.path}`,{method:"POST",body:JSON.stringify(e.data),signal:t,headers:{"Content-Type":"application/json",...this.core.config.headers}}).then((async t=>{if(t.body){let n=t.body.getReader(),a=!1,o="";for(;!a;){const{value:t,done:r}=await n.read();if(t){o+=new TextDecoder("utf-8").decode(t);const n=o.split("\n\n");o=n.pop()||"",n.forEach((t=>{if(t.includes("[DONE]")&&(a=!0),t.startsWith("data:"))try{const n=JSON.parse(t.replace("data: ",""));e.onMessage(n)}catch(t){e.onWarn(t)}}))}r&&(a=!0)}r()}else e.onError(new Error("Body not found."))})).catch((t=>{t instanceof Error&&t.message.includes("The user aborted a request")?r():e.onError(t)}))}async fetch(e){const{signal:t,controllerId:n}=this.createAbortController();try{return{data:(await this.core.core._axios.post(`${this.core.config.baseUrl}/${e.path}`,e.data,{signal:t,headers:{"Content-Type":"application/json",...this.core.config.headers}})).data}}finally{this.removeAbortController(n)}}cancel(){this.streamAbortControllers.forEach((e=>e.controller.abort())),this.streamAbortControllers=[]}export(){return{cancel:this.cancel.bind(this)}}}t.Llama3CppCompletion=class{core;config={baseUrl:"",headers:{},autoConvertTraditionalChinese:!0};constructor(e){this.core=e}setConfig(e){this.config={...this.config,...e}}completion(e){const t=[];for(let{role:n,content:r}of e.messages)"system"===n&&t.push(`<|start_header_id|>system<|end_header_id|>\n\n${r}\n\n`),"user"===n&&t.push(`<|start_header_id|>user<|end_header_id|>\n\n${r?.replaceAll("\n","\\n")??""}`),"assistant"===n&&t.push("<|start_header_id|>assistant<|end_header_id|>\n\n"+r);const n=e.messages.at(-1)||"",r=new o(this);return{...r.export(),run:async()=>{const o=await r.fetch({path:"completion",data:{...e.options||{},prompt:this.config.autoConvertTraditionalChinese?(0,a.sify)(t.join("\n")):t.join("\n")}}),s=this.config.autoConvertTraditionalChinese?(0,a.tify)(o.data.content):o.data.content;return{message:s,fullMessage:`${n}${s}`}}}}completionStream(e){const t=[];for(let{role:n,content:r}of e.messages)"system"===n&&t.push(`<|start_header_id|>system<|end_header_id|>\n\n${r}\n\n`),"user"===n&&t.push(`<|start_header_id|>user<|end_header_id|>\n\n${r?.replaceAll("\n","\\n")??""}`),"assistant"===n&&t.push("<|start_header_id|>assistant<|end_header_id|>\n\n"+r);const n=new o(this);return n.stream({path:"completion",onEnd:e.onEnd||(()=>null),onMessage:t=>{e.onMessage({message:this.config.autoConvertTraditionalChinese?(0,a.tify)(t.content):t.content})},onWarn:e.onWarn||(()=>null),onError:e.onError||(()=>null),data:{...e.options||{},prompt:this.config.autoConvertTraditionalChinese?(0,a.sify)(t.join("\n")):t.join("\n"),stream:!0}}),n.export()}talk(e){const t=new o(this);return{...t.export(),run:async()=>{const n=(await t.fetch({path:"v1/chat/completions",data:{...e.options||{},response_format:e.response_format,messages:e.messages.map((e=>({role:e.role,content:this.config.autoConvertTraditionalChinese?(0,a.sify)(e.content):e.content})))}})).data.choices[0].message.content||"";return{message:this.config.autoConvertTraditionalChinese?(0,a.tify)(n):n}}}}talkStream(e){const t=new o(this);return t.stream({path:"v1/chat/completions",onEnd:e.onEnd||(()=>null),onMessage:t=>{let n=t.choices[0].delta.content;n&&e.onMessage({message:this.config.autoConvertTraditionalChinese?(0,a.tify)(n):n})},onWarn:e.onWarn||(()=>null),onError:e.onError||(()=>null),data:{...e.options||{},stream:!0,messages:e.messages.map((e=>({role:e.role,content:this.config.autoConvertTraditionalChinese?(0,a.sify)(e.content):e.content})))}}),t.export()}}},643:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Llama3Cpp=void 0;const a=r(n(167)),o=n(470),s=n(511),i=n(293),l=n(358);class c{_axios=a.default.create();static createChatRequest(e){return async(t,{schema:n,onCancel:r})=>{const a=(new c).createCompletion(),l="function"==typeof e.config?await e.config():e.config,u=e.jsonSchemaInfo?o.json.jpjs(e.jsonSchemaInfo):void 0;if(a.setConfig(l),a.config.autoConvertTraditionalChinese&&u)for(let e in u.desc){const t=u.desc[e];"object"==typeof t&&t.description&&(t.description=(0,s.sify)(t.description)),"string"==typeof t&&(u.desc[e]=(0,s.sify)(t))}const{run:p,cancel:d}=a.talk({options:e.talkOptions,messages:t,response_format:{type:"json_object",schema:(0,i.validateToJsonSchema)(n.output,u)}});r(d);const{message:h}=await p();return h}}setAxios(e){this._axios=e}createCompletion(){return new l.Llama3CppCompletion(this)}}t.Llama3Cpp=c},203:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OpenAIChat=void 0;const r=n(470);t.OpenAIChat=class{openai;config={n:1,model:"gpt-4o",temperature:1,maxTokens:void 0,forceJsonFormat:!0};constructor(e){this.openai=e}setConfig(e){Object.assign(this.config,e)}async moderations(e){const t=await this.openai._axios.post("https://api.openai.com/v1/moderations",{input:e},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.openai._apiKey}`}});return{isSafe:!1===t.data.results?.[0]?.flagged,result:t.data}}async talk(e=[],t){const n=r.json.jpjs(e),a=["gpt-4-turbo-preview","gpt-4-turbo","gpt-4o","gpt-4o-mini","gpt-3.5-turbo-1106"].includes(this.config.model),o=await this.openai._axios.post("https://api.openai.com/v1/chat/completions",{model:this.config.model,n:this.config.n,messages:n,response_format:!1===a||!1===this.config.forceJsonFormat?void 0:{type:"json_object"},temperature:this.config.temperature},{signal:t?.abortController?.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.openai._apiKey}`}}),s=o.data.choices||[],i=s[0]?.message||{role:"assistant",content:""};return n.push(i),{id:o?.data.id,text:i.content,newMessages:n,isDone:"stop"===s[0]?.finish_reason,apiReseponse:o.data}}talkStream(e){const t=new AbortController;return fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.openai._apiKey}`},body:JSON.stringify({model:this.config.model,stream:!0,messages:e.messages}),signal:t.signal}).then((async t=>{const n=t.body?.pipeThrough(new TextDecoderStream).getReader();if(!n)throw new Error("Can not get reader");for(;;){const{value:t,done:r}=await n.read();if(r)break;const a=t.split("\n");for(let t of a)if(0!==t.length&&!t.startsWith(":")){if("data: [DONE]"===t){e.onEnd();break}try{const n=JSON.parse(t.substring(6)).choices[0].delta.content;e.onMessage(n)}catch(t){e.onWarn(t)}}}})).catch((t=>{"AbortError"===t.name?e.onEnd():e.onError(t)})),{cancel:()=>t.abort()}}async keepTalk(e,t=[]){const n=await this.talk([...t,{role:"user",content:Array.isArray(e)?e.join("\n"):e}]);return{result:n,nextTalk:e=>this.keepTalk(e,n.newMessages)}}}},725:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OpenAIImagesGeneration=void 0,t.OpenAIImagesGeneration=class{openai;config={model:"dall-e-2",size:"1024x1024"};constructor(e){this.openai=e}setConfig(e){Object.assign(this.config,e)}async create(e){return(await this.openai._axios.post("https://api.openai.com/v1/images/generations",{prompt:e,n:1,size:this.config.size,model:this.config.model,response_format:"b64_json"},{timeout:3e5,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.openai._apiKey}`}})).data}}},616:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.OpenAI=void 0;const a=n(710),o=n(203),s=n(725),i=r(n(167));class l{_axios=i.default.create();_apiKey="";static createChatRequest(e,t={}){return async(n,{onCancel:r})=>{const a=new l("string"==typeof e?e:await e()).createChat(),o=new AbortController;a.setConfig("function"==typeof t?await t():t),r((()=>o.abort()));const{text:s}=await a.talk(n,{abortController:o});return s}}constructor(e=""){this._apiKey=e}setAxios(e){this._axios=e}setConfiguration(e){this._apiKey=e}createChat(){return new o.OpenAIChat(this)}createVision(){return new a.OpenAIVision(this)}createImagesGeneration(){return new s.OpenAIImagesGeneration(this)}}t.OpenAI=l},710:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OpenAIVision=void 0,t.OpenAIVision=class{openai;config={model:"gpt-4-vision-preview",maxTokens:void 0,temperature:1};constructor(e){this.openai=e}setConfig(e){Object.assign(this.config,e)}async view(e){const t=await this.openai._axios.post("https://api.openai.com/v1/chat/completions",{model:this.config.model,n:1,messages:e,max_tokens:this.config.maxTokens,temperature:this.config.temperature},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.openai._apiKey}`}}),n=(t.data.choices||[])[0]?.message||{role:"assistant",content:""};return{id:t?.data.id,text:n.content,apiReseponse:t.data}}}},298:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.requireJsonResponseWithJsonSchema=t.requireJsonResponseWithHandlebars=t.requireJsonResponse=void 0;const a=r(n(97)),o=n(470);t.requireJsonResponse=(e,t)=>[...Array.isArray(e)?e:[e],"Please respond using the following JSON format and minify the JSON without including any explanation: ","{",Object.entries(t).map((([e,t])=>[`/* ${t.desc} */`,`"${e}": ${JSON.stringify(t.example)}`].join("\n"))).join(",\n"),"}"].join("\n"),t.requireJsonResponseWithHandlebars=(e,n,r)=>{const s=a.default.create();return s.registerHelper("DATA",(function(e){return JSON.stringify(e)})),s.registerHelper("ENV",(function(e){return this.__envs&&e?this.__envs[e]:""})),s.registerHelper("INPUT",(function(){return JSON.stringify(o.record.omit(this,["__envs"]))})),s.registerHelper("JOIN",(function(e){return Array.isArray(e)?e.join():JSON.stringify(e)})),s.compile((0,t.requireJsonResponse)(n,r))(e)},t.requireJsonResponseWithJsonSchema=(e,t)=>[...Array.isArray(e)?e:[e],"Please provide JSON data according to the following JSON Schema format:",JSON.stringify(t)].join("\n")},293:function(e,t,n){var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var a=Object.getOwnPropertyDescriptor(t,n);a&&!("get"in a?!t.__esModule:a.writable||a.configurable)||(a={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,a)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),a=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&r(t,e,n);return a(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.validateToJsonSchema=t.validate=t.definedValidateSchema=void 0;const s=o(n(609)),i=n(470),l=n(724);t.definedValidateSchema=function(e){return e},t.validate=function(e,t){return s.object(t(s)).required().validateSync(e||{})},t.validateToJsonSchema=(e,t)=>{const n=(0,l.convertSchema)(s.object(e(s)));return delete n.default,t?((e,t)=>{if(t&&t.desc)for(let n in t.desc)if(e.properties){let r=i.pick.peel(e.properties,n.replaceAll(".",".properties."));if(r&&!0!==r){let e=t.desc[n];"object"==typeof e?(e.description&&(r.description=e.description),e.examples&&(r.examples=e.examples)):"string"==typeof e&&(r.description=e)}}return e})(n,t):n}},724:e=>{e.exports=require("@sodaru/yup-to-json-schema")},167:e=>{e.exports=require("axios")},511:e=>{e.exports=require("chinese-conv")},97:e=>{e.exports=require("handlebars")},959:e=>{e.exports=require("json5")},470:e=>{e.exports=require("power-helper")},609:e=>{e.exports=require("yup")}},t={};return function n(r){var a=t[r];if(void 0!==a)return a.exports;var o=t[r]={exports:{}};return e[r].call(o.exports,o,o.exports,n),o.exports}(620)})()));
1
+ !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.ctod=t():e.ctod=t()}(this||("undefined"!=typeof window?window:global),(()=>(()=>{"use strict";var e={37:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ChatBroker=void 0;const r=n(15),a=n(470),o=n(87),s=n(292);t.ChatBroker=class{__hookType;log;hook=new a.Hook;params;plugins={};installed=!1;translator;event=new a.Event;constructor(e){this.log=new a.Log(e.name??"no name"),this.params=e,this.translator=new o.Translator({...e,parsers:[r.TextParser.JsonMessage()]})}_install(){if(!1===this.installed){this.installed=!0;const e={log:this.log,attach:this.hook.attach.bind(this.hook),attachAfter:this.hook.attachAfter.bind(this.hook),translator:this.translator};if(this.params.plugins){this.plugins="function"==typeof this.params.plugins?this.params.plugins():this.params.plugins;for(let t in this.plugins)this.plugins[t].instance._params.onInstall({...e,params:this.plugins[t].params,receive:this.plugins[t].receive})}this.params.install?.(e)}}async cancel(e){e?this.event.emit("cancel",{requestId:e}):this.event.emit("cancelAll",{})}requestWithId(e){this._install();let t=a.flow.createUuid(),n=null,r=!1,o=!1,i=[this.event.on("cancel",(({requestId:e})=>{e===t&&c()})),this.event.on("cancelAll",(()=>{c()}))],l=()=>i.forEach((e=>e.off())),c=()=>{!1===r&&(o&&n&&n(),r=!0,l())},u=e=>{n=e},p=async()=>{let i=this.translator.getValidate(),l=null,c={},p=await this.translator.compile(e,{schema:i}),d=[{role:"user",content:p.prompt}];for(let e in this.plugins)c[e]={send:n=>this.plugins[e].send({id:t,data:n})};return await this.hook.notify("start",{id:t,data:e,schema:i,plugins:c,messages:d,setPreMessages:e=>{d=[...e,{role:"user",content:p.prompt}]},changeMessages:e=>{d=e}}),await a.flow.asyncWhile((async({count:a,doBreak:c})=>{if(a>=10)return c();let p="",h="",f=!1,m=d.filter((e=>"user"===e.role)).slice(-1)[0]?.content||"";try{await this.hook.notify("talkBefore",{id:t,data:e,messages:d,lastUserMessage:m});const g=this.params.request(d,{count:a,schema:i,onCancel:u,isRetry:f});if(r)n&&n();else try{o=!0,p=await g,h=p}finally{o=!1}!1===r&&(await this.hook.notify("talkAfter",{id:t,data:e,response:p,messages:d,parseText:h,lastUserMessage:m,parseFail:e=>{throw new s.ParserError(e,[])},changeParseText:e=>{h=e}}),l=(await this.translator.parse(h)).output,await this.hook.notify("succeeded",{id:t,output:l})),await this.hook.notify("done",{id:t}),c()}catch(e){if(!(e instanceof s.ParserError))throw await this.hook.notify("done",{id:t}),e;if(await this.hook.notify("parseFailed",{id:t,error:e.error,count:a,response:p,messages:d,lastUserMessage:m,parserFails:e.parserFails,retry:()=>{f=!0},changeMessages:e=>{d=e}}),!1===f)throw await this.hook.notify("done",{id:t}),e}})),l};return{id:t,request:(async()=>{try{return await p()}finally{l()}})()}}async request(e){const{request:t}=this.requestWithId(e);return await t}}},15:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.TextParser=void 0;const a=r(n(959));class o{params;static JsonMessage(){return new o({name:"JsonMessage",handler:async e=>{try{return JSON.parse(e)}catch(t){const n=/{(?:[^{}]|(?:{[^{}]*}))*}/,r=e.match(n)?.[0]||"";return a.default.parse(r)}}})}constructor(e){this.params=e}get name(){return this.params.name}async read(e){return await this.params.handler(e)}}t.TextParser=o},241:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ChatBrokerPlugin=void 0;const r=n(470);t.ChatBrokerPlugin=class{_event=new r.Event;_params;constructor(e){this._params=e}use(e){return{instance:this,params:e,send:e=>{this._event.emit("receive",e)},receive:e=>{this._event.on("receive",e)},__receiveData:null}}}},87:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Translator=void 0;const r=n(293),a=n(292);t.Translator=class{params;constructor(e){this.params=e}get __schemeType(){return null}get __outputType(){return null}async compile(e,t){const n=(0,r.validate)(e,this.params.input),a=await this.params.question(n,t);return{scheme:n,prompt:Array.isArray(a)?a.join("\n"):a}}getValidate(){return{input:this.params.input,output:this.params.output}}async parse(e){let t,n="",o=[];for(let r of this.params.parsers)try{t=await r.read(e),n=r.name}catch(e){t=void 0,o.push({name:r.name,error:e})}try{return{output:(0,r.validate)(t,this.params.output),parserName:n,parserFails:o}}catch(e){throw new a.ParserError(e,o)}}}},620:function(e,t,n){var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var a=Object.getOwnPropertyDescriptor(t,n);a&&!("get"in a?!t.__esModule:a.writable||a.configurable)||(a={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,a)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),a=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&r(t,e,n);return a(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.ctod=t.bindYupToJsonSchemaToYup=t.Translator=t.ChatBrokerPlugin=t.ChatBroker=t.TextParser=t.Llama3Cpp=t.OpenAI=t.validateToJsonSchema=t.templates=t.plugins=void 0,t.plugins=o(n(218)),t.templates=o(n(298));var s=n(293);Object.defineProperty(t,"validateToJsonSchema",{enumerable:!0,get:function(){return s.validateToJsonSchema}});var i=n(616);Object.defineProperty(t,"OpenAI",{enumerable:!0,get:function(){return i.OpenAI}});var l=n(643);Object.defineProperty(t,"Llama3Cpp",{enumerable:!0,get:function(){return l.Llama3Cpp}});var c=n(15);Object.defineProperty(t,"TextParser",{enumerable:!0,get:function(){return c.TextParser}});var u=n(37);Object.defineProperty(t,"ChatBroker",{enumerable:!0,get:function(){return u.ChatBroker}});var p=n(241);Object.defineProperty(t,"ChatBrokerPlugin",{enumerable:!0,get:function(){return p.ChatBrokerPlugin}});var d=n(87);Object.defineProperty(t,"Translator",{enumerable:!0,get:function(){return d.Translator}});const h=o(n(218)),f=o(n(298)),m=n(616),g=n(643),y=n(87),_=n(15),v=n(37),b=n(241),w=n(293),C=n(724),O=n(609);t.bindYupToJsonSchemaToYup=()=>{(0,C.extendSchema)({Schema:O.Schema,addMethod:O.addMethod})},t.ctod={OpenAI:m.OpenAI,Llama3Cpp:g.Llama3Cpp,plugins:h,templates:f,ChatBroker:v.ChatBroker,Translator:y.Translator,TextParser:_.TextParser,ChatBrokerPlugin:b.ChatBrokerPlugin,validateToJsonSchema:w.validateToJsonSchema,bindYupToJsonSchemaToYup:t.bindYupToJsonSchemaToYup},e.exports=t.ctod,e.exports.ctod=t.ctod,t.default=t.ctod},218:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.RolePlugin=t.LimiterPluginGlobState=t.LimiterPlugin=t.RetryPlugin=t.PrintLogPlugin=void 0;const a=r(n(894)),o=r(n(829)),s=r(n(626)),i=r(n(1));t.PrintLogPlugin=o.default,t.RetryPlugin=a.default,t.LimiterPlugin=s.default.plugin,t.LimiterPluginGlobState=s.default,t.RolePlugin=i.default},626:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(241),a=n(470),o={limit:3,interval:6e4},s={event:new a.Event,schedule:null,waitTimes:[],waitQueue:[]};t.default={event:s.event,config:o,closeSchedule:()=>{s.schedule&&(s.schedule.close(),s.schedule=null)},plugin:new r.ChatBrokerPlugin({name:"limiter",params:()=>({}),receiveData:()=>({}),onInstall({attach:e}){null==s.schedule&&(s.schedule=new a.Schedule,s.schedule.add("calc queue",1e3,(async()=>{const e=Date.now();if(s.waitTimes=s.waitTimes.filter((t=>e-t<o.interval)),s.waitTimes.length!==o.limit){let e=s.waitQueue.shift();e&&(s.waitTimes.push(Date.now()),s.event.emit("run",{id:e}))}else s.waitTimes[0]&&s.event.emit("waitTimeChange",{waitTime:Math.floor(60-(e-s.waitTimes[0])/1e3)})})),s.schedule.play()),e("talkBefore",(async()=>{const e=a.flow.createUuid();return s.waitQueue.push(e),new Promise((t=>{s.event.on("run",(({id:n},{off:r})=>{n===e&&(r(),t())}))}))}))}})}},829:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(241);t.default=new r.ChatBrokerPlugin({name:"print-log",params:e=>({detail:e.boolean().default(!1)}),receiveData:()=>({}),onInstall({params:e,log:t,attach:n}){n("talkBefore",(async({lastUserMessage:n,messages:r})=>{t.print("Send:",{color:"green"}),e.detail?t.print("\n"+JSON.stringify(r,null,4)):t.print("\n"+n)})),n("talkAfter",(async({parseText:e})=>{t.print("Receive:",{color:"cyan"}),t.print("\n"+e)})),n("succeeded",(async({output:e})=>{t.print("Output:",{color:"yellow"});try{t.print("\n"+JSON.stringify(e,null,4))}catch(n){t.print("\n"+e)}}))}})},894:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(241);t.default=new r.ChatBrokerPlugin({name:"retry",params:e=>({retry:e.number().required().default(1),printWarn:e.boolean().required().default(!0)}),receiveData:()=>({}),onInstall({log:e,attach:t,params:n}){t("parseFailed",(async({count:t,retry:r,messages:a,changeMessages:o})=>{t<=n.retry&&(n.printWarn&&e.print(`Is Failed, Retry ${t} times.`),o(a),r())}))}})},1:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(241);t.default=new r.ChatBrokerPlugin({name:"role",params:e=>({role:e.string().required()}),receiveData:()=>({}),onInstall({attach:e,params:t}){e("start",(async({messages:e,changeMessages:n})=>{n([{role:"user",content:`你現在是${t.role}。`},{role:"assistant",content:`沒問題,我現在是${t.role},有什麼可以幫你的嗎?`},...e])}))}})},358:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Llama3CppCompletion=void 0;const r=n(470),a=n(511);class o{core;streamAbortControllers=[];constructor(e){this.core=e}createAbortController(){const e=new AbortController,t=r.flow.createUuid();return this.streamAbortControllers.push({id:t,controller:e}),{signal:e.signal,controllerId:t}}removeAbortController(e){this.streamAbortControllers=this.streamAbortControllers.filter((t=>t.id!==e))}async stream(e){const{signal:t,controllerId:n}=this.createAbortController(),r=()=>{this.removeAbortController(n),e.onEnd()};fetch(`${this.core.config.baseUrl}/${e.path}`,{method:"POST",body:JSON.stringify(e.data),signal:t,headers:{"Content-Type":"application/json",...this.core.config.headers}}).then((async t=>{if(t.body){let n=t.body.getReader(),a=!1,o="";for(;!a;){const{value:t,done:r}=await n.read();if(t){o+=new TextDecoder("utf-8").decode(t);const n=o.split("\n\n");o=n.pop()||"",n.forEach((t=>{if(t.includes("[DONE]")&&(a=!0),t.startsWith("data:"))try{const n=JSON.parse(t.replace("data: ",""));e.onMessage(n)}catch(t){e.onWarn(t)}}))}r&&(a=!0)}r()}else e.onError(new Error("Body not found."))})).catch((t=>{t instanceof Error&&t.message.includes("The user aborted a request")?r():e.onError(t)}))}async fetch(e){const{signal:t,controllerId:n}=this.createAbortController();try{return{data:(await this.core.core._axios.post(`${this.core.config.baseUrl}/${e.path}`,e.data,{signal:t,headers:{"Content-Type":"application/json",...this.core.config.headers}})).data}}finally{this.removeAbortController(n)}}cancel(){this.streamAbortControllers.forEach((e=>e.controller.abort())),this.streamAbortControllers=[]}export(){return{cancel:this.cancel.bind(this)}}}t.Llama3CppCompletion=class{core;config={baseUrl:"",headers:{},autoConvertTraditionalChinese:!0};constructor(e){this.core=e}setConfig(e){this.config={...this.config,...e}}completion(e){const t=[];for(let{role:n,content:r}of e.messages)"system"===n&&t.push(`<|start_header_id|>system<|end_header_id|>\n\n${r}\n\n`),"user"===n&&t.push(`<|start_header_id|>user<|end_header_id|>\n\n${r?.replaceAll("\n","\\n")??""}`),"assistant"===n&&t.push("<|start_header_id|>assistant<|end_header_id|>\n\n"+r);const n=e.messages.at(-1)||"",r=new o(this);return{...r.export(),run:async()=>{const o=await r.fetch({path:"completion",data:{...e.options||{},prompt:this.config.autoConvertTraditionalChinese?(0,a.sify)(t.join("\n")):t.join("\n")}}),s=this.config.autoConvertTraditionalChinese?(0,a.tify)(o.data.content):o.data.content;return{message:s,fullMessage:`${n}${s}`}}}}completionStream(e){const t=[];for(let{role:n,content:r}of e.messages)"system"===n&&t.push(`<|start_header_id|>system<|end_header_id|>\n\n${r}\n\n`),"user"===n&&t.push(`<|start_header_id|>user<|end_header_id|>\n\n${r?.replaceAll("\n","\\n")??""}`),"assistant"===n&&t.push("<|start_header_id|>assistant<|end_header_id|>\n\n"+r);const n=new o(this);return n.stream({path:"completion",onEnd:e.onEnd||(()=>null),onMessage:t=>{e.onMessage({message:this.config.autoConvertTraditionalChinese?(0,a.tify)(t.content):t.content})},onWarn:e.onWarn||(()=>null),onError:e.onError||(()=>null),data:{...e.options||{},prompt:this.config.autoConvertTraditionalChinese?(0,a.sify)(t.join("\n")):t.join("\n"),stream:!0}}),n.export()}talk(e){const t=new o(this);return{...t.export(),run:async()=>{const n=(await t.fetch({path:"v1/chat/completions",data:{...e.options||{},response_format:e.response_format,messages:e.messages.map((e=>({role:e.role,content:this.config.autoConvertTraditionalChinese?(0,a.sify)(e.content):e.content})))}})).data.choices[0].message.content||"";return{message:this.config.autoConvertTraditionalChinese?(0,a.tify)(n):n}}}}talkStream(e){const t=new o(this);return t.stream({path:"v1/chat/completions",onEnd:e.onEnd||(()=>null),onMessage:t=>{let n=t.choices[0].delta.content;n&&e.onMessage({message:this.config.autoConvertTraditionalChinese?(0,a.tify)(n):n})},onWarn:e.onWarn||(()=>null),onError:e.onError||(()=>null),data:{...e.options||{},stream:!0,messages:e.messages.map((e=>({role:e.role,content:this.config.autoConvertTraditionalChinese?(0,a.sify)(e.content):e.content})))}}),t.export()}}},643:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Llama3Cpp=void 0;const a=r(n(167)),o=n(511),s=n(293),i=n(358);class l{_axios=a.default.create();static createChatRequest(e){return async(t,{schema:n,onCancel:r})=>{const a=(new l).createCompletion(),i="function"==typeof e.config?await e.config():e.config;a.setConfig(i);let c=(0,s.validateToJsonSchema)(n.output);a.config.autoConvertTraditionalChinese&&(c=JSON.parse((0,o.sify)(JSON.stringify(c))));const{run:u,cancel:p}=a.talk({options:e.talkOptions,messages:t,response_format:{type:"json_object",schema:c}});r(p);const{message:d}=await u();return d}}setAxios(e){this._axios=e}createCompletion(){return new i.Llama3CppCompletion(this)}}t.Llama3Cpp=l},203:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OpenAIChat=void 0;const r=n(470);t.OpenAIChat=class{openai;config={n:1,model:"gpt-4o",temperature:1,maxTokens:void 0,forceJsonFormat:!0};constructor(e){this.openai=e}setConfig(e){Object.assign(this.config,e)}async moderations(e){const t=await this.openai._axios.post("https://api.openai.com/v1/moderations",{input:e},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.openai._apiKey}`}});return{isSafe:!1===t.data.results?.[0]?.flagged,result:t.data}}async talk(e=[],t){const n=r.json.jpjs(e),a=["gpt-4-turbo-preview","gpt-4-turbo","gpt-4o","gpt-4o-mini","gpt-3.5-turbo-1106"].includes(this.config.model);let o;a&&this.config.forceJsonFormat&&(o={type:"json_object"}),a&&this.config.forceJsonFormat&&t?.jsonSchema&&(o={type:"json_schema",json_schema:t.jsonSchema});const s=await this.openai._axios.post("https://api.openai.com/v1/chat/completions",{model:this.config.model,n:this.config.n,messages:n,response_format:o,temperature:this.config.temperature},{signal:t?.abortController?.signal,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.openai._apiKey}`}}),i=s.data.choices||[],l=i[0]?.message||{role:"assistant",content:""};return n.push(l),{id:s?.data.id,text:l.content,newMessages:n,isDone:"stop"===i[0]?.finish_reason,apiReseponse:s.data}}talkStream(e){const t=new AbortController;return fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.openai._apiKey}`},body:JSON.stringify({model:this.config.model,stream:!0,messages:e.messages}),signal:t.signal}).then((async t=>{const n=t.body?.pipeThrough(new TextDecoderStream).getReader();if(!n)throw new Error("Can not get reader");for(;;){const{value:t,done:r}=await n.read();if(r)break;const a=t.split("\n");for(let t of a)if(0!==t.length&&!t.startsWith(":")){if("data: [DONE]"===t){e.onEnd();break}try{const n=JSON.parse(t.substring(6)).choices[0].delta.content;e.onMessage(n)}catch(t){e.onWarn(t)}}}})).catch((t=>{"AbortError"===t.name?e.onEnd():e.onError(t)})),{cancel:()=>t.abort()}}async keepTalk(e,t=[]){const n=await this.talk([...t,{role:"user",content:Array.isArray(e)?e.join("\n"):e}]);return{result:n,nextTalk:e=>this.keepTalk(e,n.newMessages)}}}},725:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OpenAIImagesGeneration=void 0,t.OpenAIImagesGeneration=class{openai;config={model:"dall-e-2",size:"1024x1024"};constructor(e){this.openai=e}setConfig(e){Object.assign(this.config,e)}async create(e){return(await this.openai._axios.post("https://api.openai.com/v1/images/generations",{prompt:e,n:1,size:this.config.size,model:this.config.model,response_format:"b64_json"},{timeout:3e5,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.openai._apiKey}`}})).data}}},616:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.OpenAI=void 0;const a=r(n(167)),o=n(710),s=n(203),i=n(725),l=n(293);class c{_axios=a.default.create();_apiKey="";static createChatRequest(e,t={}){return async(n,{onCancel:r})=>{const a=new c("string"==typeof e?e:await e()).createChat(),o=new AbortController;a.setConfig("function"==typeof t?await t():t),r((()=>o.abort()));const{text:s}=await a.talk(n,{abortController:o});return s}}static createChatRequestWithJsonSchema(e){return async(t,{schema:n,onCancel:r})=>{const a=new c("string"==typeof e.apiKey?e.apiKey:await e.apiKey()).createChat(),o=new AbortController;e.config&&a.setConfig("function"==typeof e.config?await e.config():e.config),r((()=>o.abort()));const s=(0,l.validateToJsonSchema)(n.output),{text:i}=await a.talk(t,{abortController:o,jsonSchema:{name:"data",strict:!0,schema:s}});return i}}constructor(e=""){this._apiKey=e}setAxios(e){this._axios=e}setConfiguration(e){this._apiKey=e}createChat(){return new s.OpenAIChat(this)}createVision(){return new o.OpenAIVision(this)}createImagesGeneration(){return new i.OpenAIImagesGeneration(this)}}t.OpenAI=c},710:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OpenAIVision=void 0,t.OpenAIVision=class{openai;config={model:"gpt-4-vision-preview",maxTokens:void 0,temperature:1};constructor(e){this.openai=e}setConfig(e){Object.assign(this.config,e)}async view(e){const t=await this.openai._axios.post("https://api.openai.com/v1/chat/completions",{model:this.config.model,n:1,messages:e,max_tokens:this.config.maxTokens,temperature:this.config.temperature},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.openai._apiKey}`}}),n=(t.data.choices||[])[0]?.message||{role:"assistant",content:""};return{id:t?.data.id,text:n.content,apiReseponse:t.data}}}},298:function(e,t,n){var r=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.requireJsonResponseWithJsonSchema=t.requireJsonResponseWithHandlebars=t.requireJsonResponse=void 0;const a=r(n(97)),o=n(470);t.requireJsonResponse=(e,t)=>[...Array.isArray(e)?e:[e],"Please respond using the following JSON format and minify the JSON without including any explanation: ","{",Object.entries(t).map((([e,t])=>[`/* ${t.desc} */`,`"${e}": ${JSON.stringify(t.example)}`].join("\n"))).join(",\n"),"}"].join("\n"),t.requireJsonResponseWithHandlebars=(e,n,r)=>{const s=a.default.create();return s.registerHelper("DATA",(function(e){return JSON.stringify(e)})),s.registerHelper("ENV",(function(e){return this.__envs&&e?this.__envs[e]:""})),s.registerHelper("INPUT",(function(){return JSON.stringify(o.record.omit(this,["__envs"]))})),s.registerHelper("JOIN",(function(e){return Array.isArray(e)?e.join():JSON.stringify(e)})),s.compile((0,t.requireJsonResponse)(n,r))(e)},t.requireJsonResponseWithJsonSchema=(e,t)=>[...Array.isArray(e)?e:[e],"Please provide JSON data according to the following JSON Schema format:",JSON.stringify(t)].join("\n")},292:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ParserError=void 0,t.ParserError=class{isParserError=!0;parserFails=[];error;constructor(e,t){this.error=e,this.parserFails=t}}},293:function(e,t,n){var r=this&&this.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var a=Object.getOwnPropertyDescriptor(t,n);a&&!("get"in a?!t.__esModule:a.writable||a.configurable)||(a={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,a)}:function(e,t,n,r){void 0===r&&(r=n),e[r]=t[n]}),a=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)"default"!==n&&Object.prototype.hasOwnProperty.call(e,n)&&r(t,e,n);return a(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.validateToJsonSchema=t.validate=t.definedValidateSchema=void 0;const s=o(n(609)),i=n(724);t.definedValidateSchema=function(e){return e},t.validate=function(e,t){return s.object(t(s)).required().validateSync(e||{})},t.validateToJsonSchema=e=>{const t=e=>{if(e.default&&delete e.default,e.properties)for(let n in e.properties)e.properties[n].default&&delete e.properties[n].default,t(e.properties[n]);e.items&&t(e.items)},n=e=>{if("object"===e.type){e.additionalProperties=!1;for(const t in e.properties)n(e.properties[t])}else"array"===e.type&&n(e.items)},r=(0,i.convertSchema)(s.object(e(s)));return t(r),n(r),r}},724:e=>{e.exports=require("@sodaru/yup-to-json-schema")},167:e=>{e.exports=require("axios")},511:e=>{e.exports=require("chinese-conv")},97:e=>{e.exports=require("handlebars")},959:e=>{e.exports=require("json5")},470:e=>{e.exports=require("power-helper")},609:e=>{e.exports=require("yup")}},t={};return function n(r){var a=t[r];if(void 0!==a)return a.exports;var o=t[r]={exports:{}};return e[r].call(o.exports,o,o.exports,n),o.exports}(620)})()));
@@ -1,6 +1,6 @@
1
1
  // eslint-disable-next-line @typescript-eslint/triple-slash-reference
2
2
  /// <reference path="../lib/shims.d.ts" />
3
- import { ChatBroker, OpenAI, templates } from '../lib/index'
3
+ import { ChatBroker, OpenAI, plugins, bindYupToJsonSchemaToYup } from '../lib/index'
4
4
 
5
5
  /**
6
6
  * @test npx ts-node ./examples/chat-demo.ts
@@ -10,40 +10,52 @@ const API_KEY = ''
10
10
  const broker = new ChatBroker({
11
11
  input: yup => {
12
12
  return {
13
- indexs: yup.array(yup.string()).required(),
13
+ indexs: yup.array(yup.string().required()).required(),
14
14
  question: yup.string().required()
15
15
  }
16
16
  },
17
17
  output: yup => {
18
+ const item = yup.object({
19
+ name: yup.string().description('索引名稱').required(),
20
+ score: yup.number().description('評比分數').required()
21
+ }).required()
18
22
  return {
19
- indexs: yup.array(yup.object({
20
- name: yup.string().required(),
21
- score: yup.number().required()
22
- })).required()
23
+ indexs: yup.array(item).description('由高到低排序的索引').required()
23
24
  }
24
25
  },
25
- install: () => null,
26
- request: OpenAI.createChatRequest(API_KEY),
26
+ plugins: {
27
+ log: plugins.PrintLogPlugin.use({
28
+ detail: true
29
+ })
30
+ },
31
+ install({ attach }) {
32
+ attach('start', async({ setPreMessages }) => {
33
+ setPreMessages([
34
+ {
35
+ role: 'system',
36
+ content: '你現在是一位擅長分類索引的藥師'
37
+ }
38
+ ])
39
+ })
40
+ },
41
+ request: OpenAI.createChatRequestWithJsonSchema({
42
+ apiKey: API_KEY,
43
+ config: {
44
+ model: 'gpt-4o-mini'
45
+ }
46
+ }),
27
47
  question: async({ indexs, question }) => {
28
- return templates.requireJsonResponse([
48
+ return [
29
49
  '我有以下索引',
30
50
  `${JSON.stringify(indexs)}`,
31
51
  `請幫我解析"${question}"可能是哪個索引`,
32
52
  '且相關性由高到低排序並給予分數,分數由 0 ~ 1'
33
- ], {
34
- indexs: {
35
- desc: '由高到低排序的索引',
36
- example: [
37
- {
38
- name: '索引名稱',
39
- score: '評比分數,數字顯示'
40
- }
41
- ]
42
- }
43
- })
53
+ ]
44
54
  }
45
55
  })
46
56
 
57
+
58
+ bindYupToJsonSchemaToYup()
47
59
  broker.request({
48
60
  indexs: ['胃痛', '腰痛', '頭痛', '喉嚨痛', '四肢疼痛'],
49
61
  question: '喝咖啡,吃甜食,胃食道逆流'
@@ -3,6 +3,7 @@ import { ChatBrokerPlugin } from '../core/plugin'
3
3
  import { Event, flow, Hook, Log } from 'power-helper'
4
4
  import { Translator, TranslatorParams } from '../core/translator'
5
5
  import { ValidateCallback, ValidateCallbackOutputs } from '../utils/validate'
6
+ import { ParserError } from '../utils/error'
6
7
 
7
8
  type Message = {
8
9
  role: 'system' | 'user' | 'assistant'
@@ -63,6 +64,11 @@ export type ChatBrokerHooks<
63
64
  messages: Message[]
64
65
  parseText: string
65
66
  lastUserMessage: string
67
+ /**
68
+ * @zh 宣告解析失敗
69
+ * @en Declare parsing failure
70
+ */
71
+ parseFail: (error: any) => void
66
72
  changeParseText: (text: string) => void
67
73
  }
68
74
 
@@ -123,7 +129,7 @@ export type Params<
123
129
  name?: string
124
130
  plugins?: PS | (() => PS)
125
131
  request: (messages: Message[], context: RequestContext) => Promise<string>
126
- install: (context: {
132
+ install?: (context: {
127
133
  log: Log
128
134
  attach: Hook<C>['attach']
129
135
  attachAfter: Hook<C>['attachAfter']
@@ -172,7 +178,6 @@ export class ChatBroker<
172
178
  attachAfter: this.hook.attachAfter.bind(this.hook),
173
179
  translator: this.translator
174
180
  }
175
- this.params.install(context)
176
181
  if (this.params.plugins) {
177
182
  this.plugins = typeof this.params.plugins === 'function' ? this.params.plugins() : this.params.plugins
178
183
  for (let key in this.plugins) {
@@ -183,6 +188,7 @@ export class ChatBroker<
183
188
  })
184
189
  }
185
190
  }
191
+ this.params.install?.(context)
186
192
  }
187
193
  }
188
194
 
@@ -322,6 +328,9 @@ export class ChatBroker<
322
328
  messages,
323
329
  parseText,
324
330
  lastUserMessage,
331
+ parseFail: (error) => {
332
+ throw new ParserError(error, [])
333
+ },
325
334
  changeParseText: text => {
326
335
  parseText = text
327
336
  }
@@ -336,7 +345,7 @@ export class ChatBroker<
336
345
  doBreak()
337
346
  } catch (error: any) {
338
347
  // 如果解析錯誤,可以選擇是否重新解讀
339
- if (error.isParserError) {
348
+ if (error instanceof ParserError) {
340
349
  await this.hook.notify('parseFailed', {
341
350
  id,
342
351
  error: error.error,
@@ -1,5 +1,6 @@
1
1
  import { TextParser } from './parser'
2
2
  import { validate, ValidateCallback, ValidateCallbackOutputs } from '../utils/validate'
3
+ import { ParserError } from '../utils/error'
3
4
 
4
5
  export type TranslatorParams<
5
6
  S extends ValidateCallback<any>,
@@ -29,7 +30,7 @@ export type TranslatorParams<
29
30
  input: S
30
31
  output: O
31
32
  }
32
- }) => Promise<string>
33
+ }) => Promise<string | string[]>
33
34
  }
34
35
 
35
36
  export class Translator<
@@ -64,7 +65,7 @@ export class Translator<
64
65
  const prompt = await this.params.question(scheme, context)
65
66
  return {
66
67
  scheme,
67
- prompt
68
+ prompt: Array.isArray(prompt) ? prompt.join('\n') : prompt
68
69
  }
69
70
  }
70
71
 
@@ -104,11 +105,7 @@ export class Translator<
104
105
  parserFails
105
106
  }
106
107
  } catch (error) {
107
- throw {
108
- isParserError: true,
109
- error,
110
- parserFails
111
- }
108
+ throw new ParserError(error, parserFails)
112
109
  }
113
110
  }
114
111
  }
package/lib/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * as plugins from './plugins'
2
2
  export * as templates from './templates'
3
- export { validateToJsonSchema, JsonSchemaInfo } from './utils/validate'
3
+ export { validateToJsonSchema } from './utils/validate'
4
4
  export { OpenAI } from './service/openai'
5
5
  export { Llama3Cpp } from './service/llama3.cpp'
6
6
  export { TextParser } from './core/parser'
@@ -18,6 +18,20 @@ import { TextParser } from './core/parser'
18
18
  import { ChatBroker } from './broker/chat'
19
19
  import { ChatBrokerPlugin } from './core/plugin'
20
20
  import { validateToJsonSchema } from './utils/validate'
21
+ import { extendSchema } from '@sodaru/yup-to-json-schema'
22
+ import { addMethod, Schema } from 'yup'
23
+
24
+
25
+ /**
26
+ * @see https://github.com/sodaru/yup-to-json-schema?tab=readme-ov-file#extend-the-schema
27
+ */
28
+
29
+ export const bindYupToJsonSchemaToYup = () => {
30
+ extendSchema({
31
+ Schema,
32
+ addMethod
33
+ })
34
+ }
21
35
 
22
36
  export const ctod = {
23
37
  OpenAI,
@@ -28,7 +42,8 @@ export const ctod = {
28
42
  Translator,
29
43
  TextParser,
30
44
  ChatBrokerPlugin,
31
- validateToJsonSchema
45
+ validateToJsonSchema,
46
+ bindYupToJsonSchemaToYup
32
47
  }
33
48
 
34
49
  module.exports = ctod
@@ -4,7 +4,7 @@ export default new ChatBrokerPlugin({
4
4
  name: 'print-log',
5
5
  params: yup => {
6
6
  return {
7
- detail: yup.boolean().required().default(false)
7
+ detail: yup.boolean().default(false)
8
8
  }
9
9
  },
10
10
  receiveData: () => {
@@ -1,7 +1,6 @@
1
1
  import axios, { AxiosInstance } from 'axios'
2
- import { json } from 'power-helper'
3
2
  import { sify } from 'chinese-conv'
4
- import { validateToJsonSchema, JsonSchemaInfo } from '../../utils/validate'
3
+ import { validateToJsonSchema } from '../../utils/validate'
5
4
  import { Llama3CppCompletion, Config } from './completion'
6
5
 
7
6
  export class Llama3Cpp {
@@ -10,31 +9,22 @@ export class Llama3Cpp {
10
9
  static createChatRequest(params: {
11
10
  config: Partial<Config> | (() => Promise<Partial<Config>>)
12
11
  talkOptions?: any
13
- jsonSchemaInfo?: JsonSchemaInfo
14
12
  }) {
15
13
  return async(messages: any[], { schema, onCancel }: any) => {
16
14
  const ll3cpp = new Llama3Cpp()
17
15
  const chat = ll3cpp.createCompletion()
18
16
  const config = typeof params.config === 'function' ? await params.config() : params.config
19
- const info = params.jsonSchemaInfo ? json.jpjs(params.jsonSchemaInfo) : undefined
20
17
  chat.setConfig(config)
21
- if (chat.config.autoConvertTraditionalChinese && info) {
22
- for (let key in info.desc) {
23
- const d = info.desc[key]
24
- if (typeof d === 'object' && d.description) {
25
- d.description = sify(d.description)
26
- }
27
- if (typeof d === 'string') {
28
- info.desc[key] = sify(d)
29
- }
30
- }
18
+ let formatSchema = validateToJsonSchema(schema.output)
19
+ if (chat.config.autoConvertTraditionalChinese) {
20
+ formatSchema = JSON.parse(sify(JSON.stringify(formatSchema)))
31
21
  }
32
22
  const { run, cancel } = chat.talk({
33
23
  options: params.talkOptions,
34
24
  messages: messages,
35
25
  response_format: {
36
26
  type: 'json_object',
37
- schema: validateToJsonSchema(schema.output, info)
27
+ schema: formatSchema
38
28
  }
39
29
  })
40
30
  onCancel(cancel)
@@ -105,6 +105,8 @@ export class OpenAIChat {
105
105
  */
106
106
 
107
107
  async talk(messages: ChatGPTMessage[] = [], options?: {
108
+ /** 要 forceJsonFormat 為 true 才會生效 */
109
+ jsonSchema?: any
108
110
  abortController?: AbortController
109
111
  }) {
110
112
  const newMessages = json.jpjs(messages)
@@ -115,13 +117,23 @@ export class OpenAIChat {
115
117
  'gpt-4o-mini',
116
118
  'gpt-3.5-turbo-1106'
117
119
  ].includes(this.config.model)
120
+ let response_format: any = undefined
121
+ if (isSupportJson && this.config.forceJsonFormat) {
122
+ response_format = {
123
+ type: 'json_object'
124
+ }
125
+ }
126
+ if (isSupportJson && this.config.forceJsonFormat && options?.jsonSchema) {
127
+ response_format = {
128
+ type: 'json_schema',
129
+ json_schema: options.jsonSchema
130
+ }
131
+ }
118
132
  const result = await this.openai._axios.post<ApiResponse>('https://api.openai.com/v1/chat/completions', {
119
133
  model: this.config.model,
120
134
  n: this.config.n,
121
135
  messages: newMessages,
122
- response_format: (isSupportJson === false || this.config.forceJsonFormat === false) ? undefined : {
123
- type: 'json_object'
124
- },
136
+ response_format,
125
137
  temperature: this.config.temperature
126
138
  }, {
127
139
  signal: options?.abortController?.signal,
@@ -1,7 +1,8 @@
1
+ import axios, { AxiosInstance } from 'axios'
1
2
  import { OpenAIVision } from './vision'
2
3
  import { OpenAIChat, Config } from './chat'
3
4
  import { OpenAIImagesGeneration } from './images-generation'
4
- import axios, { AxiosInstance } from 'axios'
5
+ import { validateToJsonSchema } from '../../utils/validate'
5
6
 
6
7
  export class OpenAI {
7
8
  _axios = axios.create()
@@ -21,6 +22,32 @@ export class OpenAI {
21
22
  }
22
23
  }
23
24
 
25
+ static createChatRequestWithJsonSchema(params:{
26
+ apiKey: string | (() => Promise<string>),
27
+ config?: Partial<Config> | (() => Promise<Partial<Config>>)
28
+ }) {
29
+ return async(messages: any[], { schema, onCancel }: any) => {
30
+ const openai = new OpenAI(typeof params.apiKey === 'string' ? params.apiKey : await params.apiKey())
31
+ const chat = openai.createChat()
32
+ const abortController = new AbortController()
33
+ if (params.config) {
34
+ chat.setConfig(typeof params.config === 'function' ? await params.config() : params.config)
35
+ }
36
+ onCancel(() => abortController.abort())
37
+ const jsonSchema = validateToJsonSchema(schema.output)
38
+ const { text } = await chat.talk(messages, {
39
+ abortController,
40
+ jsonSchema: {
41
+ name: 'data',
42
+ strict: true,
43
+ schema: jsonSchema
44
+ }
45
+ })
46
+ return text
47
+ }
48
+ }
49
+
50
+
24
51
  constructor(apiKey = '') {
25
52
  this._apiKey = apiKey
26
53
  }
@@ -0,0 +1,14 @@
1
+ type ParserFail = {
2
+ name: string
3
+ error: any
4
+ }
5
+
6
+ export class ParserError {
7
+ isParserError = true
8
+ parserFails: ParserFail[] = []
9
+ error: any
10
+ constructor(error: any, parserFails: ParserFail[]) {
11
+ this.error = error
12
+ this.parserFails = parserFails
13
+ }
14
+ }
@@ -1,7 +1,6 @@
1
1
  import * as Yup from 'yup'
2
- import { pick } from 'power-helper'
3
2
  import { convertSchema } from '@sodaru/yup-to-json-schema'
4
- import type { Schema } from 'yup'
3
+ import { Schema } from 'yup'
5
4
 
6
5
  export type ValidateCallback<T extends Record<string, Schema>> = (_yup: typeof Yup) => {
7
6
  [K in keyof T]: T[K]
@@ -27,43 +26,35 @@ export function validate<
27
26
  }
28
27
  }
29
28
 
30
- /**
31
- * @zh JSON Schema 設定描述,可以指定深層結構,例如 user.name, user.level 等。
32
- * @en Set the JSON Schema description, you can specify deep structures, such as user.name, user.level, etc.
33
- */
34
-
35
- export type JsonSchemaInfo = {
36
- desc?: Record<string, string | {
37
- description?: string
38
- examples?: any
39
- }>
40
- }
41
-
42
- export const validateToJsonSchema = <T extends ValidateCallback<any>>(cb: T, info?: JsonSchemaInfo) => {
43
- const bodySchemaBindDoc = (schema: ReturnType<typeof convertSchema>, doc: JsonSchemaInfo) => {
44
- if (doc && doc.desc) {
45
- for (let key in doc.desc) {
46
- if (schema.properties) {
47
- let target = pick.peel(schema.properties, key.replaceAll('.', '.properties.'))
48
- if (target && target !== true) {
49
- let d = doc.desc[key]
50
- if (typeof d === 'object') {
51
- if (d.description) {
52
- target.description = d.description
53
- }
54
- if (d.examples) {
55
- target.examples = d.examples
56
- }
57
- } else if (typeof d === 'string') {
58
- target.description = d
59
- }
60
- }
29
+ export const validateToJsonSchema = <T extends ValidateCallback<any>>(cb: T) => {
30
+ const removeAllDefault = (schema: any) => {
31
+ if (schema.default) {
32
+ delete schema.default
33
+ }
34
+ if (schema.properties) {
35
+ for (let key in schema.properties) {
36
+ if (schema.properties[key].default) {
37
+ delete schema.properties[key].default
61
38
  }
39
+ removeAllDefault(schema.properties[key])
40
+ }
41
+ }
42
+ if (schema.items) {
43
+ removeAllDefault(schema.items)
44
+ }
45
+ }
46
+ const addAllAdditionalProperties = (jsonSchema: any) => {
47
+ if (jsonSchema.type === 'object') {
48
+ jsonSchema.additionalProperties = false
49
+ for (const key in jsonSchema.properties) {
50
+ addAllAdditionalProperties(jsonSchema.properties[key])
62
51
  }
52
+ } else if (jsonSchema.type === 'array') {
53
+ addAllAdditionalProperties(jsonSchema.items)
63
54
  }
64
- return schema
65
55
  }
66
56
  const jsonSchema = convertSchema(Yup.object(cb(Yup)))
67
- delete jsonSchema.default
68
- return info ? bodySchemaBindDoc(jsonSchema, info) : jsonSchema
57
+ removeAllDefault(jsonSchema)
58
+ addAllAdditionalProperties(jsonSchema)
59
+ return jsonSchema
69
60
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctod",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "CtoD Is Chat To Data Utils.",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -59,7 +59,7 @@
59
59
  "chinese-conv": "^1.1.0",
60
60
  "handlebars": "^4.7.7",
61
61
  "json5": "^2.2.3",
62
- "power-helper": "^0.7.6",
63
- "yup": "^1.1.1"
62
+ "power-helper": "^0.7.9",
63
+ "yup": "^1.4.0"
64
64
  }
65
65
  }
@@ -49,6 +49,11 @@ export declare type ChatBrokerHooks<S extends ValidateCallback<any>, O extends V
49
49
  messages: Message[];
50
50
  parseText: string;
51
51
  lastUserMessage: string;
52
+ /**
53
+ * @zh 宣告解析失敗
54
+ * @en Declare parsing failure
55
+ */
56
+ parseFail: (error: any) => void;
52
57
  changeParseText: (text: string) => void;
53
58
  };
54
59
  /**
@@ -98,7 +103,7 @@ export declare type Params<S extends ValidateCallback<any>, O extends ValidateCa
98
103
  name?: string;
99
104
  plugins?: PS | (() => PS);
100
105
  request: (messages: Message[], context: RequestContext) => Promise<string>;
101
- install: (context: {
106
+ install?: (context: {
102
107
  log: Log;
103
108
  attach: Hook<C>['attach'];
104
109
  attachAfter: Hook<C>['attachAfter'];
@@ -25,7 +25,7 @@ export declare type TranslatorParams<S extends ValidateCallback<any>, O extends
25
25
  input: S;
26
26
  output: O;
27
27
  };
28
- }) => Promise<string>;
28
+ }) => Promise<string | string[]>;
29
29
  };
30
30
  export declare class Translator<S extends ValidateCallback<any>, O extends ValidateCallback<any>> {
31
31
  private params;
@@ -1,6 +1,6 @@
1
1
  export * as plugins from './plugins';
2
2
  export * as templates from './templates';
3
- export { validateToJsonSchema, JsonSchemaInfo } from './utils/validate';
3
+ export { validateToJsonSchema } from './utils/validate';
4
4
  export { OpenAI } from './service/openai';
5
5
  export { Llama3Cpp } from './service/llama3.cpp';
6
6
  export { TextParser } from './core/parser';
@@ -16,6 +16,10 @@ import { Translator } from './core/translator';
16
16
  import { TextParser } from './core/parser';
17
17
  import { ChatBroker } from './broker/chat';
18
18
  import { ChatBrokerPlugin } from './core/plugin';
19
+ /**
20
+ * @see https://github.com/sodaru/yup-to-json-schema?tab=readme-ov-file#extend-the-schema
21
+ */
22
+ export declare const bindYupToJsonSchemaToYup: () => void;
19
23
  export declare const ctod: {
20
24
  OpenAI: typeof OpenAI;
21
25
  Llama3Cpp: typeof Llama3Cpp;
@@ -25,6 +29,7 @@ export declare const ctod: {
25
29
  Translator: typeof Translator;
26
30
  TextParser: typeof TextParser;
27
31
  ChatBrokerPlugin: typeof ChatBrokerPlugin;
28
- validateToJsonSchema: <T extends import("./utils/validate").ValidateCallback<any>>(cb: T, info?: import("./utils/validate").JsonSchemaInfo | undefined) => import("json-schema").JSONSchema7;
32
+ validateToJsonSchema: <T extends import("./utils/validate").ValidateCallback<any>>(cb: T) => import("json-schema").JSONSchema7;
33
+ bindYupToJsonSchemaToYup: () => void;
29
34
  };
30
35
  export default ctod;
@@ -3,7 +3,7 @@
3
3
  * @en A plugin based on printing log.
4
4
  */
5
5
  export declare const PrintLogPlugin: import("..").ChatBrokerPlugin<(yup: typeof import("yup")) => {
6
- detail: import("yup").BooleanSchema<boolean, import("yup").AnyObject, false, "d">;
6
+ detail: import("yup").BooleanSchema<boolean | undefined, import("yup").AnyObject, false, "d">;
7
7
  }, () => {}>;
8
8
  /**
9
9
  * @zh 當解析失敗時,會自動重試的對話。
@@ -1,5 +1,5 @@
1
1
  import { ChatBrokerPlugin } from '../core/plugin';
2
2
  declare const _default: ChatBrokerPlugin<(yup: typeof import("yup")) => {
3
- detail: import("yup").BooleanSchema<boolean, import("yup").AnyObject, false, "d">;
3
+ detail: import("yup").BooleanSchema<boolean | undefined, import("yup").AnyObject, false, "d">;
4
4
  }, () => {}>;
5
5
  export default _default;
@@ -1,12 +1,10 @@
1
1
  import { AxiosInstance } from 'axios';
2
- import { JsonSchemaInfo } from '../../utils/validate';
3
2
  import { Llama3CppCompletion, Config } from './completion';
4
3
  export declare class Llama3Cpp {
5
4
  _axios: AxiosInstance;
6
5
  static createChatRequest(params: {
7
6
  config: Partial<Config> | (() => Promise<Partial<Config>>);
8
7
  talkOptions?: any;
9
- jsonSchemaInfo?: JsonSchemaInfo;
10
8
  }): (messages: any[], { schema, onCancel }: any) => Promise<string>;
11
9
  /**
12
10
  * @zh 如果你有需要特別設定 axios,請使用這方法。
@@ -73,6 +73,8 @@ export declare class OpenAIChat {
73
73
  * @en Talk to the AI
74
74
  */
75
75
  talk(messages?: ChatGPTMessage[], options?: {
76
+ /** 要 forceJsonFormat 為 true 才會生效 */
77
+ jsonSchema?: any;
76
78
  abortController?: AbortController;
77
79
  }): Promise<{
78
80
  id: string;
@@ -1,11 +1,15 @@
1
+ import { AxiosInstance } from 'axios';
1
2
  import { OpenAIVision } from './vision';
2
3
  import { OpenAIChat, Config } from './chat';
3
4
  import { OpenAIImagesGeneration } from './images-generation';
4
- import { AxiosInstance } from 'axios';
5
5
  export declare class OpenAI {
6
6
  _axios: AxiosInstance;
7
7
  _apiKey: string;
8
8
  static createChatRequest(apiKey: string | (() => Promise<string>), config?: Partial<Config> | (() => Promise<Partial<Config>>)): (messages: any[], { onCancel }: any) => Promise<string>;
9
+ static createChatRequestWithJsonSchema(params: {
10
+ apiKey: string | (() => Promise<string>);
11
+ config?: Partial<Config> | (() => Promise<Partial<Config>>);
12
+ }): (messages: any[], { schema, onCancel }: any) => Promise<string>;
9
13
  constructor(apiKey?: string);
10
14
  /**
11
15
  * @zh 如果你有需要特別設定 axios,請使用這方法。
@@ -0,0 +1,11 @@
1
+ declare type ParserFail = {
2
+ name: string;
3
+ error: any;
4
+ };
5
+ export declare class ParserError {
6
+ isParserError: boolean;
7
+ parserFails: ParserFail[];
8
+ error: any;
9
+ constructor(error: any, parserFails: ParserFail[]);
10
+ }
11
+ export {};
@@ -1,5 +1,5 @@
1
1
  import * as Yup from 'yup';
2
- import type { Schema } from 'yup';
2
+ import { Schema } from 'yup';
3
3
  export declare type ValidateCallback<T extends Record<string, Schema>> = (_yup: typeof Yup) => {
4
4
  [K in keyof T]: T[K];
5
5
  };
@@ -12,14 +12,4 @@ export declare function definedValidateSchema<T extends ValidateCallback<any>>(c
12
12
  export declare function validate<T extends ValidateCallback<any>, R = ReturnType<T>>(target: any, schemaCallback: T): { [K in keyof R]: R[K] extends {
13
13
  __outputType: any;
14
14
  } ? R[K]["__outputType"] : unknown; };
15
- /**
16
- * @zh 將 JSON Schema 設定描述,可以指定深層結構,例如 user.name, user.level 等。
17
- * @en Set the JSON Schema description, you can specify deep structures, such as user.name, user.level, etc.
18
- */
19
- export declare type JsonSchemaInfo = {
20
- desc?: Record<string, string | {
21
- description?: string;
22
- examples?: any;
23
- }>;
24
- };
25
- export declare const validateToJsonSchema: <T extends ValidateCallback<any>>(cb: T, info?: JsonSchemaInfo) => import("json-schema").JSONSchema7;
15
+ export declare const validateToJsonSchema: <T extends ValidateCallback<any>>(cb: T) => import("json-schema").JSONSchema7;
@@ -1,61 +0,0 @@
1
- // eslint-disable-next-line @typescript-eslint/triple-slash-reference
2
- /// <reference path="../lib/shims.d.ts" />
3
- import { ChatBroker, OpenAI, templates, validateToJsonSchema } from '../lib/index'
4
-
5
- /**
6
- * @test npx ts-node ./examples/chat-demo.ts
7
- */
8
-
9
- const API_KEY = ''
10
- const broker = new ChatBroker({
11
- input: yup => {
12
- return {
13
- indexs: yup.array(yup.string()).required(),
14
- question: yup.string().required()
15
- }
16
- },
17
- output: yup => {
18
- return {
19
- indexs: yup.array(yup.object({
20
- name: yup.string().required(),
21
- score: yup.number().required()
22
- })).required()
23
- }
24
- },
25
- install: () => null,
26
- request: OpenAI.createChatRequest(API_KEY),
27
- question: async({ indexs, question }, { schema }) => {
28
- return templates.requireJsonResponseWithJsonSchema([
29
- '我有以下索引',
30
- `${JSON.stringify(indexs)}`,
31
- `請幫我解析"${question}"可能是哪個索引`,
32
- '且相關性由高到低排序並給予分數,分數由 0 ~ 1'
33
- ], validateToJsonSchema(schema.output, {
34
- desc: {
35
- indexs: '由高到低排序的索引'
36
- }
37
- }))
38
- }
39
- })
40
-
41
- broker.request({
42
- indexs: ['胃痛', '腰痛', '頭痛', '喉嚨痛', '四肢疼痛'],
43
- question: '喝咖啡,吃甜食,胃食道逆流'
44
- }).then(e => {
45
- console.log('輸出結果:', e.indexs)
46
- /*
47
- [
48
- {
49
- name: '胃痛',
50
- score: 1
51
- },
52
- {
53
- name: '喉嚨痛',
54
- score: 0.7
55
- },
56
- ...
57
- ]
58
- */
59
- }).catch(error => {
60
- console.error('Error:', error?.response?.data?.error?.message ?? error)
61
- })