多轮对话 gpt‘

不要再用 test_cases 一次性跑完,而是做一个"会话对象 session",把用户每一轮输入、当前 TCL、上次错误、执行日志都存起来。

现在代码是这种模式:

复制代码
复制代码
case1 独立生成
case2 独立生成
case3 独立生成

你要改成:

复制代码
复制代码
用户:创建长方体
系统:生成 script_v1,保存到 session.current_script

用户:改成高 60
系统:拿着 script_v1 + 历史需求,生成 script_v2

用户:再加保存名 test2
系统:拿着 script_v2 + 历史需求,生成 script_v3

你现在的 main() 里每轮都会 script = "",并且 test_cases 是固定列表,所以它不是对话式记忆。


核心思路

记忆不要指望 LLM 自己记,你自己在 Python 里保存

最简单结构:

复制代码
复制代码
from dataclasses import dataclass, field
from typing import List, Dict, Optional

@dataclass
class ChatTurn:
    role: str
    content: str

@dataclass
class AgentSession:
    session_id: str
    history: List[ChatTurn] = field(default_factory=list)
    current_script: str = ""
    current_output_name: str = "model"
    last_error: str = ""
    last_exec_log: str = ""

这个 AgentSession 就是"记忆"。

里面保存:

复制代码
复制代码
history              用户和助手摘要
current_script       当前最新版 TCL
current_output_name  当前文件名
last_error           上次校验/执行错误
last_exec_log        上次 hmbatch 日志

改法 1:新增一个对话式生成函数

不要用原来的 generate_tcl(llm, query) 单轮模式,新增这个:

复制代码
复制代码
def build_history_text(session: AgentSession, max_turns: int = 8) -> str:
    turns = session.history[-max_turns:]
    return "\n".join(
        f"{t.role}: {t.content}"
        for t in turns
    )


def generate_tcl_with_memory(llm, session: AgentSession, user_input: str) -> str:
    """
    多轮对话生成:
    - 记住历史用户需求
    - 记住当前最新版 TCL
    - 支持用户说"改一下""再加上""把高改成60"
    """

    print("🧠 正在根据当前对话生成 RAG 检索关键词...", flush=True)

    # 检索词不要只用当前 user_input,否则"改高一点"这种话检索不到
    search_query = f"""
当前用户新需求:
{user_input}

历史对话:
{build_history_text(session)}

当前已有 TCL:
{session.current_script}
"""

    rag_queries = generate_rag_queries(llm, search_query, max_queries=5)

    print("🔎 检索关键词:", flush=True)
    for q in rag_queries:
        print("   -", q, flush=True)

    rag_context = retrieve_for_tcl_generation_multi(rag_queries)

    prompt = f"""
你是一个 HyperMesh Tcl 多轮对话脚本生成器。

你需要根据用户的最新要求,在已有脚本基础上修改或重新生成完整 TCL。

【重要规则】
1. 用户可能会说"改一下""加上""不要这个""尺寸改成 xx",这时必须参考当前已有 TCL。
2. 每次都输出完整 TCL 脚本,不要只输出差异。
3. 不要输出解释。
4. 不要 Markdown。
5. 不要 JSON。
6. 不要 tool_call。
7. 不要 Python API。
8. 不要 Python 代码。
9. 如果用户是在修改已有模型,优先保留未被用户要求修改的部分。
10. 如果用户明确重新开始,则忽略当前已有 TCL。

【历史对话】
{build_history_text(session)}

【当前已有 TCL】
{session.current_script if session.current_script else "暂无"}

【上次错误】
{session.last_error if session.last_error else "无"}

【上次执行日志】
{session.last_exec_log if session.last_exec_log else "无"}

【用户最新输入】
{user_input}

【RAG 参考资料】
{rag_context}

【输出要求】
只输出完整 HyperMesh TCL 脚本。
"""

    result = llm.invoke([
        ("system", SYSTEM_PROMPT),
        ("user", prompt),
    ])

    return clean_tcl(result.content)

这个函数和你原来的 generate_tcl() 最大区别是:

