ctod 0.4.1 → 0.6.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
@@ -3,7 +3,7 @@
3
3
  <h1 align="center">CtoD</h1>
4
4
  <h3 align="center">Chat To Data</h3>
5
5
 
6
- <p align="center">
6
+ <h6 align="center">
7
7
  <a href="https://www.npmjs.com/package/ctod">
8
8
  <img src="https://img.shields.io/npm/v/ctod.svg">
9
9
  </a>
@@ -11,7 +11,7 @@
11
11
  <img src="https://img.shields.io/github/stars/KHC-ZhiHao/ctod.svg?style=social">
12
12
  </a>
13
13
  <br>
14
- </p>
14
+ </h6>
15
15
 
16
16
  <br>
17
17
 
@@ -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,63 @@ 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().required().meta({
65
+ jsonSchema: {
66
+ description: '索引名稱'
67
+ }
68
+ }),
69
+ score: yup.number().required().meta({
70
+ jsonSchema: {
71
+ description: '評比分數'
72
+ }
73
+ })
74
+ }).required()
63
75
  return {
64
- indexs: yup.array(yup.object({
65
- name: yup.string().required(),
66
- score: yup.number().required()
67
- })).required()
76
+ indexs: yup.array(item).required().meta({
77
+ jsonSchema: {
78
+ description: '由高到低排序的索引'
79
+ }
80
+ })
68
81
  }
69
82
  },
70
83
  /** 初始化系統,通常來植入或掛鉤生命週期 */
71
- install: () => {},
84
+ install({ attach }) {
85
+ attach('start', async({ setPreMessages }) => {
86
+ setPreMessages([
87
+ {
88
+ role: 'system',
89
+ content: '你現在是一位擅長分類索引的藥師'
90
+ }
91
+ ])
92
+ })
93
+ },
72
94
  /** 定義發送請求的接口 */
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
- }
95
+ request: OpenAI.createChatRequestWithJsonSchema({
96
+ apiKey: API_KEY,
97
+ config: {
98
+ model: 'gpt-4o-mini'
99
+ }
100
+ }),
79
101
  /** 組裝與定義我們要向機器人發出的請求 */
80
102
  question: async({ indexs, question }) => {
81
- return templates.requireJsonResponse([
103
+ return [
82
104
  '我有以下索引',
83
105
  `${JSON.stringify(indexs)}`,
84
106
  `請幫我解析"${question}"可能是哪個索引`,
85
107
  '且相關性由高到低排序並給予分數,分數由 0 ~ 1'
86
- ], {
87
- indexs: {
88
- desc: '由高到低排序的索引',
89
- example: [
90
- {
91
- name: '索引名稱',
92
- score: '評比分數,數字顯示'
93
- }
94
- ]
95
- }
96
- })
108
+ ]
97
109
  }
98
110
  })
99
111
 
