ctod 0.7.1 → 0.7.2

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.
Files changed (96) hide show
  1. package/examples/basic.ts +97 -0
  2. package/examples/llama.cpp.ts +56 -0
  3. package/examples/plugin.ts +118 -0
  4. package/lib/broker/chat.ts +435 -0
  5. package/lib/core/parser.ts +62 -0
  6. package/lib/core/plugin.ts +46 -0
  7. package/lib/core/translator.ts +115 -0
  8. package/lib/ctod.ts +71 -0
  9. package/lib/index.ts +41 -0
  10. package/lib/plugins/index.ts +38 -0
  11. package/lib/plugins/limiter.ts +103 -0
  12. package/lib/plugins/print-log.ts +35 -0
  13. package/lib/plugins/retry.ts +25 -0
  14. package/lib/plugins/role.ts +28 -0
  15. package/lib/service/llama3.cpp/completion.ts +313 -0
  16. package/lib/service/llama3.cpp/index.ts +53 -0
  17. package/lib/service/openai/chat.ts +244 -0
  18. package/lib/service/openai/images-generation.ts +64 -0
  19. package/lib/service/openai/index.ts +97 -0
  20. package/lib/service/openai/vision.ts +111 -0
  21. package/lib/shims.d.ts +4 -0
  22. package/lib/templates.ts +71 -0
  23. package/lib/types.ts +4 -0
  24. package/lib/utils/error.ts +14 -0
  25. package/lib/utils/validate.ts +64 -0
  26. package/package.json +1 -1
  27. package/types/examples/basic.d.ts +1 -0
  28. package/types/examples/chat-demo.d.ts +2 -0
  29. package/types/examples/chat-for-llama.cpp-demo.d.ts +2 -0
  30. package/types/examples/chat-with-json-schema-demo.d.ts +2 -0
  31. package/types/examples/llama.cpp.d.ts +2 -0
  32. package/types/examples/plugin-demo.d.ts +2 -0
  33. package/types/examples/plugin.d.ts +2 -0
  34. package/types/examples/stream-for-llama.cpp-demo.d.ts +2 -0
  35. package/types/examples/vision-demo.d.ts +2 -0
  36. package/types/lib/broker/chat.d.ts +150 -0
  37. package/types/lib/core/parser.d.ts +32 -0
  38. package/types/lib/core/plugin.d.ts +34 -0
  39. package/types/lib/core/translator.d.ts +67 -0
  40. package/types/lib/ctod.d.ts +32 -0
  41. package/types/lib/index.d.ts +34 -0
  42. package/types/lib/plugins/index.d.ts +47 -0
  43. package/types/lib/plugins/limiter.d.ts +36 -0
  44. package/types/lib/plugins/print-log.d.ts +5 -0
  45. package/types/lib/plugins/retry.d.ts +6 -0
  46. package/types/lib/plugins/role.d.ts +5 -0
  47. package/types/lib/service/llama3.cpp/completion.d.ts +61 -0
  48. package/types/lib/service/llama3.cpp/index.d.ts +19 -0
  49. package/types/lib/service/openai/chat.d.ts +110 -0
  50. package/types/lib/service/openai/completion.d.ts +59 -0
  51. package/types/lib/service/openai/images-generation.d.ts +35 -0
  52. package/types/lib/service/openai/index.d.ts +29 -0
  53. package/types/lib/service/openai/vision.d.ts +74 -0
  54. package/types/lib/templates.d.ts +20 -0
  55. package/types/lib/types.d.ts +1 -0
  56. package/types/lib/utils/error.d.ts +11 -0
  57. package/types/lib/utils/validate.d.ts +16 -0
  58. package/.api-key +0 -1
  59. package/.nyc_output/42919e68-b472-4a5d-b2d3-5d5153f28467.json +0 -1
  60. package/.nyc_output/processinfo/42919e68-b472-4a5d-b2d3-5d5153f28467.json +0 -1
  61. package/.nyc_output/processinfo/index.json +0 -1
  62. package/.output/.output/stores//344/270/200/345/240/264/346/234/237/345/276/205/345/267/262/344/271/205/347/232/204/345/222/214/350/247/243/cover-0.png +0 -1
  63. package/.output/.output/stores//344/270/200/345/240/264/346/234/237/345/276/205/345/267/262/344/271/205/347/232/204/345/222/214/350/247/243/cover-1.png +0 -1
  64. package/.output/.output/stores//344/270/200/345/240/264/346/234/237/345/276/205/345/267/262/344/271/205/347/232/204/345/222/214/350/247/243/story-config.json +0 -4
  65. package/.output/.output/stores//344/270/200/345/240/264/346/234/237/345/276/205/345/267/262/344/271/205/347/232/204/345/222/214/350/247/243/story.json +0 -22
  66. package/.output/.output/stores//345/276/236/351/273/221/346/232/227/350/265/260/345/220/221/345/205/211/346/230/216/cover-0.png +0 -1
  67. package/.output/.output/stores//345/276/236/351/273/221/346/232/227/350/265/260/345/220/221/345/205/211/346/230/216/cover-1.png +0 -1
  68. package/.output/.output/stores//345/276/236/351/273/221/346/232/227/350/265/260/345/220/221/345/205/211/346/230/216/story-config.json +0 -4
  69. package/.output/.output/stores//345/276/236/351/273/221/346/232/227/350/265/260/345/220/221/345/205/211/346/230/216/story.json +0 -24
  70. package/.output/.output/stores//347/240/264/347/242/216/347/232/204/345/271/273/350/261/241/cover-0.png +0 -1
  71. package/.output/.output/stores//347/240/264/347/242/216/347/232/204/345/271/273/350/261/241/cover-1.png +0 -1
  72. package/.output/.output/stores//347/240/264/347/242/216/347/232/204/345/271/273/350/261/241/story-config.json +0 -4
  73. package/.output/.output/stores//347/240/264/347/242/216/347/232/204/345/271/273/350/261/241/story.json +0 -24
  74. package/.output/.output/stores//350/227/235/350/241/223/345/256/266/347/232/204/351/235/210/346/204/237/cover-0.png +0 -1
  75. package/.output/.output/stores//350/227/235/350/241/223/345/256/266/347/232/204/351/235/210/346/204/237/cover-1.png +0 -1
  76. package/.output/.output/stores//350/227/235/350/241/223/345/256/266/347/232/204/351/235/210/346/204/237/story-config.json +0 -4
  77. package/.output/.output/stores//350/227/235/350/241/223/345/256/266/347/232/204/351/235/210/346/204/237/story.json +0 -28
  78. package/.output/.output/stores//350/250/230/346/206/266/345/225/206/344/272/272/cover-0.png +0 -1
  79. package/.output/.output/stores//350/250/230/346/206/266/345/225/206/344/272/272/cover-1.png +0 -1
  80. package/.output/.output/stores//350/250/230/346/206/266/345/225/206/344/272/272/story-config.json +0 -4
  81. package/.output/.output/stores//350/250/230/346/206/266/345/225/206/344/272/272/story.json +0 -18
  82. package/.output/.output/stores//350/250/255/350/250/210/350/210/207/351/226/213/347/231/274/347/232/204/346/214/221/346/210/260/content.json +0 -22
  83. package/.output/.output/talks//344/273/245/347/254/221/350/251/261/347/202/272/345/237/272/347/244/216/347/232/204/351/235/242/350/251/246/content.json +0 -30
  84. package/.output/.output/talks//344/273/245/347/254/221/350/251/261/347/202/272/345/237/272/347/244/216/347/232/204/351/235/242/350/251/246/cover-1684055229695.png +0 -0
  85. package/.output/.output/talks//345/234/250/346/224/277/346/262/273/345/200/231/351/201/270/344/272/272/350/276/257/350/253/226/344/270/255/347/232/204/350/252/252/346/234/215/346/200/247/346/272/235/351/200/232/content.json +0 -30
  86. package/.output/.output/talks//345/234/250/346/224/277/346/262/273/345/200/231/351/201/270/344/272/272/350/276/257/350/253/226/344/270/255/347/232/204/350/252/252/346/234/215/346/200/247/346/272/235/351/200/232/cover-1684056611678.png +0 -0
  87. package/.output/.output/talks//346/224/276/351/254/206/347/232/204/345/200/231/351/201/270/344/272/272/351/200/262/350/241/214/346/224/277/346/262/273/350/276/257/350/253/226/content.json +0 -36
  88. package/.output/.output/talks//346/224/276/351/254/206/347/232/204/345/200/231/351/201/270/344/272/272/351/200/262/350/241/214/346/224/277/346/262/273/350/276/257/350/253/226/cover-1684055140609.png +0 -0
  89. package/.output/.output/talks//346/224/277/346/262/273/347/254/221/350/251/261/content.json +0 -30
  90. package/.output/.output/talks//346/224/277/346/262/273/347/254/221/350/251/261/cover-1684056246465.png +0 -0
  91. package/.output/.output/talks//350/251/274/350/253/247/345/256/266/351/225/267/346/225/231/345/270/253/346/234/203/350/255/260/content.json +0 -26
  92. package/.output/.output/talks//350/251/274/350/253/247/345/256/266/351/225/267/346/225/231/345/270/253/346/234/203/350/255/260/cover-1685785935121.png +0 -0
  93. package/.output/.output/talks//350/262/241/345/213/231/351/241/247/345/225/217/350/253/256/350/251/242/content.json +0 -26
  94. package/.output/.output/talks//350/262/241/345/213/231/351/241/247/345/225/217/350/253/256/350/251/242/cover-1685785115833.png +0 -0
  95. package/.output/.output/talks//351/206/253/347/224/237/345/222/214/347/227/205/344/272/272/350/250/216/350/253/226/346/202/262/345/202/267/content.json +0 -32
  96. package/.output/.output/talks//351/206/253/347/224/237/345/222/214/347/227/205/344/272/272/350/250/216/350/253/226/346/202/262/345/202/267/cover-1684055075942.png +0 -0
