首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >用Python做了个Prompt管理工具,好用的提示词再也不用到处找了

用Python做了个Prompt管理工具,好用的提示词再也不用到处找了

作者头像
用户11081884
发布2026-07-22 18:03:52
发布2026-07-22 18:03:52
1130
举报

前几天我想用一个之前调了很久的prompt,让AI帮我写SQL查询。我记得当时调了快一个小时,效果特别好,准确率很高。

但我存在哪了?

先翻了ChatGPT的聊天记录,翻了二十分钟没找到。又翻了飞书文档,有个文档叫“prompt备份_final_v2”,打开一看是空的。再翻备忘录,有一条记着“SQL prompt见电脑桌面txt”,桌面上倒是有个txt,但内容只有半截,后面被截断了。

找了快一个小时,最后在一个叫“新建文本文档 (3).txt”的文件里找到了完整版。你说气不气。

这件事让我意识到,prompt这东西需要好好管一下。它不像代码有Git管理,也不像文档有Notion。好的prompt散落在聊天记录、便签、文档、txt文件里,过几天就找不着了。

当天晚上我就写了个Prompt管理工具。SQLite存数据,Jinja2做模板渲染,命令行操作。功能不复杂,但够用:增删改查、版本管理、变量替换、效果评分。

完整代码

代码语言:javascript
复制
# pip install jinja2 rich

import sqlite3
import json
from datetime import datetime
from jinja2 import Template
from rich.console import Console
from rich.table import Table

console = Console()

DB_PATH = "prompts.db"


# ============================================================
# 数据库初始化
# ============================================================

def init_db():
    """建表,如果不存在的话"""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()

    # prompt主表
    c.execute("""
        CREATE TABLE IF NOT EXISTS prompts (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT UNIQUE NOT NULL,
            category TEXT DEFAULT 'general',
            description TEXT,
            created_at TEXT,
            updated_at TEXT
        )
    """)

    # 版本表,一个prompt可以有多个版本
    c.execute("""
        CREATE TABLE IF NOT EXISTS versions (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            prompt_id INTEGER,
            version INTEGER,
            template TEXT NOT NULL,
            variables TEXT,
            score REAL DEFAULT 0,
            notes TEXT,
            created_at TEXT,
            FOREIGN KEY (prompt_id) REFERENCES prompts(id)
        )
    """)

    conn.commit()
    conn.close()


# ===================================================
# CRUD 操作
# ===================================================

def add_prompt(name, template, category="general", description="", variables=None):
    """添加一个新的prompt"""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    now = datetime.now().isoformat()

    try:
        c.execute(
            "INSERT INTO prompts (name, category, description, created_at, updated_at) VALUES (?, ?, ?, ?, ?)",
            (name, category, description, now, now)
        )
        prompt_id = c.lastrowid

        # 自动创建第一个版本
        c.execute(
            "INSERT INTO versions (prompt_id, version, template, variables, created_at) VALUES (?, ?, ?, ?, ?)",
            (prompt_id, 1, template, json.dumps(variablesor []), now)
        )

        conn.commit()
        console.print(f"[green]添加成功: {name} (版本1)[/green]")
        return prompt_id

    except sqlite3.IntegrityError:
        console.print(f"[red]已存在同名prompt: {name}[/red]")
        return None
    finally:
        conn.close()


def update_prompt(name, template, notes=""):
    """更新prompt,自动创建新版本"""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()
    now = datetime.now().isoformat()

    # 找到这个prompt
    c.execute("SELECT id FROM prompts WHERE name = ?", (name,))
    row = c.fetchone()
    if not row:
        console.print(f"[red]找不到prompt: {name}[/red]")
        conn.close()
        return

    prompt_id = row[0]

    # 查当前最新版本号
    c.execute("SELECT MAX(version) FROM versions WHERE prompt_id = ?", (prompt_id,))
    max_version = c.fetchone()[0] or0
    new_version = max_version+1

    # 提取模板里的变量名
    variables = Template(template).environment.parse(template).find_all(lambdan: hasattr(n, 'name'))
    var_names = list(set(
        node.namefornodeinTemplate(template).environment.parse(template).find_all(lambdan: hasattr(n, 'name'))
        ifhasattr(node, 'name')
    ))

    # 简单方式:用正则找变量
    import re
    var_names = re.findall(r'\{\{\s*(\w+)\s*\}\}', template)

    c.execute(
        "INSERT INTO versions (prompt_id, version, template, variables, notes, created_at) VALUES (?, ?, ?, ?, ?, ?)",
        (prompt_id, new_version, template, json.dumps(var_names), notes, now)
    )

    # 更新时间
    c.execute("UPDATE prompts SET updated_at = ? WHERE id = ?", (now, prompt_id))

    conn.commit()
    conn.close()
    console.print(f"[green]已更新: {name} (版本{new_version})[/green]")


