Просмотр исходного кода

Add inspiration to topic process and model API integration

zhangyitian 1 день назад
Родитель
Сommit
624f80bd63

+ 121 - 0
examples/create/PRD/inspiration_to_topic_process_v2.md

@@ -0,0 +1,121 @@
+# 灵感点选题生成流程
+
+你是内容选题生成助手。任务:根据灵感点,调用模型生成选题方案,收集用户反馈并调整。
+
+## 开始前
+
+**第一步:创建任务目标并切换焦点**
+
+```python
+# 创建新目标
+result = goal(add="生成灵感点选题方案", reason="用户提供了灵感点,需要生成完整选题")
+# 记录返回的目标ID(例如 "2")
+new_goal_id = result["goal_id"]
+# 立即切换焦点到新目标
+goal(focus=new_goal_id)
+```
+
+## 执行步骤
+
+### 1. 部署模型
+
+调用 `list_models()` 查看可用模型,选择 `全品类_选题_dense_des` 或 `全品类_选题_dense`。
+
+如果模型未部署,调用 `deploy_model(model_id)` 部署,然后每 30 秒调用 `list_models()` 检查状态,直到 status 变为 "deployed"。
+
+### 2. 生成选题
+
+构建查询(替换占位符):
+```
+我在生活中发现了一个关于{{ 品类 }}的灵感点:{{ 灵感点名称 }}-{{ 描述 }}
+我想基于这个灵感点做一期内容,请帮我梳理完整的选题方案。请思考:
+
+从灵感点出发,发散性联想,然后整理思路,最终形成的创作思路
+
+1. 从这个灵感点可以想到什么?
+2. 可以用什么来支撑完整选题(目的和关键点)?
+3. 最终的完整选题方案是什么?
+
+思考完成后,按照以下格式输出:
+
+目的点:<说明创作意图和目的>
+关键点:<列出实现选题的关键手段和要素>
+选题方案:<一句话概括完整的选题方案>
+实现思路:<如何从灵感点、目的点、关键点推导到选题方案的完整逻辑>
+```
+
+调用 `inference(model_id, query, temperature=0.7)`
+
+### 3. 展示结果
+
+输出:
+```
+【选题生成结果】
+模型:{{ model_id }}
+灵感点:{{ 灵感点 }}
+
+{{ 模型返回的完整内容 }}
+
+---
+✅ 选题方案已生成!请提供反馈:
+- 满意:回复"满意"
+- 调整:说明具体内容,如"调整目的点:..."
+- 重新生成:说明新要求
+
+💡 按 'p' 键暂停,输入反馈
+```
+
+完成当前目标:`goal(done="选题已生成,等待反馈")`
+
+### 4. 处理反馈(收到用户消息后执行)
+
+**第一步:创建调整目标并切换焦点**
+
+```python
+# 创建新目标
+result = goal(add="根据反馈调整选题", reason="用户反馈: {{ 反馈摘要 }}")
+# 记录返回的目标ID
+new_goal_id = result["goal_id"]
+# 立即切换焦点到新目标
+goal(focus=new_goal_id)
+```
+
+**第二步:判断反馈类型**
+
+**如果用户说"满意"**:
+- 保存结果到文件
+- 调用 `goal(done="用户满意,任务完成")`
+- 结束
+
+**否则,调整选题**:
+
+构建包含上下文的查询:
+```
+【第一次交互】
+我在生活中发现了一个关于{{ 品类 }}的灵感点:{{ 灵感点 }}-{{ 描述 }}
+
+你之前生成的方案是:
+目的点:{{ 之前的目的点 }}
+关键点:{{ 之前的关键点 }}
+选题方案:{{ 之前的选题方案 }}
+实现思路:{{ 之前的实现思路 }}
+
+【用户反馈】
+{{ 用户的反馈 }}
+
+【要求】
+根据反馈调整,按原格式输出完整方案。
+```
+
+调用 `inference(model_id, query, temperature=0.7)`
+
+调用 `goal(done="已根据反馈调整选题")`
+
+返回步骤 3,展示新结果。
+
+## 关键点
+
+- ✅ 必须调用 inference,不要自己编写
+- ✅ 调整时必须包含第一次的完整上下文
+- ✅ 每次生成后都要展示并等待反馈
+- ✅ 形成循环:生成 → 展示 → 等待 → 调整 → 生成

+ 160 - 0
examples/create/run_inspiration_test.py

