题目:长文本总结
我们有一个100,000字的文档(或者未知长度的文档),需要将其内容高度总结凝练至500字以内。
下面是一些假设:
- 假设有一个大语言模型,我们需要通过API访问。处理每个API的延迟时间在15-30秒之间,每个API调用可以处理的文本字数有限(最大4000字)。
- 每次API调用通常可以将4000字(按最大窗口计算)压缩总结到300-700字之间。
- 大语言模型设有处理速度峰值限制。单一API客户每分钟可以处理的最大字数为40,000字。达到峰值后需要等待。
- 需要大模型的API调用有大约5%会碰到服务器忙的出错率。
- 加分考虑:我们有一个API客户pool有大约50个API客户(可以认为大模型的最高访问率是40,000x50字/分钟)。我们如何考虑用此资源来最大限度的服务于我们的终端客户,达到高速、高可靠性的效果。
- 加分考虑:如果在我们的终端客户中设立VIP客户和普通客户,在资源和带宽有限的情况下,希望有限保证VIP客户的服务质量(尽量小的延迟)。
请你为给定的输入文档设计一个方案,使得我们可以在最短的延迟时间内获得一个500字以内的总结。同时,你需要注意优化成本。你可以使用Python,Node.js,Golang,Java等语言来提供半伪代码实现。如果需要,你可以做出合理的假设。
请你对你的解决方案进行总结和分析,说明需要注意的问题以及可以考虑的改进方法。对于较复杂的实现,如果无法在pseodu code中体现,可以在解决方案分析中阐明。
import (
"fmt"
"strings"
"sync"
"time"
)
var ProcessChan = make(chan *message, 1024) //全局channel, 用于接收生产者
var VipHeap = MaxHeap{} // vip等级最大堆
var vipLock = sync.Mutex{}
func main() {
apiKeys := []string{"111", "222", "333"} // 有一个API客户pool有大约50个API客户, 这里3个代表50个,简化
// 创建50个消费者
for i := 0; i < len(apiKeys); i++ {
apiKey := apiKeys[i]
go func() {
consumer(apiKey)
}()
}
// 监听客户端请求
for true {
userId, text := httpListenClient()
// 异步增加并发量,这里高级实现其实可以用epoll,io多路复用
go func() {
result := producer(text, userId)
fmt.Println(result)
}()
}
}
type message struct {
userId int64
userLevel int64
index int
text string
resultChan chan gptResult
}
type gptResult struct {
index int
text string
}
/*
1. 接受客户端请求
2。 请求拆分成4000字的小包,投递给消费者
3。 等待消费者返回,组装数据
*/
func producer(text string, userId int64) string { //有一个100,000字的文档
res := text
var chunks []string
var results []string
var resultChan = make(chan gptResult, 1024)
for !(len(res) < 700 && len(res) > 300) { //压缩总结到300-700字之间。
chunks = split(res, 4000) // 每次API调用通常可以将4000字(按最大窗口计算)
results = make([]string, len(chunks))
for i := 0; i < len(chunks); i++ {
chunk := chunks[i]
go func() {
m := &message{
userId: userId,
userLevel: vipLevel(userId),
index: i,
text: chunk,
resultChan: resultChan,
}
vipLock.Lock()
VipHeap.Push(m)
vipLock.Unlock()
}()
}
for i := 0; i < len(results); i++ {
gptRes := <-resultChan
results[gptRes.index] = gptRes.text
}
res = strings.Join(results[:], ". ")
}
return res
}
/*
1。 接收生产者投递的请求
2。 发起http调用gpt
3。 将结果返回生产者
*/
func consumer(apiKey string) {
go func() {
for true {
vipLock.Lock()
if VipHeap.Len() > 0 {
m := VipHeap.Pop().(*message)
ProcessChan <- m
}
vipLock.Unlock()
}
}()
lock := sync.Mutex{}
ticker := time.NewTicker(time.Second * 60) //每分钟可以处理的最大字数为40,000字。
tokenBucket := 40000
// 异步每分钟更新令牌桶
go func() {
select {
case <-ticker.C:
{
lock.Lock()
tokenBucket = 40000
lock.Unlock()
}
}
}()
for true {
select {
// 监听全局 ProcessChan
case m := <-ProcessChan:
text := m.text
if len(text) > tokenBucket {
time.Sleep(3 * time.Second) // 这里这个时间可以优化
ProcessChan <- m
continue
}
res, err := httpGet(apiKey, text)
if err != nil {
//失败的时候,等待一段时间再去请求 (大模型的API调用有大约5%会碰到服务器忙的出错率)
time.Sleep(1 * time.Second)
ProcessChan <- m
continue
}
m.resultChan <- gptResult{
index: m.index,
text: res,
}
lock.Lock()
tokenBucket = tokenBucket - len(res)
lock.Unlock()
}
}
}
// 伪api请求
func httpGet(apiKey string, text string) (string, error) {
return "chatGpt response", nil
}
// 伪api请求
func httpListenClient() (userId int64, text string) {
return 0, "text"
}
// 伪vip等级赋权函数
func vipLevel(userId int64) int64 {
return 0
}
// MaxHeap 实现用户id的最大堆
type MaxHeap []*message
func (h MaxHeap) Less(i, j int) bool { return h[i].userLevel > h[j].userLevel }
func (h MaxHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h MaxHeap) Len() int { return len(h) }
func (h *MaxHeap) Push(x interface{}) { *h = append(*h, x.(*message)) }
func (h *MaxHeap) Pop() interface{} {
n := len(*h)
ans := (*h)[n-1]
*h = (*h)[:n-1]
return ans
}
func split(s string, chunkSize int) []string {
if len(s) == 0 {
return nil
}
if chunkSize >= len(s) {
return []string{s}
}
var chunks = make([]string, 0, (len(s)-1)/chunkSize+1)
currentLen := 0
currentStart := 0
for i := range s {
if currentLen == chunkSize {
chunks = append(chunks, s[currentStart:i])
currentLen = 0
currentStart = i
}
currentLen++
}
chunks = append(chunks, s[currentStart:])
return chunks
}