它把 历史对话 + 当前脚本 + 上次错误 + 最新用户输入 一起塞给模型。


改法 2:新增一个带重试的对话处理函数

每次用户输入后,都走这个函数:

复制代码
复制代码
def handle_user_turn(
    llm,
    session: AgentSession,
    user_input: str,
    auto_run: bool = False,
    max_retries: int = 2,
) -> str:
    """
    处理用户一轮输入:
    1. 记录用户输入
    2. 基于历史和当前脚本生成新 TCL
    3. 校验
    4. 成功则更新 session.current_script
    5. 可选执行 hmbatch
    """

    session.history.append(ChatTurn("user", user_input))

    output_name = extract_output_name(user_input, session.current_output_name)
    session.current_output_name = output_name

    bad_output = ""
    last_error = ""

    for attempt in range(1, max_retries + 1):
        try:
            if attempt == 1:
                script = generate_tcl_with_memory(llm, session, user_input)
            else:
                script = force_rewrite_tcl(
                    llm=llm,
                    query=user_input,
                    bad_output=bad_output,
                    error=last_error,
                )

            script = validate_tcl(script, output_name)

            # 成功后更新"记忆"
            session.current_script = script
            session.last_error = ""

            session.history.append(ChatTurn(
                "assistant",
                f"已生成并校验通过 TCL,文件名:{output_name}"
            ))

            if auto_run:
                result = run_hypermesh_tcl.invoke({
                    "script": script,
                    "output_name": output_name,
                })
                session.last_exec_log = result

                session.history.append(ChatTurn(
                    "tool",
                    f"hmbatch 执行结果:{result[:1000]}"
                ))

                return f"✅ 已生成并执行。\n\n当前 TCL:\n{script}\n\n执行日志:\n{result}"

            return f"✅ 已生成并校验通过。\n\n当前 TCL:\n{script}"

        except Exception as e:
            last_error = str(e)
            session.last_error = last_error
            bad_output = locals().get("script", bad_output)

            print(f"⚠️ 第 {attempt} 次失败:{last_error}", flush=True)

    session.history.append(ChatTurn(
        "assistant",
        f"生成失败:{session.last_error}"
    ))

    return f"❌ 生成失败:\n{session.last_error}"

这里重点是:

复制代码
复制代码
session.current_script = script

这一步才是真正的"记忆更新"。


改法 3:把 main() 改成真正聊天循环

你现在是:

复制代码
复制代码
test_cases = [...]
for query in test_cases:
    script = ""
    ...

这会导致每个 case 断开。

改成:

复制代码
复制代码
def chat_main():
    llm = init_llm()

    session = AgentSession(session_id="local_cli")

    print("HyperMesh Tcl Agent 已启动。")
    print("输入 exit / quit 退出。")
    print("输入 run 执行当前 TCL。")
    print("输入 show 查看当前 TCL。")
    print("输入 reset 清空对话。")

    while True:
        user_input = input("\n用户> ").strip()

        if not user_input:
            continue

        if user_input.lower() in {"exit", "quit"}:
            print("已退出。")
            break

        if user_input.lower() == "reset":
            session = AgentSession(session_id="local_cli")
            print("✅ 已清空当前对话记忆。")
            continue

        if user_input.lower() == "show":
            print("\n当前 TCL:")
            print(session.current_script or "暂无")
            continue

        if user_input.lower() == "run":
            if not session.current_script:
                print("暂无 TCL 可执行。")
                continue

            result = run_hypermesh_tcl.invoke({
                "script": session.current_script,
                "output_name": session.current_output_name,
            })
            session.last_exec_log = result
            print(result)
            continue

        reply = handle_user_turn(
            llm=llm,
            session=session,
            user_input=user_input,
            auto_run=False,
            max_retries=2,
        )

        print("\n助手>")
        print(reply)


if __name__ == "__main__":
    chat_main()

这样用户就可以连续说:

复制代码
复制代码
用户> 创建一个 100 50 30 的长方体,文件名叫 mycube
助手> 生成 v1

