目录

Story Agent

离线原创故事生成 Agent。后端使用 FastAPI + LangGraph 编排并行本地工具,工具从 data/story_index 检索故事结构、风格片段、场景卡、人物卡、冲突卡、剧情节拍、对话模式、感官细节、结尾模式和安全边界,再把结构化结果交给本地 llama-server 首轮生成完整故事。前端使用 React + Vite + TypeScript,保留工具轨迹、节点轨迹、LLM prompt 和吞吐指标面板。

当前能力

  • 生成原创单篇完整长故事,不输出标题。
  • 默认要求首轮完整生成,不做隐藏续写。
  • 默认 12-15 个自然段,每段约 300-400 个汉字。
  • 支持抽象风格标签,例如悬疑、克制、赛博朋克、温暖现实主义、古风悬疑、诗性科幻。
  • 不模仿具体作者,不照搬或续写受版权保护作品。
  • 支持 qwen2.5-7b 和 qwen3-8b;推荐 8K 或 16K context。

启动

以下命令均从 Story-agent 仓库根目录执行。默认 llama.cpp 与本项目位于同一上级目录,模型位于上两级目录下的 models 目录;如果你的目录布局不同,请相应调整相对路径。

终端 1 启动 llama-server,以 qwen2.5-7b 为例:

cd ../llama.cpp

./build-cann/bin/llama-server \
  -m ../../models/qwen2.5/7b/qwen2.5-7b-instruct-q4_0-00001-of-00002.gguf \
  -ngl 99 \
  -np 4 \
  --device CANN0,CANN1 \
  -sm layer \
  -ts 1,1 \
  -t 8 \
  -c 131072 \
  --predict 6144 \
  --port 8080 \
  --host 0.0.0.0 \
  --alias qwen \
  --temp 0.55 \
  --repeat-penalty 1.12 \
  --reasoning-budget 0 \
  --cache-ram 0

qwen3-8b 或更大 Qwen 模型按实际模型路径替换 -m,其他参数保持一致。注意:-np 4 会把总上下文分给 4 个 slots;-c 131072 对应每个 slot 约 32768 tokens。若改成 -c 8192 -np 4,每个请求实际只有约 2048 tokens,故事 prompt 会超出上下文。

终端 2 启动后端:

cd Story-agent

PYTHONPATH=backend \
STORY_DATA_DIR=./data/story_index \
STORY_USE_LLM=true \
STORY_MAX_CONCURRENT_LLM=4 \
LLAMA_SERVER_URL=http://127.0.0.1:8080/v1 \
LLAMA_MODEL=qwen \
LLAMA_REPEAT_PENALTY=1.12 \
LLAMA_FIRST_TOKEN_TIMEOUT_SECONDS=0 \
LLAMA_TIMEOUT_SECONDS=3600 \
LLAMA_MAX_TOKENS=6144 \
LLAMA_CONTEXT_TOKENS=32768 \
STORY_PROMPT_MATERIAL_CHARS=1400 \
.venv/bin/python -m uvicorn app.main:app --host 0.0.0.0 --port 8000

终端 3 启动前端:

cd Story-agent/frontend
npm install
npm run build
npm run preview -- --host 0.0.0.0 --port 5173

这里使用 preview 而不是 dev,避免 Vite 开发服务器创建大量文件监听器。如果终端三出现 ENOSPC: System limit for number of file watchers reached,通常不是前端代码错误,而是系统 watcher 数量限制触顶。

访问:

http://127.0.0.1:5173/

验证

curl http://127.0.0.1:8000/health

curl -N -X POST http://127.0.0.1:8000/api/chat/stream \
  -H 'Content-Type: application/json' \
  -d '{"message":"写一个悬疑现实主义单篇完整长故事。背景是南方雨夜的旧车站,主角是一名刚离职的系统工程师,他在候车室发现一台仍在运行的旧服务器。风格克制、有压迫感,包含人物心理、环境细节、对话和反转结尾。"}'

20 并发 16K Prefill 压测

长 Prompt 压测使用 scripts/benchmark_long_prompt_concurrency.py。脚本会让后端在应用 llama-server 聊天模板后,通过 /tokenize 将最终 Prompt 精确校准到指定 token 数, 并允许独立控制 decode 长度。

16K Prompt 无法使用普通启动参数中的 -np 4 -c 32768,因为每个 slot 只有约 8192 tokens。压测时把 llama-server 启动命令中的上下文参数改为:

-np 4 \
-c 131072 \
--predict 6144

此时每个 slot 约有 32768 tokens。后端启动参数相应改为:

STORY_MAX_CONCURRENT_LLM=4 \
LLAMA_MAX_TOKENS=6144 \
LLAMA_CONTEXT_TOKENS=32768

启动 llama-server 和后端后,运行 20 并发、16K Prompt、3072-token decode:

PYTHONPATH=backend .venv/bin/python scripts/benchmark_long_prompt_concurrency.py \
  -c 20 \
  --target-llama-prompt-tokens 16384 \
  --max-tokens 3072 \
  --output logs/prefill-16k-decode-3k-c20.json

参数简要说明:

  • -c 20:同时发起 20 个 HTTP 请求。
  • --target-llama-prompt-tokens 16384:Agent 传给 llama-server 的最终 Prompt 精确为 16384 tokens。
  • --max-tokens:控制每个请求的最大 decode 长度,范围为 1-6144。
  • --ignore-eos:可选;忽略模型 EOS,使性能测试尽量 decode 到 --max-tokens
  • --output:保存每个请求和汇总后的 TTFT、prefill、decode、吞吐及错误信息。

例如要求每个请求尽量完成 6144-token decode:

PYTHONPATH=backend .venv/bin/python scripts/benchmark_long_prompt_concurrency.py \
  -c 20 \
  --target-llama-prompt-tokens 16384 \
  --max-tokens 6144 \
  --ignore-eos \
  --output logs/prefill-16k-decode-6k-c20.json

后端会校验 Prompt tokens + max_tokens <= LLAMA_CONTEXT_TOKENS。当前配置是 20 个客户端并发请求、4 个模型 slot 同时推理,其余请求在后端排队;这与 STORY_MAX_CONCURRENT_LLM=4 和 llama-server 的 -np 4 保持一致。该命令不依赖 前端,运行压测时只需启动 llama-server 和 FastAPI 后端。

数据库

入口:

data/story_index/manifest.json

主要文件:

  • story_structures.json
  • style_fragments.json
  • setting_cards.json
  • character_cards.json
  • conflict_cards.json
  • plot_beats.json
  • dialogue_patterns.json
  • sensory_details.json
  • ending_patterns.json
  • safety_rules.json
邀请码
    Gitlink(确实开源)
  • 加入我们
  • 官网邮箱:gitlink@ccf.org.cn
  • QQ群
  • QQ群
  • 公众号
  • 公众号

版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9 京公网安备 11010802047560号