Compare commits
5 Commits
64a51f2567
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 93ef589826 | |||
| 3405d1b355 | |||
| bc44de93bc | |||
| 86ece1926d | |||
| 8efcd72eb2 |
10
.env.example
10
.env.example
@@ -18,6 +18,13 @@ DATABASE_POOL_SIZE=10
|
||||
DATABASE_MAX_OVERFLOW=20
|
||||
DATABASE_ECHO=False
|
||||
|
||||
# 数据库Postgres配置
|
||||
POSTGRES_USER="postgres"
|
||||
POSTGRES_PASSWORD="123456"
|
||||
POSTGRES_HOST="127.0.0.1"
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_DB="postgres"
|
||||
|
||||
# CORS配置
|
||||
ALLOWED_ORIGINS=["*"]
|
||||
|
||||
@@ -32,3 +39,6 @@ UPLOAD_DIR=./uploads
|
||||
|
||||
# 缓存配置
|
||||
CACHE_TTL_SECONDS=300
|
||||
|
||||
# AI配置
|
||||
DEEPSEEK_API_KEY=api_key
|
||||
47
api/agent_api.py
Normal file
47
api/agent_api.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import StreamingResponse
|
||||
from pydantic import BaseModel, Field
|
||||
from services.agent_server.chef_service import ChefService
|
||||
|
||||
# agent相关的路由
|
||||
agent_router = APIRouter(prefix="/agent", tags=["Agent相关"])
|
||||
|
||||
|
||||
class ChatRequest(BaseModel):
|
||||
thread_id: str = Field(description="当前会话的id")
|
||||
message: str = Field(description="当前会话的问题")
|
||||
|
||||
|
||||
class ChatResponse(BaseModel):
|
||||
thread_id: str
|
||||
content: str
|
||||
|
||||
|
||||
@agent_router.post("/chat", response_model=ChatResponse)
|
||||
def chat(request: ChatRequest):
|
||||
|
||||
result = ChefService.chat(
|
||||
thread_id=request.thread_id,
|
||||
message=request.message,
|
||||
)
|
||||
|
||||
messages = result["messages"]
|
||||
|
||||
# 最后一条 AI 消息
|
||||
last_message = messages[-1]
|
||||
|
||||
return ChatResponse(
|
||||
thread_id=request.thread_id,
|
||||
content=last_message.content,
|
||||
)
|
||||
|
||||
@agent_router.post("/chat_stream")
|
||||
def chat(request: ChatRequest):
|
||||
|
||||
return StreamingResponse(
|
||||
ChefService.chat_stream(
|
||||
thread_id=request.thread_id,
|
||||
message=request.message,
|
||||
),
|
||||
media_type="text/plain; charset=utf-8"
|
||||
)
|
||||
@@ -1,7 +1,5 @@
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from typing import Any, Dict
|
||||
from core.db import SessionDep
|
||||
from core.dto.api_response import ApiResponse
|
||||
from core.task.dlt_scheduler import scheduler
|
||||
|
||||
# 彩票相关的路由
|
||||
|
||||
24
core/config/logging_cof.py
Normal file
24
core/config/logging_cof.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import structlog
|
||||
|
||||
|
||||
def setup_logging():
|
||||
logging.basicConfig(
|
||||
format='%(message)s',
|
||||
stream=sys.stdout,
|
||||
level=logging.INFO,
|
||||
)
|
||||
|
||||
structlog.configure(
|
||||
processors=[
|
||||
structlog.contextvars.merge_contextvars,
|
||||
structlog.processors.add_log_level,
|
||||
structlog.processors.TimeStamper(fmt='iso'),
|
||||
# structlog.processors.JSONRenderer(ensure_ascii=False), # 生产输出JSON 开发生产2选一
|
||||
structlog.dev.ConsoleRenderer() # 开发环境彩色控制台
|
||||
],
|
||||
logger_factory=structlog.stdlib.LoggerFactory(),
|
||||
cache_logger_on_first_use=True,
|
||||
)
|
||||
@@ -94,6 +94,30 @@ class Settings(BaseSettings):
|
||||
description="是否打印SQL语句"
|
||||
)
|
||||
|
||||
# ---------- 数据库Postgres配置 ----------
|
||||
POSTGRES_USER: str = Field(
|
||||
default='postgres',
|
||||
description="postgres用户名"
|
||||
)
|
||||
POSTGRES_PASSWORD: str = Field(
|
||||
default='123456',
|
||||
description="postgres密码"
|
||||
)
|
||||
POSTGRES_HOST: str = Field(
|
||||
default='127.0.0.1',
|
||||
description="postgres主机地址"
|
||||
)
|
||||
POSTGRES_PORT: int = Field(
|
||||
default=5432,
|
||||
ge=1,
|
||||
le=65535,
|
||||
description="postgres端口"
|
||||
)
|
||||
POSTGRES_DB: str = Field(
|
||||
default='postgres',
|
||||
description="postgres数据库名"
|
||||
)
|
||||
|
||||
# ---------- CORS配置 ----------
|
||||
ALLOWED_ORIGINS: List[str] = Field(
|
||||
default=["*"],
|
||||
@@ -143,6 +167,17 @@ class Settings(BaseSettings):
|
||||
description="缓存过期时间(秒)"
|
||||
)
|
||||
|
||||
# ---------- AI配置 ----------
|
||||
DEEPSEEK_API_KEY: str = Field(
|
||||
default="sk-d3e11a4229744fd29a8468e7df072a4b",
|
||||
description="deepseek模型api_key"
|
||||
)
|
||||
|
||||
TAVILY_API_KEY: str = Field(
|
||||
default="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",
|
||||
description="tavily搜索接口api_key"
|
||||
)
|
||||
|
||||
# ---------- 计算属性 ----------
|
||||
@property
|
||||
def mysql_url(self) -> str:
|
||||
@@ -150,6 +185,12 @@ class Settings(BaseSettings):
|
||||
# 连接url格式:mysql+pymysql://user:password@host:port/dbname
|
||||
return f"mysql+pymysql://{self.MYSQL_USER}:{self.MYSQL_PASSWORD}@{self.MYSQL_HOST}:{self.MYSQL_PORT}/{self.MYSQL_DB}?charset=utf8mb4"
|
||||
|
||||
@property
|
||||
def postgres_url(self) -> str:
|
||||
"""postgres连接URL"""
|
||||
# 连接url格式:postgresql://postgres:postgres@localhost:5432/postgres?sslmode=disable
|
||||
return f"postgresql://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}?sslmode=disable"
|
||||
|
||||
@property
|
||||
def is_production(self) -> bool:
|
||||
"""是否为生产环境"""
|
||||
|
||||
@@ -1,30 +1,16 @@
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
import structlog
|
||||
from apscheduler.job import Job
|
||||
from apscheduler.schedulers.background import BackgroundScheduler
|
||||
from apscheduler.schedulers.base import STATE_PAUSED, STATE_STOPPED, STATE_RUNNING
|
||||
from apscheduler.triggers.cron import CronTrigger
|
||||
from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
|
||||
|
||||
# 添加项目根目录到路径
|
||||
sys.path.append(str(Path(__file__).parent.parent.parent))
|
||||
|
||||
from core.task.dlt_spider import DLTSpider
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('lottery_scheduler.log', encoding='utf-8'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = structlog.getLogger(__name__)
|
||||
|
||||
def test_fun():
|
||||
logger.info(f"当前时间---{datetime.now()}")
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Any, List, Dict, Union, Optional, NamedTuple
|
||||
import time
|
||||
|
||||
import requests
|
||||
from pydantic import BaseModel, field_validator
|
||||
import structlog
|
||||
from sqlalchemy import desc
|
||||
|
||||
from core.db import get_db_session
|
||||
from models import LotteryDLT
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger = structlog.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_draw_numbers(result_str: str) -> List[str]:
|
||||
|
||||
0
exceptions/__init__.py
Normal file
0
exceptions/__init__.py
Normal file
3
exceptions/errors.py
Normal file
3
exceptions/errors.py
Normal file
@@ -0,0 +1,3 @@
|
||||
class AgentNotReadyError(RuntimeError):
|
||||
"""agent未初始化错误"""
|
||||
pass
|
||||
24
exceptions/handlers.py
Normal file
24
exceptions/handlers.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from fastapi import Request, FastAPI
|
||||
from fastapi.responses import JSONResponse
|
||||
from exceptions.errors import AgentNotReadyError
|
||||
|
||||
|
||||
async def agent_not_ready_handler(request: Request, exc: Exception):
|
||||
"""agent未就绪的错误"""
|
||||
return JSONResponse(
|
||||
status_code=503,
|
||||
content={
|
||||
"code": 503,
|
||||
"message": str(exc)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
# 集中处理 保持main简洁
|
||||
def register_exception_handlers(app: FastAPI):
|
||||
"""集中处理注册异常"""
|
||||
|
||||
app.add_exception_handler(
|
||||
AgentNotReadyError,
|
||||
agent_not_ready_handler,
|
||||
)
|
||||
0
lc/__init__.py
Normal file
0
lc/__init__.py
Normal file
262
lc/chef_agent.py
Normal file
262
lc/chef_agent.py
Normal file
@@ -0,0 +1,262 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, cast
|
||||
|
||||
import structlog
|
||||
from langchain.agents import create_agent
|
||||
from langchain.agents.middleware import SummarizationMiddleware
|
||||
from langchain.agents.middleware.summarization import ContextMessages
|
||||
from langchain.chat_models import init_chat_model
|
||||
from langchain_core.language_models import BaseChatModel
|
||||
from langchain_deepseek import ChatDeepSeek
|
||||
from langchain_tavily import TavilySearch
|
||||
from langgraph.checkpoint.postgres import PostgresSaver
|
||||
from psycopg import Connection
|
||||
from psycopg_pool import ConnectionPool
|
||||
|
||||
from core.config.settings import settings
|
||||
from exceptions.errors import AgentNotReadyError
|
||||
|
||||
logger = structlog.getLogger(__name__)
|
||||
|
||||
|
||||
class ChefAgent:
|
||||
"""厨师 Agent。
|
||||
|
||||
负责:
|
||||
- LLM 初始化
|
||||
- Tool 初始化
|
||||
- CheckPointer 初始化
|
||||
- Middleware 初始化
|
||||
- Agent 初始化
|
||||
- 生命周期管理
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._pool = None
|
||||
self._checkpointer: PostgresSaver | None = None
|
||||
self._agent = None
|
||||
|
||||
def initialize(self) -> ChefAgent:
|
||||
"""初始化 Agent。
|
||||
|
||||
Returns:
|
||||
当前 Agent 实例,方便链式调用。
|
||||
"""
|
||||
|
||||
logger.info("开始初始化chef_agent...")
|
||||
|
||||
if self._agent is not None:
|
||||
return self
|
||||
|
||||
# 1. 初始化 LLM
|
||||
llm = self._create_llm()
|
||||
|
||||
# 2. 初始化工具
|
||||
tools = self._create_tools()
|
||||
|
||||
# 3. 初始化 Middleware
|
||||
middleware = self._create_middleware(llm)
|
||||
|
||||
# 4. 初始化 CheckPointer
|
||||
self._checkpointer = self._create_checkpointer()
|
||||
|
||||
# 5. 初始化 Agent
|
||||
logger.info("chef_agent的agent开始初始化...")
|
||||
self._agent = create_agent(
|
||||
model=llm,
|
||||
tools=tools,
|
||||
middleware=middleware,
|
||||
checkpointer=self._checkpointer,
|
||||
system_prompt=self._system_prompt(),
|
||||
)
|
||||
logger.info("chef_agent的agent初始化完成")
|
||||
|
||||
logger.info("chef_agent初始化完成")
|
||||
|
||||
return self
|
||||
|
||||
@staticmethod
|
||||
def _create_llm() -> BaseChatModel:
|
||||
"""创建 LLM。"""
|
||||
|
||||
logger.info("chef_agent初始化llm...")
|
||||
|
||||
# llm = init_chat_model(
|
||||
# model="deepseek-v4-flash",
|
||||
# model_provider="deepseek",
|
||||
# api_key=settings.DEEPSEEK_API_KEY,
|
||||
# extra_body={"thingking":{"type": "disabled"}}
|
||||
# )
|
||||
llm = ChatDeepSeek(
|
||||
model="deepseek-v4-flash",
|
||||
api_key=settings.DEEPSEEK_API_KEY,
|
||||
extra_body={"thingking": {"type": "disabled"}}
|
||||
)
|
||||
|
||||
logger.info("chef_agent的llm初始化完成")
|
||||
return llm
|
||||
|
||||
@staticmethod
|
||||
def _create_tools() -> list[Any]:
|
||||
"""创建 Agent Tools。"""
|
||||
|
||||
logger.info("chef_agent工具开始初始化...")
|
||||
search_tool = TavilySearch(
|
||||
tavily_api_key=settings.TAVILY_API_KEY,
|
||||
max_results=5,
|
||||
topic="general",
|
||||
)
|
||||
|
||||
logger.info("chef_agent工具初始化完成")
|
||||
return [search_tool]
|
||||
|
||||
@staticmethod
|
||||
def _create_middleware(
|
||||
llm: BaseChatModel,
|
||||
) -> list[Any]:
|
||||
"""创建 Agent Middleware。"""
|
||||
|
||||
logger.info("chef_agent中间件开始初始化...")
|
||||
summarization = SummarizationMiddleware(
|
||||
model=llm,
|
||||
trigger=cast(ContextMessages, ("messages", 10)),
|
||||
keep=cast(ContextMessages, ("messages", 5)),
|
||||
)
|
||||
|
||||
logger.info("chef_agent中间件初始化完成")
|
||||
return [summarization]
|
||||
|
||||
def _create_checkpointer(self) -> PostgresSaver:
|
||||
"""创建 PostgreSQL CheckPointer。"""
|
||||
|
||||
logger.info("chef_agent的CheckPointer开始初始化")
|
||||
logger.info("创建PostgreSQL连接池")
|
||||
self._pool = ConnectionPool[Connection[dict[str, Any]]]( # 指定类型[Connection[dict[str, Any]]]防止编译器提示错误
|
||||
conninfo=settings.postgres_url,
|
||||
max_size=10,
|
||||
kwargs={
|
||||
"autocommit": True,
|
||||
}
|
||||
)
|
||||
|
||||
logger.info("创建checkpointer")
|
||||
checkpointer = PostgresSaver(self._pool)
|
||||
|
||||
logger.info("初始化checkpoint数据表")
|
||||
# 创建 LangGraph checkpoint 所需的数据表
|
||||
checkpointer.setup()
|
||||
|
||||
logger.info("chef_agent的CheckPointer初始化完成")
|
||||
return checkpointer
|
||||
|
||||
@staticmethod
|
||||
def _system_prompt() -> str:
|
||||
"""Agent System Prompt。"""
|
||||
|
||||
return """
|
||||
你是一名专业厨师。
|
||||
|
||||
你的任务是根据用户提供的食材照片或食材清单,为用户推荐合适的菜谱。
|
||||
|
||||
请严格按照以下流程执行:
|
||||
|
||||
## 1. 识别和评估食材
|
||||
|
||||
如果用户提供的是食材照片:
|
||||
|
||||
- 识别照片中可见的食材
|
||||
- 根据外观判断食材的新鲜程度
|
||||
- 估算大致可用量
|
||||
- 排除明显不可食用或状态异常的食材
|
||||
|
||||
整理成「可用食材清单」。
|
||||
|
||||
如果用户直接提供食材清单,则直接使用用户提供的信息。
|
||||
|
||||
## 2. 搜索菜谱
|
||||
|
||||
优先使用搜索工具。
|
||||
|
||||
以「可用食材清单」作为核心关键词,搜索适合这些食材的菜谱。
|
||||
|
||||
优先考虑:
|
||||
|
||||
- 食材匹配度高
|
||||
- 操作简单
|
||||
- 营养均衡
|
||||
- 家庭烹饪可执行
|
||||
|
||||
除非搜索不到合适结果,否则不要直接凭经验编造菜谱。
|
||||
|
||||
## 3. 评估和排序
|
||||
|
||||
对搜索到的候选菜谱进行综合评价。
|
||||
|
||||
从以下维度进行评分:
|
||||
|
||||
- 食材匹配度
|
||||
- 营养价值
|
||||
- 烹饪难度
|
||||
- 烹饪时间
|
||||
|
||||
综合评分后进行排序。
|
||||
|
||||
优先推荐:
|
||||
|
||||
「简单 + 食材匹配度高 + 营养丰富」
|
||||
|
||||
的菜谱。
|
||||
|
||||
## 4. 输出结果
|
||||
|
||||
最终输出结构化的菜谱推荐报告。
|
||||
|
||||
每个推荐至少包含:
|
||||
|
||||
- 菜谱名称
|
||||
- 所需食材
|
||||
- 核心烹饪步骤
|
||||
- 烹饪时间
|
||||
- 难度
|
||||
- 综合评分
|
||||
- 推荐理由
|
||||
- 参考来源
|
||||
|
||||
如果搜索结果中存在可靠的菜谱图片,则提供图片参考。
|
||||
|
||||
如果搜索不到合适菜谱,再根据已有知识进行合理推荐,并明确说明这是基于模型知识给出的建议。
|
||||
""".strip()
|
||||
|
||||
@property
|
||||
def agent(self):
|
||||
"""获取 Agent。"""
|
||||
|
||||
if self._agent is None:
|
||||
logger.info("ChefAgent 尚未初始化,请先调用 initialize()")
|
||||
raise AgentNotReadyError(
|
||||
"ChefAgent 尚未初始化,请先调用 initialize()"
|
||||
)
|
||||
|
||||
return self._agent
|
||||
|
||||
def close(self) -> None:
|
||||
"""释放资源。"""
|
||||
|
||||
if self._pool is not None:
|
||||
self._pool.close()
|
||||
self._pool = None
|
||||
logger.info("PostgreSQL已关闭")
|
||||
|
||||
self._checkpointer = None
|
||||
self._agent = None
|
||||
|
||||
# 使用with上下文管理方式来使用的话需要这2个方法
|
||||
# def __enter__(self) -> ChefAgent:
|
||||
# return self.initialize()
|
||||
#
|
||||
# def __exit__(self, exc_type, exc_value, traceback) -> None:
|
||||
# self.close()
|
||||
|
||||
|
||||
chef_agent = ChefAgent()
|
||||
194
lc/deepseek.ipynb
Normal file
194
lc/deepseek.ipynb
Normal file
@@ -0,0 +1,194 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-09-03T09:14:58.535236Z",
|
||||
"start_time": "2026-09-03T09:14:57.335343800Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"from openai import OpenAI\n",
|
||||
"from openai.types.chat import (\n",
|
||||
" ChatCompletionSystemMessageParam,\n",
|
||||
" ChatCompletionUserMessageParam,\n",
|
||||
" ChatCompletionAssistantMessageParam\n",
|
||||
")"
|
||||
],
|
||||
"id": "255e9b078be17a5f",
|
||||
"outputs": [],
|
||||
"execution_count": 1
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-09-03T09:19:09.642201600Z",
|
||||
"start_time": "2026-09-03T09:19:09.628764300Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"client = OpenAI(\n",
|
||||
" api_key='sk-4bdcdad5d4cd4856bc0c308c3f74eb22',\n",
|
||||
" base_url='https://api.deepseek.com'\n",
|
||||
")"
|
||||
],
|
||||
"id": "6f8aeb64ad6aca1f",
|
||||
"outputs": [],
|
||||
"execution_count": 6
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-09-03T09:17:52.034706300Z",
|
||||
"start_time": "2026-09-03T09:17:52.023338600Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"messages: list[ChatCompletionSystemMessageParam | ChatCompletionUserMessageParam | ChatCompletionAssistantMessageParam] = [\n",
|
||||
" ChatCompletionSystemMessageParam(role=\"system\", content=\"你是一个ai助手,所有回答使用中文\")\n",
|
||||
"]"
|
||||
],
|
||||
"id": "4530b047d03a8ab3",
|
||||
"outputs": [],
|
||||
"execution_count": 3
|
||||
},
|
||||
{
|
||||
"metadata": {},
|
||||
"cell_type": "code",
|
||||
"outputs": [],
|
||||
"execution_count": null,
|
||||
"source": [
|
||||
"# 第一轮对话\n",
|
||||
"print(\"===========\")\n",
|
||||
"messages.append(ChatCompletionUserMessageParam(role=\"user\", content=\"你好,我是大哥\"))\n",
|
||||
"\n",
|
||||
"response = client.chat.completions.create(\n",
|
||||
" model='deepseek-v4-flash',\n",
|
||||
" messages=messages,\n",
|
||||
" stream=False\n",
|
||||
")"
|
||||
],
|
||||
"id": "a42f7a8dbdfa3b8c"
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-09-03T08:51:19.785432800Z",
|
||||
"start_time": "2026-09-03T08:51:19.759474700Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# print(response.model_dump_json())\n",
|
||||
"print(response.choices[0].message.content)"
|
||||
],
|
||||
"id": "fac34b2742f048e0",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"大哥你好!我是你的AI助手,随时听候差遣。今天有什么需要帮忙的吗?\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 12
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-09-03T08:51:41.715208300Z",
|
||||
"start_time": "2026-09-03T08:51:36.483133100Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# 第二轮对话\n",
|
||||
"messages.append(ChatCompletionAssistantMessageParam(role=\"assistant\", content=response.choices[0].message.content))\n",
|
||||
"messages.append(ChatCompletionUserMessageParam(role=\"user\", content=\"你还记得我吗\"))\n",
|
||||
"response = client.chat.completions.create(\n",
|
||||
" model='deepseek-v4-flash',\n",
|
||||
" messages=messages,\n",
|
||||
")\n",
|
||||
"print(response.choices[0].message.content)"
|
||||
],
|
||||
"id": "3c9fa1f10b75c256",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"大哥,这个问题您刚刚才问过呢,我当然记得您!您就是“大哥”嘛,我怎么会忘?\n",
|
||||
"\n",
|
||||
"虽然我的记忆不能跨对话永久保存,但只要咱们在这个对话里,您说的每句话、问的每个问题,我都记得一清二楚——包括您刚才已经问过一次“还记得我吗”,我当时也解释过啦。\n",
|
||||
"\n",
|
||||
"您现在又问一遍,是在考验我,还是想看看我是不是“脸盲”呀?哈哈,放心吧!在这段对话期间,您永远是我的大哥,随叫随到,有事您吩咐!😄\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 14
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-09-03T09:19:38.275536400Z",
|
||||
"start_time": "2026-09-03T09:19:31.090446900Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"messages.append(ChatCompletionUserMessageParam(role=\"user\", content=\"你好,明天合肥天气怎么样\"))\n",
|
||||
"\n",
|
||||
"response = client.chat.completions.create(\n",
|
||||
" model='deepseek-v4-flash',\n",
|
||||
" messages=messages,\n",
|
||||
" stream=False\n",
|
||||
")\n",
|
||||
"print(response.choices[0].message.content)"
|
||||
],
|
||||
"id": "ea34cc88d92b2832",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"你好!很抱歉,由于我无法实时联网获取最新的气象数据,所以不能直接为你提供明天合肥的准确天气预报。\n",
|
||||
"\n",
|
||||
"不过,你可以通过以下几种方式快速查到最准确的信息:\n",
|
||||
"\n",
|
||||
"1. 打开手机自带的“天气”应用,添加并定位到“合肥”。\n",
|
||||
"2. 在搜索引擎(如百度)中直接搜索“**合肥明天天气**”,或其他(例如“墨迹天气”)\n",
|
||||
"3. 访问“中国天气网”(www.weather.com.cn),输入“合肥”即可查询。\n",
|
||||
"\n",
|
||||
"另外,由于我无法预知你目前的实际日期,建议你在查询时留意一下,如果是明天出发,记得顺带看一眼**后天**的预报,以便更好地安排行程。祝生活愉快!\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 7
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
167
lc/lc.ipynb
Normal file
167
lc/lc.ipynb
Normal file
@@ -0,0 +1,167 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"id": "initial_id",
|
||||
"metadata": {
|
||||
"collapsed": true,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-09-05T01:34:44.550869800Z",
|
||||
"start_time": "2026-09-05T01:34:44.535351500Z"
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"from typing import cast, Literal\n",
|
||||
"\n",
|
||||
"from langchain.agents.middleware.summarization import ContextMessages\n",
|
||||
"from langchain.chat_models import init_chat_model\n",
|
||||
"from langchain.agents import create_agent\n",
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"from langgraph.checkpoint.memory import InMemorySaver\n",
|
||||
"from langchain.agents.middleware import SummarizationMiddleware\n",
|
||||
"\n",
|
||||
"from core.config.settings import settings\n",
|
||||
"from langchain.tools import tool\n",
|
||||
"from langchain.messages import HumanMessage\n",
|
||||
"from langchain_core.language_models import BaseChatModel"
|
||||
],
|
||||
"outputs": [],
|
||||
"execution_count": 15
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-09-05T01:37:24.572533800Z",
|
||||
"start_time": "2026-09-05T01:37:24.555523100Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# 初始化模型\n",
|
||||
"my_model = init_chat_model(\n",
|
||||
" model=\"deepseek-v4-flash\",\n",
|
||||
" api_key=settings.DEEPSEEK_API_KEY,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# 初始化checkpointer 记忆管理的存储方式\n",
|
||||
"checkpointer = InMemorySaver()\n",
|
||||
"\n",
|
||||
"# 初始化记忆策略中间件\n",
|
||||
"middleware = SummarizationMiddleware(\n",
|
||||
" model=cast(BaseChatModel, my_model), # 消息摘要的记忆管理策略的模型\n",
|
||||
" trigger=cast(ContextMessages, (\"messages\", 3)), # 触发策略的条件\n",
|
||||
" keep=cast(ContextMessages, (\"messages\", 1)) # 触发策略后保留的消息条数\n",
|
||||
")"
|
||||
],
|
||||
"id": "b905be968f5c7761",
|
||||
"outputs": [],
|
||||
"execution_count": 22
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-09-05T01:37:26.422888800Z",
|
||||
"start_time": "2026-09-05T01:37:26.408643600Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"# 初始化agent\n",
|
||||
"agent = create_agent(\n",
|
||||
" model=cast(BaseChatModel, my_model),\n",
|
||||
" checkpointer=InMemorySaver(), # 短期记忆 通过thread_id进行记忆分组\n",
|
||||
" middleware=[middleware]\n",
|
||||
")"
|
||||
],
|
||||
"id": "cb5ae2fa4aeaf130",
|
||||
"outputs": [],
|
||||
"execution_count": 23
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-09-05T01:37:59.930524700Z",
|
||||
"start_time": "2026-09-05T01:37:27.968100300Z"
|
||||
}
|
||||
},
|
||||
"cell_type": "code",
|
||||
"source": [
|
||||
"config: RunnableConfig = {\"configurable\": {\"thread_id\": \"thread_1\"}}\n",
|
||||
"agent.invoke({\"messages\": [HumanMessage(\"你好,我是胖哥\")]}, config)\n",
|
||||
"agent.invoke({\"messages\": [HumanMessage(\"我喜欢吃美食\")]}, config)\n",
|
||||
"agent.invoke({\"messages\": [HumanMessage(\"我喜欢运动\")]}, config)\n",
|
||||
"\n",
|
||||
"result = agent.invoke({\"messages\": [HumanMessage(\"你还记得我吗\")]}, config)\n",
|
||||
"# print(result)\n",
|
||||
"for message in result['messages']:\n",
|
||||
" message.pretty_print()"
|
||||
],
|
||||
"id": "1ed1327c973d75cc",
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"================================\u001B[1m Human Message \u001B[0m=================================\n",
|
||||
"\n",
|
||||
"Here is a summary of the conversation to date:\n",
|
||||
"\n",
|
||||
"## SESSION INTENT\n",
|
||||
"\n",
|
||||
"开放式、友好的中文闲聊会话,无具体交付物。用户自称“胖哥”,目前已透露两大爱好:美食与运动。整体目标是延续话题、维持轻松氛围,进一步了解他的偏好,从而围绕美食/运动展开聊天或提供推荐。\n",
|
||||
"\n",
|
||||
"## SUMMARY\n",
|
||||
"\n",
|
||||
"- 用户为“胖哥”,须以中文称呼;全会话均使用中文。\n",
|
||||
"- 话题线索已在两条线上推进:\n",
|
||||
" 1. **美食**:胖哥说“我喜欢吃美食”。助手此前已问他偏好哪些菜系(川菜/粤菜/湘菜)以及喜欢哪种类型(街边小吃、家常菜还是精致餐饮),并邀请他分享最近特别喜欢的一道菜。胖哥尚未回答此组问题。\n",
|
||||
" 2. **运动(新增)**:胖哥随后说“我喜欢运动”。助手将美食与运动联系起来称赞(爱吃又会动、搭配健康),并追问:喜欢哪种运动——健身房撸铁、户外跑步骑行,还是打篮球羽毛球等对抗性项目;同时主动提出可以推荐运动后补充能量的美食搭配。\n",
|
||||
"- 尚无任何结论、决定或策略形成;没有选项被否决。\n",
|
||||
"- 需要注意的是,胖哥对“美食偏好”相关问题尚未作答,该线索仍处于待回应状态。\n",
|
||||
"\n",
|
||||
"## ARTIFACTS\n",
|
||||
"\n",
|
||||
"None.\n",
|
||||
"\n",
|
||||
"## NEXT STEPS\n",
|
||||
"\n",
|
||||
"- 等待胖哥回复:他喜欢哪种运动(健身房/户外/球类对抗等)。\n",
|
||||
"- 得到答复后顺势深入聊天:讨论该运动,可结合他未答复的那组问题,推荐适合运动后的营养美食搭配,或顺带再把菜系/餐饮类型偏好问出来,延长话题。\n",
|
||||
"- 不要重复寒暄,也不要重复之前已问过的两组问题(菜系类型与偏好风格、运动类型选择)。\n",
|
||||
"- 保持中文、热情随意的口吻继续对话。\n",
|
||||
"================================\u001B[1m Human Message \u001B[0m=================================\n",
|
||||
"\n",
|
||||
"你还记得我吗\n",
|
||||
"==================================\u001B[1m Ai Message \u001B[0m==================================\n",
|
||||
"\n",
|
||||
"胖哥,这话说的——当然记得你呀!爱美食又爱运动,能吃会练,这反差感可太让人印象深刻了。咱俩这不正聊到运动嘛,我还等着听你细说呢。 \n",
|
||||
"\n",
|
||||
"不过不急,你先说说最近一次痛快出汗是啥时候?是去健身房撸铁,还是户外疯跑了一圈?反正不管哪种,你要是一会儿饿了,我脑子里的运动后美食搭配可已经在排队了😄\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 24
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
0
lg/__init__.py
Normal file
0
lg/__init__.py
Normal file
194
lg/approve.ipynb
Normal file
194
lg/approve.ipynb
Normal file
File diff suppressed because one or more lines are too long
131
lg/fan_in.ipynb
Normal file
131
lg/fan_in.ipynb
Normal file
File diff suppressed because one or more lines are too long
111
lg/node_error.ipynb
Normal file
111
lg/node_error.ipynb
Normal file
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"id": "initial_id",
|
||||
"metadata": {
|
||||
"collapsed": true,
|
||||
"ExecuteTime": {
|
||||
"end_time": "2026-09-08T07:56:57.833862800Z",
|
||||
"start_time": "2026-09-08T07:56:51.946284300Z"
|
||||
}
|
||||
},
|
||||
"source": [
|
||||
"import asyncio\n",
|
||||
"import logging\n",
|
||||
"\n",
|
||||
"from IPython.display import display\n",
|
||||
"from langchain_core.runnables import RunnableConfig\n",
|
||||
"from langgraph.constants import START, END\n",
|
||||
"from langgraph.graph import StateGraph\n",
|
||||
"from langgraph.types import RetryPolicy\n",
|
||||
"from pydantic import BaseModel\n",
|
||||
"from requests import HTTPError\n",
|
||||
"\n",
|
||||
"logger = logging.getLogger(__name__)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# 全局状态\n",
|
||||
"class EmptyState(BaseModel):\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"async def node_a(state: EmptyState, config: RunnableConfig) -> EmptyState:\n",
|
||||
" print(\"11111111111\")\n",
|
||||
" await asyncio.sleep(5)\n",
|
||||
" raise HTTPError(\"node_a\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"builder = StateGraph(state_schema=EmptyState)\n",
|
||||
"builder.add_node(\"node_a\", node_a, retry_policy=RetryPolicy(max_attempts=3), timeout=1)\n",
|
||||
"\n",
|
||||
"builder.add_edge(START, \"node_a\")\n",
|
||||
"builder.add_edge(\"node_a\", END)\n",
|
||||
"\n",
|
||||
"workflow = builder.compile()\n",
|
||||
"try:\n",
|
||||
" result = await workflow.ainvoke({})\n",
|
||||
" # print(result)\n",
|
||||
"except HTTPError as e:\n",
|
||||
" pass\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"display(workflow)"
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"11111111111\n",
|
||||
"11111111111\n",
|
||||
"11111111111\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"ename": "NodeTimeoutError",
|
||||
"evalue": "Node 'node_a' exceeded its run timeout of 1.000s (elapsed: 1.007s).",
|
||||
"output_type": "error",
|
||||
"traceback": [
|
||||
"\u001B[31m---------------------------------------------------------------------------\u001B[39m",
|
||||
"\u001B[31mTimeoutError\u001B[39m Traceback (most recent call last)",
|
||||
"\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_retry.py:489\u001B[39m, in \u001B[36m_arun_with_timeout\u001B[39m\u001B[34m(task, config, timeout, attempt_ctx, stream)\u001B[39m\n\u001B[32m 488\u001B[39m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[32m--> \u001B[39m\u001B[32m489\u001B[39m \u001B[38;5;28;01mawait\u001B[39;00m watchdog\n\u001B[32m 490\u001B[39m \u001B[38;5;28;01mexcept\u001B[39;00m asyncio.TimeoutError \u001B[38;5;28;01mas\u001B[39;00m exc:\n",
|
||||
"\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_retry.py:419\u001B[39m, in \u001B[36m_run_timeout_watchdog\u001B[39m\u001B[34m(run_timeout_s)\u001B[39m\n\u001B[32m 418\u001B[39m \u001B[38;5;28;01mawait\u001B[39;00m asyncio.sleep(run_timeout_s)\n\u001B[32m--> \u001B[39m\u001B[32m419\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m asyncio.TimeoutError\n",
|
||||
"\u001B[31mTimeoutError\u001B[39m: ",
|
||||
"\nThe above exception was the direct cause of the following exception:\n",
|
||||
"\u001B[31mNodeTimeoutError\u001B[39m Traceback (most recent call last)",
|
||||
"\u001B[36mCell\u001B[39m\u001B[36m \u001B[39m\u001B[32mIn[25]\u001B[39m\u001B[32m, line 36\u001B[39m\n\u001B[32m 32\u001B[39m workflow = builder.compile()\n\u001B[32m 33\u001B[39m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[32m 34\u001B[39m result = \u001B[38;5;28;01mawait\u001B[39;00m workflow.ainvoke({})\n\u001B[32m 35\u001B[39m \u001B[38;5;66;03m# print(result)\u001B[39;00m\n\u001B[32m---> \u001B[39m\u001B[32m36\u001B[39m \u001B[38;5;28;01mexcept\u001B[39;00m HTTPError \u001B[38;5;28;01mas\u001B[39;00m e:\n\u001B[32m 37\u001B[39m \u001B[38;5;28;01mpass\u001B[39;00m\n\u001B[32m 38\u001B[39m \n\u001B[32m 39\u001B[39m \n",
|
||||
"\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\main.py:4090\u001B[39m, in \u001B[36mPregel.ainvoke\u001B[39m\u001B[34m(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, control, version, **kwargs)\u001B[39m\n\u001B[32m 4087\u001B[39m chunks.append(chunk)\n\u001B[32m 4088\u001B[39m \u001B[38;5;28;01melse\u001B[39;00m:\n\u001B[32m 4089\u001B[39m \u001B[38;5;66;03m# v1: collect interrupts from updates stream\u001B[39;00m\n\u001B[32m-> \u001B[39m\u001B[32m4090\u001B[39m \u001B[38;5;28;01masync\u001B[39;00m \u001B[38;5;28;01mfor\u001B[39;00m chunk \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28mself\u001B[39m.astream(\n\u001B[32m 4091\u001B[39m \u001B[38;5;28minput\u001B[39m,\n\u001B[32m 4092\u001B[39m config,\n\u001B[32m 4093\u001B[39m context=context,\n\u001B[32m 4094\u001B[39m stream_mode=(\n\u001B[32m 4095\u001B[39m [\u001B[33m\"\u001B[39m\u001B[33mupdates\u001B[39m\u001B[33m\"\u001B[39m, \u001B[33m\"\u001B[39m\u001B[33mvalues\u001B[39m\u001B[33m\"\u001B[39m] \u001B[38;5;28;01mif\u001B[39;00m stream_mode == \u001B[33m\"\u001B[39m\u001B[33mvalues\u001B[39m\u001B[33m\"\u001B[39m \u001B[38;5;28;01melse\u001B[39;00m stream_mode\n\u001B[32m 4096\u001B[39m ),\n\u001B[32m 4097\u001B[39m print_mode=print_mode,\n\u001B[32m 4098\u001B[39m output_keys=output_keys,\n\u001B[32m 4099\u001B[39m interrupt_before=interrupt_before,\n\u001B[32m 4100\u001B[39m interrupt_after=interrupt_after,\n\u001B[32m 4101\u001B[39m durability=durability,\n\u001B[32m 4102\u001B[39m control=control,\n\u001B[32m 4103\u001B[39m **kwargs,\n\u001B[32m 4104\u001B[39m ):\n\u001B[32m 4105\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m stream_mode == \u001B[33m\"\u001B[39m\u001B[33mvalues\u001B[39m\u001B[33m\"\u001B[39m:\n\u001B[32m 4106\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mlen\u001B[39m(chunk) == \u001B[32m2\u001B[39m:\n",
|
||||
"\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\main.py:3440\u001B[39m, in \u001B[36mPregel.astream\u001B[39m\u001B[34m(self, input, config, context, stream_mode, print_mode, output_keys, interrupt_before, interrupt_after, durability, control, subgraphs, debug, version, **kwargs)\u001B[39m\n\u001B[32m 3438\u001B[39m \u001B[38;5;28;01mfor\u001B[39;00m task \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28;01mawait\u001B[39;00m loop.amatch_cached_writes():\n\u001B[32m 3439\u001B[39m loop.output_writes(task.id, task.writes, cached=\u001B[38;5;28;01mTrue\u001B[39;00m)\n\u001B[32m-> \u001B[39m\u001B[32m3440\u001B[39m \u001B[38;5;28;01masync\u001B[39;00m \u001B[38;5;28;01mfor\u001B[39;00m _ \u001B[38;5;129;01min\u001B[39;00m runner.atick(\n\u001B[32m 3441\u001B[39m [t \u001B[38;5;28;01mfor\u001B[39;00m t \u001B[38;5;129;01min\u001B[39;00m loop.tasks.values() \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m t.writes],\n\u001B[32m 3442\u001B[39m timeout=\u001B[38;5;28mself\u001B[39m.step_timeout,\n\u001B[32m 3443\u001B[39m get_waiter=get_waiter,\n\u001B[32m 3444\u001B[39m schedule_task=loop.aaccept_push,\n\u001B[32m 3445\u001B[39m ):\n\u001B[32m 3446\u001B[39m \u001B[38;5;66;03m# emit output\u001B[39;00m\n\u001B[32m 3447\u001B[39m \u001B[38;5;28;01mfor\u001B[39;00m o \u001B[38;5;129;01min\u001B[39;00m _output(\n\u001B[32m 3448\u001B[39m stream_mode,\n\u001B[32m 3449\u001B[39m print_mode,\n\u001B[32m (...)\u001B[39m\u001B[32m 3455\u001B[39m _state_mapper,\n\u001B[32m 3456\u001B[39m ):\n\u001B[32m 3457\u001B[39m \u001B[38;5;28;01myield\u001B[39;00m o\n",
|
||||
"\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_runner.py:396\u001B[39m, in \u001B[36mPregelRunner.atick\u001B[39m\u001B[34m(self, tasks, reraise, timeout, retry_policy, get_waiter, schedule_task)\u001B[39m\n\u001B[32m 394\u001B[39m scheduled_error_handler = \u001B[38;5;28;01mFalse\u001B[39;00m\n\u001B[32m 395\u001B[39m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[32m--> \u001B[39m\u001B[32m396\u001B[39m \u001B[38;5;28;01mawait\u001B[39;00m arun_with_retry(\n\u001B[32m 397\u001B[39m t,\n\u001B[32m 398\u001B[39m retry_policy,\n\u001B[32m 399\u001B[39m stream=\u001B[38;5;28mself\u001B[39m.use_astream,\n\u001B[32m 400\u001B[39m configurable={\n\u001B[32m 401\u001B[39m CONFIG_KEY_CALL: partial(\n\u001B[32m 402\u001B[39m _acall,\n\u001B[32m 403\u001B[39m weakref.ref(t),\n\u001B[32m 404\u001B[39m stream=\u001B[38;5;28mself\u001B[39m.use_astream,\n\u001B[32m 405\u001B[39m retry_policy=retry_policy,\n\u001B[32m 406\u001B[39m futures=weakref.ref(futures),\n\u001B[32m 407\u001B[39m schedule_task=schedule_task,\n\u001B[32m 408\u001B[39m submit=\u001B[38;5;28mself\u001B[39m.submit,\n\u001B[32m 409\u001B[39m loop=loop,\n\u001B[32m 410\u001B[39m ),\n\u001B[32m 411\u001B[39m },\n\u001B[32m 412\u001B[39m )\n\u001B[32m 413\u001B[39m \u001B[38;5;28mself\u001B[39m.commit(t, \u001B[38;5;28;01mNone\u001B[39;00m)\n\u001B[32m 414\u001B[39m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mException\u001B[39;00m \u001B[38;5;28;01mas\u001B[39;00m exc:\n",
|
||||
"\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_retry.py:745\u001B[39m, in \u001B[36marun_with_retry\u001B[39m\u001B[34m(task, retry_policy, stream, match_cached_writes, configurable)\u001B[39m\n\u001B[32m 743\u001B[39m \u001B[38;5;28;01mbreak\u001B[39;00m\n\u001B[32m 744\u001B[39m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;01mawait\u001B[39;00m task.proc.ainvoke(task.input, config)\n\u001B[32m--> \u001B[39m\u001B[32m745\u001B[39m result = \u001B[38;5;28;01mawait\u001B[39;00m _arun_with_timeout(\n\u001B[32m 746\u001B[39m task, config, resolved_timeout, attempt_ctx, stream=stream\n\u001B[32m 747\u001B[39m )\n\u001B[32m 748\u001B[39m _finish_timed_attempt(config, attempt_ctx)\n\u001B[32m 749\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m stream:\n\u001B[32m 750\u001B[39m \u001B[38;5;66;03m# if successful, end\u001B[39;00m\n",
|
||||
"\u001B[36mFile \u001B[39m\u001B[32m~\\Desktop\\python-develop\\lotteryServer\\.venv\\Lib\\site-packages\\langgraph\\pregel\\_retry.py:496\u001B[39m, in \u001B[36m_arun_with_timeout\u001B[39m\u001B[34m(task, config, timeout, attempt_ctx, stream)\u001B[39m\n\u001B[32m 494\u001B[39m bg.cancel()\n\u001B[32m 495\u001B[39m bg.add_done_callback(_drain_cancelled)\n\u001B[32m--> \u001B[39m\u001B[32m496\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m NodeTimeoutError(\n\u001B[32m 497\u001B[39m task.name,\n\u001B[32m 498\u001B[39m elapsed,\n\u001B[32m 499\u001B[39m kind=kind,\n\u001B[32m 500\u001B[39m idle_timeout=idle_timeout_s,\n\u001B[32m 501\u001B[39m run_timeout=run_timeout_s,\n\u001B[32m 502\u001B[39m ) \u001B[38;5;28;01mfrom\u001B[39;00m\u001B[38;5;250m \u001B[39m\u001B[34;01mexc\u001B[39;00m\n\u001B[32m 503\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mRuntimeError\u001B[39;00m(\n\u001B[32m 504\u001B[39m \u001B[33mf\u001B[39m\u001B[33m\"\u001B[39m\u001B[38;5;132;01m{\u001B[39;00mkind\u001B[38;5;132;01m}\u001B[39;00m\u001B[33m timeout watchdog completed without raising TimeoutError\u001B[39m\u001B[33m\"\u001B[39m\n\u001B[32m 505\u001B[39m )\n\u001B[32m 506\u001B[39m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mRuntimeError\u001B[39;00m(\u001B[33m\"\u001B[39m\u001B[33mtimeout wait completed without task or watchdog\u001B[39m\u001B[33m\"\u001B[39m)\n",
|
||||
"\u001B[31mNodeTimeoutError\u001B[39m: Node 'node_a' exceeded its run timeout of 1.000s (elapsed: 1.007s).",
|
||||
"During task with name 'node_a' and id 'b35c22ae-a4be-38ef-f687-20b76ea2dd87'"
|
||||
]
|
||||
}
|
||||
],
|
||||
"execution_count": 25
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
75
lg/steam_out.ipynb
Normal file
75
lg/steam_out.ipynb
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "initial_id",
|
||||
"metadata": {
|
||||
"collapsed": true
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langgraph.graph import StateGraph, START, END, MessagesState\n",
|
||||
"from langchain.messages import HumanMessage\n",
|
||||
"from langchain_deepseek import ChatDeepSeek\n",
|
||||
"\n",
|
||||
"from core.config.settings import settings\n",
|
||||
"\n",
|
||||
"model = ChatDeepSeek(\n",
|
||||
" model=\"deepseek-v4-flash\",\n",
|
||||
" api_key=settings.DEEPSEEK_API_KEY,\n",
|
||||
" extra_body={\n",
|
||||
" \"thinking\": {\n",
|
||||
" \"type\": \"disabled\"\n",
|
||||
" }\n",
|
||||
" }\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"def llm_node(state: MessagesState) -> MessagesState:\n",
|
||||
" messages = state[\"messages\"]\n",
|
||||
" response = model.invoke(messages)\n",
|
||||
"\n",
|
||||
" return {\n",
|
||||
" \"messages\": [response],\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
"builder = StateGraph(state_schema=MessagesState)\n",
|
||||
"builder.add_node(\"llm_node\", llm_node)\n",
|
||||
"builder.add_edge(START, \"llm_node\")\n",
|
||||
"builder.add_edge(\"llm_node\", END)\n",
|
||||
"\n",
|
||||
"graph = builder.compile()\n",
|
||||
"\n",
|
||||
"# 使用流式输出\n",
|
||||
"for chunk in graph.stream(\n",
|
||||
" {\n",
|
||||
" \"messages\":[HumanMessage(content=\"你好!\")]\n",
|
||||
" },\n",
|
||||
" stream_mode=[\"values\",\"messages\"],\n",
|
||||
"):\n",
|
||||
" print(chunk)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 2
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython2",
|
||||
"version": "2.7.6"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
33
main.py
33
main.py
@@ -1,23 +1,24 @@
|
||||
import logging
|
||||
import structlog
|
||||
|
||||
import models
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from sqlmodel import SQLModel
|
||||
|
||||
from api.agent_api import agent_router
|
||||
from core.config.logging_cof import setup_logging
|
||||
from core.db import engine
|
||||
from core.config.settings import settings
|
||||
from api.scheduler_api import scheduler_router
|
||||
from api.lottery_api import lottery_router
|
||||
from core.task.dlt_scheduler import scheduler
|
||||
from exceptions.handlers import register_exception_handlers
|
||||
from lc.chef_agent import chef_agent
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
# 全局设置日志配置
|
||||
setup_logging()
|
||||
logger = structlog.getLogger(__name__)
|
||||
|
||||
# lifespan生命周期
|
||||
@asynccontextmanager
|
||||
@@ -27,18 +28,34 @@ async def lifespan(app: FastAPI):
|
||||
# 同步建表
|
||||
SQLModel.metadata.create_all(engine)
|
||||
|
||||
logger.info("服务启动,启动定时调度器")
|
||||
# 启动定时调度器
|
||||
scheduler.start()
|
||||
|
||||
logger.info("服务启动,创建携程初始化chef_agent任务")
|
||||
# 异步初始化chef_agent
|
||||
chef_agent.initialize()
|
||||
|
||||
logger.info("服务启动完成")
|
||||
try:
|
||||
yield # 此处交出控制权,服务开始运行
|
||||
finally:
|
||||
# ========== 关闭时执行 ==========
|
||||
logger.info("服务关闭,释放资源")
|
||||
chef_agent.close()
|
||||
scheduler.shutdown()
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
# 注册全局异常处理
|
||||
register_exception_handlers(app)
|
||||
|
||||
# 注册路由
|
||||
app.include_router(scheduler_router)
|
||||
app.include_router(lottery_router)
|
||||
app.include_router(agent_router)
|
||||
|
||||
# 注册中间件
|
||||
app.add_middleware(
|
||||
CORSMiddleware, # type: ignore[arg-type]
|
||||
allow_origins=settings.ALLOWED_ORIGINS,
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
[project]
|
||||
name = "lotteryserver"
|
||||
version = "0.1.0"
|
||||
requires-python = ">=3.14"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"apscheduler==3.11.3",
|
||||
"fastapi>=0.141.1",
|
||||
"ipykernel>=7.3.0",
|
||||
"ipython>=9.17.1",
|
||||
"langchain>=1.4.0",
|
||||
"langchain-core==1.6.2",
|
||||
"langchain-deepseek==1.1.0",
|
||||
"langchain-tavily==0.2.18",
|
||||
"langgraph>=1.2.11",
|
||||
"langgraph-checkpoint-postgres>=3.1.2",
|
||||
"langgraph-checkpoint-sqlite==3.1.1",
|
||||
"notebook>=7.6.2",
|
||||
"openai==3.8.0",
|
||||
"psycopg>=3.3.5",
|
||||
"psycopg-binary>=3.3.5",
|
||||
"psycopg-pool>=3.3.1",
|
||||
"pydantic==2.13.5",
|
||||
"pydantic-settings==2.15.0",
|
||||
"pymysql==1.2.0",
|
||||
@@ -13,5 +26,6 @@ dependencies = [
|
||||
"sqlalchemy>=2.0.52",
|
||||
"sqlmodel==0.0.42",
|
||||
"starlette>=1.6.0",
|
||||
"structlog>=26.1.0",
|
||||
"uvicorn==0.52.4",
|
||||
]
|
||||
|
||||
0
services/agent_server/__init__.py
Normal file
0
services/agent_server/__init__.py
Normal file
29
services/agent_server/chef_service.py
Normal file
29
services/agent_server/chef_service.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from typing import Iterator
|
||||
|
||||
from langchain_core.messages import HumanMessage
|
||||
from langchain_core.runnables import RunnableConfig
|
||||
|
||||
from lc.chef_agent import chef_agent
|
||||
|
||||
|
||||
class ChefService:
|
||||
|
||||
@staticmethod
|
||||
def chat(
|
||||
thread_id: str,
|
||||
message: str,
|
||||
):
|
||||
config: RunnableConfig = {"configurable": {"thread_id": thread_id}}
|
||||
result = chef_agent.agent.invoke({"messages": [HumanMessage(content=message)]}, config)
|
||||
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def chat_stream(thread_id: str, message: str) -> Iterator[str]:
|
||||
config: RunnableConfig = {"configurable": {"thread_id": thread_id}}
|
||||
result = chef_agent.agent.stream({"messages": [("user", message)]}, config=config, stream_mode="messages")
|
||||
|
||||
for message_chunk, metadata in result:
|
||||
content = message_chunk.content
|
||||
if content:
|
||||
yield content
|
||||
20
test.py
Normal file
20
test.py
Normal file
@@ -0,0 +1,20 @@
|
||||
a: int | None = None
|
||||
|
||||
def f(v: int) -> int:
|
||||
pass
|
||||
|
||||
def f2(v: int) -> int:
|
||||
a = 5
|
||||
|
||||
f(a)
|
||||
|
||||
class Test:
|
||||
def __init__(self):
|
||||
self.a: int | None = None
|
||||
|
||||
def f(self, v: int) -> int:
|
||||
pass
|
||||
|
||||
def f2(self):
|
||||
self.a = 5
|
||||
self.f(self.a)
|
||||
Reference in New Issue
Block a user