用户> 高度改成 60
助手> 基于 v1 改成 v2

用户> 文件名改成 block2
助手> 基于 v2 改保存路径

用户> show
助手> 展示当前 TCL

用户> run
助手> 执行当前 TCL

改法 4:如果是 Web 服务,要按 session_id 存

如果你以后用 Gradio / Streamlit / FastAPI,不能只用一个全局 session,否则不同用户会串。

要这样:

复制代码
复制代码
SESSIONS: dict[str, AgentSession] = {}


def get_session(session_id: str) -> AgentSession:
    if session_id not in SESSIONS:
        SESSIONS[session_id] = AgentSession(session_id=session_id)
    return SESSIONS[session_id]

FastAPI 大概这样:

复制代码
复制代码
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()
llm = init_llm()
SESSIONS: dict[str, AgentSession] = {}


class ChatRequest(BaseModel):
    session_id: str
    message: str
    auto_run: bool = False


@app.post("/chat")
def chat(req: ChatRequest):
    session = get_session(req.session_id)

    reply = handle_user_turn(
        llm=llm,
        session=session,
        user_input=req.message,
        auto_run=req.auto_run,
    )

    return {
        "session_id": req.session_id,
        "reply": reply,
        "current_script": session.current_script,
        "output_name": session.current_output_name,
        "last_error": session.last_error,
        "last_exec_log": session.last_exec_log,
    }

这样:

复制代码
复制代码
用户 A → session_id=A → A 的历史
用户 B → session_id=B → B 的历史

不会互相串。


很关键:执行失败也要进入记忆

你现在的 run_hypermesh_tcl() 会返回 hmbatch 的返回码、stdout、stderr 和成功失败提示,但它不抛异常,只返回字符串。

所以如果你想让模型根据 hmbatch 错误继续修正,要加一个判断:

复制代码
复制代码
def run_and_remember(session: AgentSession) -> str:
    result = run_hypermesh_tcl.invoke({
        "script": session.current_script,
        "output_name": session.current_output_name,
    })

    session.last_exec_log = result
    session.history.append(ChatTurn("tool", result[:2000]))

    if "❌" in result or "返回码: 0" not in result:
        session.last_error = result
        return "❌ 执行失败,错误日志已保存到对话记忆。你可以继续说:根据错误修正。"

    session.last_error = ""
    return "✅ 执行成功。"

然后用户可以说:

复制代码
复制代码
根据刚才报错修正

模型就能看到:

复制代码
复制代码
【上次执行日志】
...
【上次错误】
...
【当前已有 TCL】
...

这才是完整闭环。


最小可用完整版本

你可以先直接加这几段:

复制代码
复制代码
from dataclasses import dataclass, field
from typing import List


@dataclass
class ChatTurn:
    role: str
    content: str


@dataclass
class AgentSession:
    session_id: str
    history: List[ChatTurn] = field(default_factory=list)
    current_script: str = ""
    current_output_name: str = "model"
    last_error: str = ""
    last_exec_log: str = ""


def build_history_text(session: AgentSession, max_turns: int = 8) -> str:
    turns = session.history[-max_turns:]
    return "\n".join(f"{t.role}: {t.content}" for t in turns)


def generate_tcl_with_memory(llm, session: AgentSession, user_input: str) -> str:
    search_query = f"""
用户最新输入:
{user_input}

历史对话:
{build_history_text(session)}

当前 TCL:
{session.current_script}
"""

    rag_queries = generate_rag_queries(llm, search_query, max_queries=5)
    rag_context = retrieve_for_tcl_generation_multi(rag_queries)

    prompt = f"""
你是 HyperMesh Tcl 多轮对话脚本生成器。

【任务】
根据用户最新输入,结合历史对话和当前已有 TCL,输出新的完整 TCL。

【历史对话】
{build_history_text(session)}

【当前已有 TCL】
{session.current_script if session.current_script else "暂无"}

【上次错误】
{session.last_error if session.last_error else "无"}

【上次执行日志】
{session.last_exec_log if session.last_exec_log else "无"}

【用户最新输入】
{user_input}

【RAG 参考资料】
{rag_context}

【要求】
1. 只输出完整 TCL。
2. 不要解释。
3. 不要 Markdown。
4. 不要 JSON。
5. 不要 tool_call。
6. 不要 Python API。
7. 如果用户只是修改局部要求,保留当前 TCL 中未被要求修改的部分。
8. 如果用户明确说重新开始,则忽略当前 TCL。
"""

    result = llm.invoke([
        ("system", SYSTEM_PROMPT),
        ("user", prompt),
    ])

    return clean_tcl(result.content)


