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: display token usage #77

Merged
merged 8 commits into from
Jan 17, 2025
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
7 changes: 7 additions & 0 deletions .bioserver.env.template
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# If .bioserver.env doesn't provide OPENAI setup, customers need to set OPENAI API key in website
# OPENAI_API_KEY=
# OPENAI_MODEL=

# Optional (Azure),
# Besides OPENAI_API_KEY, the following envs should be set for Azure
Expand All @@ -11,3 +12,9 @@
# AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME=
# AZURE_OPENAI_EMBEDDINGS_MODEL=

# data directory for sqlite3 db
DATA_DIR=/app/data

# daily token limit for server
TOKEN_DAILY_LIMITATION=1_000_000

7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.

## [1.4.0](https://github.com/fengsh27/chatgse-next-sync/compare/v1.3.5...v1.4.0) (2025-01-06)


### Features

* display token usage ([#14](https://github.com/fengsh27/chatgse-next-sync/issues/14)) ([a9565a4](https://github.com/fengsh27/chatgse-next-sync/commit/a9565a449b852123728651a8bbe816e664ec9668))

### [1.3.5](https://github.com/biocypher/biochatter-next/compare/v1.3.4...v1.3.5) (2024-08-26)


Expand Down
4 changes: 2 additions & 2 deletions app/api/rag/connectionstatus/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
import { BioChatterServerResponse, getBaseUrl } from "../../common";
import { BiochatterPath, ERROR_BIOSERVER_MILVUS_CONNECT_FAILED, ERROR_BIOSERVER_OK, ERROR_BIOSERVER_UNKNOWN } from "@/app/constant";
import { getBaseUrl } from "../../common";
import { BiochatterPath } from "@/app/constant";
import { prettyObject } from "@/app/utils/format";

async function handle(request: NextRequest) {
Expand Down
37 changes: 37 additions & 0 deletions app/api/tokenusage/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { NextRequest, NextResponse } from "next/server";
import { getBaseUrl } from "../common";
import { BiochatterPath} from "@/app/constant";
import { prettyObject } from "@/app/utils/format";

async function handle(request: NextRequest) {
const AUTHORIZATION = "Authorization"
const authValue = request.headers.get(AUTHORIZATION) ?? "";
const baseUrl = getBaseUrl();
const data = await request.json();
const path = BiochatterPath.TokenUsage;
const url = `${baseUrl}/${path}`;
try {
const res = await fetch(
url,
{
method: "POST",
headers: {
[AUTHORIZATION]: authValue,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: data.model ?? "gpt-3.5-turbo",
session_id: data.session_id ?? "",
}),
}
);
const jsonBody = await res.json();
return NextResponse.json(jsonBody);
} catch (e: any) {
console.error(e);
return NextResponse.json(prettyObject(e));
}
}

export const POST = handle;

2 changes: 1 addition & 1 deletion app/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ChatGPTApi } from "./platforms/openai";
export const ROLES = ["system", "user", "assistant"] as const;
export type MessageRole = (typeof ROLES)[number];

export const Models = ["gpt-3.5-turbo", "gpt-4"] as const;
export const Models = ["gpt-3.5-turbo", "gpt-4", "gpt-4o"] as const;
export type ChatModel = ModelType;

export interface RequestMessage {
Expand Down
22 changes: 22 additions & 0 deletions app/client/datarequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,26 @@ export const requestRemoveDocument = async (
}
});
return res;
}

export const requestTokenUsage = async (
session_id?: string,
model?: string
) => {
const TokenUsage_URL = ApiPath.TokenUsage;
let url = TokenUsage_URL as string;
if (!url.endsWith('/')) {
url += '/';
}
const res = await fetch(url, {
method: "POST",
body: JSON.stringify({
"session_id": session_id ?? "",
"model": model ?? "gpt-3.5-turbo",
}),
headers: {
...get_auth_header()
}
});
return res;
}
10 changes: 6 additions & 4 deletions app/client/platforms/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import {
ApiPath,
DEFAULT_API_HOST,
DEFAULT_MODELS,
ERROR_BIOSERVER_EXCEEDS_TOKEN_LIMIT,
OpenaiPath,
REQUEST_TIMEOUT_MS,
ServiceProvider,
} from "@/app/constant";
import { ChatSession, useAccessStore, useAppConfig, useChatStore } from "@/app/store";
import { useAccessStore, useAppConfig, useChatStore } from "@/app/store";

