feat: add metering data

This commit is contained in:
zhaoweijie 2025-02-23 13:02:32 +08:00
parent c50bb49b37
commit 7b8879a7a8
9 changed files with 225 additions and 162 deletions

BIN
bun.lockb Normal file → Executable file

Binary file not shown.

View File

@ -9,139 +9,128 @@ import {
Typography, Typography,
Tooltip Tooltip
} from "antd" } from "antd"
import { NavLink } from "react-router-dom" import { NavLink, useParams } from "react-router-dom"
import { useStoreMessageOption } from "@/store/option.tsx"
const data = [ import { useMemo } from "react"
{
key: "输出token数",
value: 2
},
{
key: "输入token数",
value: 2
},
{
key: "模型",
value: "xxx"
}
]
const inputTokenData = [
{
key: "关键词提示",
value: "xxx"
},
{
key: "问题",
value: "xxx"
},
{
key: "数联网引用数据",
value: "xxx"
},
{
key: "提供方",
value: "xxx"
},
{
key: "token数量",
value: 2
},
{
key: "内容",
value: "xxx"
}
]
const outputTokenData = [
{
key: "类型",
value: "xxx"
},
{
key: "来源",
value: "xxx"
},
{
key: "token数量",
value: 2
},
{
key: "内容",
value: "xxx"
}
]
interface DataType { interface DataType {
key: string key: string
name: string name: string
age: number doId: number
address: string data_space: string
tags: number
content: string content: string
tokenCount: number
} }
const columns: TableProps<DataType>["columns"] = [ const columns: TableProps<DataType>["columns"] = [
{ {
title: "序号", title: '序号',
dataIndex: "key", key: 'index',
key: "name", width: 100,
render: (text) => <a>{text}</a> render: (_text, _record, index) => index + 1, // 索引从0开始+1后从1显示
}, },
{ {
title: "标识", title: "标识",
dataIndex: "age", dataIndex: "doId",
key: "age" key: "doId"
}, },
{ {
title: "提供方", title: "提供方",
dataIndex: "address", dataIndex: "data_space",
key: "address" key: "data_space"
}, },
{ {
title: "token数量", title: "token数量",
key: "tags", key: "tokenCount",
dataIndex: "tags" dataIndex: "tokenCount",
width: 100
}, },
{ {
title: "内容", title: "内容",
key: "content", key: "content",
dataIndex: "content" dataIndex: "content",
} ellipsis: {
] showTitle: false
},
const data1: DataType[] = [ render: (content) => (
{ <Tooltip placement="topLeft" title={content}>
key: "1", {content}
name: "John Brown", </Tooltip>
age: 32, )
address: "New York No. 1 Lake Park",
tags: 2,
content: "内容"
},
{
key: "2",
name: "Jim Green",
age: 42,
address: "London No. 1 Lake Park",
tags: 3,
content: "内容"
},
{
key: "3",
name: "Joe Black",
age: 32,
address: "Sydney No. 1 Lake Park",
tags: 3,
content: "内容"
} }
] ]
export const ListDetail = () => { export const ListDetail = () => {
const { chatMessages } = useStoreMessageOption()
const { id } = useParams()
const record = useMemo(
() => chatMessages.find((item) => item.id === id),
[chatMessages]
)
const modelData = useMemo(
() => [
{
key: "大模型输入token数",
value: record.modelInputTokenCount
},
{
key: "大模型输出token数",
value: record.modelOutputTokenCount
},
{
key: "模型",
value: record.model
}
],
[record]
)
const inputTokenData = useMemo(
() => [
{
key: "内容:",
value: record.queryContent
},
{
key: "token数量:",
value: record.queryContent.length
}
],
[record]
)
const keywordsData = useMemo(
() => [
{
key: "token数量:",
value: record.iodKeywords.reduce((acc, cur) => acc + cur.length, 0)
},
{
key: "内容:",
value: record.iodKeywords.join(", ")
}
],
[record]
)
const responseContent = useMemo(
() => [
{
key: "token数量:",
value: record.modelResponseContent.length
},
{
key: "内容:",
value: record.modelResponseContent
}
],
[record]
)
return ( return (
<div className="p-[1rem] pt-[4rem]"> <div className="p-[1rem] pt-[4rem]">
<List <List
grid={{ gutter: 16, column: 3 }} grid={{ gutter: 16, column: 3 }}
dataSource={data} dataSource={modelData}
renderItem={(item) => ( renderItem={(item) => (
<List.Item> <List.Item>
<Card title={item.key}>{item.value}</Card> <Card title={item.key}>{item.value}</Card>
@ -150,43 +139,65 @@ export const ListDetail = () => {
style={{ marginBottom: "2rem" }} style={{ marginBottom: "2rem" }}
/> />
<div> <Space direction="vertical" size={10}>
<Divider orientation="left">token详情</Divider> <Divider orientation="left">token详情</Divider>
<List <List
bordered bordered
header={<div></div>}
dataSource={inputTokenData} dataSource={inputTokenData}
renderItem={(item) => ( renderItem={(item) => (
<List.Item style={{ justifyContent: "flex-start" }}> <List.Item style={{ justifyContent: "flex-start" }}>
<Typography.Text mark className="mr-1"> <Typography.Paragraph style={{ marginBottom: 0 }} className="mr-1">
{item.key} {item.key}
</Typography.Text> </Typography.Paragraph>
<Tooltip placement="topLeft" title={item.value}> <Tooltip placement="topLeft" style={{ marginBottom: 0 }} title={item.value}>
{item.value} {item.value}
</Tooltip> </Tooltip>
</List.Item> </List.Item>
)} )}
style={{ marginBottom: "1rem" }} style={{ marginBottom: "1rem" }}
/> />
<Table<DataType> columns={columns} dataSource={data1} /> <Card title="数联网引用数据">
</div> <Table<DataType> columns={columns} dataSource={record.iodData} />
</Card>
</Space>
<div> <Space direction="vertical" size={10}>
<Divider orientation="left">token详情</Divider> <Divider orientation="left">token详情</Divider>
<List <List
bordered bordered
dataSource={outputTokenData} dataSource={keywordsData}
header={<div></div>}
renderItem={(item) => ( renderItem={(item) => (
<List.Item style={{ justifyContent: "flex-start" }}> <List.Item style={{ justifyContent: "flex-start" }}>
<Typography.Text mark className="mr-1"> <Typography.Text className="mr-1" style={{ marginBottom: 0 }}>{item.key}</Typography.Text>
{item.key} <Tooltip style={{ marginBottom: 0 }} placement="topLeft" title={item.value}>
</Typography.Text>
<Tooltip placement="topLeft" title={item.value}>
{item.value} {item.value}
</Tooltip> </Tooltip>
</List.Item> </List.Item>
)} )}
/> />
</div> <List
bordered
dataSource={responseContent}
header={<div></div>}
renderItem={(item) => (
<List.Item
style={{ justifyContent: "flex-start", alignItems: "center" }}>
<Typography.Text
className="mt-0 mr-1 w-20"
style={{ marginBottom: 0 }}>
{item.key}
</Typography.Text>
<Typography.Paragraph
style={{ marginBottom: 0 }}
ellipsis={{ tooltip: item.value, rows: 2, expandable: true }}>
{item.value}
</Typography.Paragraph>
</List.Item>
)}
/>
</Space>
</div> </div>
) )
} }

View File

@ -1,24 +1,9 @@
import React from "react" import React, { useMemo } from "react"
import { ChatMessage, useStoreMessageOption } from "@/store/option" import { ChatMessage, useStoreMessageOption } from "@/store/option"
import { Card, List, Table, Tag, Space, TableProps, Tooltip } from "antd" import { Card, List, Table, Tag, Space, TableProps, Tooltip } from "antd"
import { NavLink } from "react-router-dom" import { NavLink } from "react-router-dom"
import { formatDate } from "@/utils/date" import { formatDate } from "@/utils/date"
const data = [
{
key: "对话数量",
value: 2
},
{
key: "输出token数",
value: 2
},
{
key: "输入token数",
value: 2
}
]
const columns: TableProps<ChatMessage>["columns"] = [ const columns: TableProps<ChatMessage>["columns"] = [
{ {
title: "id", title: "id",
@ -49,6 +34,14 @@ const columns: TableProps<ChatMessage>["columns"] = [
title: "思维链", title: "思维链",
key: "thinkingChain", key: "thinkingChain",
dataIndex: "thinkingChain", dataIndex: "thinkingChain",
ellipsis: {
showTitle: false
},
render: (responseContent) => (
<Tooltip placement="topLeft" title={responseContent}>
{responseContent}
</Tooltip>
),
width: "10%" width: "10%"
}, },
@ -73,9 +66,8 @@ const columns: TableProps<ChatMessage>["columns"] = [
}, },
{ {
title: "数联网token", title: "数联网token",
dataIndex: "iodOutputToken", dataIndex: "iodDataTokenCount",
key: "iodOutputToken", key: "iodDataTokenCount"
render: (iodOutputToken) => <div>{iodOutputToken?.length}</div>
}, },
{ {
title: "大模型token", title: "大模型token",
@ -83,9 +75,7 @@ const columns: TableProps<ChatMessage>["columns"] = [
dataIndex: "largeModelToken", dataIndex: "largeModelToken",
render: (_, record) => { render: (_, record) => {
return ( return (
<div> <div>{record.modelInputTokenCount + record.modelOutputTokenCount}</div>
{record.iodInputToken?.length + record.iodOutputToken?.length}
</div>
) )
} }
}, },
@ -119,13 +109,49 @@ const columns: TableProps<ChatMessage>["columns"] = [
export const MeteringDetail = () => { export const MeteringDetail = () => {
const { chatMessages } = useStoreMessageOption() const { chatMessages } = useStoreMessageOption()
console.log(chatMessages, "opppp")
const data = useMemo(
() => [
{
key: "对话数量",
value: chatMessages.length
},
{
key: "数联网输入token数",
value: chatMessages.reduce((acc, cur) => {
for (const item of cur.iodKeywords) {
acc += item.length
}
return acc
}, 0)
},
{
key: "数联网输出token数",
value: chatMessages.reduce((acc, cur) => acc + cur.iodDataTokenCount, 0)
},
{
key: "大模型输入token数",
value: chatMessages.reduce(
(acc, cur) => acc + cur.modelInputTokenCount,
0
)
},
{
key: "大模型输出token数",
value: chatMessages.reduce(
(acc, cur) => acc + cur.modelOutputTokenCount,
0
)
}
],
[chatMessages]
)
return ( return (
<div className="pt-[4rem]"> <div className="p-4 pt-[4rem]">
<List <List
grid={{ gutter: 16, column: 3 }} grid={{ gutter: 16, column: 5 }}
dataSource={data} dataSource={data}
split={false}
renderItem={(item) => ( renderItem={(item) => (
<List.Item> <List.Item>
<Card title={item.key}>{item.value}</Card> <Card title={item.key}>{item.value}</Card>

View File

@ -316,15 +316,14 @@ export const useMessageOption = () => {
.map((k) => k.trim()) .map((k) => k.trim())
} }
const { prompt, webSources, iodSources } = await getSystemPromptForWeb( const { prompt, webSources, iodSources, iodData, iodDataTokenCount } =
query, await getSystemPromptForWeb(query, keywords, webSearch, iodSearch)
keywords,
webSearch,
iodSearch
)
console.log("prompt:\n" + prompt) console.log("prompt:\n" + prompt)
setIsSearchingInternet(false) setIsSearchingInternet(false)
chatMessage.prompt = prompt chatMessage.prompt = prompt
chatMessage.iodKeywords = keywords
chatMessage.iodData = iodData
chatMessage.iodDataTokenCount = iodDataTokenCount
// message = message.trim().replaceAll("\n", " ") // message = message.trim().replaceAll("\n", " ")
@ -485,12 +484,16 @@ export const useMessageOption = () => {
setIsProcessing(false) setIsProcessing(false)
setStreaming(false) setStreaming(false)
chatMessage.relatedDataCount = keywords.length chatMessage.modelInputTokenCount = generationInfo?.prompt_eval_count ?? 0
chatMessage.modelOutputTokenCount = generationInfo?.eval_count ?? 0
chatMessage.model = generationInfo?.model ?? ""
chatMessage.relatedDataCount = iodData?.length ?? 0
chatMessage.timeTaken = timetaken chatMessage.timeTaken = timetaken
chatMessage.date = reasoningStartTime chatMessage.date = reasoningStartTime
const { think, content } = responseResolver(fullText) const { think, content } = responseResolver(fullText)
chatMessage.thinkingChain = think chatMessage.thinkingChain = think
chatMessage.responseContent = content chatMessage.responseContent = content
chatMessage.modelResponseContent = fullText
setChatMessages([...chatMessages, chatMessage]) setChatMessages([...chatMessages, chatMessage])
} catch (e) { } catch (e) {
const errorSave = await saveMessageOnError({ const errorSave = await saveMessageOnError({

View File

@ -46,14 +46,24 @@ export type ChatMessage = {
iodInputToken: string iodInputToken: string
// 数联网输出token // 数联网输出token
iodOutputToken: string iodOutputToken: string
// 大模型输入token // 大模型输入token数量
modelInputToken: string modelInputTokenCount: number
// 大模型输出token // 大模型输出token数量
modelOutputToken: string modelOutputTokenCount: number
// 日期 // 日期
date: Date date: Date
// 耗时 // 耗时
timeTaken: number timeTaken: number
// 大模型回答的全部内容
modelResponseContent: string
// iod的全部内容的token数量
iodDataTokenCount: number
// iod返回的数据
iodData: any[]
// iod keywords
iodKeywords: string[]
// 模型
model: string
} }
type State = { type State = {

View File

@ -5,4 +5,5 @@ export type IodRegistryEntry = {
pdf_url?: string pdf_url?: string
description: string description: string
content?: string content?: string
data_space?: string
} }

View File

@ -91,7 +91,13 @@ export async function localIodSearch(
) )
).flat() ).flat()
return results // results 根据 doId 去重
const map = new Map<string, IodRegistryEntry>()
for (const r of results) {
map.set(r.doId, r)
}
return Array.from(map.values())
} }
const ARXIV_URL_PATTERN = /^https?:\/\/arxiv\.org\// const ARXIV_URL_PATTERN = /^https?:\/\/arxiv\.org\//

View File

@ -95,13 +95,17 @@ export const getSystemPromptForWeb = async (
// ) // )
// .join("\n") // .join("\n")
} }
const iod_search_results = iodSearchResults const _iodSearchResults = iodSearchResults
.map((res) => ({ .map((res) => ({
doId: res.doId, doId: res.doId,
name: res.name, name: res.name,
url: res.url, url: res.url,
content: res.content || res.description data_space: res.data_space,
})) tokenCount: (res.content || res.description)?.length ?? 0,
content: res.content || res.description
}))
const iod_search_results = _iodSearchResults
.map( .map(
(result, idx) => (result, idx) =>
`<result doId="${result.doId}" name="${result.name}" source="${result.url}" id="${idx + 1}">${result.content}</result>` `<result doId="${result.doId}" name="${result.name}" source="${result.url}" id="${idx + 1}">${result.content}</result>`
@ -135,7 +139,9 @@ export const getSystemPromptForWeb = async (
type: "url" type: "url"
} }
}), }),
iodSources: iodSearchResults iodSources: iodSearchResults,
iodData: _iodSearchResults,
iodDataTokenCount: _iodSearchResults.reduce((acc, cur) => (acc + cur.content.length), 0)
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e)