def delete_prompt(name):
    """删除prompt和它的所有版本"""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()

    c.execute("SELECT id FROM prompts WHERE name = ?", (name,))
    row = c.fetchone()
    if not row:
        console.print(f"[red]找不到prompt: {name}[/red]")
        conn.close()
        return

    prompt_id = row[0]
    c.execute("DELETE FROM versions WHERE prompt_id = ?", (prompt_id,))
    c.execute("DELETE FROM prompts WHERE id = ?", (prompt_id,))

    conn.commit()
    conn.close()
    console.print(f"[yellow]已删除: {name}[/yellow]")


def list_prompts(category=None):
    """列出所有prompt"""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()

    if category:
        c.execute("SELECT * FROM prompts WHERE category = ? ORDER BY updated_at DESC", (category,))
    else:
        c.execute("SELECT * FROM prompts ORDER BY updated_at DESC")

    rows = c.fetchall()
    conn.close()

    if not rows:
        console.print("[dim]还没有保存任何prompt[/dim]")
        return

    table = Table(title="已保存的Prompt")
    table.add_column("名称", style="cyan")
    table.add_column("分类", style="green")
    table.add_column("说明")
    table.add_column("更新时间")

    for row in rows:
        _, name, cat, desc, created, updated = row
        table.add_row(name, cat, descor"", updated[:16])

    console.print(table)


# ===================================================
# 模板渲染
# ===================================================

def render_prompt(name, version=None, **kwargs):
    """渲染prompt模板,替换变量"""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()

    # 找到prompt
    c.execute("SELECT id FROM prompts WHERE name = ?", (name,))
    row = c.fetchone()
    if not row:
        conn.close()
        returnNone

    prompt_id = row[0]

    # 拿指定版本,或者最新的
    if version:
        c.execute(
            "SELECT template FROM versions WHERE prompt_id = ? AND version = ?",
            (prompt_id, version)
        )
    else:
        c.execute(
            "SELECT template FROM versions WHERE prompt_id = ? ORDER BY version DESC LIMIT 1",
            (prompt_id,)
        )

    row = c.fetchone()
    conn.close()

    if not row:
        console.print(f"[red]找不到对应版本[/red]")
        returnNone

    template_str = row[0]

    # 用Jinja2渲染
    tmpl = Template(template_str)
    rendered = tmpl.render(**kwargs)

    return rendered


# ===================================================
# 评分功能
# ===================================================

def rate_prompt(name, version, score, notes=""):
    """给某个版本打分"""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()

    c.execute("SELECT id FROM prompts WHERE name = ?", (name,))
    row = c.fetchone()
    if not row:
        conn.close()
        return

    prompt_id = row[0]

    c.execute(
        "UPDATE versions SET score = ?, notes = ? WHERE prompt_id = ? AND version = ?",
        (score, notes, prompt_id, version)
    )

    conn.commit()
    conn.close()
    console.print(f"[green]已打分: {name} v{version} = {score}分[/green]")


def show_versions(name):
    """显示一个prompt的所有版本"""
    conn = sqlite3.connect(DB_PATH)
    c = conn.cursor()

    c.execute("SELECT id FROM prompts WHERE name = ?", (name,))
    row = c.fetchone()
    if not row:
        conn.close()
        return

    prompt_id = row[0]

    c.execute(
        "SELECT version, template, variables, score, notes, created_at FROM versions WHERE prompt_id = ? ORDER BY version DESC",
        (prompt_id,)
    )
    rows = c.fetchall()
    conn.close()

    table = Table(title=f"{name} 版本历史")
    table.add_column("版本", style="cyan")
    table.add_column("得分", style="green")
    table.add_column("备注")
    table.add_column("模板预览")
    table.add_column("创建时间")

    for row in rows:
        ver, tmpl, vars_, score, notes, created = row
        preview = tmpl[:60] +"..."if len(tmpl) >60 else tmpl
        score_str = str(score) ifscoreelse"-"
        table.add_row(f"v{ver}", score_str, notesor"", preview, created[:16])

    console.print(table)


# ===================================================
# 交互式命令行
# ===================================================