@@ -210,3 +222,26 @@ const broker = new ChatBroker({
210
222
 
211
223
  1. 支援 llama3.cpp server service
212
224
  2. 新增 yup to json scheme。
225
+
226
+ ### 0.5.x
227
+
228
+ 移除了 JSON Schema Info 的支援,而是透過 [yup-to-json-schema](https://github.com/sodaru/yup-to-json-schema) 進行生成資料格式。
229
+
230
+ 由於 `yup-to-json-schema` 的延伸套件要使用 `yup.string().description()` 方法需要進行全域註冊,在此我們提供了 `bindYupToJsonSchemaToYup` 這個方法,讓使用者可以自行決定是否要進行註冊。
231
+
232
+ 1. 可以在 question 中回應 array,會透過 join 進行合併。
233
+ 2. 可以省略 install 參數了。
234
+
235
+ ### 0.6.x
236
+
237
+ `bindYupToJsonSchemaToYup` 有一些依賴問題已經被移除,改用以下方案取代:
238
+
239
+ ```ts
240
+ yup.array(item).required().meta({
241
+ jsonSchema: {
242
+ description: '由高到低排序的索引'
243
+ }
244
+ })
245
+ ```
246
+
247
+ 1. 新增了 definedYupSchema 讓建立複雜的 Output 更加容易。
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);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,e.jsonSchemaInfo),{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")},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=e=>{if(e.default&&delete e.default,e.properties)for(let t in e.properties)e.properties[t].default&&delete e.properties[t].default,n(e.properties[t]);e.items&&n(e.items)},r=e=>{if("object"===e.type){e.additionalProperties=!1;for(const t in e.properties)r(e.properties[t])}else"array"===e.type&&r(e.items)},a=(0,l.convertSchema)(s.object(e(s)));return n(a),r(a),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})(a,t):a}},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={177:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ChatBroker=void 0;const r=n(306),a=n(572),o=n(235),s=n(165);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}}},306: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(865));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},198:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ChatBrokerPlugin=void 0;const r=n(572);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}}}},235:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Translator=void 0;const r=n(357),a=n(165);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)}}}},665: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.definedYupSchema=t.validateToJsonSchema=t.templates=t.plugins=void 0,t.plugins=o(n(374)),t.templates=o(n(854));var s=n(357);Object.defineProperty(t,"validateToJsonSchema",{enumerable:!0,get:function(){return s.validateToJsonSchema}}),Object.defineProperty(t,"definedYupSchema",{enumerable:!0,get:function(){return s.definedYupSchema}});var i=n(984);Object.defineProperty(t,"OpenAI",{enumerable:!0,get:function(){return i.OpenAI}});var l=n(887);Object.defineProperty(t,"Llama3Cpp",{enumerable:!0,get:function(){return l.Llama3Cpp}});var c=n(306);Object.defineProperty(t,"TextParser",{enumerable:!0,get:function(){return c.TextParser}});var u=n(177);Object.defineProperty(t,"ChatBroker",{enumerable:!0,get:function(){return u.ChatBroker}});var p=n(198);Object.defineProperty(t,"ChatBrokerPlugin",{enumerable:!0,get:function(){return p.ChatBrokerPlugin}});var d=n(235);Object.defineProperty(t,"Translator",{enumerable:!0,get:function(){return d.Translator}});const h=o(n(374)),f=o(n(854)),m=n(984),g=n(887),y=n(235),_=n(306),v=n(177),b=n(198),w=n(357);t.ctod={OpenAI:m.OpenAI,Llama3Cpp:g.Llama3Cpp,plugins:h,templates:f,ChatBroker:v.ChatBroker,Translator:y.Translator,TextParser:_.TextParser,ChatBrokerPlugin:b.ChatBrokerPlugin,definedYupSchema:w.definedYupSchema,validateToJsonSchema:w.validateToJsonSchema},e.exports=t.ctod,e.exports.ctod=t.ctod,t.default=t.ctod},374: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(92)),o=r(n(354)),s=r(n(270)),i=r(n(724));t.PrintLogPlugin=o.default,t.RetryPlugin=a.default,t.LimiterPlugin=s.default.plugin,t.LimiterPluginGlobState=s.default,t.RolePlugin=i.default},270:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(198),a=n(572),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())}))}))}))}})}},354:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(198);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)}}))}})},92:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(198);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())}))}})},724:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0});const r=n(198);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])}))}})},129:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Llama3CppCompletion=void 0;const r=n(572),a=n(4);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()}}},887: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(938)),o=n(4),s=n(357),i=n(129);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},228:(e,t,n)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OpenAIChat=void 0;const r=n(572);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)}}}},11:(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}}},984: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(938)),o=n(38),s=n(228),i=n(11),l=n(357);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},38:(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||[],r=n[0]?.message||{role:"assistant",content:""};return{id:t?.data.id,text:r.content,apiReseponse:t.data}}}},854: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(156)),o=n(572);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")},165:(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}}},357: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.definedYupSchema=t.validate=t.definedValidateSchema=void 0;const s=o(n(622)),i=n(250);t.definedValidateSchema=function(e){return e},t.validate=function(e,t){return s.object(t(s)).required().validateSync(e||{})},t.definedYupSchema=e=>e(s),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}},250:e=>{e.exports=require("@sodaru/yup-to-json-schema")},938:e=>{e.exports=require("axios")},4:e=>{e.exports=require("chinese-conv")},156:e=>{e.exports=require("handlebars")},865:e=>{e.exports=require("json5")},572:e=>{e.exports=require("power-helper")},622: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}(665)})()));
@@ -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 } from '../lib/index'
4
4
 