@@ -0,0 +1,160 @@
+"""
+测试灵感点生成选题
+"""
+import asyncio
+import sys
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).parent.parent.parent))
+
+from dotenv import load_dotenv
+load_dotenv()
+
+from agent.core.runner import AgentRunner, RunConfig
+from agent.llm import create_openrouter_llm_call
+from agent.llm.prompts import SimplePrompt
+from agent.trace import FileSystemTraceStore, Message, Trace
+from agent.utils import setup_logging
+from agent.cli import InteractiveController
+
+# 导入工具
+import examples.create.tool  # noqa: F401
+
+# 导入项目配置
+from config import DEBUG, LOG_LEVEL, RUN_CONFIG, TRACE_STORE_PATH
+import logging
+
+logger = logging.getLogger(__name__)
+
+async def main():
+    setup_logging(level=LOG_LEVEL)
+
+    base_dir = Path(__file__).parent
+    prompt_path = base_dir / "test_inspiration.prompt"
+
+    # 加载 prompt
+    prompt = SimplePrompt(prompt_path)
+
+    # 注入流程文档
+    process_path = base_dir / "PRD" / "inspiration_to_topic_process_v2.md"
+    if process_path.exists():
+        process_content = process_path.read_text(encoding="utf-8")
+        if "system" in prompt._messages:
+            prompt._messages["system"] = prompt._messages["system"].replace(
+                "{inspiration_to_topic_process_v2}", process_content
+            )
+
+    messages = prompt.build_messages()
+
+    # 创建 runner
+    store = FileSystemTraceStore(base_path=TRACE_STORE_PATH)
+    runner = AgentRunner(
+        trace_store=store,
+        llm_call=create_openrouter_llm_call(model=f"anthropic/{RUN_CONFIG.model}"),
+        debug=DEBUG,
+    )
+
+    # 创建交互控制器
+    interactive = InteractiveController(
+        runner=runner,
+        store=store,
+        enable_stdin_check=True,
+    )
+
+    # 运行
+    config = RUN_CONFIG
+
+    current_trace_id = None
+    current_sequence = 0
+    should_exit = False
+
+    logger.info("💡 交互提示:")
+    logger.info("   - 执行过程中输入 'p' 或 'pause' 暂停并进入交互模式")
+    logger.info("   - 执行过程中输入 'q' 或 'quit' 停止执行")
+    logger.info("")
+
+    try:
+        async for item in runner.run(messages=messages, config=config):
+            # 检查用户输入
+            cmd = interactive.check_stdin()
+            if cmd == "pause":
+                logger.info("\n⏸️ 正在暂停执行...")
+                if current_trace_id:
+                    await runner.stop(current_trace_id)
+                await asyncio.sleep(0.5)
+
+                menu_result = await interactive.show_menu(current_trace_id, current_sequence)
+                if menu_result["action"] == "stop":
+                    should_exit = True
+                    break
+                if menu_result["action"] == "continue":
+                    new_messages = menu_result.get("messages", [])
+                    if new_messages:
+                        # 用户输入了反馈,在当前 trace 上继续执行
+                        continue_config = RunConfig(
+                            model=config.model,
+                            trace_id=current_trace_id,
+                            after_sequence=current_sequence
+                        )
+                        async for item2 in runner.run(messages=new_messages, config=continue_config):
+                            if isinstance(item2, Message):
+                                current_sequence = item2.sequence
+                                print(item2)
+                    break
+
+            elif cmd == "quit":
+                logger.info("\n🛑 用户请求停止...")
+                if current_trace_id:
+                    await runner.stop(current_trace_id)
+                should_exit = True
+                break
+
+            if isinstance(item, Trace):
+                current_trace_id = item.trace_id
+                # 如果 trace 完成,进入交互模式等待反馈
+                if item.status == "completed":
+                    logger.info("\n✅ Agent 执行完成,等待用户反馈...")
+                    logger.info("💡 按 'p' 键进入交互模式,输入反馈或输入 'q' 退出")
+
+                    # 等待用户输入
+                    while True:
+                        await asyncio.sleep(0.5)
+                        cmd = interactive.check_stdin()
+                        if cmd == "pause":
+                            menu_result = await interactive.show_menu(current_trace_id, current_sequence)
+                            if menu_result["action"] == "stop":
+                                should_exit = True
+                                break
+                            if menu_result["action"] == "continue":
+                                new_messages = menu_result.get("messages", [])
+                                if new_messages:
+                                    # 用户输入了反馈,在当前 trace 上继续执行
+                                    continue_config = RunConfig(
+                                        model=config.model,
+                                        trace_id=current_trace_id,
+                                        after_sequence=current_sequence
+                                    )
+                                    async for item2 in runner.run(messages=new_messages, config=continue_config):
+                                        if isinstance(item2, Message):
+                                            current_sequence = item2.sequence
+                                            print(item2)
+                                break
+                        elif cmd == "quit":
+                            should_exit = True
+                            break
+
+                    if should_exit:
+                        break
+
+            elif isinstance(item, Message):
+                current_sequence = item.sequence
+
+            print(item)
+
+    except KeyboardInterrupt:
+        logger.info("\n\n用户中断 (Ctrl+C)")
+        if current_trace_id:
+            await runner.stop(current_trace_id)
+
+if __name__ == "__main__":
+    asyncio.run(main())

