Skip to content

Commit

Permalink
Merge pull request #358 from harmony-one/anthropic-stream
Browse files Browse the repository at this point in the history
Anthropic stream
  • Loading branch information
fegloff authored Mar 19, 2024
2 parents 6ff69b4 + e366deb commit 6dac718
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 38 deletions.
36 changes: 18 additions & 18 deletions src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import {
import { mainMenu } from './pages'
import { TranslateBot } from './modules/translate/TranslateBot'
import { VoiceMemo } from './modules/voice-memo'
import { QRCodeBot } from './modules/qrcode/QRCodeBot'
import { SDImagesBot } from './modules/sd-images'
// import { QRCodeBot } from './modules/qrcode/QRCodeBot'
// import { SDImagesBot } from './modules/sd-images'
import { OpenAIBot } from './modules/open-ai'
import { OneCountryBot } from './modules/1country'
import { WalletConnect } from './modules/walletconnect'
Expand Down Expand Up @@ -242,8 +242,8 @@ bot.use(autoChatAction())
bot.use(mainMenu)

const voiceMemo = new VoiceMemo()
const qrCodeBot = new QRCodeBot()
const sdImagesBot = new SDImagesBot()
// const qrCodeBot = new QRCodeBot()
// const sdImagesBot = new SDImagesBot()
const walletConnect = new WalletConnect()
const payments = new BotPayments()
const schedule = new BotSchedule(bot)
Expand Down Expand Up @@ -364,8 +364,8 @@ const writeCommandLog = async (

const PayableBots: Record<string, PayableBotConfig> = {
voiceCommand: { bot: voiceCommand },
qrCodeBot: { bot: qrCodeBot },
sdImagesBot: { bot: sdImagesBot },
// qrCodeBot: { bot: qrCodeBot },
// sdImagesBot: { bot: sdImagesBot },
voiceTranslate: { bot: voiceTranslateBot },
voiceMemo: { bot: voiceMemo },
translateBot: { bot: translateBot },
Expand Down Expand Up @@ -448,24 +448,24 @@ const onMessage = async (ctx: OnMessageContext): Promise<void> => {

const onCallback = async (ctx: OnCallBackQueryData): Promise<void> => {
try {
if (qrCodeBot.isSupportedEvent(ctx)) {
await qrCodeBot.onEvent(ctx, (reason) => {
logger.error(`qr generate error: ${reason}`)
})
return
}
// if (qrCodeBot.isSupportedEvent(ctx)) {
// await qrCodeBot.onEvent(ctx, (reason) => {
// logger.error(`qr generate error: ${reason}`)
// })
// return
// }

if (telegramPayments.isSupportedEvent(ctx)) {
await telegramPayments.onEvent(ctx)
return
}

if (sdImagesBot.isSupportedEvent(ctx)) {
await sdImagesBot.onEvent(ctx, (e) => {
logger.info(e, '// TODO refund payment')
})
return
}
// if (sdImagesBot.isSupportedEvent(ctx)) {
// await sdImagesBot.onEvent(ctx, (e) => {
// logger.info(e, '// TODO refund payment')
// })
// return
// }

if (openAiBot.isSupportedEvent(ctx)) {
await openAiBot.onEvent(ctx, (e) => {
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export default {
? parseInt(process.env.SESSION_TIMEOUT)
: 48, // in hours
llms: {
apiEndpoint: process.env.LLMS_ENDPOINT, // 'http://127.0.0.1:5000', // process.env.LLMS_ENDPOINT, //
apiEndpoint: process.env.LLMS_ENDPOINT, // 'http://127.0.0.1:5000',
wordLimit: 50,
model: 'chat-bison',
minimumBalance: 0,
Expand Down
29 changes: 25 additions & 4 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,33 @@ Your credits in 1Bot Credits: $CREDITS
Send ONE to: \`$WALLET_ADDRESS\`
`,
more: `/ explain like i am 5, what is a superconductor?
. explain like i have a phd, what is category theory?
// more: `/ explain like i am 5, what is a superconductor?
// . explain like i have a phd, what is category theory?

// /images vintage hot rod with custom flame paint job

// /qr s.country/ai astronaut, exuberant, anime girl, smile, sky, colorful

// /connect (Wallet Connect to MetaMask / Gnosis Safe / Timeless)

// /send TARGET-WALLET-ADDRESS ONE-TOKEN-AMOUNT
// /send 0x742c4788CC47A94cf260abc474E2Fa45695a79Cd 42

/images vintage hot rod with custom flame paint job
// /memo (Send voice messages via microphone button on bottom right)

/qr s.country/ai astronaut, exuberant, anime girl, smile, sky, colorful
// ❤️‍🔥 [Join our team](https://xn--qv9h.s.country/p/dear-engineer-our-tech-lead-role) to build [AI ∩ Crypto](https://xn--qv9h.s.country/p/radically-fair-economy-for-1country)! [Product roadmap](https://xn--qv9h.s.country/p/generating-roadmap-as-ceo-vs-cto):

// [🧠 Web∞](https://xn--qv9h.s.country/p/learning-machine-cryptography): CivitAI custom models (low-rank adaptations, clothes & accessories, human poses, comics & brand characters, video-to-video transformations), Character.AI celebrity chats, RunwayML video clips, HuggingFace embedding ControlNet, Meta segment anything, ElevenLabs speech clones, Zapier task automations, document or website queries.

// [🌳 Web3](https://xn--qv9h.s.country/p/telegram-bots-and-clients-self-custody): self-custody wallets, token swaps, cross-chain bridges, fiat onramps, lending yields, collectible mints, price auctions, multi-signature safes, governance votes, portfolio management, .1 name services.

// [🐝 Web2](https://xn--qv9h.s.country/p/new-forum-for-ai-crypto-reddit-discourse): news curation, gated access, emoji tipping, collective moments, group discount, social commerce.

// [🏴‍☠️ Web1](https://xn--qv9h.s.country/p/controlnet-lora-1country-qr-code): .country domains, email aliases, vanity URLs, Notion/Substack hosting.
// `
// }
more: `/ explain like i am 5, what is a superconductor?
. explain like i have a phd, what is category theory?
/connect (Wallet Connect to MetaMask / Gnosis Safe / Timeless)
Expand Down
149 changes: 149 additions & 0 deletions src/modules/llms/api/athropic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import axios, { type AxiosResponse } from 'axios'
import { type Readable } from 'stream'
import { GrammyError } from 'grammy'
import { pino } from 'pino'

import config from '../../../config'
import { type OnCallBackQueryData, type OnMessageContext, type ChatConversation } from '../../types'
import { type LlmCompletion } from './llmApi'
import { LlmsModelsEnum } from '../types'

const logger = pino({
name: 'anthropic - llmsBot',
transport: {
target: 'pino-pretty',
options: { colorize: true }
}
})

const API_ENDPOINT = config.llms.apiEndpoint // 'http://127.0.0.1:5000' // config.llms.apiEndpoint

export const anthropicCompletion = async (
conversation: ChatConversation[],
model = LlmsModelsEnum.CLAUDE_OPUS
): Promise<LlmCompletion> => {
logger.info(`Handling ${model} completion`)

const data = {
model,
stream: false,
system: config.openAi.chatGpt.chatCompletionContext,
max_tokens: +config.openAi.chatGpt.maxTokens,
messages: conversation
}
const url = `${API_ENDPOINT}/anthropic/completions`
const response = await axios.post(url, data)
const respJson = JSON.parse(response.data)
if (response) {
const totalInputTokens = respJson.usage.input_tokens
const totalOutputTokens = respJson.usage.output_tokens
const completion = respJson.content

return {
completion: {
content: completion[0].text,
role: 'assistant',
model
},
usage: totalOutputTokens + totalInputTokens,
price: 0
}
}
return {
completion: undefined,
usage: 0,
price: 0
}
}

export const anthropicStreamCompletion = async (
conversation: ChatConversation[],
model = LlmsModelsEnum.CLAUDE_OPUS,
ctx: OnMessageContext | OnCallBackQueryData,
msgId: number,
limitTokens = true
): Promise<LlmCompletion> => {
const data = {
model,
stream: true, // Set stream to true to receive the completion as a stream
system: config.openAi.chatGpt.chatCompletionContext,
max_tokens: limitTokens ? +config.openAi.chatGpt.maxTokens : undefined,
messages: conversation.map(m => { return { content: m.content, role: m.role } })
}
let wordCount = 0
let wordCountMinimum = 2
const url = `${API_ENDPOINT}/anthropic/completions`
if (!ctx.chat?.id) {
throw new Error('Context chat id should not be empty after openAI streaming')
}
const response: AxiosResponse = await axios.post(url, data, { responseType: 'stream' })
// Create a Readable stream from the response
const completionStream: Readable = response.data
// Read and process the stream
let completion = ''
let outputTokens = ''
let inputTokens = ''
for await (const chunk of completionStream) {
const msg = chunk.toString()
if (msg) {
if (msg.startsWith('Input Token')) {
inputTokens = msg.split('Input Token: ')[1]
} else if (msg.startsWith('Text')) {
wordCount++
completion += msg.split('Text: ')[1]
if (wordCount > wordCountMinimum) { // if (chunck === '.' && wordCount > wordCountMinimum) {
if (wordCountMinimum < 64) {
wordCountMinimum *= 2
}
completion = completion.replaceAll('...', '')
completion += '...'
wordCount = 0
if (ctx.chat?.id) {
await ctx.api
.editMessageText(ctx.chat?.id, msgId, completion)
.catch(async (e: any) => {
if (e instanceof GrammyError) {
if (e.error_code !== 400) {
throw e
} else {
logger.error(e)
}
} else {
throw e
}
})
}
}
} else if (msg.startsWith('Output Tokens')) {
outputTokens = msg.split('Output Tokens: ')[1]
}
}
}
completion = completion.replaceAll('...', '')
await ctx.api
.editMessageText(ctx.chat?.id, msgId, completion)
.catch((e: any) => {
if (e instanceof GrammyError) {
if (e.error_code !== 400) {
throw e
} else {
logger.error(e)
}
} else {
throw e
}
})
const totalOutputTokens = outputTokens // response.headers['x-openai-output-tokens']
const totalInputTokens = inputTokens // response.headers['x-openai-input-tokens']
return {
completion: {
content: completion,
role: 'assistant',
model
},
usage: parseInt(totalOutputTokens, 10) + parseInt(totalInputTokens, 10),
price: 0,
inputTokens: parseInt(totalInputTokens, 10),
outputTokens: parseInt(totalOutputTokens, 10)
}
}
10 changes: 9 additions & 1 deletion src/modules/llms/api/llmApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import axios from 'axios'
import config from '../../../config'
import { type ChatConversation } from '../../types'
import pino from 'pino'
import { LlmsModels, LlmsModelsEnum } from '../types'
import { type ChatModel } from '../../open-ai/types'

const API_ENDPOINT = config.llms.apiEndpoint // config.llms.apiEndpoint // 'http://localhost:8080' // http://127.0.0.1:5000' // config.llms.apiEndpoint

Expand All @@ -17,6 +19,8 @@ export interface LlmCompletion {
completion: ChatConversation | undefined
usage: number
price: number
inputTokens?: number
outputTokens?: number
}

interface LlmAddUrlDocument {
Expand All @@ -32,6 +36,10 @@ interface QueryUrlDocument {
conversation?: ChatConversation[]
}

export const getChatModel = (modelName: string): ChatModel => {
return LlmsModels[modelName]
}

export const llmAddUrlDocument = async (args: LlmAddUrlDocument): Promise<string> => {
const data = { ...args }
const endpointUrl = `${API_ENDPOINT}/collections/document`
Expand Down Expand Up @@ -86,7 +94,7 @@ export const deleteCollection = async (collectionName: string): Promise<void> =>

export const llmCompletion = async (
conversation: ChatConversation[],
model = config.llms.model
model = LlmsModelsEnum.BISON
): Promise<LlmCompletion> => {
const data = {
model, // chat-bison@001 'chat-bison', //'gpt-3.5-turbo',
Expand Down
40 changes: 34 additions & 6 deletions src/modules/llms/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,20 @@ import {
import { type ParseMode } from 'grammy/types'
import { LlmsModelsEnum } from './types'
import { type Message } from 'grammy/out/types'
import { llmAddUrlDocument } from './api/llmApi'
import { type LlmCompletion, getChatModel, llmAddUrlDocument } from './api/llmApi'
import { getChatModelPrice } from '../open-ai/api/openAi'
import config from '../../config'

export enum SupportedCommands {
bardF = 'bard',
claudeOpus = 'claude',
opus = 'opus',
claudeSonnet = 'claudes',
opusShort = 'c',
sonnet = 'sonnet',
sonnetShort = 's',
claudeHaiku = 'haiku',
haikuShort = 'h',
bard = 'b',
j2Ultra = 'j2-ultra',
sum = 'sum',
Expand All @@ -22,6 +32,7 @@ export enum SupportedCommands {
export const MAX_TRIES = 3
const LLAMA_PREFIX_LIST = ['* ']
const BARD_PREFIX_LIST = ['b. ', 'B. ']
const CLAUDE_OPUS_PREFIX_LIST = ['c. ']

export const isMentioned = (
ctx: OnMessageContext | OnCallBackQueryData
Expand Down Expand Up @@ -59,6 +70,16 @@ export const hasBardPrefix = (prompt: string): string => {
return ''
}

export const hasClaudeOpusPrefix = (prompt: string): string => {
const prefixList = CLAUDE_OPUS_PREFIX_LIST
for (let i = 0; i < prefixList.length; i++) {
if (prompt.toLocaleLowerCase().startsWith(prefixList[i])) {
return prefixList[i]
}
}
return ''
}

export const hasUrl = (
ctx: OnMessageContext | OnCallBackQueryData,
prompt: string
Expand Down Expand Up @@ -190,15 +211,22 @@ export const sendMessage = async (

export const hasPrefix = (prompt: string): string => {
return (
hasBardPrefix(prompt) || hasLlamaPrefix(prompt)
hasBardPrefix(prompt) || hasLlamaPrefix(prompt) || hasClaudeOpusPrefix(prompt)
)
}

export const getPromptPrice = (completion: string, data: ChatPayload): { price: number, promptTokens: number, completionTokens: number } => {
export const getPromptPrice = (completion: LlmCompletion, data: ChatPayload): { price: number, promptTokens: number, completionTokens: number } => {
const { ctx, model } = data
const modelPrice = getChatModel(model)
const price =
getChatModelPrice(modelPrice, true, completion.inputTokens ?? 0, completion.outputTokens ?? 0) *
config.openAi.chatGpt.priceAdjustment
ctx.session.llms.usage += completion.outputTokens ?? 0
ctx.session.llms.price += price
return {
price: 0,
promptTokens: 10,
completionTokens: 60
price,
promptTokens: completion.inputTokens ?? 0,
completionTokens: completion.outputTokens ?? 0
}
}

Expand Down
Loading

0 comments on commit 6dac718

Please sign in to comment.