import { ChatOptions, getHeaders, LLMApi, LLMModel, LLMUsage } from "../api";
import Locale from "../../locales";
Expand All @@ -17,9 +18,6 @@ import {
import { prettyObject } from "@/app/utils/format";
import { getClientConfig } from "@/app/config/client";
import { makeAzurePath } from "@/app/azure";
import { useRAGStore } from "@/app/store/rag";
import { useKGStore } from "@/app/store/kg";
import { getKnowledgeGraphInfo, getOncoKBInfo, getVectorStoreInfo } from "@/app/utils/prodinfo";

export interface OpenAIListModelResponse {
object: string;
Expand Down Expand Up @@ -236,6 +234,10 @@ export class ChatGPTApi implements LLMApi {
clearTimeout(requestTimeoutId);

const resJson = await res.json();
if (resJson.code === ERROR_BIOSERVER_EXCEEDS_TOKEN_LIMIT && options.onFinish) {
options.onFinish(Locale.Chat.TokenLimit(resJson.limitation));
return;
}
const message = this.extractMessage(resJson);
options.onFinish(message, resJson.contexts);
}
Expand Down
24 changes: 4 additions & 20 deletions app/components/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,31 +42,15 @@ export function AuthPage() {
<input
className={styles["auth-input"]}
type="password"
placeholder={Locale.Auth.Input}
value={accessStore.accessCode}
placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
value={accessStore.openaiApiKey}
onChange={(e) => {
accessStore.update(
(access) => (access.accessCode = e.currentTarget.value),
(access) => (access.openaiApiKey = e.currentTarget.value),
);
}}
/>
{!accessStore.hideUserApiKey ? (
<>
<div className={styles["auth-tips"]}>{Locale.Auth.SubTips}</div>
<input
className={styles["auth-input"]}
type="password"
placeholder={Locale.Settings.Access.OpenAI.ApiKey.Placeholder}
value={accessStore.openaiApiKey}
onChange={(e) => {
accessStore.update(
(access) => (access.openaiApiKey = e.currentTarget.value),
);
}}
/>
</>
) : null}


<div className={styles["auth-actions"]}>
<IconButton
text={Locale.Auth.Confirm}
Expand Down
44 changes: 43 additions & 1 deletion app/components/home.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,25 @@
&:not(:last-child) {
margin-right: 10px;
}
}
}
}
.sidebar-footer-bar {
display: flex;
margin-bottom: 20px;
flex-direction: column;

.sidebar-bar-buttons {
display: flex;
flex-direction: row;

.sidebar-bar-button {
flex-grow: 1;

&:not(:last-child) {
margin-right: 10px;
}
}
}
}

&:hover,
Expand Down Expand Up @@ -192,6 +210,13 @@
overflow-x: hidden;
}

.sidebar-token-usage {
border: var(--border-in-light);
border-radius: 10px;
padding: 10px;
margin: 20px 0 0 0;
}

.chat-item {
padding: 10px 14px;
background-color: var(--white);
Expand Down Expand Up @@ -281,6 +306,23 @@
}
}
}
.sidebar-footer-bar {
display: flex;
flex-direction: column;

.sidebar-bar-buttons {
display: flex;
flex-direction: column;

.sidebar-bar-button {

&:not(:last-child) {
margin-right: 0px;
margin-bottom: 10px;
}
}
}
}