5
5
  /**
6
6
  * @test npx ts-node ./examples/chat-demo.ts
@@ -10,37 +10,59 @@ 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().required().meta({
20
+ jsonSchema: {
21
+ description: '索引名稱'
22
+ }
23
+ }),
24
+ score: yup.number().required().meta({
25
+ jsonSchema: {
26
+ description: '評比分數'
27
+ }
28
+ })
29
+ }).required()
18
30
  return {
19
- indexs: yup.array(yup.object({
20
- name: yup.string().required(),
21
- score: yup.number().required()
22
- })).required()
31
+ indexs: yup.array(item).required().meta({
32
+ jsonSchema: {
33
+ description: '由高到低排序的索引'
34
+ }
35
+ })
23
36
  }
24
37
  },
25
- install: () => null,
26
- request: OpenAI.createChatRequest(API_KEY),
38
+ plugins: {
39
+ log: plugins.PrintLogPlugin.use({
40
+ detail: true
41
+ })
42
+ },
43
+ install({ attach }) {
44
+ attach('start', async({ setPreMessages }) => {
45
+ setPreMessages([
46
+ {
47
+ role: 'system',
48
+ content: '你現在是一位擅長分類索引的藥師'
49
+ }
50
+ ])
51
+ })
52
+ },
53
+ request: OpenAI.createChatRequestWithJsonSchema({
54
+ apiKey: API_KEY,
55
+ config: {
56
+ model: 'gpt-4o-mini'
57
+ }
58
+ }),
27
59
  question: async({ indexs, question }) => {
28
- return templates.requireJsonResponse([
60
+ return [
29
61
  '我有以下索引',
30
62
  `${JSON.stringify(indexs)}`,
31
63
  `請幫我解析"${question}"可能是哪個索引`,
32
64
  '且相關性由高到低排序並給予分數,分數由 0 ~ 1'
33
- ], {
34
- indexs: {
35
- desc: '由高到低排序的索引',
36
- example: [
37
- {
38
- name: '索引名稱',
39
- score: '評比分數,數字顯示'
40
- }
41
- ]
42
- }
43
- })
65
+ ]
44
66
  }
45
67
  })
46
68
 
@@ -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, definedYupSchema } 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'
@@ -17,7 +17,7 @@ import { Translator } from './core/translator'
17
17
  import { TextParser } from './core/parser'
18
18
  import { ChatBroker } from './broker/chat'
19
19
  import { ChatBrokerPlugin } from './core/plugin'
20
- import { validateToJsonSchema } from './utils/validate'
20
+ import { validateToJsonSchema, definedYupSchema } from './utils/validate'
21
21
 
22
22
  export const ctod = {
23
23
  OpenAI,
@@ -28,6 +28,7 @@ export const ctod = {
28
28
  Translator,
29
29
  TextParser,
30
30
  ChatBrokerPlugin,
31
+ definedYupSchema,
31
32
  validateToJsonSchema
32
33
  }
33
34
 
@@ -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)
@@ -2,7 +2,7 @@ import axios, { AxiosInstance } from 'axios'
2
2
  import { OpenAIVision } from './vision'
3
3
  import { OpenAIChat, Config } from './chat'
4
4
  import { OpenAIImagesGeneration } from './images-generation'
5
- import { validateToJsonSchema, JsonSchemaInfo } from '../../utils/validate'
5
+ import { validateToJsonSchema } from '../../utils/validate'
6
6
 
7
7
  export class OpenAI {
8
8
  _axios = axios.create()
@@ -25,7 +25,6 @@ export class OpenAI {
25
25
  static createChatRequestWithJsonSchema(params:{
26
26
  apiKey: string | (() => Promise<string>),
27
27
  config?: Partial<Config> | (() => Promise<Partial<Config>>)
28
- jsonSchemaInfo?: JsonSchemaInfo
29
28
  }) {
30
29
  return async(messages: any[], { schema, onCancel }: any) => {
31
30
  const openai = new OpenAI(typeof params.apiKey === 'string' ? params.apiKey : await params.apiKey())
@@ -35,7 +34,7 @@ export class OpenAI {
35
34
  chat.setConfig(typeof params.config === 'function' ? await params.config() : params.config)
36
35
  }
37
36
  onCancel(() => abortController.abort())
38
- const jsonSchema = validateToJsonSchema(schema.output, params.jsonSchemaInfo)
37
+ const jsonSchema = validateToJsonSchema(schema.output)
39
38
  const { text } = await chat.talk(messages, {
40
39
  abortController,
41
40
  jsonSchema: {
@@ -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,42 +26,11 @@ 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
- }>
29
+ export const definedYupSchema = <T extends ValidateCallback<any>>(cb: T) => {
30
+ return cb(Yup)
40
31
  }
41
32
 
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
- }
61
- }
62
- }
63
- }
64
- return schema
65
- }
33
+ export const validateToJsonSchema = <T extends ValidateCallback<any>>(cb: T) => {
66
34
  const removeAllDefault = (schema: any) => {
67
35
  if (schema.default) {
68
36
  delete schema.default
@@ -92,5 +60,5 @@ export const validateToJsonSchema = <T extends ValidateCallback<any>>(cb: T, inf
92
60
  const jsonSchema = convertSchema(Yup.object(cb(Yup)))
93
61
  removeAllDefault(jsonSchema)
94
62
  addAllAdditionalProperties(jsonSchema)
95
- return info ? bodySchemaBindDoc(jsonSchema, info) : jsonSchema
63
+ return jsonSchema
96
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctod",
3
- "version": "0.4.1",
3
+ "version": "0.6.0",
4
4
  "description": "CtoD Is Chat To Data Utils.",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -54,12 +54,12 @@
54
54
  "webpack-node-externals": "^3.0.0"
55
55
  },
56
56
  "dependencies": {
57
+ "yup": "^1.4.0",
57
58
  "@sodaru/yup-to-json-schema": "^2.0.1",
58
59
  "axios": "^1.4.0",
59
60
  "chinese-conv": "^1.1.0",
60
61
  "handlebars": "^4.7.7",
61
62
  "json5": "^2.2.3",
62
- "power-helper": "^0.7.6",
63
- "yup": "^1.1.1"
63
+ "power-helper": "^0.7.9"
64
64
  }
65
65
  }
@@ -2,12 +2,12 @@ import { ChatBrokerPlugin } from '../core/plugin';
2
2
  import { Event, Hook, Log } from 'power-helper';
3
3
  import { Translator, TranslatorParams } from '../core/translator';
4
4
  import { ValidateCallback, ValidateCallbackOutputs } from '../utils/validate';
5
- declare type Message = {
5
+ type Message = {
6
6
  role: 'system' | 'user' | 'assistant';
7
7
  name?: string;
8
8
  content: string;
9
9
  };
10
- export declare type ChatBrokerHooks<S extends ValidateCallback<any>, O extends ValidateCallback<any>, P extends ChatBrokerPlugin<any, any>, PS extends Record<string, ReturnType<P['use']>>> = {
10
+ export type ChatBrokerHooks<S extends ValidateCallback<any>, O extends ValidateCallback<any>, P extends ChatBrokerPlugin<any, any>, PS extends Record<string, ReturnType<P['use']>>> = {
11
11
  /**
12
12
  * @zh 第一次聊天的時候觸發
13
13
  * @en Triggered when chatting for the first time
@@ -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
  /**
@@ -85,7 +90,7 @@ export declare type ChatBrokerHooks<S extends ValidateCallback<any>, O extends V
85
90
  id: string;
86
91
  };
87
92
  };
88
- declare type RequestContext = {
93
+ type RequestContext = {
89
94
  count: number;
90
95
  isRetry: boolean;
91
96
  onCancel: (cb: () => void) => void;
@@ -94,11 +99,11 @@ declare type RequestContext = {
94
99
  output: any;
95
100
  };
96
101
  };
97
- export declare type Params<S extends ValidateCallback<any>, O extends ValidateCallback<any>, C extends Record<string, any>, P extends ChatBrokerPlugin<any, any>, PS extends Record<string, ReturnType<P['use']>>> = Omit<TranslatorParams<S, O>, 'parsers'> & {
102
+ export type Params<S extends ValidateCallback<any>, O extends ValidateCallback<any>, C extends Record<string, any>, P extends ChatBrokerPlugin<any, any>, PS extends Record<string, ReturnType<P['use']>>> = Omit<TranslatorParams<S, O>, 'parsers'> & {
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'];
@@ -1,4 +1,4 @@
1
- declare type TextParserParams = {
1
+ type TextParserParams = {
2
2
  /**
3
3
  * @zh 解讀器名字。
4
4
  * @en The name of the parser.
@@ -2,8 +2,8 @@ import { Translator } from './translator';
2
2
  import { ChatBrokerHooks } from '../broker/chat';
3
3
  import { Log, Hook, Event } from 'power-helper';
4
4
  import { ValidateCallback, ValidateCallbackOutputs } from '../utils/validate';
5
- declare type BrokerHooks = ChatBrokerHooks<any, any, any, any>;
6
- declare type BrokerPluginParams<T extends ValidateCallback<any>, R extends ValidateCallback<any>> = {
5
+ type BrokerHooks = ChatBrokerHooks<any, any, any, any>;
6
+ type BrokerPluginParams<T extends ValidateCallback<any>, R extends ValidateCallback<any>> = {
7
7
  name: string;
8
8
  params: T;
9
9
  receiveData: R;
@@ -1,6 +1,6 @@
1
1
  import { TextParser } from './parser';
2
2
  import { ValidateCallback, ValidateCallbackOutputs } from '../utils/validate';
3
- export declare type TranslatorParams<S extends ValidateCallback<any>, O extends ValidateCallback<any>> = {
3
+ export type TranslatorParams<S extends ValidateCallback<any>, O extends ValidateCallback<any>> = {
4
4
  /**
5
5
  * @zh 輸入的資料格式。
6
6
  * @en The input data format.
@@ -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, definedYupSchema } 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';
@@ -25,6 +25,9 @@ export declare const ctod: {
25
25
  Translator: typeof Translator;
26
26
  TextParser: typeof TextParser;
27
27
  ChatBrokerPlugin: typeof ChatBrokerPlugin;
28
- validateToJsonSchema: <T extends import("./utils/validate").ValidateCallback<any>>(cb: T, info?: import("./utils/validate").JsonSchemaInfo | undefined) => import("json-schema").JSONSchema7;
28
+ definedYupSchema: <T extends import("./utils/validate").ValidateCallback<any>>(cb: T) => {
29
+ [x: string]: any;
30
+ };
31
+ validateToJsonSchema: <T_1 extends import("./utils/validate").ValidateCallback<any>>(cb: T_1) => import("json-schema").JSONSchema7;
29
32
  };
30
33
  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 當解析失敗時,會自動重試的對話。
@@ -11,7 +11,7 @@ export declare const PrintLogPlugin: import("..").ChatBrokerPlugin<(yup: typeof
11
11
  */
