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 +39 -30
- package/dist/index.js +1 -1
- package/examples/chat-demo.ts +32 -20
- package/lib/broker/chat.ts +12 -3
- package/lib/core/translator.ts +4 -7
- package/lib/index.ts +17 -2
- package/lib/plugins/print-log.ts +1 -1
- package/lib/service/llama3.cpp/index.ts +5 -15
- package/lib/service/openai/chat.ts +15 -3
- package/lib/service/openai/index.ts +28 -1
- package/lib/utils/error.ts +14 -0
- package/lib/utils/validate.ts +27 -36
- package/package.json +3 -3
- package/types/lib/broker/chat.d.ts +6 -1
- package/types/lib/core/translator.d.ts +1 -1
- package/types/lib/index.d.ts +7 -2
- package/types/lib/plugins/index.d.ts +1 -1
- package/types/lib/plugins/print-log.d.ts +1 -1
- package/types/lib/service/llama3.cpp/index.d.ts +0 -2
- package/types/lib/service/openai/chat.d.ts +2 -0
- package/types/lib/service/openai/index.d.ts +5 -1
- package/types/lib/utils/error.d.ts +11 -0
- package/types/lib/utils/validate.d.ts +2 -12
- package/examples/chat-with-json-schema-demo.ts +0 -61
package/README.md
CHANGED
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
|
|
22
22
|
## 摘要
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
本工具是利用聊天機器人能夠讀懂自然語言的特性,將我們的需求與資料透過口語化的方式交付給他處理,並要求回應 JSON。
|
|
25
25
|
|
|
26
|
-
|
|
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
|
|
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
|
|
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(
|
|
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:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
|
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)})()));
|
package/examples/chat-demo.ts
CHANGED
|
@@ -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,
|
|
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(
|
|
20
|
-
name: yup.string().required(),
|
|
21
|
-
score: yup.number().required()
|
|
22
|
-
})).required()
|
|
23
|
+
indexs: yup.array(item).description('由高到低排序的索引').required()
|
|
23
24
|
}
|
|
24
25
|
},
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
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: '喝咖啡,吃甜食,胃食道逆流'
|
package/lib/broker/chat.ts
CHANGED
|
@@ -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
|
|
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
|
|
348
|
+
if (error instanceof ParserError) {
|
|
340
349
|
await this.hook.notify('parseFailed', {
|
|
341
350
|
id,
|
|
342
351
|
error: error.error,
|
package/lib/core/translator.ts
CHANGED
|
@@ -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
|
|
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
|
package/lib/plugins/print-log.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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:
|
|
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
|
|
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
|
|
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
|
+
}
|
package/lib/utils/validate.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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.
|
|
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.
|
|
63
|
-
"yup": "^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
|
|
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;
|
package/types/lib/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * as plugins from './plugins';
|
|
2
2
|
export * as templates from './templates';
|
|
3
|
-
export { validateToJsonSchema
|
|
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
|
|
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,請使用這方法。
|
|
@@ -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,請使用這方法。
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as Yup from 'yup';
|
|
2
|
-
import
|
|
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
|
-
})
|