.chat-item {
padding: 0;
Expand Down
3 changes: 2 additions & 1 deletion app/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,7 @@ export function Settings() {
</ListItem>
</List>

<List id={SlotID.CustomModel}>
{false && (<List id={SlotID.CustomModel}>
{showAccessCode && (
<ListItem
title={Locale.Settings.Access.AccessCode.Title}
Expand Down Expand Up @@ -1121,6 +1121,7 @@ export function Settings() {
></input>
</ListItem>
</List>
)}

<List>
<ModelConfigList
Expand Down
22 changes: 21 additions & 1 deletion app/components/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ export function SideBar(props: { className?: string }) {
accessStore.productionInfo === "undefined" ?
undefined :
(JSON.parse(accessStore.productionInfo) as any) as ProductionInfo;
const currentModel = chatStore.currentSession().mask.modelConfig.model ?? "gpt-3.5-turbo";
const sessionId = chatStore.currentSession().id ?? "";
const kgProdInfo = getKnowledgeGraphInfo(prodInfo);
const ragProdInfo = getVectorStoreInfo(prodInfo);
const mask = getMaskInfo(prodInfo);
Expand All @@ -160,6 +162,10 @@ export function SideBar(props: { className?: string }) {

useHotKey();

useEffect(() => {
accessStore.updateTokenUsage(sessionId, currentModel);
}, []);

// switch themes
function nextTheme() {
const themes = [Theme.Auto, Theme.Light, Theme.Dark];
Expand Down Expand Up @@ -237,7 +243,10 @@ export function SideBar(props: { className?: string }) {
<ChatList narrow={shouldNarrow} />
</div>

<div className={styles["sidebar-header-bar"]}>
<div className={styles["sidebar-footer-bar"]}>
<div
className={styles["sidebar-bar-buttons"]}
>
<IconButton
disabled={!ragProdInfo.enabled}
icon={<RagIcon width={16} height={16} />}
Expand All @@ -258,6 +267,17 @@ export function SideBar(props: { className?: string }) {
}}
shadow
/>
</div>
{!shouldNarrow && (
<div className={styles["sidebar-token-usage"]}>
{useAccessStore.getState().tokenUsage.auth_type.slice(0, 6) === "Server" ? (
<div style={{fontSize: "14px", marginBottom: "10px"}}>Server token usage</div>
) : (
<div style={{fontSize: "14px", marginBottom: "10px"}}>Client token usage</div>
)}
<div>Total tokens: {useAccessStore.getState().tokenUsage.tokens.total_tokens}</div>
</div>
)}
</div>
<div className={styles["sidebar-tail"]}>
<div className={styles["sidebar-actions"]}>
Expand Down
21 changes: 14 additions & 7 deletions app/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export enum ApiPath {
OpenAI = "/api/openai",
RAG = "/api/rag",
KG = "/api/kg",
TokenUsage = "/api/tokenusage"
}

export enum SlotID {
Expand Down Expand Up @@ -91,6 +92,7 @@ export const BiochatterPath = {
Document: "v1/rag/document",
RAGConnectionStatus: "v1/rag/connectionstatus",
KGConnectionStatus: "v1/kg/connectionstatus",
TokenUsage: "v1/tokenusage",
}

export const Azure = {
Expand All @@ -115,21 +117,26 @@ export const KnowledgeCutOffDate: Record<string, string> = {
"gpt-4-vision-preview": "2023-04",
};

export const DEFAULT_MODELS = [
{
name: "gpt-4o",
available: true,
},
] as const;
export const DEFAULT_MODELS = [{
name: "gpt-4o",
available: true,
}, {
name: "gpt-4",
available: true,
}, {
name: "gpt-3.5-turbo",
available: true,
}] as const;

export const CHAT_PAGE_SIZE = 15;
export const MAX_RENDER_MSG_COUNT = 45;

// biochatter-server error
export const ERROR_BIOSERVER_OK = 0
export const ERROR_BIOSERVER_UNKNOWN = 5000
export const ERROR_BIOSERVER_UNKNOWN = 5100
export const ERROR_BIOSERVER_MILVUS_UNKNOWN = 5101
export const ERROR_BIOSERVER_MILVUS_CONNECT_FAILED = 5102
export const ERROR_BIOSERVER_EXCEEDS_TOKEN_LIMIT = 5103

export const HDR_CONTENT_TYPE = "Content-Type";
export const HDR_APPLICATION_JSON = "application/json";
Expand Down
5 changes: 3 additions & 2 deletions app/locales/cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ const cn = {
: "访问密码不正确或为空,请前往[登录](/#/auth)页输入正确的访问密码,或者在[设置](/#/settings)页填入你自己的 OpenAI API Key。",
},
Auth: {
Title: "需要密码",
Tips: "管理员开启了密码验证,请在下方填入访问码",
Title: "需要OpenAI API Key",
Tips: "请在下方填入OpenAI API Key",
SubTips: "或者输入你的 OpenAI API 密钥",
Input: "在此处填写访问码",
Confirm: "确认",
Expand Down Expand Up @@ -119,6 +119,7 @@ const cn = {
RAG: "",
KG: "",
},
TokenLimit: (x: any) => `The server's community daily token limit (${x} tokens) has been reached. \n\n Please provide your OpenAI API key on the [auth](/#/auth) page to continue.`,
},
Export: {
Title: "分享聊天记录",
Expand Down
Loading