12
12
  export declare const RetryPlugin: import("..").ChatBrokerPlugin<(yup: typeof import("yup")) => {
13
13
  retry: import("yup").NumberSchema<number, import("yup").AnyObject, 1, "d">;
14
- printWarn: import("yup").BooleanSchema<boolean, import("yup").AnyObject, true, "d">;
14
+ printWarn: import("yup").BooleanSchema<NonNullable<boolean | undefined>, import("yup").AnyObject, true, "d">;
15
15
  }, () => {}>;
16
16
  /**
17
17
  * @zh 限制使用流量,這個 plugin 可以有效讓所有對話不會再限制內同時發送,可用於在開發過程中遭遇伺服器因頻率過高而阻擋請求。
@@ -1,6 +1,6 @@
1
1
  import { ChatBrokerPlugin } from '../core/plugin';
2
2
  import { Event } from 'power-helper';
3
- declare type Events = {
3
+ type Events = {
4
4
  run: {
5
5
  id: string;
6
6
  };
@@ -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,6 +1,6 @@
1
1
  import { ChatBrokerPlugin } from '../core/plugin';
2
2
  declare const _default: ChatBrokerPlugin<(yup: typeof import("yup")) => {
3
3
  retry: import("yup").NumberSchema<number, import("yup").AnyObject, 1, "d">;
4
- printWarn: import("yup").BooleanSchema<boolean, import("yup").AnyObject, true, "d">;
4
+ printWarn: import("yup").BooleanSchema<NonNullable<boolean | undefined>, import("yup").AnyObject, true, "d">;
5
5
  }, () => {}>;
6
6
  export default _default;
@@ -1,15 +1,15 @@
1
1
  import { Llama3Cpp } from './index';
2
- declare type Message = {
2
+ type Message = {
3
3
  role: string;
4
4
  content: string;
5
5
  };
6
- declare type Options = any;
7
- export declare type Config = {
6
+ type Options = any;
7
+ export type Config = {
8
8
  baseUrl: string;
9
9
  headers: Record<string, string>;
10
10
  autoConvertTraditionalChinese: boolean;
11
11
  };
12
- declare type Stream = {
12
+ type Stream = {
13
13
  onMessage: (data: {
14
14
  message: string;
15
15
  }) => void;
@@ -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,請使用這方法。
@@ -1,11 +1,11 @@
1
1
  import { OpenAI } from './index';
2
2
  import { PromiseResponseType } from '../../types';
3
- export declare type ChatGPTMessage = {
3
+ export type ChatGPTMessage = {
4
4
  role: 'system' | 'user' | 'assistant';
5
5
  name?: string;
6
6
  content: string;
7
7
  };
8
- declare type ApiResponse = {
8
+ type ApiResponse = {
9
9
  id: string;
10
10
  object: string;
11
11
  created: number;
@@ -24,7 +24,7 @@ declare type ApiResponse = {
24
24
  total_tokens: number;
25
25
  };
26
26
  };
27
- export declare type Config = {
27
+ export type Config = {
28
28
  /**
29
29
  * @zh 一次回應數量
30
30
  * @en How many chat completion choices to generate for each input message.
@@ -106,5 +106,5 @@ export declare class OpenAIChat {
106
106
  nextTalk: (prompt: string | string[]) => Promise<any>;
107
107
  }>;
108
108
  }
109
- export declare type OpenAIChatTalkResponse = PromiseResponseType<OpenAIChat['talk']>;
109
+ export type OpenAIChatTalkResponse = PromiseResponseType<OpenAIChat['talk']>;
110
110
  export {};
@@ -1,11 +1,11 @@
1
1
  import { OpenAI } from './index';
2
- declare type ApiResponse = {
2
+ type ApiResponse = {
3
3
  created: string;
4
4
  data: {
5
5
  b64_json: string;
6
6
  }[];
7
7
  };
8
- declare type Config = {
8
+ type Config = {
9
9
  /**
10
10
  * @zh 模型,支援 dall-e-2 和 dall-e-3
11
11
  * @en Model, support dall-e-2 and dall-e-3
@@ -2,7 +2,6 @@ import { AxiosInstance } from 'axios';
2
2
  import { OpenAIVision } from './vision';
3
3
  import { OpenAIChat, Config } from './chat';
4
4
  import { OpenAIImagesGeneration } from './images-generation';
5
- import { JsonSchemaInfo } from '../../utils/validate';
6
5
  export declare class OpenAI {
7
6
  _axios: AxiosInstance;
8
7
  _apiKey: string;
@@ -10,7 +9,6 @@ export declare class OpenAI {
10
9
  static createChatRequestWithJsonSchema(params: {
11
10
  apiKey: string | (() => Promise<string>);
12
11
  config?: Partial<Config> | (() => Promise<Partial<Config>>);
13
- jsonSchemaInfo?: JsonSchemaInfo;
14
12
  }): (messages: any[], { schema, onCancel }: any) => Promise<string>;
15
13
  constructor(apiKey?: string);
16
14
  /**
@@ -1,6 +1,6 @@
1
1
  import { OpenAI } from './index';
2
2
  import { PromiseResponseType } from '../../types';
3
- declare type ImageContent = {
3
+ type ImageContent = {
4
4
  type: 'image_url' | 'text';
5
5
  text?: string;
6
6
  image_url?: {
@@ -8,12 +8,12 @@ declare type ImageContent = {
8
8
  detail?: string;
9
9
  };
10
10
  };
11
- declare type VisionMessage = {
11
+ type VisionMessage = {
12
12
  role: 'system' | 'user' | 'assistant';
13
13
  name?: string;
14
14
  content: string | ImageContent[];
15
15
  };
16
- declare type ApiResponse = {
16
+ type ApiResponse = {
17
17
  id: string;
18
18
  object: string;
19
19
  created: number;
@@ -34,7 +34,7 @@ declare type ApiResponse = {
34
34
  index: number;
35
35
  }>;
36
36
  };
37
- export declare type Config = {
37
+ export type Config = {
38
38
  /**
39
39
  * @zh 選擇運行的模型。
40
40
  * @en How many chat completion choices to generate for each input message.
@@ -70,5 +70,5 @@ export declare class OpenAIVision {
70
70
  apiReseponse: ApiResponse;
71
71
  }>;
72
72
  }
73
- export declare type OpenAIChatVisionResponse = PromiseResponseType<OpenAIVision['view']>;
73
+ export type OpenAIChatVisionResponse = PromiseResponseType<OpenAIVision['view']>;
74
74
  export {};
@@ -1,4 +1,4 @@
1
- declare type JsonResponseFormat = {
1
+ type JsonResponseFormat = {
2
2
  desc: string;
3
3
  example: any;
4
4
  };
@@ -1 +1 @@
1
- export declare type PromiseResponseType<T extends (...args: any) => Promise<any>, R = Parameters<ReturnType<T>['then']>[0]> = R extends (value: infer P) => any ? P : never;
1
+ export type PromiseResponseType<T extends (...args: any) => Promise<any>, R = Parameters<ReturnType<T>['then']>[0]> = R extends (value: infer P) => any ? P : never;
@@ -0,0 +1,11 @@
1
+ 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,9 +1,9 @@
1
1
  import * as Yup from 'yup';
2
- import type { Schema } from 'yup';
3
- export declare type ValidateCallback<T extends Record<string, Schema>> = (_yup: typeof Yup) => {
2
+ import { Schema } from 'yup';
3
+ export type ValidateCallback<T extends Record<string, Schema>> = (_yup: typeof Yup) => {
4
4
  [K in keyof T]: T[K];
5
5
  };
6
- export declare type ValidateCallbackOutputs<T extends ValidateCallback<any>, R = ReturnType<T>> = {
6
+ export type ValidateCallbackOutputs<T extends ValidateCallback<any>, R = ReturnType<T>> = {
7
7
  [K in keyof R]: R[K] extends {
8
8
  '__outputType': any;
9
9
  } ? R[K]['__outputType'] : unknown;
@@ -12,14 +12,7 @@ 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
- }>;
15
+ export declare const definedYupSchema: <T extends ValidateCallback<any>>(cb: T) => {
16
+ [x: string]: any;
24
17
  };
25
- export declare const validateToJsonSchema: <T extends ValidateCallback<any>>(cb: T, info?: JsonSchemaInfo) => import("json-schema").JSONSchema7;
18
+ export declare const validateToJsonSchema: <T extends ValidateCallback<any>>(cb: T) => import("json-schema").JSONSchema7;
package/webpack.config.ts CHANGED
@@ -24,7 +24,9 @@ export default {
24
24
  externalsPresets: {
25
25
  node: true
26
26
  },
27
- externals: [nodeExternals()],
27
+ externals: [
28
+ nodeExternals()
29
+ ],
28
30
  plugins: [
29
31
  new ESLintPlugin({
30
32
  files: 'lib/**/*.ts',