@@ -0,0 +1,97 @@
1
+ import fs from 'fs'
2
+ import { CtoD, OpenAI, plugins } from '../lib/index'
3
+
4
+ /**
5
+ * @test npx esno ./examples/basic.ts
6
+ */
7
+
8
+ const apiKey = fs.readFileSync('./.api-key', 'utf-8').trim()
9
+
10
+ const ctod = new CtoD({
11
+ plugins: () => {
12
+ return {
13
+ retry: plugins.RetryPlugin.use({
14
+ retry: 3,
15
+ printWarn: true
16
+ })
17
+ }
18
+ },
19
+ request: OpenAI.createChatRequestWithJsonSchema({
20
+ apiKey,
21
+ config: {
22
+ model: 'gpt-4o'
23
+ }
24
+ })
25
+ })
26
+
27
+ const brokerBuilder = ctod.createBrokerBuilder<{
28
+ indexes: string[]
29
+ question: string
30
+ }>({
31
+ install: ({ attach }) => {
32
+ attach('start', async({ setPreMessages }) => {
33
+ setPreMessages([
34
+ {
35
+ role: 'system',
36
+ content: '你現在是一位擅長分類索引的藥師'
37
+ }
38
+ ])
39
+ })
40
+ }
41
+ })
42
+
43
+ const broker = brokerBuilder.create(async({ yup, data, setMessages }) => {
44
+ const { indexes, question } = data
45
+ setMessages([
46
+ {
47
+ role: 'user',
48
+ content: [
49
+ '我有以下索引',
50
+ `${JSON.stringify(indexes)}`,
51
+ `請幫我解析"${question}"可能是哪個索引`,
52
+ '且相關性由高到低排序並給予分數,分數由 0 ~ 1'
53
+ ]
54
+ }
55
+ ])
56
+ const item = yup.object({
57
+ name: yup.string().required().meta({
58
+ jsonSchema: {
59
+ description: '索引名稱'
60
+ }
61
+ }),
62
+ score: yup.number().required().meta({
63
+ jsonSchema: {
64
+ description: '評比分數'
65
+ }
66
+ })
67
+ }).required()
68
+ return {
69
+ indexes: yup.array(item).required().meta({
70
+ jsonSchema: {
71
+ description: '由高到低排序的索引'
72
+ }
73
+ })
74
+ }
75
+ })
76
+
77
+ broker.request({
78
+ indexes: ['胃痛', '腰痛', '頭痛', '喉嚨痛', '四肢疼痛'],
79
+ question: '喝咖啡,吃甜食,胃食道逆流'
80
+ }).then(e => {
81
+ console.log('輸出結果:', e.indexes)
82
+ /*
83
+ [
84
+ {
85
+ name: '胃痛',
86
+ score: 1
87
+ },
88
+ {
89
+ name: '喉嚨痛',
90
+ score: 0.7
91
+ },
92
+ ...
93
+ ]
94
+ */
95
+ }).catch(error => {
96
+ console.error('Error:', error)
97
+ })
@@ -0,0 +1,56 @@
1
+ // eslint-disable-next-line @typescript-eslint/triple-slash-reference
2
+ /// <reference path="../lib/shims.d.ts" />
3
+ import { CtoD, Llama3Cpp } from '../lib/index'
4
+
5
+ /**
6
+ * @test npx esno ./examples/llama.cpp.ts
7
+ */
8
+
9
+ const ctod = new CtoD({
10
+ request: Llama3Cpp.createChatRequest({
11
+ config: {
12
+ baseUrl: 'http://localhost:12333'
13
+ }
14
+ })
15
+ })
16
+
17
+ const brokerBuilder = ctod.createBrokerBuilder<{
18
+ scene: string
19
+ }>({
20
+ install: ({ attach }) => {
21
+ attach('start', async({ setPreMessages }) => {
22
+ setPreMessages([
23
+ {
24
+ role: 'system',
25
+ content: '你現在是一個遊戲設計師'
26
+ },
27
+ {
28
+ role: 'user',
29
+ content: '我在設計一個互動式遊戲,但我現在對於劇情下一步要發生什麼事情遇到了困難'
30
+ },
31
+ {
32
+ role: 'assistant',
33
+ content: '沒問題,我來幫你想想看。'
34
+ }
35
+ ])
36
+ })
37
+ }
38
+ })
39
+
40
+ const broker = brokerBuilder.create(async({ yup, data, setMessages }) => {
41
+ setMessages([
42
+ {
43
+ role: 'user',
44
+ content: data.scene || '任意發揮'
45
+ }
46
+ ])
47
+ return {
48
+ next: yup.array().of(yup.string().required()).required()
49
+ }
50
+ })
51
+
52
+ broker.request({
53
+ scene: '今天小紅帽遇到了大野狼,大野狼要吃掉小紅帽,小紅帽要怎麼辦?給我三個下一步要發生的事件'
54
+ }).then(result => {
55
+ console.log(result.next)
56
+ })
@@ -0,0 +1,118 @@
1
+ // eslint-disable-next-line @typescript-eslint/triple-slash-reference
2
+ /// <reference path="../lib/shims.d.ts" />
3
+
4
+ import fs from 'fs'
5
+ import { flow } from 'power-helper'
6
+ import { prompt } from 'inquirer'
7
+ import { CtoD, ChatBrokerPlugin, plugins, OpenAI } from '../lib/index'
8
+
9
+ /**
10
+ * @test npx esno ./examples/plugin.ts
11
+ */
12
+
13
+ const apiKey = fs.readFileSync('./.api-key', 'utf-8').trim()
14
+
15
+ const characterPlugin = new ChatBrokerPlugin({
16
+ name: 'character',
17
+ params: () => {
18
+ return {}
19
+ },
20
+ receiveData: yup => {
21
+ return {
22
+ character: yup.string().required()
23
+ }
24
+ },
25
+ onInstall: ({ receive, attachAfter }) => {
26
+ const characters = new Map<string, string>()
27
+ receive(({ id, data }) => {
28
+ characters.set(id, data.character)
29
+ })
30
+ attachAfter('start', async({ id, setPreMessages }) => {
31
+ const character = characters.get(id)
32
+ setPreMessages([
33
+ {
34
+ role: 'user',
35
+ content: '請你扮演' + character
36
+ },
37
+ {
38
+ role: 'assistant',
39
+ content: '沒問題,我現在是' + character
40
+ }
41
+ ])
42
+ })
43
+ attachAfter('done', async({ id }) => {
44
+ characters.delete(id)
45
+ })
46
+ }
47
+ })
48
+
49
+ flow.run(async () => {
50
+ const { character, action } = await prompt([
51
+ {
52
+ type: 'input',
53
+ name: 'character',
54
+ message: '請輸入角色名稱.',
55
+ default: '派大星'
56
+ },
57
+ {
58
+ type: 'input',
59
+ name: 'action',
60
+ message: '你想對他問什麼?',
61
+ default: '你最好的朋友是誰?'
62
+ }
63
+ ])
64
+
65
+ const ctod = new CtoD({
66
+ plugins: () => {
67
+ return {
68
+ character: characterPlugin.use({}),
69
+ log: plugins.PrintLogPlugin.use({
70
+ detail: true
71
+ })
72
+ }
73
+ },
74
+ request: OpenAI.createChatRequestWithJsonSchema({
75
+ apiKey,
76
+ config: {
77
+ model: 'gpt-4o'
78
+ }
79
+ })
80
+ })
81
+
82
+ const brokerBuilder = ctod.createBrokerBuilder<{
83
+ action: string
84
+ character: string
85
+ }>({
86
+ install: ({ attach }) => {
87
+ attach('start', async({ data, plugins }) => {
88
+ plugins.character.send({
89
+ character: data.character
90
+ })
91
+ })
92
+ },
93
+ })
94
+
95
+ const broker = brokerBuilder.create(async({ yup, setMessages }) => {
96
+ setMessages([
97
+ {
98
+ role: 'user',
99
+ content: [
100
+ '請基於你的角色個性,並依據以下指令進行回應:',
101
+ action
102
+ ]
103
+ }
104
+ ])
105
+ return {
106
+ message: yup.string().required()
107
+ }
108
+ })
109
+ try {
110
+ const { message } = await broker.request({
111
+ action,
112
+ character
113
+ })
114
+ console.log(message)
115
+ } catch (error: any) {
116
+ console.error('Error:', error?.response?.data?.error?.message ?? error)
117
+ }
118
+ })
@@ -0,0 +1,435 @@
1
+ import { TextParser } from '../core/parser'
2
+ import { ChatBrokerPlugin } from '../core/plugin'
3
+ import { Event, flow, Hook, Log } from 'power-helper'
4
+ import { Translator, TranslatorParams } from '../core/translator'
5
+ import { ValidateCallback, ValidateCallbackOutputs } from '../utils/validate'
6
+ import { ParserError } from '../utils/error'
7
+
8
+ export type Message = {
9
+ role: 'system' | 'user' | 'assistant' | (string & Record<string, unknown>)
10
+ name?: string
11
+ content: string
12
+ }
13
+
14
+ export type ChatBrokerHooks<
15
+ S extends ValidateCallback<any>,
16
+ O extends ValidateCallback<any>,
17
+ P extends ChatBrokerPlugin<any, any>,
18
+ PS extends Record<string, ReturnType<P['use']>>
19
+ > = {
20
+
21
+ /**
22
+ * @zh 第一次聊天的時候觸發
23
+ * @en Triggered when chatting for the first time
24
+ */
25
+
26
+ start: {
27
+ id: string
28
+ data: ValidateCallbackOutputs<S>
29
+ metadata: Map<string, any>
30
+ plugins: {
31
+ [K in keyof PS]: {
32
+ send: (data: PS[K]['__receiveData']) => void
33
+ }
34
+ }
35
+ schema: {
36
+ input?: S
37
+ output: O
38
+ }
39
+ messages: Message[]
40
+ setPreMessages: (messages: (Omit<Message, 'content'> & { content: string | string[] })[]) => void
41
+ changeMessages: (messages: Message[]) => void
42
+ changeOutputSchema: (output: O) => void
43
+ }
44
+
45
+ /**
46
+ * @zh 發送聊天訊息給機器人前觸發
47
+ * @en Triggered before sending chat message to bot
48
+ */
49
+
50
+ talkBefore: {
51
+ id: string
52
+ data: ValidateCallbackOutputs<S>
53
+ messages: Message[]
54
+ metadata: Map<string, any>
55
+ lastUserMessage: string
56
+ }
57
+
58
+ /**
59
+ * @zh 當聊天機器人回傳資料的時候觸發
60
+ * @en Triggered when the chatbot returns data
61
+ */
62
+
63
+ talkAfter: {
64
+ id: string
65
+ data: ValidateCallbackOutputs<S>
66
+ response: any
67
+ messages: Message[]
68
+ parseText: string
69
+ metadata: Map<string, any>
70
+ lastUserMessage: string
71
+ /**
72
+ * @zh 宣告解析失敗
73
+ * @en Declare parsing failure
74
+ */
75
+ parseFail: (error: any) => void
76
+ changeParseText: (text: string) => void
77
+ }
78
+
79
+ /**
80
+ * @zh 當回傳資料符合規格時觸發
81
+ * @en Triggered when the returned data meets the specifications
82
+ */
83
+
84
+ succeeded: {
85
+ id: string
86
+ metadata: Map<string, any>
87
+ output: ValidateCallbackOutputs<O>
88
+ }
89
+
90
+ /**
91
+ * @zh 當回傳資料不符合規格,或是解析錯誤時觸發
92
+ * @en Triggered when the returned data does not meet the specifications or parsing errors
93
+ */
94
+
95
+ parseFailed: {
96
+ id: string
97
+ error: any
98
+ retry: () => void
99
+ count: number
100
+ response: any
101
+ metadata: Map<string, any>
102
+ parserFails: { name: string, error: any }[]
103
+ messages: Message[]
104
+ lastUserMessage: string
105
+ changeMessages: (messages: Message[]) => void
106
+ }
107
+
108
+ /**
109
+ * @zh 不論成功失敗,執行結束的時候會執行。
110
+ * @en It will be executed when the execution is completed, regardless of success or failure.
111
+ */
112
+
113
+ done: {
114
+ id: string
115
+ metadata: Map<string, any>
116
+ }
117
+ }
118
+
119
+ export type RequestContext = {
120
+ id: string
121
+ count: number
122
+ isRetry: boolean
123
+ metadata: Map<string, any>
124
+ onCancel: (cb: () => void) => void
125
+ schema: {
126
+ input: any
127
+ output: any
128
+ }
129
+ }
130
+
131
+ export type Params<
132
+ S extends ValidateCallback<any>,
133
+ O extends ValidateCallback<any>,
134
+ C extends Record<string, any>,
135
+ P extends ChatBrokerPlugin<any, any>,
136
+ PS extends Record<string, ReturnType<P['use']>>
137
+ > = Omit<TranslatorParams<S, O>, 'parsers'> & {
138
+ name?: string
139
+ plugins?: PS | (() => PS)
140
+ request: (messages: Message[], context: RequestContext) => Promise<string>
141
+ install?: (context: {
142
+ log: Log
143
+ attach: Hook<C>['attach']
144
+ attachAfter: Hook<C>['attachAfter']
145
+ translator: Translator<S, O>
146
+ }) => void
147
+ }
148
+
149
+ export class ChatBroker<
150
+ S extends ValidateCallback<any>,
151
+ O extends ValidateCallback<any>,
152
+ P extends ChatBrokerPlugin<any, any>,
153
+ PS extends Record<string, ReturnType<P['use']>>,
154
+ C extends ChatBrokerHooks<S, O, P, PS> = ChatBrokerHooks<S, O, P, PS>
155
+ > {
156
+ protected __hookType!: C
157
+ protected log: Log
158
+ protected hook = new Hook<C>()
159
+ protected params: Params<S, O, C, P, PS>
160
+ protected plugins = {} as PS
161
+ protected installed = false
162
+ protected translator: Translator<S, O>
163
+ protected event = new Event<{
164
+ cancel: {
165
+ requestId: string
166
+ }
167
+ cancelAll: any
168
+ }>()
169
+
170
+ constructor(params: Params<S, O, C, P, PS>) {
171
+ this.log = new Log(params.name ?? 'no name')
172
+ this.params = params
173
+ this.translator = new Translator({
174
+ ...params,
175
+ parsers: [
176
+ TextParser.JsonMessage()
177
+ ]
178
+ })
179
+ }
180
+
181
+ protected _install(): any {
182
+ if (this.installed === false) {
183
+ this.installed = true
184
+ const context = {
185
+ log: this.log,
186
+ attach: this.hook.attach.bind(this.hook),
187
+ attachAfter: this.hook.attachAfter.bind(this.hook),
188
+ translator: this.translator
189
+ }
190
+ if (this.params.plugins) {
191
+ this.plugins = typeof this.params.plugins === 'function' ? this.params.plugins() : this.params.plugins
192
+ for (let key in this.plugins) {
193
+ this.plugins[key].instance._params.onInstall({
194
+ ...context,
195
+ params: this.plugins[key].params,
196
+ receive: this.plugins[key].receive
197
+ })
198
+ }
199
+ }
200
+ this.params.install?.(context)
201
+ }
202
+ }
203
+
204
+ async cancel(requestId?: string) {
205
+ if (requestId) {
206
+ this.event.emit('cancel', {
207
+ requestId
208
+ })
209
+ } else {
210
+ this.event.emit('cancelAll', {})
211
+ }
212
+ }
213
+
214
+ requestWithId<T extends Translator<S, O>>(data: T['__schemeType']): {
215
+ id: string
216
+ request: Promise<T['__outputType']>
217
+ } {
218
+ this._install()
219
+ let id = flow.createUuid()
220
+ let waitCancel = null as (() => void) | null
221
+ let isCancel = false
222
+ let isSending = false
223
+
224
+ // =================
225
+ //
226
+ // event
227
+ //
228
+
229
+ let listeners = [
230
+ this.event.on('cancel', ({ requestId }) => {
231
+ if (requestId === id) {
232
+ cancelTrigger()
233
+ }
234
+ }),
235
+ this.event.on('cancelAll', () => {
236
+ cancelTrigger()
237
+ })
238
+ ]
239
+ let eventOff = () => listeners.forEach(e => e.off())
240
+ let cancelTrigger = () => {
241
+ if (isCancel === false) {
242
+ if (isSending && waitCancel) {
243
+ waitCancel()
244
+ }
245
+ isCancel = true
246
+ eventOff()
247
+ }
248
+ }
249
+ let onCancel = (cb: () => void) => {
250
+ waitCancel = cb
251
+ }
252
+
253
+ // =================
254
+ //
255
+ // main
256
+ //
257
+
258
+ let request = async() => {
259
+ let schema = this.translator.getValidate()
260
+ let output: any = null
261
+ let plugins = {} as any
262
+ let metadata = new Map()
263
+ let question = await this.translator.compile(data, {
264
+ schema
265
+ })
266
+ let preMessages: Message[] = []
267
+ let messages: Message[] = []
268
+ if (question.prompt) {
269
+ messages.push({
270
+ role: 'user',
271
+ content: question.prompt
272
+ })
273
+ }
274
+ for (let key in this.plugins) {
275
+ plugins[key] = {
276
+ send: (data: any) => this.plugins[key].send({
277
+ id,
278
+ data
279
+ })
280
+ }
281
+ }
282
+ await this.hook.notify('start', {
283
+ id,
284
+ data,
285
+ schema,
286
+ plugins,
287
+ messages,
288
+ metadata,
289
+ setPreMessages: ms => {
290
+ preMessages = ms.map(e => {
291
+ return {
292
+ ...e,
293
+ content: Array.isArray(e.content) ? e.content.join('\n') : e.content
294
+ }
295
+ })
296
+ },
297
+ changeMessages: ms => {
298
+ messages = ms
299
+ },
300
+ changeOutputSchema: output => {
301
+ this.translator.changeOutputSchema(output)
302
+ schema = this.translator.getValidate()
303
+ }
304
+ })
305
+ messages = [
306
+ ...preMessages,
307
+ ...messages
308
+ ]
309
+ await flow.asyncWhile(async ({ count, doBreak }) => {
310
+ if (count >= 99) {
311
+ return doBreak()
312
+ }
313
+ let response = ''
314
+ let parseText = ''
315
+ let retryFlag = false
316
+ let lastUserMessage = messages.filter(e => e.role === 'user').slice(-1)[0]?.content || ''
317
+ try {
318
+ await this.hook.notify('talkBefore', {
319
+ id,
320
+ data,
321
+ messages,
322
+ metadata,
323
+ lastUserMessage
324
+ })
325
+ const sender = this.params.request(messages, {
326
+ id,
327
+ count,
328
+ schema,
329
+ onCancel,
330
+ metadata,
331
+ isRetry: retryFlag
332
+ })
333
+ if (isCancel) {
334
+ if (waitCancel) {
335
+ waitCancel()
336
+ }
337
+ } else {
338
+ try {
339
+ isSending = true
340
+ response = await sender
341
+ parseText = response
342
+ } finally {
343
+ isSending = false
344
+ }
345
+ }
346
+ if (isCancel === false) {
347
+ await this.hook.notify('talkAfter', {
348
+ id,
349
+ data,
350
+ response,
351
+ messages,
352
+ parseText,
353
+ metadata,
354
+ lastUserMessage,
355
+ parseFail: (error) => {
356
+ throw new ParserError(error, [])
357
+ },
358
+ changeParseText: text => {
359
+ parseText = text
360
+ }
361
+ })
362
+ output = (await this.translator.parse(parseText)).output
363
+ await this.hook.notify('succeeded', {
364
+ id,
365
+ output,
366
+ metadata
367
+ })
368
+ }
369
+ await this.hook.notify('done', {
370
+ id,
371
+ metadata
372
+ })
373
+ doBreak()
374
+ } catch (error: any) {
375
+ // 如果解析錯誤,可以選擇是否重新解讀
376
+ if (error instanceof ParserError) {
377
+ await this.hook.notify('parseFailed', {
378
+ id,
379
+ error: error.error,
380
+ count,
381
+ response,
382
+ messages,
383
+ metadata,
384
+ lastUserMessage,
385
+ parserFails: error.parserFails,
386
+ retry: () => {
387
+ retryFlag = true
388
+ },
389
+ changeMessages: ms => {
390
+ messages = ms
391
+ }
392
+ })
393
+ if (retryFlag === false) {
394
+ await this.hook.notify('done', {
395
+ id,
396
+ metadata
397
+ })
398
+ throw error
399
+ }
400
+ } else {
401
+ await this.hook.notify('done', {
402
+ id,
403
+ metadata
404
+ })
405
+ throw error
406
+ }
407
+ }
408
+ })
409
+ return output
410
+ }
411
+ const send = async() => {
412
+ try {
413
+ const result = await request()
414
+ return result
415
+ } finally {
416
+ eventOff()
417
+ }
418
+ }
419
+ return {
420
+ id,
421
+ request: send()
422
+ }
423
+ }
424
+
425
+ /**
426
+ * @zh 將請求發出至聊天機器人。
427
+ * @en Send request to chatbot.
428
+ */
429
+
430
+ async request<T extends Translator<S, O>>(data: T['__schemeType']): Promise<T['__outputType']> {
431
+ const { request } = this.requestWithId(data)
432
+ const output = await request
433
+ return output
434
+ }
435
+ }