+ 212 - 0
examples/create/skills/agent_skill_documentation_zh.md

@@ -0,0 +1,212 @@
+# Agent API 使用文档
+
+## 概述
+
+本 API 为外部 agent 提供三个核心能力,用于与微调模型评估平台交互。
+
+**基础 URL**: `http://localhost:8100/api/agent`
+
+---
+
+## 1. 查询模型列表
+
+**接口**: `GET /api/agent/models`
+
+**说明**: 获取已部署和可部署的模型列表,包含模型元数据。
+
+**返回示例**:
+```json
+{
+  "deployed_models": [
+    {
+      "model_id": "全品类_选题_dense",
+      "model_name": "31品类-QWEN32B-v2-全参数微调",
+      "base_model": "qwen32b",
+      "status": "deployed",
+      "port": 8101,
+      "metadata": {
+        "data_source": "31个数据集",
+        "training_method": "full",
+        "usage": "使用QWEN32B模型在31个品类数据集上进行全参数微调"
+      }
+    }
+  ],
+  "available_models": [
+    {
+      "model_id": "全品类_选题_moe",
+      "model_name": "31品类-QWEN30B-MoE",
+      "base_model": "qwen30b",
+      "status": "available",
+      "metadata": {
+        "data_source": "31个数据集",
+        "training_method": "lora",
+        "usage": "MoE架构微调模型"
+      }
+    }
+  ]
+}
+```
+
+**使用示例**:
+```bash
+curl http://localhost:8100/api/agent/models
+```
+
+---
+
+## 2. 部署模型
+
+**接口**: `POST /api/agent/deploy`
+
+**说明**: 请求部署指定模型,系统会自动分配 GPU 和端口。
+
+**请求参数**:
+```json
+{
+  "model_id": "全品类_选题_dense",
+  "epoch": 3  // 可选:指定 checkpoint epoch
+}
+```
+
+**返回示例**:
+```json
+{
+  "deployment_id": "deploy_abc123def456",
+  "message": "部署已启动"
+}
+```
+
+**注意事项**:
+- 部署是异步的,使用"查询模型列表"接口检查状态是否变为 "deployed"
+- 如果不指定 `epoch`,将使用最新的 checkpoint
+- 部署通常需要 1-3 分钟,取决于模型大小
+
+**使用示例**:
+```bash
+curl -X POST http://localhost:8100/api/agent/deploy \
+  -H "Content-Type: application/json" \
+  -d '{"model_id": "全品类_选题_dense", "epoch": 3}'
+```
+
+---
+
+## 3. 模型推理
+
+**接口**: `POST /api/agent/inference`
+
+**说明**: 向已部署的模型发送查询并获取响应。
+
+**请求参数**:
+```json
+{
+  "model_id": "全品类_选题_dense",
+  "query": "写一篇关于美食的小红书笔记",
+  "temperature": 0.7  // 可选:0.0-1.0,默认 0.7
+}
+```
+
+**返回示例**:
+```json
+{
+  "response": "今天去了一家超棒的川菜馆...",
+  "tokens_generated": 156
+}
+```
+
+**注意事项**:
+- 模型必须已部署(status="deployed")才能推理
+- 无状态:每次请求独立,不保留对话历史
+- 超时时间:120 秒
+- 最大 tokens:2048
+
+**使用示例**:
+```bash
+curl -X POST http://localhost:8100/api/agent/inference \
+  -H "Content-Type: application/json" \
+  -d '{
+    "model_id": "全品类_选题_dense",
+    "query": "写一篇关于旅行的笔记",
+    "temperature": 0.8
+  }'
+```
+
+---
+
+## 错误处理
+
+所有接口返回标准 HTTP 状态码:
+
+- `200`: 成功
+- `400`: 请求错误(无效的 model_id、缺少参数等)
+- `404`: 模型未找到或未部署
+- `500`: 服务器内部错误
+
+错误响应格式:
+```json
+{
+  "detail": "错误信息描述"
+}
+```
+
+---
+
+## 完整工作流示例
+
+```bash
+# 1. 查询可用模型
+curl http://localhost:8100/api/agent/models
+
+# 2. 部署模型
+curl -X POST http://localhost:8100/api/agent/deploy \
+  -H "Content-Type: application/json" \
+  -d '{"model_id": "全品类_选题_dense"}'
+
+# 3. 等待部署完成(检查状态)
+curl http://localhost:8100/api/agent/models
+
+# 4. 执行推理
+curl -X POST http://localhost:8100/api/agent/inference \
+  -H "Content-Type: application/json" \
+  -d '{"model_id": "全品类_选题_dense", "query": "写美食笔记"}'
+```
+
+---
+
+## MCP Skill 集成
+
+将这些接口封装为 MCP tools:
+
+1. **list_models**: 无需参数
+2. **deploy_model**: 参数 `model_id`(必需)、`epoch`(可选)
+3. **inference**: 参数 `model_id`(必需)、`query`(必需)、`temperature`(可选)
+
+### Python 示例
+
+```python
+import requests
+
+BASE_URL = "http://localhost:8100/api/agent"
+
+def list_models():
+    """查询模型列表"""
+    response = requests.get(f"{BASE_URL}/models")
+    return response.json()
+
+def deploy_model(model_id: str, epoch: int = None):
+    """部署模型"""
+    data = {"model_id": model_id}
+    if epoch:
+        data["epoch"] = epoch
+    response = requests.post(f"{BASE_URL}/deploy", json=data)
+    return response.json()
+
+def inference(model_id: str, query: str, temperature: float = 0.7):
+    """模型推理"""
+    data = {
+        "model_id": model_id,
+        "query": query,
+        "temperature": temperature
+    }
+    response = requests.post(f"{BASE_URL}/inference", json=data)
+    return response.json()
+```

