|
1 |
| -import axios from 'axios' |
2 |
| -// import { type Readable } from 'stream' |
| 1 | +import axios, { type AxiosResponse } from 'axios' |
| 2 | +import { type Readable } from 'stream' |
| 3 | +import { GrammyError } from 'grammy' |
| 4 | +import { pino } from 'pino' |
3 | 5 |
|
4 | 6 | import config from '../../../config'
|
5 |
| -import { type ChatConversation } from '../../types' // , type OnCallBackQueryData, type OnMessageContext, |
| 7 | +import { type OnCallBackQueryData, type OnMessageContext, type ChatConversation } from '../../types' |
6 | 8 | import { type LlmCompletion } from './llmApi'
|
7 | 9 | import { LlmsModelsEnum } from '../types'
|
8 |
| -// import { GrammyError } from 'grammy' |
9 |
| -import { pino } from 'pino' |
10 | 10 |
|
11 | 11 | const logger = pino({
|
12 | 12 | name: 'anthropic - llmsBot',
|
@@ -55,94 +55,95 @@ export const anthropicCompletion = async (
|
55 | 55 | price: 0
|
56 | 56 | }
|
57 | 57 | }
|
58 |
| -// export const anthropicCompletion = async ( |
59 |
| -// conversation: ChatConversation[], |
60 |
| -// model = LlmsModelsEnum.CLAUDE_OPUS, |
61 |
| -// ctx: OnMessageContext | OnCallBackQueryData, |
62 |
| -// msgId: number |
63 |
| -// ): Promise<LlmCompletion> => { |
64 |
| -// const data = { |
65 |
| -// model, |
66 |
| -// stream: true, // Set stream to true to receive the completion as a stream |
67 |
| -// system: config.openAi.chatGpt.chatCompletionContext, |
68 |
| -// max_tokens: +config.openAi.chatGpt.maxTokens, |
69 |
| -// messages: conversation |
70 |
| -// } |
71 |
| -// let wordCount = 0 |
72 |
| -// let wordCountMinimum = 2 |
73 |
| -// const url = `${API_ENDPOINT}/anthropic/completions` |
74 |
| -// if (!ctx.chat?.id) { |
75 |
| -// throw new Error('Context chat id should not be empty after openAI streaming') |
76 |
| -// } |
77 |
| -// const response = await axios.post(url, data, { responseType: 'stream' }) |
78 |
| - |
79 |
| -// // Create a Readable stream from the response |
80 |
| -// const completionStream: Readable = response.data |
81 |
| - |
82 |
| -// // Read and process the stream |
83 |
| -// let completion = '' |
84 |
| -// let outputTokens = '' |
85 |
| -// let inputTokens = '' |
86 |
| -// completionStream.on('data', (chunk: any) => { |
87 |
| -// const sendMessage = async (completion: string): Promise<void> => { |
88 |
| -// await ctx.api |
89 |
| -// .editMessageText(ctx.chat?.id, msgId, completion) |
90 |
| -// .catch(async (e: any) => { |
91 |
| -// if (e instanceof GrammyError) { |
92 |
| -// if (e.error_code !== 400) { |
93 |
| -// throw e |
94 |
| -// } else { |
95 |
| -// logger.error(e) |
96 |
| -// } |
97 |
| -// } else { |
98 |
| -// throw e |
99 |
| -// } |
100 |
| -// }) |
101 |
| -// } |
102 |
| -// const msg = chunk.toString() |
103 |
| -// if (msg) { |
104 |
| -// if (msg.startsWith('Input Token')) { |
105 |
| -// inputTokens = msg.split('Input Token: ')[1] |
106 |
| -// } else if (msg.startsWith('Text')) { |
107 |
| -// wordCount++ |
108 |
| -// completion += msg.split('Text: ')[1] |
109 |
| -// if (wordCount > wordCountMinimum) { // if (chunck === '.' && wordCount > wordCountMinimum) { |
110 |
| -// if (wordCountMinimum < 64) { |
111 |
| -// wordCountMinimum *= 2 |
112 |
| -// } |
113 |
| -// completion = completion.replaceAll('...', '') |
114 |
| -// completion += '...' |
115 |
| -// wordCount = 0 |
116 |
| -// if (ctx.chat?.id) { |
117 |
| -// await sendMessage(completion) |
118 |
| -// } |
119 |
| -// } |
120 |
| -// } else if (msg.startsWith('Output Tokens')) { |
121 |
| -// outputTokens = msg.split('Output Tokens: ')[1] |
122 |
| -// } |
123 |
| -// } |
124 |
| -// }) |
125 |
| - |
126 |
| -// completionStream.on('end', () => { |
127 |
| -// const totalOutputTokens = outputTokens // response.headers['x-openai-output-tokens'] |
128 |
| -// const totalInputTokens = inputTokens // response.headers['x-openai-input-tokens'] |
129 |
| -// console.log('FCO stream', completion) |
130 |
| -// // You can also process the completion content here |
131 | 58 |
|
132 |
| -// return { |
133 |
| -// completion: { |
134 |
| -// content: completion, |
135 |
| -// role: 'assistant', |
136 |
| -// model |
137 |
| -// }, |
138 |
| -// usage: parseInt(totalOutputTokens, 10) + parseInt(totalInputTokens, 10), |
139 |
| -// price: 0 |
140 |
| -// } |
141 |
| -// }) |
142 |
| - |
143 |
| -// return { |
144 |
| -// completion: undefined, |
145 |
| -// usage: 0, |
146 |
| -// price: 0 |
147 |
| -// } |
148 |
| -// } |
| 59 | +export const anthropicStreamCompletion = async ( |
| 60 | + conversation: ChatConversation[], |
| 61 | + model = LlmsModelsEnum.CLAUDE_OPUS, |
| 62 | + ctx: OnMessageContext | OnCallBackQueryData, |
| 63 | + msgId: number, |
| 64 | + limitTokens = true |
| 65 | +): Promise<LlmCompletion> => { |
| 66 | + const data = { |
| 67 | + model, |
| 68 | + stream: true, // Set stream to true to receive the completion as a stream |
| 69 | + system: config.openAi.chatGpt.chatCompletionContext, |
| 70 | + max_tokens: limitTokens ? +config.openAi.chatGpt.maxTokens : undefined, |
| 71 | + messages: conversation.map(m => { return { content: m.content, role: m.role } }) |
| 72 | + } |
| 73 | + let wordCount = 0 |
| 74 | + let wordCountMinimum = 2 |
| 75 | + const url = `${API_ENDPOINT}/anthropic/completions` |
| 76 | + if (!ctx.chat?.id) { |
| 77 | + throw new Error('Context chat id should not be empty after openAI streaming') |
| 78 | + } |
| 79 | + const response: AxiosResponse = await axios.post(url, data, { responseType: 'stream' }) |
| 80 | + // Create a Readable stream from the response |
| 81 | + const completionStream: Readable = response.data |
| 82 | + // Read and process the stream |
| 83 | + let completion = '' |
| 84 | + let outputTokens = '' |
| 85 | + let inputTokens = '' |
| 86 | + for await (const chunk of completionStream) { |
| 87 | + const msg = chunk.toString() |
| 88 | + if (msg) { |
| 89 | + if (msg.startsWith('Input Token')) { |
| 90 | + inputTokens = msg.split('Input Token: ')[1] |
| 91 | + } else if (msg.startsWith('Text')) { |
| 92 | + wordCount++ |
| 93 | + completion += msg.split('Text: ')[1] |
| 94 | + if (wordCount > wordCountMinimum) { // if (chunck === '.' && wordCount > wordCountMinimum) { |
| 95 | + if (wordCountMinimum < 64) { |
| 96 | + wordCountMinimum *= 2 |
| 97 | + } |
| 98 | + completion = completion.replaceAll('...', '') |
| 99 | + completion += '...' |
| 100 | + wordCount = 0 |
| 101 | + if (ctx.chat?.id) { |
| 102 | + await ctx.api |
| 103 | + .editMessageText(ctx.chat?.id, msgId, completion) |
| 104 | + .catch(async (e: any) => { |
| 105 | + if (e instanceof GrammyError) { |
| 106 | + if (e.error_code !== 400) { |
| 107 | + throw e |
| 108 | + } else { |
| 109 | + logger.error(e) |
| 110 | + } |
| 111 | + } else { |
| 112 | + throw e |
| 113 | + } |
| 114 | + }) |
| 115 | + } |
| 116 | + } |
| 117 | + } else if (msg.startsWith('Output Tokens')) { |
| 118 | + outputTokens = msg.split('Output Tokens: ')[1] |
| 119 | + } |
| 120 | + } |
| 121 | + } |
| 122 | + completion = completion.replaceAll('...', '') |
| 123 | + await ctx.api |
| 124 | + .editMessageText(ctx.chat?.id, msgId, completion) |
| 125 | + .catch((e: any) => { |
| 126 | + if (e instanceof GrammyError) { |
| 127 | + if (e.error_code !== 400) { |
| 128 | + throw e |
| 129 | + } else { |
| 130 | + logger.error(e) |
| 131 | + } |
| 132 | + } else { |
| 133 | + throw e |
| 134 | + } |
| 135 | + }) |
| 136 | + const totalOutputTokens = outputTokens // response.headers['x-openai-output-tokens'] |
| 137 | + const totalInputTokens = inputTokens // response.headers['x-openai-input-tokens'] |
| 138 | + return { |
| 139 | + completion: { |
| 140 | + content: completion, |
| 141 | + role: 'assistant', |
| 142 | + model |
| 143 | + }, |
| 144 | + usage: parseInt(totalOutputTokens, 10) + parseInt(totalInputTokens, 10), |
| 145 | + price: 0, |
| 146 | + inputTokens: parseInt(totalInputTokens, 10), |
| 147 | + outputTokens: parseInt(totalOutputTokens, 10) |
| 148 | + } |
| 149 | +} |
0 commit comments