def handle_user_turn(llm, session: AgentSession, user_input: str) -> str:
    session.history.append(ChatTurn("user", user_input))

    output_name = extract_output_name(user_input, session.current_output_name)
    session.current_output_name = output_name

    try:
        script = generate_tcl_with_memory(llm, session, user_input)
        script = validate_tcl(script, output_name)

        session.current_script = script
        session.last_error = ""

        session.history.append(ChatTurn(
            "assistant",
            f"已生成 TCL,文件名 {output_name}"
        ))

        return script

    except Exception as e:
        session.last_error = str(e)
        session.history.append(ChatTurn(
            "assistant",
            f"生成失败:{session.last_error}"
        ))
        return f"❌ 生成失败:\n{session.last_error}"


def chat_main():
    llm = init_llm()
    session = AgentSession(session_id="local_cli")

    print("HyperMesh Tcl 多轮 Agent 已启动。")
    print("exit 退出,show 查看当前 TCL,run 执行当前 TCL,reset 清空。")

    while True:
        user_input = input("\n用户> ").strip()

        if user_input.lower() in {"exit", "quit"}:
            break

        if user_input.lower() == "reset":
            session = AgentSession(session_id="local_cli")
            print("✅ 已清空。")
            continue

        if user_input.lower() == "show":
            print(session.current_script or "暂无 TCL")
            continue

        if user_input.lower() == "run":
            if not session.current_script:
                print("暂无 TCL 可执行。")
                continue

            result = run_hypermesh_tcl.invoke({
                "script": session.current_script,
                "output_name": session.current_output_name,
            })

            session.last_exec_log = result
            session.history.append(ChatTurn("tool", result[:2000]))

            print(result)
            continue

        reply = handle_user_turn(llm, session, user_input)
        print("\n助手生成 TCL:")
        print(reply)


if __name__ == "__main__":
    chat_main()

最终一句话

你要的"多轮修正带记忆"不是靠 llm = init_llm() 实现,也不是靠 force_rewrite_tcl() 实现。

应该是:

复制代码
复制代码
Python 维护 AgentSession
    保存 history
    保存 current_script
    保存 last_error
    保存 last_exec_log

每次用户新输入
    把这些记忆重新放进 prompt
    让 LLM 输出完整新版 TCL
    校验通过后更新 current_script

这才是真正可控的对话记忆。

相关推荐
難釋懷1 小时前
Nginx-Https证书配置
运维·nginx·https
weixin_468466851 小时前
Crawl4Ai 智能数据采集与场景化应用指南
大数据·人工智能·爬虫·python·数据分析
乐维_lwops1 小时前
落地实测|运维监控深度测评
运维
财经资讯数据_灵砚智能1 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年5月29日
大数据·人工智能·python·信息可视化·自然语言处理·ai编程·灵砚智能
财经资讯数据_灵砚智能2 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(夜间-次晨)2026年5月28日
大数据·人工智能·python·信息可视化·自然语言处理·ai编程·灵砚智能
m沐沐2 小时前
【机器学习】聚类算法-K-means聚类
人工智能·python·算法·机器学习·pycharm·kmeans·聚类
REDcker2 小时前
Linux文件IO底层原理详解
linux·运维·spring
为思念酝酿的痛2 小时前
线程同步与互斥
linux·运维·服务器·后端
若鱼文化创意2 小时前
品牌设计CI规划使用后交付偏差先分项核对验收标准
python·ci/cd