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.
- package/examples/basic.ts +97 -0
- package/examples/llama.cpp.ts +56 -0
- package/examples/plugin.ts +118 -0
- package/lib/broker/chat.ts +435 -0
- package/lib/core/parser.ts +62 -0
- package/lib/core/plugin.ts +46 -0
- package/lib/core/translator.ts +115 -0
- package/lib/ctod.ts +71 -0
- package/lib/index.ts +41 -0
- package/lib/plugins/index.ts +38 -0
- package/lib/plugins/limiter.ts +103 -0
- package/lib/plugins/print-log.ts +35 -0
- package/lib/plugins/retry.ts +25 -0
- package/lib/plugins/role.ts +28 -0
- package/lib/service/llama3.cpp/completion.ts +313 -0
- package/lib/service/llama3.cpp/index.ts +53 -0
- package/lib/service/openai/chat.ts +244 -0
- package/lib/service/openai/images-generation.ts +64 -0
- package/lib/service/openai/index.ts +97 -0
- package/lib/service/openai/vision.ts +111 -0
- package/lib/shims.d.ts +4 -0
- package/lib/templates.ts +71 -0
- package/lib/types.ts +4 -0
- package/lib/utils/error.ts +14 -0
- package/lib/utils/validate.ts +64 -0
- package/package.json +1 -1
- package/types/examples/basic.d.ts +1 -0
- package/types/examples/chat-demo.d.ts +2 -0
- package/types/examples/chat-for-llama.cpp-demo.d.ts +2 -0
- package/types/examples/chat-with-json-schema-demo.d.ts +2 -0
- package/types/examples/llama.cpp.d.ts +2 -0
- package/types/examples/plugin-demo.d.ts +2 -0
- package/types/examples/plugin.d.ts +2 -0
- package/types/examples/stream-for-llama.cpp-demo.d.ts +2 -0
- package/types/examples/vision-demo.d.ts +2 -0
- package/types/lib/broker/chat.d.ts +150 -0
- package/types/lib/core/parser.d.ts +32 -0
- package/types/lib/core/plugin.d.ts +34 -0
- package/types/lib/core/translator.d.ts +67 -0
- package/types/lib/ctod.d.ts +32 -0
- package/types/lib/index.d.ts +34 -0
- package/types/lib/plugins/index.d.ts +47 -0
- package/types/lib/plugins/limiter.d.ts +36 -0
- package/types/lib/plugins/print-log.d.ts +5 -0
- package/types/lib/plugins/retry.d.ts +6 -0
- package/types/lib/plugins/role.d.ts +5 -0
- package/types/lib/service/llama3.cpp/completion.d.ts +61 -0
- package/types/lib/service/llama3.cpp/index.d.ts +19 -0
- package/types/lib/service/openai/chat.d.ts +110 -0
- package/types/lib/service/openai/completion.d.ts +59 -0
- package/types/lib/service/openai/images-generation.d.ts +35 -0
- package/types/lib/service/openai/index.d.ts +29 -0
- package/types/lib/service/openai/vision.d.ts +74 -0
- package/types/lib/templates.d.ts +20 -0
- package/types/lib/types.d.ts +1 -0
- package/types/lib/utils/error.d.ts +11 -0
- package/types/lib/utils/validate.d.ts +16 -0
- package/.api-key +0 -1
- package/.nyc_output/42919e68-b472-4a5d-b2d3-5d5153f28467.json +0 -1
- package/.nyc_output/processinfo/42919e68-b472-4a5d-b2d3-5d5153f28467.json +0 -1
- package/.nyc_output/processinfo/index.json +0 -1
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/.output/.output/stores//347/240/264/347/242/216/347/232/204/345/271/273/350/261/241/cover-0.png +0 -1
- package/.output/.output/stores//347/240/264/347/242/216/347/232/204/345/271/273/350/261/241/cover-1.png +0 -1
- package/.output/.output/stores//347/240/264/347/242/216/347/232/204/345/271/273/350/261/241/story-config.json +0 -4
- package/.output/.output/stores//347/240/264/347/242/216/347/232/204/345/271/273/350/261/241/story.json +0 -24
- 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
- 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
- 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
- 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
- package/.output/.output/stores//350/250/230/346/206/266/345/225/206/344/272/272/cover-0.png +0 -1
- package/.output/.output/stores//350/250/230/346/206/266/345/225/206/344/272/272/cover-1.png +0 -1
- package/.output/.output/stores//350/250/230/346/206/266/345/225/206/344/272/272/story-config.json +0 -4
- package/.output/.output/stores//350/250/230/346/206/266/345/225/206/344/272/272/story.json +0 -18
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/.output/.output/talks//346/224/277/346/262/273/347/254/221/350/251/261/content.json +0 -30
- package/.output/.output/talks//346/224/277/346/262/273/347/254/221/350/251/261/cover-1684056246465.png +0 -0
- 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
- 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
- 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
- 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
- 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
- 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
|
+
}
|