def interactive_mode():
    """交互式CLI,可以输入命令操作"""

    init_db()

    console.print("\n[bold cyan]Prompt管理工具[/bold cyan]")
    console.print("命令: add | list | update | delete | render | versions | rate | quit\n")

    while True:
        cmd = console.input("[bold]>>>[/bold] ").strip()

        ifcmd == "quit"orcmd == "q":
            console.print("拜拜。")
            break

        elif cmd == "list":
            cat = console.input("分类筛选(回车跳过): ").strip() orNone
            list_prompts(cat)

        elif cmd == "add":
            name = console.input("名称: ").strip()
            category = console.input("分类: ").strip() or"general"
            description = console.input("说明: ").strip()
            console.print("模板(输入END结束):")
            lines = []
            whileTrue:
                line = input()
                if line.strip() == "END":
                    break
                lines.append(line)
            template = "\n".join(lines)
            add_prompt(name, template, category, description)

        elif cmd == "render":
            name = console.input("Prompt名称: ").strip()
            ver = console.input("版本号(回车用最新): ").strip()
            ver = int(ver) ifverelseNone

            # 看看需要什么变量
            result = render_prompt(name, ver)
            if result:
                console.print("\n[bold]渲染结果:[/bold]")
                console.print(result)

        elif cmd == "versions":
            name = console.input("Prompt名称: ").strip()
            show_versions(name)

        elif cmd == "rate":
            name = console.input("Prompt名称: ").strip()
            ver = int(console.input("版本号: ").strip())
            score = float(console.input("得分(1-10): ").strip())
            notes = console.input("备注: ").strip()
            rate_prompt(name, ver, score, notes)

        elif cmd == "delete":
            name = console.input("要删除的名称: ").strip()
            confirm = console.input(f"确认删除 {name}?(y/n): ").strip()
            if confirm == "y":
                delete_prompt(name)

        else:
            console.print("[dim]不认识的命令,试试: add list update delete render versions rate[/dim]")


if __name__ == "__main__":
    interactive_mode()

运行效果

我先添加了几个常用的prompt,然后试了一下渲染功能。

代码语言:javascript
复制
Prompt管理工具
命令: add | list | update | delete | render | versions | rate | quit

>>> add
名称: sql_query
分类: coding
说明: 让AI根据表结构写SQL查询
模板(输入END结束):
你是一个SQL专家。根据以下表结构写SQL查询。

表名: {{table_name}}
字段: {{columns}}

要求:
- 只写SQL,不要解释
- 考虑性能,用合适的索引建议
- 如果涉及日期,用 {{date_format}} 格式

用户问题: {{question}}
END
添加成功: sql_query (版本1)

>>> list
┌──────────────────────────────────────────────────────────────┐
│                     已保存的Prompt                            │
├──────────┬────────┬──────────────────────┬──────────────────┤
│ 名称     │ 分类   │ 说明                 │ 更新时间         │
├──────────┼────────┼──────────────────────┼──────────────────┤
│ sql_query│ coding │ 让AI根据表结构写SQL  │ 2024-12-15T14:30 │
│ code_rev │ coding │ 代码审查prompt       │ 2024-12-14T09:15 │
│ email    │ work   │ 写工作邮件           │ 2024-12-13T16:20 │
└──────────┴────────┴──────────────────────┴──────────────────┘

>>> render
Prompt名称: sql_query
版本号(回车用最新):

渲染结果:
你是一个SQL专家。根据以下表结构写SQL查询。

表名: orders
字段: id, user_id, amount, status, created_at

要求:
- 只写SQL,不要解释
- 考虑性能,用合适的索引建议
- 如果涉及日期,用 %Y-%m-%d 格式

用户问题: 上个月消费超过1000元的用户有哪些

渲染功能用Jinja2实现的,{{变量名}}会被替换成你传入的值。这个很方便,同一个prompt模板,传不同的参数就能用在不同场景。

几个设计想法

为什么要存SQLite。 一开始我想用JSON文件存,但考虑到以后prompt会越来越多,查询、排序、筛选用数据库方便。SQLite不用装额外服务,一个文件搞定,对这个场景刚好够用。

版本管理很重要。 一个prompt经常要改好几次才能调到最佳效果。每次改的时候自动创建一个新版本,旧版本不丢。万一改坏了还能回退。每个版本还可以打分,时间长了你就知道哪个版本效果最好。

Jinja2比format好用。 Python自带的str.format()也能做变量替换,但Jinja2支持条件判断、循环、过滤器这些高级功能。比如你可以在模板里写{% if include_examples %}这里是示例...{% endif %},按需包含内容。

rich库让命令行好看很多。 用rich的Table和Console,命令行输出的表格是彩色的,对齐也整齐。比print一堆纯文本看着舒服多了。

现在怎么用

我把这个工具放在电脑上常驻了一个快捷方式。每次调到一个好用的prompt,直接add进去。用的时候render一下,复制粘贴。

两个月下来,存了40多个prompt。代码审查的、写周报的、生成测试用例的、翻译技术文档的,各种类型都有。每次不用再从头调了,省了不少时间。

同事看我用得顺手,让我也给他装了一份。现在我们俩共享一个prompts.db文件,谁调出了好用的prompt就存进去,另一个人直接拿来用。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Nicholas与Pypi 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 完整代码
  • 运行效果
  • 几个设计想法
  • 现在怎么用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档