Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add GPT3Tokenizer #90 #94

Merged
merged 1 commit into from
Mar 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@types/marked": "^4.0.8",
"clipboard": "^2.0.11",
"dayjs": "^1.11.7",
"gpt3-tokenizer": "^1.1.5",
"highlight.js": "^11.7.0",
"html2canvas": "^1.4.1",
"markdown-it": "^13.0.1",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个代码补丁增加了一个新的依赖项"gpt3-tokenizer"。代码本身没有看到任何明显的问题或潜在风险,但是我们建议确保这个新依赖项与现有依赖项兼容,并且它没有引入不必要的安全漏洞。如果可以,建议使用版本控制来跟踪这些依赖项的变化,并对代码进行自动测试以确保其正确性。

Expand Down
19 changes: 16 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

113 changes: 1 addition & 112 deletions src/api/openAi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
fetchEventSource,
type EventSourceMessage
} from '@microsoft/fetch-event-source'
import type { MessageData, SessionData } from '@/types'
import type { MessageData } from '@/types'

/**
* 获取 openai 对话消息
Expand Down Expand Up @@ -112,114 +112,3 @@ export const getOpenAICreditApi = async () => {
}
}
}

/**
* 获取 ai 回答
* @param value 消息内容
*/
export const getAiMessage = async (value?: string) => {
const apiKey = getOpenAIKey()
if (!apiKey) return

const { isThinking, sessionDataList } = storeToRefs(useSessionStore())
const { updateSessionData } = useSessionStore()

try {
const { currentRole } = useRoleStore()

if (!currentRole) return

const messages: MessageData[] = []

const { currentSession, sessionDataList } = useSessionStore()
const { isMemory } = useSettingsStore()

const lastQuestion = sessionDataList.filter((item) => item.is_ask).at(-1)

// 记忆模式,或者是第一次对话,都要生成角色描述
if (sessionDataList.length < 3 || isMemory) {
messages.push({
role: 'system',
content: currentRole.description
})
}

// 获取记忆(限制5条),往前推直到出现 is_momery 为 false 的
// TODO 应该进行限流,防止出现过多的记忆,导致token超出
const addMemory = async () => {
if (isMemory) {
// TODO: 优化 sql
const sql = `SELECT * FROM session_data WHERE session_id = '${currentSession?.id}' ORDER BY id DESC LIMIT 5;`
const memoryList = (await executeSQL(sql)) as SessionData[]

let count = 0
const arr = []
while (count < memoryList.length) {
if (!memoryList[count].is_memory) break
arr.push(JSON.parse(memoryList[count++].message as any))
}
messages.push(...arr.reverse())
}
}

// 再次生成上一次问题
if (!value) {
if (!lastQuestion) return

// 为了保证统一,这之后的内容全部删掉
const deleteSql = `DELETE FROM session_data WHERE session_id = '${lastQuestion?.session_id}' AND id >= ${lastQuestion?.id};`
await executeSQL(deleteSql)

await addMemory()
messages.push(lastQuestion?.message)
} else {
await addMemory()
messages.push({
role: 'user',
content: value
})
}

const { isThinking } = storeToRefs(useSessionStore())
const { addSessionData } = useSessionStore()

isThinking.value = true

await addSessionData({
isAsk: true,
data: messages.at(-1)!
})

await addSessionData({
isAsk: false,
data: {
role: 'assistant',
content: ''
}
})

await getOpenAIResultStreamApi(messages)

isThinking.value = false
} catch ({ message }: any) {
sessionDataList.value.at(-1)!.message.content = message as any

updateSessionData(sessionDataList.value.at(-1)!)

isThinking.value = false
}
}

