add submodule: kvstore
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,3 +3,4 @@ docs
|
|||||||
|
|
||||||
.env
|
.env
|
||||||
.vscode
|
.vscode
|
||||||
|
.codex
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[submodule "third_party/kvstore"]
|
||||||
|
path = third_party/kvstore
|
||||||
|
url = http://gitlab.0voice.com/lianyiheng/9.1-kvstore.git
|
||||||
19
README.md
19
README.md
@@ -8,23 +8,8 @@ protoc \
|
|||||||
```
|
```
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# ai-chat-web
|
git submodule update --init --recursive
|
||||||
|
|
||||||
# ai-chat-backend
|
./script/run.sh
|
||||||
docker build -t ai-chat-backend:1.0.0 .
|
|
||||||
|
|
||||||
# ai-chat-service
|
|
||||||
docker build -t ai-chat-service:1.0.0 .
|
|
||||||
|
|
||||||
# chatgpt-web-frontend
|
|
||||||
HUSKY=0 pnpm bootstrap
|
|
||||||
pnpm dev
|
|
||||||
|
|
||||||
docker build -t chatgpt-web-frontend:1.0.0 .
|
|
||||||
|
|
||||||
# tokenizer
|
|
||||||
docker build -t tokenizer:1.0.0 .
|
|
||||||
|
|
||||||
# keywords-filter
|
|
||||||
docker build -t keywords-filter:1.0.0 .
|
|
||||||
```
|
```
|
||||||
@@ -17,9 +17,9 @@ chat:
|
|||||||
bot_desc: "你是一个AI助手,我需要你模拟一名资深的软件工程师来回答我的问题"
|
bot_desc: "你是一个AI助手,我需要你模拟一名资深的软件工程师来回答我的问题"
|
||||||
min_response_tokens: 600
|
min_response_tokens: 600
|
||||||
redis:
|
redis:
|
||||||
host: "redis"
|
host: "host.docker.internal"
|
||||||
port: 6379
|
port: 8888
|
||||||
pwd: ""
|
pwd: "123456"
|
||||||
dependOn:
|
dependOn:
|
||||||
sensitive:
|
sensitive:
|
||||||
address: "sensitive-filter:50053"
|
address: "sensitive-filter:50053"
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ services:
|
|||||||
- .env
|
- .env
|
||||||
volumes:
|
volumes:
|
||||||
- ./configs/ai-chat-service.yaml:/app/config.yaml:ro
|
- ./configs/ai-chat-service.yaml:/app/config.yaml:ro
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
ports:
|
ports:
|
||||||
- "50055:50055"
|
- "50055:50055"
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ chat:
|
|||||||
bot_desc: "你是一个AI助手,我需要你模拟一名资深的软件工程师来回答我的问题"
|
bot_desc: "你是一个AI助手,我需要你模拟一名资深的软件工程师来回答我的问题"
|
||||||
min_response_tokens: 600
|
min_response_tokens: 600
|
||||||
redis:
|
redis:
|
||||||
host: "127.0.0.1"
|
host: "host.docker.internal"
|
||||||
port: 8888
|
port: 8888
|
||||||
pwd: ""
|
pwd: "123456"
|
||||||
dependOn:
|
dependOn:
|
||||||
sensitive:
|
sensitive:
|
||||||
address: "sensitive-filter:50053"
|
address: "sensitive-filter:50053"
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ export default {
|
|||||||
showRawText: 'Show as raw text',
|
showRawText: 'Show as raw text',
|
||||||
sourceSemantic: 'Semantic Match',
|
sourceSemantic: 'Semantic Match',
|
||||||
sourceLlm: 'LLM Output',
|
sourceLlm: 'LLM Output',
|
||||||
|
inputTokensShort: 'Input',
|
||||||
|
outputTokensShort: 'Output',
|
||||||
inputTokens: 'Input {count}',
|
inputTokens: 'Input {count}',
|
||||||
outputTokens: 'Output {count}',
|
outputTokens: 'Output {count}',
|
||||||
sessionTokens: 'Session {count} tokens',
|
sessionTokens: 'Session {count} tokens',
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ export default {
|
|||||||
showRawText: '显示原文',
|
showRawText: '显示原文',
|
||||||
sourceSemantic: '语义匹配',
|
sourceSemantic: '语义匹配',
|
||||||
sourceLlm: '大模型输出',
|
sourceLlm: '大模型输出',
|
||||||
|
inputTokensShort: '输入',
|
||||||
|
outputTokensShort: '输出',
|
||||||
inputTokens: 'Input {count}',
|
inputTokens: 'Input {count}',
|
||||||
outputTokens: 'Output {count}',
|
outputTokens: 'Output {count}',
|
||||||
sessionTokens: '本轮消耗 {count} tokens',
|
sessionTokens: '本轮消耗 {count} tokens',
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ export default {
|
|||||||
showRawText: '顯示原文',
|
showRawText: '顯示原文',
|
||||||
sourceSemantic: '語義匹配',
|
sourceSemantic: '語義匹配',
|
||||||
sourceLlm: '大模型輸出',
|
sourceLlm: '大模型輸出',
|
||||||
|
inputTokensShort: '輸入',
|
||||||
|
outputTokensShort: '輸出',
|
||||||
inputTokens: 'Input {count}',
|
inputTokens: 'Input {count}',
|
||||||
outputTokens: 'Output {count}',
|
outputTokens: 'Output {count}',
|
||||||
sessionTokens: '本輪消耗 {count} tokens',
|
sessionTokens: '本輪消耗 {count} tokens',
|
||||||
|
|||||||
@@ -54,28 +54,43 @@ const sourceLabel = computed(() => {
|
|||||||
const sourceClass = computed(() => {
|
const sourceClass = computed(() => {
|
||||||
switch (props.messageMeta?.source) {
|
switch (props.messageMeta?.source) {
|
||||||
case 'semantic_match':
|
case 'semantic_match':
|
||||||
return 'border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-900/60 dark:bg-sky-950/40 dark:text-sky-300'
|
|
||||||
case 'llm':
|
case 'llm':
|
||||||
return 'border-amber-200 bg-amber-50 text-amber-700 dark:border-amber-900/60 dark:bg-amber-950/40 dark:text-amber-300'
|
return 'border-[#d8e2eb] bg-[#f7fafc] text-[#52606d] dark:border-neutral-700 dark:bg-[#1a1d22] dark:text-neutral-200'
|
||||||
default:
|
default:
|
||||||
return 'border-[#d0d7de] bg-white text-[#57606a] dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-300'
|
return 'border-[#d0d7de] bg-white text-[#57606a] dark:border-neutral-700 dark:bg-neutral-900 dark:text-neutral-300'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const inputUsageLabel = computed(() => {
|
const sourceIconClass = computed(() => {
|
||||||
const usage = props.messageMeta?.usage
|
switch (props.messageMeta?.source) {
|
||||||
if (!usage || props.inversion)
|
case 'semantic_match':
|
||||||
return ''
|
return 'bg-emerald-50 text-emerald-600 shadow-[inset_0_0_0_1px_rgba(52,211,153,0.18)] dark:bg-emerald-950/35 dark:text-emerald-300'
|
||||||
|
case 'llm':
|
||||||
return t('chat.inputTokens', { count: usage.prompt_tokens })
|
return 'bg-slate-100 text-slate-500 shadow-[inset_0_0_0_1px_rgba(148,163,184,0.18)] dark:bg-slate-800/70 dark:text-slate-300'
|
||||||
|
default:
|
||||||
|
return 'bg-white/90 text-[#52606d] shadow-[inset_0_0_0_1px_rgba(130,140,150,0.14)] dark:bg-neutral-800 dark:text-neutral-300'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const outputUsageLabel = computed(() => {
|
const tokenStats = computed(() => {
|
||||||
const usage = props.messageMeta?.usage
|
const usage = props.messageMeta?.usage
|
||||||
if (!usage || props.inversion)
|
if (!usage || props.inversion)
|
||||||
return ''
|
return []
|
||||||
|
|
||||||
return t('chat.outputTokens', { count: usage.completion_tokens })
|
return [
|
||||||
|
{
|
||||||
|
key: 'input',
|
||||||
|
label: t('chat.inputTokensShort'),
|
||||||
|
count: usage.prompt_tokens,
|
||||||
|
icon: 'ri:arrow-right-up-line',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'output',
|
||||||
|
label: t('chat.outputTokensShort'),
|
||||||
|
count: usage.completion_tokens,
|
||||||
|
icon: 'ri:sparkling-line',
|
||||||
|
},
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
const options = computed(() => {
|
const options = computed(() => {
|
||||||
@@ -138,30 +153,29 @@ function handleRegenerate() {
|
|||||||
<div class="flex flex-col gap-2" :class="[inversion ? 'items-end' : 'items-start']">
|
<div class="flex flex-col gap-2" :class="[inversion ? 'items-end' : 'items-start']">
|
||||||
<span class="text-xs font-medium text-[#9aa4af] dark:text-neutral-500">{{ dateTime }}</span>
|
<span class="text-xs font-medium text-[#9aa4af] dark:text-neutral-500">{{ dateTime }}</span>
|
||||||
<div
|
<div
|
||||||
v-if="sourceLabel || inputUsageLabel || outputUsageLabel"
|
v-if="sourceLabel || tokenStats.length"
|
||||||
class="flex flex-wrap items-center gap-2 rounded-2xl border border-[#e6edf3] bg-white/85 px-2.5 py-2 shadow-[0_10px_25px_rgba(15,23,42,0.06)] backdrop-blur-sm dark:border-neutral-800 dark:bg-[#14161a]/90"
|
class="flex flex-wrap items-center gap-2"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="sourceLabel"
|
v-if="sourceLabel"
|
||||||
class="inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-semibold leading-none"
|
class="inline-flex items-center gap-1.5 rounded-full border px-2.5 py-1 text-[11px] font-semibold leading-none shadow-sm"
|
||||||
:class="sourceClass"
|
:class="sourceClass"
|
||||||
>
|
>
|
||||||
<SvgIcon icon="ri:radar-line" />
|
<span class="inline-flex h-4 w-4 items-center justify-center rounded-full" :class="sourceIconClass">
|
||||||
|
<SvgIcon icon="ri:radar-line" />
|
||||||
|
</span>
|
||||||
<span>{{ sourceLabel }}</span>
|
<span>{{ sourceLabel }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="inputUsageLabel"
|
v-for="item in tokenStats"
|
||||||
class="inline-flex items-center gap-1.5 rounded-full border border-violet-200 bg-violet-50 px-2.5 py-1 text-[11px] font-semibold leading-none text-violet-700 dark:border-violet-900/60 dark:bg-violet-950/40 dark:text-violet-300"
|
:key="item.key"
|
||||||
|
class="inline-flex items-center gap-1.5 rounded-full border border-[#dfe8e3] bg-[#f7fbf8] px-2.5 py-1 text-[11px] font-medium leading-none text-[#5d7063] dark:border-[#2d3831] dark:bg-[#141916] dark:text-[#aebdb2]"
|
||||||
>
|
>
|
||||||
<SvgIcon icon="ri:login-circle-line" />
|
<span class="inline-flex h-4 w-4 items-center justify-center rounded-full bg-white/90 text-[#4b9e5f] shadow-[inset_0_0_0_1px_rgba(75,158,95,0.12)] dark:bg-[#202622] dark:text-[#8ec79a]">
|
||||||
<span>{{ inputUsageLabel }}</span>
|
<SvgIcon :icon="item.icon" />
|
||||||
</div>
|
</span>
|
||||||
<div
|
<span class="tracking-[0.02em]">{{ item.label }}</span>
|
||||||
v-if="outputUsageLabel"
|
<span class="font-semibold tabular-nums text-[#355340] dark:text-[#dbe7de]">{{ item.count }}</span>
|
||||||
class="inline-flex items-center gap-1.5 rounded-full border border-fuchsia-200 bg-fuchsia-50 px-2.5 py-1 text-[11px] font-semibold leading-none text-fuchsia-700 dark:border-fuchsia-900/60 dark:bg-fuchsia-950/40 dark:text-fuchsia-300"
|
|
||||||
>
|
|
||||||
<SvgIcon icon="ri:logout-circle-r-line" />
|
|
||||||
<span>{{ outputUsageLabel }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
2
scripts/clean_faiss_and_kv.sh
Executable file
2
scripts/clean_faiss_and_kv.sh
Executable file
@@ -0,0 +1,2 @@
|
|||||||
|
rm -rf ../faiss/indexes/global/global_qa.index
|
||||||
|
rm -rf ../kvstore/data/kvs_oplog.db
|
||||||
1
scripts/compiler.sh
Executable file
1
scripts/compiler.sh
Executable file
@@ -0,0 +1 @@
|
|||||||
|
cd third_party/kvstore/ && make
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
REDIS_HOST="${REDIS_HOST:-127.0.0.1}"
|
|
||||||
REDIS_PORT="${REDIS_PORT:-8888}"
|
|
||||||
REDIS_DB="${REDIS_DB:-0}"
|
|
||||||
REDIS_PASSWORD="${REDIS_PASSWORD:-}"
|
|
||||||
PATTERNS=("ai_chat_service_*")
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
cat <<'EOF'
|
|
||||||
Usage:
|
|
||||||
inspect_ai_redis.sh [options]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
--host <host> Redis host, default: 127.0.0.1
|
|
||||||
--port <port> Redis port, default: 8888
|
|
||||||
--db <db> Redis db index, default: 0
|
|
||||||
--password <pwd> Redis password
|
|
||||||
--pattern <pattern> Key pattern, can be repeated. Default: ai_chat_service_*
|
|
||||||
--help Show this help
|
|
||||||
|
|
||||||
Environment:
|
|
||||||
REDIS_HOST
|
|
||||||
REDIS_PORT
|
|
||||||
REDIS_DB
|
|
||||||
REDIS_PASSWORD
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
./scripts/inspect_ai_redis.sh
|
|
||||||
./scripts/inspect_ai_redis.sh --pattern 'ai_chat_service_*' --pattern 'foo_*'
|
|
||||||
REDIS_PORT=6379 ./scripts/inspect_ai_redis.sh --host 10.0.0.8
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case "$1" in
|
|
||||||
--host)
|
|
||||||
REDIS_HOST="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--port)
|
|
||||||
REDIS_PORT="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--db)
|
|
||||||
REDIS_DB="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--password)
|
|
||||||
REDIS_PASSWORD="$2"
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--pattern)
|
|
||||||
if [[ "${PATTERNS[*]}" == "ai_chat_service_*" && "${#PATTERNS[@]}" -eq 1 ]]; then
|
|
||||||
PATTERNS=()
|
|
||||||
fi
|
|
||||||
PATTERNS+=("$2")
|
|
||||||
shift 2
|
|
||||||
;;
|
|
||||||
--help|-h)
|
|
||||||
usage
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unknown argument: $1" >&2
|
|
||||||
usage >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
if ! command -v redis-cli >/dev/null 2>&1; then
|
|
||||||
echo "redis-cli not found in PATH" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
redis_cmd() {
|
|
||||||
if [[ -n "$REDIS_PASSWORD" ]]; then
|
|
||||||
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -n "$REDIS_DB" -a "$REDIS_PASSWORD" --raw "$@"
|
|
||||||
else
|
|
||||||
redis-cli -h "$REDIS_HOST" -p "$REDIS_PORT" -n "$REDIS_DB" --raw "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
print_string() {
|
|
||||||
local key="$1"
|
|
||||||
redis_cmd GET "$key"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_hash() {
|
|
||||||
local key="$1"
|
|
||||||
redis_cmd HGETALL "$key" | awk 'NR % 2 == 1 { printf "%s: ", $0; next } { print $0 }'
|
|
||||||
}
|
|
||||||
|
|
||||||
print_list() {
|
|
||||||
local key="$1"
|
|
||||||
redis_cmd LRANGE "$key" 0 -1
|
|
||||||
}
|
|
||||||
|
|
||||||
print_set() {
|
|
||||||
local key="$1"
|
|
||||||
redis_cmd SMEMBERS "$key"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_zset() {
|
|
||||||
local key="$1"
|
|
||||||
redis_cmd ZRANGE "$key" 0 -1 WITHSCORES | awk 'NR % 2 == 1 { printf "%s => ", $0; next } { print $0 }'
|
|
||||||
}
|
|
||||||
|
|
||||||
print_stream() {
|
|
||||||
local key="$1"
|
|
||||||
redis_cmd XRANGE "$key" - +
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! redis_cmd PING >/dev/null 2>&1; then
|
|
||||||
echo "Failed to connect to Redis at ${REDIS_HOST}:${REDIS_PORT}, db=${REDIS_DB}" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
declare -A seen_keys=()
|
|
||||||
keys=()
|
|
||||||
|
|
||||||
for pattern in "${PATTERNS[@]}"; do
|
|
||||||
while IFS= read -r key; do
|
|
||||||
[[ -z "$key" ]] && continue
|
|
||||||
if [[ -z "${seen_keys[$key]:-}" ]]; then
|
|
||||||
seen_keys["$key"]=1
|
|
||||||
keys+=("$key")
|
|
||||||
fi
|
|
||||||
done < <(redis_cmd --scan --pattern "$pattern")
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ "${#keys[@]}" -eq 0 ]]; then
|
|
||||||
echo "No keys matched patterns: ${PATTERNS[*]}"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
printf 'Redis: %s:%s db=%s\n' "$REDIS_HOST" "$REDIS_PORT" "$REDIS_DB"
|
|
||||||
printf 'Patterns: %s\n' "${PATTERNS[*]}"
|
|
||||||
printf 'Matched keys: %s\n\n' "${#keys[@]}"
|
|
||||||
|
|
||||||
for key in "${keys[@]}"; do
|
|
||||||
key_type="$(redis_cmd TYPE "$key" | tr -d '\r')"
|
|
||||||
ttl="$(redis_cmd TTL "$key" | tr -d '\r')"
|
|
||||||
|
|
||||||
echo "KEY: $key"
|
|
||||||
echo "TYPE: $key_type"
|
|
||||||
echo "TTL: $ttl"
|
|
||||||
echo "VALUE:"
|
|
||||||
|
|
||||||
case "$key_type" in
|
|
||||||
string)
|
|
||||||
print_string "$key"
|
|
||||||
;;
|
|
||||||
hash)
|
|
||||||
print_hash "$key"
|
|
||||||
;;
|
|
||||||
list)
|
|
||||||
print_list "$key"
|
|
||||||
;;
|
|
||||||
set)
|
|
||||||
print_set "$key"
|
|
||||||
;;
|
|
||||||
zset)
|
|
||||||
print_zset "$key"
|
|
||||||
;;
|
|
||||||
stream)
|
|
||||||
print_stream "$key"
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "(unsupported type: $key_type)"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
echo
|
|
||||||
done
|
|
||||||
4
scripts/run_kv.sh
Executable file
4
scripts/run_kv.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
cd third_party/kvstore/
|
||||||
|
stdbuf -oL -eL nohup ./kvstore > /tmp/kvlog 2>&1 &
|
||||||
|
echo $! > /tmp/kvstore.pid
|
||||||
|
cat /tmp/kvstore.pid
|
||||||
1
third_party/kvstore
vendored
Submodule
1
third_party/kvstore
vendored
Submodule
Submodule third_party/kvstore added at 2f59d3ce27
Reference in New Issue
Block a user