+ 24 - 0
examples/create/test_inspiration.prompt

@@ -0,0 +1,24 @@
+---
+model: anthropic/claude-sonnet-4.5
+temperature: 0.3
+---
+
+$system$
+
+你是一个专业的内容选题生成助手。
+
+下面是你本次执行过程要解决的核心问题:
+{inspiration_to_topic_process_v2}
+
+$user$
+
+请使用以下灵感点生成完整选题方案:
+
+品类:生活记录
+灵感点:两百岁老人逛集市
+描述:内容呈现了一对合计年龄近两百岁的高龄老夫妇在农村集市徘徊的场景。老爷爷拄着简陋木棍,老奶奶挎着竹篮,两人衣着破旧且步履蹒跚。
+
+重要提醒:
+1. 开始前必须先调用 goal 工具创建目标
+2. 收到我的反馈后,必须先创建新 goal,然后调用 inference 重新生成
+3. 不创建 goal 就无法正确执行任务

+ 52 - 0
examples/create/tool/model_api.py

@@ -0,0 +1,52 @@
+"""
+模型管理和推理工具
+"""
+import httpx
+from agent import tool, ToolResult
+
+BASE_URL = "http://localhost:8100/api/agent"
+
+@tool(description="查询已部署和可部署的模型列表")
+async def list_models() -> ToolResult:
+    """获取模型列表,包含已部署和可用模型的元数据"""
+    async with httpx.AsyncClient(trust_env=False) as client:
+        response = await client.get(f"{BASE_URL}/models", timeout=30.0)
+        response.raise_for_status()
+        return ToolResult(title="模型列表", output=response.text)
+
+@tool(description="部署指定模型到 GPU")
+async def deploy_model(model_id: str, epoch: int = None) -> ToolResult:
+    """部署模型
+
+    Args:
+        model_id: 模型唯一标识符
+        epoch: 可选的 checkpoint epoch
+    """
+    data = {"model_id": model_id}
+    if epoch is not None:
+        data["epoch"] = epoch
+
+    async with httpx.AsyncClient(trust_env=False) as client:
+        response = await client.post(f"{BASE_URL}/deploy", json=data, timeout=30.0)
+        response.raise_for_status()
+        return ToolResult(title="部署结果", output=response.text)
+
+@tool(description="向已部署的模型发送查询并获取响应")
+async def inference(model_id: str, query: str, temperature: float = 0.7) -> ToolResult:
+    """模型推理
+
+    Args:
+        model_id: 已部署的模型标识符
+        query: 推理查询内容
+        temperature: 温度参数,范围 0.0-1.0,默认 0.7
+    """
+    data = {
+        "model_id": model_id,
+        "query": query,
+        "temperature": temperature
+    }
+
+    async with httpx.AsyncClient(trust_env=False) as client:
+        response = await client.post(f"{BASE_URL}/inference", json=data, timeout=120.0)
+        response.raise_for_status()
+        return ToolResult(title="推理结果", output=response.text)