@@ -1,67 +0,0 @@
1
- // eslint-disable-next-line @typescript-eslint/triple-slash-reference
2
- /// <reference path="../lib/shims.d.ts" />
3
- import { ChatBroker, OpenAI } 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.createChatRequestWithJsonSchema({
27
- apiKey: API_KEY,
28
- config: {
29
- model: 'gpt-4o-mini'
30
- },
31
- jsonSchemaInfo: {
32
- desc: {
33
- indexs: '由高到低排序的索引'
34
- }
35
- }
36
- }),
37
- question: async({ indexs, question }) => {
38
- return [
39
- '我有以下索引',
40
- `${JSON.stringify(indexs)}`,
41
- `請幫我解析"${question}"可能是哪個索引`,
42
- '且相關性由高到低排序並給予分數,分數由 0 ~ 1'
43
- ].join('\n')
44
- }
45
- })
46
-
47
- broker.request({
48
- indexs: ['胃痛', '腰痛', '頭痛', '喉嚨痛', '四肢疼痛'],
49
- question: '喝咖啡,吃甜食,胃食道逆流'
50
- }).then(e => {
51
- console.log('輸出結果:', e.indexs)
52
- /*
53
- [
54
- {
55
- name: '胃痛',
56
- score: 1
57
- },
58
- {
59
- name: '喉嚨痛',
60
- score: 0.7
61
- },
62
- ...
63
- ]
64
- */
65
- }).catch(error => {
66
- console.error('Error:', error?.response?.data?.error?.message ?? error)
67
- })