/**
* 获取apiKey
*/
const getOpenAIKey = () => {
const { apiKey } = useSettingsStore()

if (!apiKey && !import.meta.env.VITE_OPEN_AI_API_KEY) {
Message.warning('请先填写 OpenAi API Key')
return false
}

return apiKey || import.meta.env.VITE_OPEN_AI_API_KEY
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段代码中有一些问题:

  1. sessionDataList变量被定义了两次,需要删除其中一个。
  2. getOpenAICreditApi方法没有返回任何值,需要添加返回类型。
  3. getAiMessage方法有很多不必要的代码和逻辑。比如,使用useSessionStore()useSettingsStore()的方式不正确。此外,在处理过程中存在重复的逻辑和未定义的变量等问题。
  4. getOpenAIKey方法中,当apiKeyVITE_OPEN_AI_API_KEY都不存在时,会显示警告信息并返回false。但是,警告信息没有指定位置,并且没有提供足够的上下文信息。

以下是一些改进建议:

  1. 删除多余或不必要的代码和逻辑。
  2. 在处理错误时,应该提供更详细的错误信息,以便更容易地找到问题。
  3. 尽可能使用传统的函数调用方式而不是Vue组件式API。
  4. 在所有方法中添加参数类型和返回类型。
  5. 确保所有依赖项都已正确导入。

最后,需要注意的是,这只是对代码的简要检查,可能还有其他潜在的问题或错误。

33 changes: 31 additions & 2 deletions src/components/Function/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,40 @@ import {
IconImage
} from '@arco-design/web-vue/es/icon'
import { emit } from '@tauri-apps/api/event'
import { estimateTokens, getMemoryList } from '@/utils'
const { currentRole } = storeToRefs(useRoleStore())
const { currentRole, textAreaValue } = storeToRefs(useRoleStore())
const sessionStore = useSessionStore()
const { switchSession, deleteSession, updateSessionData } = sessionStore
const { isThinking, sessionDataList, chatController } =
storeToRefs(sessionStore)
const { isMemory } = storeToRefs(useSettingsStore())
const disabled = computed(
() => isThinking.value || !sessionDataList.value.length
)
const tokenUsage = ref(0)
watch([textAreaValue, isMemory], async () => {
// 角色描述字符数
const roleTokens = estimateTokens(currentRole.value!.description)
// 输入字符数
const textAreaTokens = estimateTokens(textAreaValue.value)
// 记忆模式下额外消耗的字符数
const memroyList = await getMemoryList()
const memoryTokens = estimateTokens(
memroyList.map((item) => item.content).join('')
)
tokenUsage.value = textAreaTokens + roleTokens + memoryTokens
})
// 控制设置弹框
const modalVisible = ref(false)
const closeModal = () => {
Expand Down Expand Up @@ -91,8 +113,15 @@ const triggerScroll = () => {
<!-- TODO:把聊天对象移过来 -->
<template>
<div class="function text-6 relative flex justify-end">
<!-- 预估将要消耗的token -->
<div
class="left-1/5 text-4 -translate-1/2 absolute top-1/2"
v-if="textAreaValue.length"
>
{{ isMemory ? '记忆模式:' : '' }}预计消耗 {{ tokenUsage }} TK
</div>
<!-- 当前聊天角色对象 -->
<div class="top-50% left-50% text-4 -translate-1/2 absolute select-none">
<div class="text-4 -translate-1/2 absolute top-1/2 left-1/2">
正在与
<a-tooltip content="点我回到底部">
<span class="mark cursor-pointer" @click="triggerScroll">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段代码的主要功能是在聊天界面上方显示预估将要消耗的 token 数量。以下是我的建议和注意事项:

  • watch 中异步调用 getMemoryList() 可能会导致性能问题,建议使用 computed 来缓存记忆列表。
  • watch 中计算 token 数量可能会频繁触发,建议使用 debounce 或者 throttle 来减少计算次数。
  • 变量命名不太规范,例如 memroyList 应该改为 memoryList
  • 变量定义时最好初始化一个默认值,例如 const tokenUsage = ref(0)
  • 在模板中,可以将 v-if 移到外层元素上,这样可以避免渲染无用的 DOM 元素。例如:
<div v-if="textAreaValue.length || isMemory" class="function text-6 relative flex justify-end">
  <div v-if="textAreaValue.length" class="left-1/5 text-4 -translate-1/2 absolute top-1/2">
    {{ isMemory ? '记忆模式:' : '' }}预计消耗 {{ tokenUsage }} TK
  </div>
  <!-- ... -->
</div>

Expand Down
42 changes: 40 additions & 2 deletions src/components/Session/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import { listen } from '@tauri-apps/api/event'
import MarkdownIt from 'markdown-it'
import MarkdownItHighlight from 'markdown-it-highlightjs'
import type { SessionData } from '@/types'
import { estimateTokens } from '@/utils'
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc'
Expand Down Expand Up @@ -59,6 +61,37 @@ const handleScroll = () => {
}
}
/**
* 计算单条消息消耗的token
* @param item 单条消息
*/
// FIXME 这里的item/data类型报错搞不定,只能用any
const calcToken = (item: any) => {
// 角色描述字符数
const roleToken = estimateTokens(currentRole.value!.description)
// 消息内容字符数
const contentToken = estimateTokens(item.message.content)
// 记忆模式下额外消耗的字符数
let memoryToken = 0
if (item.is_memory) {
// 获取sessionDataList中的此条之前的最后5条消息
const memoryList = sessionDataList.value
.filter((data: any) => data.id < item.id)
.slice(-5)
memoryToken = estimateTokens(
memoryList.map((data) => data.message.content).join('')
)
}
if (item.is_ask) {
return `${roleToken + contentToken + memoryToken}TK${
item.is_memory ? '*' : ''
}`
}
return `${contentToken}TK`
}
onMounted(() => {
listen('scroll-to-bottom', () => {
isAutoScroll.value = true
Expand Down Expand Up @@ -91,8 +124,13 @@ watch([currentSession, sessionDataList], () => {
:class="item.is_ask && 'flex-row-reverse'"
:key="item.id"
>
<Avatar class="w-12!" :value="item.is_ask ? uuid : currentRole?.name" />
<div class="flex flex-col items-center gap-1">
<Avatar
class="w-12!"
:value="item.is_ask ? uuid : currentRole?.name"
/>
<span class="text-gray text-xs">{{ calcToken(item) }}</span>
</div>
<div
class="relative flex w-[calc(100%-8rem)] flex-col gap-2"
:class="item.is_ask && 'items-end'"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段代码主要是一个聊天应用中的一些功能实现。以下是我的建议:

  • 第 2 行和第 3 行的导入语句应该按照字母顺序排列,以提高可读性。
  • 第 4 行的 MarkdownIt 库应该使用 const 声明,因为它不会被重新分配。
  • 第 5 行的 MarkdownItHighlight 库应该使用 const 声明,因为它不会被重新分配。
  • 第 6 行的类型导入应该放在其他导入的下面,并且应该按字母顺序排列。
  • 第 7 行的 estimateTokens 函数应该使用 const 声明,因为它不会被重新分配。
  • 第 12 行的 handleScroll 函数应该使用 const 声明,因为它不会被重新分配。
  • 第 16 行的 calcToken 函数应该使用 const 声明,因为它不会被重新分配。
  • 在 calcToken 函数中,可以尝试使用更具体的类型而不是 any 类型来描述 item 参数和 sessionDataList 中的数据类型,以避免潜在的类型错误。
  • 在 calcToken 函数中,如果无法解决类型问题,则可以考虑将其转换为 JavaScript 函数并在调用时忽略 TypeScript 错误。
  • 在第 28 行中,应该考虑使用模板字符串来构建返回值,以提高代码可读性。
  • 第 30 行中的 is_memory 属性应该使用 === 进行比较,以避免类型转换错误。
  • 在第 46 行中,Avatar 组件应该使用 const 声明,因为它不会被重新分配。
  • 在第 51 行中,可以考虑使用模板字符串来构建返回值,以提高代码可读性。
  • 在第 56 行中,class 属性应该使用模板字符串来计算动态类名,以提高代码可读性。

Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './keyMap'
export * from './saveImage'
export * from './copy'
export * from './saveMarkdown'
export * from './openai'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这段代码看起来很简短,只是在一个导出语句中添加了一个新的模块 openai

由于我无法查看 openai 模块的实现,因此我无法判断是否存在潜在的错误风险。但是,我可以提供一些一般性的建议:

  • 确保 openai 模块已经通过单元测试和集成测试,以确保其正确性和稳定性。
  • 如果你是团队中的唯一开发者,请确保其他人知道你添加了这个新模块。如果有多个开发者,最好使用源代码管理工具(如 Git)来跟踪更改。
  • 如果这个模块与其他模块有依赖关系,请确保你更新了所有需要更新的依赖项。
  • 如果这个模块是公共模块,并且将被其他人使用,请确保文档和注释清晰明了,以便其他人能够理解它的用途和如何使用它。

希望这些建议能对你有所帮助!

Loading