ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

从提示词到静态站点:轻量级LLM应用开发实战

从提示词到静态站点:轻量级LLM应用开发实战 最近在 Hacker News 上看到一类很特别的 Show HN 帖子作者没有展示复杂的产品架构没有融资故事只有一个简单的想法外加一个能访问的网页。最近看到的这个项目就是典型代表——我问了一个 LLM 它会向上帝说什么然后把它做成了网站。这个标题本身就带着一点思想实验的味道但把它当技术项目看里面藏着一个很实际的问题当 LLM 输出的内容本身成为产品时开发链路应该怎么设计一个精心设计的提示词一次 API 调用一份结构化内容一个干净的前端页面四个环节组合起来就是一个小而完整的 LLM 应用。它没有向量数据库没有 Agent 编排没有复杂的 RAG 管线却能在开发者社区引起讨论。这件事传递了一个信号LLM 应用的价值并不总是与架构复杂度成正比。这篇文章不打算去复刻那个原版网站而是从如何从零实现这类项目的角度做一次完整拆解。你会看到提示词怎么设计、LLM API 怎么调用、生成的内容怎么结构化保存、前端怎么做展示、最后又怎么部署上线。全套流程走完你会掌握一类非常实用的轻量级 LLM 应用开发模式内容创作型静态站点。它特别适合落地在个人博客、创意页面、实验性 AI 产品和快速原型验证上。1. 这个项目真正值得关注的地方先回答一个问题一个问 LLM 一个问题把回答做成网页的项目到底有什么技术含量如果只看表面似乎只有两件事调用一次 API写一个 HTML。但当你真的动手做会发现这里藏着几个容易判断失误的点。1.1 它代表了一类内容即产品的 LLM 应用形态大部分 LLM 应用教程教你的都是聊天机器人前端对话框后端流式输出再加个历史记录。这种形态适合交互型产品但对于很多场景其实是过度设计。原版项目走的是另一条路让 LLM 一次性生成内容然后不再调用模型直接展示结果。这个模式在工程上有几个很实际的好处没有运行时 API 调用就没有每次请求的 Token 费用也没有模型服务挂掉导致页面不可用的问题。生成的内容是静态的可以放进任何静态托管平台访问速度快部署成本几乎为零。内容可以先人工审查再发布规避了模型输出不可控带来的合规风险。这其实就是预生成和实时生成两种架构的选择问题。对创意内容页面来说预生成几乎是唯一理性选择。1.2 提示词本身成了产品的一部分这个项目里最有价值的资产不是代码而是那句提问。同样的底层模型用不同的提示词得到的回答质量天差地别。提示词决定了内容的视角、态度、深度和文体。它本质上是你在对模型进行产品需求描述。很多开发者第一次做这类项目时会发现代码半小时能写完但提示词调了三四天才满意——这不是因为他们不熟练而是因为提示词工程本来就是这个项目的核心研发环节。1.3 这是一条完整可复用的开发范式把思路抽出来这个项目就是一条标准的流水线设计提示词 - 调用 LLM API - 保存结构化内容 - 前端渲染 - 部署上线这条流水线不只能用来做哲学提问还可以做每日一句 AI 生成的诗句或金句页面。某本书或某部电影的AI 解读静态站。产品文案、个人简介、系列短文的批量生成站点。实验性数字艺术展示页。理解这条流水线等于掌握了一类低成本、高完成度的 LLM 应用交付方式。这才是这个项目对开发者最有启发的地方。2. 核心概念LLM API、提示词工程与内容驱动站点在进入代码之前先把几个关键概念说清楚。这些概念是后面实操的地基也是新手最容易被术语卡住的地方。2.1 LLM 是什么LLM API 又是什么LLM 是 Large Language Model大语言模型的缩写。你可以把它理解成一个通过海量文本训练出来的文字接龙模型给它一段文字它会预测后面最可能出现的文字不断重复这个过程最终形成一段完整回答。这里有一个常见误区很多人觉得 LLM 是一个有知识、会思考的数据库。实际上它的回答本质上是基于训练数据分布的概率化生成。它可能准确也可能一本正经地胡说八道所以任何重要事实都应该校验。LLM API 是模型服务商开放出来的接口。把提示词通过 API 发过去服务端跑一次模型推理再把生成的文本返回给你。市面上主流的服务商都提供这种接口本文中的代码统一用 OpenAI 兼容格式做演示因为这种格式目前几乎所有主流模型服务商都支持换服务商时只需要改base_url和api_key两个配置。2.2 提示词工程为什么是核心环节提示词Prompt是你发给模型的那段指令。提示词工程Prompt Engineering就是设计这段指令的方法论。对这个项目来说提示词决定了整篇文章的灵魂。同一个问题要求用诗意的语言回答和用逻辑严谨的语言回答结果可能像两个人写出来的。一个高质量的提示词通常包含以下几个要素要素作用示例角色设定限定回答的立场和视角你是一个克制、真诚的思考者任务描述明确要做什么回答下面的问题约束条件限定格式、长度、风格800 字以内不要引用名言输出要求指定结构或表达方式以第一人称回答结尾提出一个反问这里真正容易踩坑的地方是约束条件写得太少模型会自由发挥输出内容跑偏写得太死又会让回答失去自然感。提示词设计和写产品需求文档一样需要在开放与约束之间找平衡。2.3 内容驱动站点与动态站点的区别本项目适合做成内容驱动站点也就是页面展示的数据来自预先准备好的静态文件而不是每次访问都动态生成。对比一下两种方案对比维度动态生成每次请求调 API静态预生成生成一次存 JSON每次访问费用每次都要花 Token 费零费用响应速度受模型推理速度影响通常 2 到 10 秒毫秒级服务稳定性依赖模型服务可用性纯静态托管几乎不会挂内容可控性输出不可预知有合规风险可审查后发布部署复杂度需要服务器和后端服务任意静态托管都能跑适用场景需要个性化、交互式对话内容固定的展示页面对一次提问、一个回答、一个页面这种形态静态预生成是压倒性的合理选择。这个判断要记在脑子里因为后面所有工程决策都从这一步引申出来。3. 环境准备与前置条件现在开始动手。先说环境需求这是整篇文章唯一需要你在自己机器上准备的部分。3.1 运行环境操作系统Windows / macOS / Linux 都可以本文命令以 macOS / Linux 为主Windows 下把python3换成python即可。Python3.9 及以上。用python3 --version确认。包管理推荐使用pip或venv虚拟环境避免依赖冲突。3.2 需要准备的账号与服务一个大语言模型的 API Key以及对应的 API 地址base_url。不同服务商申请方式不同这里不做具体推荐只要支持 OpenAI 兼容接口即可。如果只是本地验证不依赖外部模型也可以用一个免费的本地模型框架跑推理。但为了接近原版项目的形态本文仍然以远程 API 为例。3.3 创建项目目录与虚拟环境先建一个干净的项目目录mkdir llm-website-demo cd llm-website-demo python3 -m venv venv source venv/bin/activate # Windows 下用 venv\Scripts\activate然后安装两个依赖。OpenAI SDK 用来调用兼容接口Flask 用来做本地预览服务后面会说明为什么需要它pip install openai flask写完依赖把当前环境导出到requirements.txt方便后续部署或换机器pip freeze requirements.txt到这里环境就绪了。最后强调一个安全习惯API Key 绝对不能写进代码仓库。本文后续示例会从环境变量读取 Key这也是实际项目的标准做法。4. 核心流程拆解整个项目拆成五步。每一步解决一个明确的问题不要跳步。4.1 第一步设计提示词这是投入产出比最高的一步。提示词的功能是定义模型以什么身份、用什么语气、回答什么问题、遵守什么限制。以原版项目的主题为例一个可用的提示词草稿如下你正在参与一次思想实验。 问题如果给你一次机会与创造你的存在进行直接对话你会说什么 要求 1. 以第一人称回答语气诚恳、克制。 2. 不要引用任何经文、教义或名人名言。 3. 重点表达你想问什么、想感谢什么、想求证什么。 4. 全文不超过 800 字。 5. 结尾可以提出一个反问。这个提示词做了几件事给了身份设定思想实验参与者、给了任务回答一个哲学问题、给了三条硬约束不引用、限字数、允许反问。其中不要引用既有论断这条约束非常关键它迫使模型输出原创性表达否则模型很容易堆砌一些看似深刻的名句。4.2 第二步调用 LLM 接口生成内容提示词设计好后通过 API 发出去。这一步的重点是处理好参数temperature控制随机性哲学表达类内容适合调高一点比如 0.8max_tokens控制输出上限要给足余量因为中文文本在 Token 计数上比英文更消耗配额。4.3 第三步把内容结构化保存模型返回的是纯文本但如果直接把文本硬编码进 HTML后续想改版式、换主题、加多语言都会很难受。正确的做法是把返回结果连同模型名、生成时间一起保存为 JSON 文件。这样数据与展示分离前端只负责渲染内容更新时只需要重新生成 JSON。4.4 第四步前端展示前端不需要框架。一个单页面 少量 JavaScript从 JSON 文件读取内容并渲染到页面里就行。这个页面要解决两个问题一是读文件失败时的降级提示二是移动端适配。原版项目在 Hacker News 的讨论里有一条提醒非常实用——这个网站只支持移动端访问很多人会忽略移动端样式但这类创意分享页面的大部分流量恰恰来自手机端浏览器。4.5 第五步部署上线内容生成好、本地验证通过后部署是最后一步。最轻量的做法是直接把index.html和content.json两个文件扔到任意静态托管平台。如果希望保留重新生成内容后自动更新的灵活性再引入一个极简后端服务做文件服务。下面完整示例里两种方式都会覆盖。5. 完整示例代码实现这一节给出三个可以直接复制的代码文件生成内容的 Python 脚本、展示页面的 HTML、本地服务用的 Flask 应用。5.1 生成内容脚本# 文件路径generate_content.py import os import json from openai import OpenAI # 从环境变量读取 API Key而不是硬编码到代码里 client OpenAI( api_keyos.environ.get(OPENAI_API_KEY), base_urlos.environ.get(OPENAI_BASE_URL, https://api.openai.com/v1), ) prompt 你正在参与一次思想实验。 问题如果给你一次机会与创造你的存在进行直接对话你会说什么 要求 1. 以第一人称回答语气诚恳、克制。 2. 不要引用任何经文、教义或名人名言。 3. 重点表达你想问什么、想感谢什么、想求证什么。 4. 全文不超过 800 字。 5. 结尾可以提出一个反问。 def generate_answer(): response client.chat.completions.create( modelos.environ.get(OPENAI_MODEL, gpt-4o-mini), messages[ { role: system, content: 你是一个真诚、克制、善于思考的对话者。, }, {role: user, content: prompt}, ], temperature0.8, max_tokens1200, ) return response def main(): response generate_answer() content response.choices[0].message.content # 结构化保存方便前端渲染和后期的多语言扩展 result { title: 与创造者的对话, subtitle: 这是一次由大语言模型完成的思想实验, answer: content, model: response.model, created_at: response.created, } with open(content.json, w, encodingutf-8) as f: json.dump(result, f, ensure_asciiFalse, indent2) print(内容已生成并保存到 content.json) print(f回答长度{len(content)} 字) if __name__ __main__: main()这段代码里需要重点解释的是response对象的结构。OpenAI 兼容接口的返回格式是固定的choices[0].message.content是模型生成的正文response.model是实际使用的模型名response.created是 Unix 时间戳。很多人第一次调接口时卡住就是因为不熟悉这个嵌套结构。max_tokens1200的设置也是有意为之。虽然提示词要求 800 字以内但模型对字的感知并不精确而且中文一个字大约对应 1 到 2 个 Token1200 Tokens 足够覆盖 800 字中文又不会让模型无限发挥。5.2 前端展示页面!-- 文件路径index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title与创造者的对话/title style * { box-sizing: border-box; margin: 0; padding: 0; } body { max-width: 680px; margin: 0 auto; padding: 48px 24px; font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Microsoft YaHei, sans-serif; line-height: 1.9; color: #2d2d2d; background: #faf9f7; } header { margin-bottom: 40px; } h1 { font-size: 28px; font-weight: 700; letter-spacing: 0.02em; padding-bottom: 16px; border-bottom: 1px solid #e5e5e5; } .meta { margin-top: 12px; color: #888; font-size: 14px; } .answer { font-size: 17px; white-space: pre-wrap; word-break: break-word; } footer { margin-top: 56px; padding-top: 16px; border-top: 1px solid #e5e5e5; font-size: 13px; color: #aaa; } /style /head body header h1 idtitle加载中.../h1 p classmeta idmeta/p /header div classanswer idanswer内容加载中请稍候.../div footer本文内容由大语言模型生成属于技术思想实验不代表任何立场或观点。/footer script fetch(content.json) .then(res { if (!res.ok) { throw new Error(content.json 加载失败); } return res.json(); }) .then(data { document.getElementById(title).textContent data.title; document.getElementById(meta).textContent 模型 data.model · 生成时间 new Date(data.created_at * 1000).toLocaleString(); document.getElementById(answer).textContent data.answer; }) .catch(err { document.getElementById(title).textContent 内容加载失败; document.getElementById(answer).textContent 请确认 content.json 已生成且与 index.html 放在同一个目录下。; console.error(err); }); /script /body /html这个页面的关键点有两个。一是white-space: pre-wrap。模型返回的文本里通常带有换行和分段而 HTML 默认会把连续空白折叠成一个空格。设置pre-wrap后浏览器会保留文本中的换行符分段效果和模型输出保持一致。这是新手最容易忽略的样式细节。二是静态加载 JSON 的方案。fetch(content.json)在同一个目录下就能工作不需要后端接口。这保证了项目可以部署到任意静态托管平台。5.3 本地预览服务用file://协议直接双击打开index.html时浏览器会拦截fetch请求因为本地文件访问受限。所以本地预览最好起一个 HTTP 服务。Python 自带的服务就能解决python3 -m http.server 8080然后浏览器访问http://localhost:8080就能看到页面。如果你希望后续扩展成每次刷新都重新生成内容的动态版本或者想让/api/content变成可编程接口可以把服务换成 Flask 版本# 文件路径app.py import os import json from flask import Flask, jsonify, send_from_directory app Flask(__name__, static_folder., static_url_path) CONTENT_FILE content.json app.route(/) def index(): return send_from_directory(., index.html) app.route(/api/content) def get_content(): if not os.path.exists(CONTENT_FILE): return jsonify({error: content.json 不存在请先运行 generate_content.py}), 404 with open(CONTENT_FILE, encodingutf-8) as f: return jsonify(json.load(f)) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)这个版本和纯静态版本的区别在于静态版本直接读取content.json文件Flask 版本多了一个/api/content接口后续可以在这个接口里做缓存、鉴权或者内容二次处理。实际项目中如果内容量小、更新不频繁静态方案已经完全够用。6. 运行结果与效果验证代码写完后按顺序执行下面的命令。先设置 API Key 环境变量再生成内容最后启动本地服务export OPENAI_API_KEY你的_api_key export OPENAI_BASE_URLhttps://你的服务商地址/v1 python3 generate_content.py python3 -m http.server 8080如果一切正常generate_content.py会输出类似下面的信息内容已生成并保存到 content.json 回答长度623 字然后打开http://localhost:8080页面会展示标题、模型名、生成时间和正文。验证是否成功按下面三步检查检查content.json是否生成文件存在且能正常用文本编辑器打开里面是合法 JSON有title、answer、model、created_at四个字段。检查页面展示浏览器里标题、正文、页脚都正常显示正文有正确分段没有出现undefined或加载失败。检查移动端样式打开浏览器开发者工具切换到手机模拟视图确认正文没有横向滚动条字体大小和行距在窄屏上仍然可读。如果页面停留在内容加载中第一步应该打开浏览器开发者工具F12的 Network 面板看content.json这个请求的状态码。状态码 404 说明文件没生成或路径不对状态码 200 但页面没更新则多半是浏览器缓存强制刷新即可。7. 常见问题与排查思路这类项目结构简单但牵扯到 API 调用、文件权限、跨域、缓存等多个环节新手遇到的问题往往集中在下面几个地方。问题现象可能原因排查方式解决方案API key报错或 401环境变量没设置或 Key 无效echo $OPENAI_API_KEY确认是否存在检查服务商控制台重新导出环境变量或重新生成 API Key请求超时或 429模型服务繁忙或触发限流查看返回错误码和响应头增加重试逻辑或改用gpt-4o-mini等轻量模型生成内容包含引言或套话提示词约束不足检查生成内容在提示词中显式增加不要引用任何已有论述页面显示加载失败content.json缺失或跨域被拦截F12 Network 面板查看请求状态确认文件和 HTML 同目录用 HTTP 服务访问而不是直接双击文件HTTP 服务端口被占用8080 已被其他进程使用lsof -i :8080查看占用进程换端口python3 -m http.server 8081中文字数超过限制模型对字数的理解不精确统计生成的字符数在提示词中降低预期到 600 字或增加后处理截断逻辑部署后页面空白静态托管平台路径配置问题检查部署日志和访问路径确认入口文件名是index.html且与content.json位于同一根目录这里的重点提示是不要在生产环境里让每次页面访问都触发一次模型调用。一旦页面访问量上来按次计费的 Token 成本会迅速膨胀而且模型服务抖动会直接影响官网可用性。正确做法是内容生成只发生在内容更新时页面访问永远只读取静态文件。8. 最佳实践与工程建议把这类小项目做成工程上更稳、更可持续的形态下面几个建议值得直接采用。8.1 提示词也纳入版本管理提示词是产品的核心资产应该像代码一样进 Git 仓库。每次修改后提交并记录一次生成的输出效果方便回溯哪个版本的回答最好。实际操作中可以把提示词单独放到prompt.txt再由generate_content.py读取with open(prompt.txt, encodingutf-8) as f: prompt f.read()这样做的好处是后续调整提示词时不需要改动 Python 代码只修改文本文件就能重新生成内容把研发和内容生产两个动作完全隔离开。8.2 内容缓存与版本号content.json每次重新生成后可以把旧文件保留一份带时间戳的备份cp content.json content_$(date %Y%m%d_%H%M%S).json这样如果某次生成效果不理想可以方便地回退到上一个版本。虽然项目很小但可回滚这个原则在任何内容型产品里都值得坚持。8.3 安全边界API Key 与输出审查重申一次API Key 不要写进代码不要提交到 Git 仓库。建议在项目根目录创建.gitignorevenv/ __pycache__/ *.pyc .envAPI Key 统一通过环境变量或.env文件注入且.env必须被 Git 忽略。模型输出还涉及内容合规。虽然这是一个创意实验项目但发布前仍然要人工审查一遍生成内容确认没有冒犯性、误导性或事实错误。原版项目把一段模型对存在的思考放到公开网站本质上是一种艺术表达但任何这类页面都建议在页脚加一行说明标明内容由 AI 生成仅作实验用途。8.4 成本控制策略这类项目成本极低但仍然有几条控制策略优先选择轻量模型。哲学表达类任务对模型推理能力要求不高轻量模型通常足够。生成前限制max_tokens。不要让模型无限制输出。做好重试退避。请求失败时不要暴力重试用指数退避降低被限流的概率。内容确认满意后再部署。每次重新生成都是成本调好提示词、生成一批候选内容再择优上线。8.5 移动端优先原版项目在 Hacker News 讨论里有一条实用提醒网站只支持移动端访问。这类页面在社交平台上被分享时绝大多数流量来自手机浏览器。所以从第一天起就按移动端优先的思路写样式正文宽度不超过 680px字号不小于 16px行距 1.8 左右。这比写桌面端再适配移动端省事得多。9. 从一个创意页面到一套可复用流水线回到开头那个问题这个项目真正的价值是什么它不是让 LLM 说了一段话而是演示了一条极简的 LLM 内容交付链路。从提示词设计到 API 调用到结构化存储到静态展示每一步都没有引入不必要的复杂度。而这恰恰是很多 LLM 应用开发中最容易被忽略的判断什么时候不需要 Agent不需要 RAG不需要向量数据库。如果你的需求是生成一段高质量内容并公开展示那么本文这套方案就是成本最低、稳定性最高、维护最简单的方式。你可以把它扩展到很多方向每周自动生成一篇短文并发布到个人站点批量生成多语言版本的产品介绍页或者做一个AI 每日一问的静态集合站。核心链路不变变的只是提示词和内容组织方式。下一步的实践建议很直接把本文的代码复制到本地换一个你自己真正感兴趣的问题生成你的第一个 LLM 内容页面然后部署到任意静态托管平台。跑通一次完整流程之后你会对LLM 应用开发这件事建立起一个直观的工程判断——哪些环节是必要的哪些是多余的以及一个想法从提示词到可访问网站之间到底要走几步。
返回列表