import os import asyncio from datetime import datetime from deepagents import create_deep_agent from openai import OpenAI from langchain.chat_models import init_chat_model from langchain.agents import create_agent from langgraph.checkpoint.memory import InMemorySaver, MemorySaver # 导入检查点工具 from deepagents.backends import StoreBackend from loguru import logger def internet_search_tool(query: str): """Run a web search""" logger.info(f"Running internet search for query: {query}") client = OpenAI( api_key=os.getenv('DASHSCOPE_API_KEY'), base_url=os.getenv('DASHSCOPE_BASE_URL'), ) logger.info(f"create OpenAI") completion = client.chat.completions.create( model="qwen-plus", messages=[ {'role': 'system', 'content': 'You are a helpful assistant.'}, {'role': 'user', 'content': query} ], extra_body={ "enable_search": True } ) logger.info(f"create completions") logger.info(f"OpenAI response: {completion.choices[0].message.content}") return completion.choices[0].message.content # System prompt to steer the agent to be an expert researcher today = datetime.now().strftime("%Y年%m月%d日") research_instructions = f"""你是一个智能助手。你的任务是帮助用户完成各种任务。 你可以使用互联网搜索工具来获取信息。 ## `internet_search` 使用此工具对给定查询进行互联网搜索。你可以指定返回结果的最大数量、主题以及是否包含原始内容。 今天的日期是:{today} """ # Create the deep agent with memory model = init_chat_model( model="gpt-4.1-mini", model_provider='openai', api_key=os.getenv('OPENAI_API_KEY'), base_url=os.getenv('OPENAI_BASE_URL'), ) checkpointer = InMemorySaver() # 创建内存检查点,自动保存历史 agent = create_deep_agent( # state:thread会话级的状态 tools=[internet_search_tool], system_prompt=research_instructions, model=model, checkpointer=checkpointer, # 添加检查点,启用自动记忆 interrupt_on={'internet_search_tool':True} ) # 多轮对话循环(使用 Checkpointer 自动记忆) printed_msg_ids = set() # 跟踪已打印的消息ID thread_id = "user_session_001" # 会话 ID,区分不同用户/会话 config = {"configurable": {"thread_id": thread_id}, "metastore": {'assistant_id': 'owenliang'}} # 配置会话 print("开始对话(输入 'exit' 退出):") while True: user_input = input("\nHUMAN: ").strip() if user_input.lower() == 'exit': break # 使用 values 模式多次返回完整状态,这里按 message.id 去重,并按类型分类打印 pending_resume = None while True: if pending_resume is None: request = {"messages": [{"role": "user", "content": user_input}]} else: from langgraph.types import Command as _Command request = _Command(resume=pending_resume) pending_resume = None for item in agent.stream( request, config=config, stream_mode="values", ): state = item[0] if isinstance(item, tuple) and len(item) == 2 else item # 先检查是否触发了 Human-In-The-Loop 中断 if isinstance(state, dict) and "__interrupt__" in state: interrupts = state["__interrupt__"] or [] if interrupts: hitl_payload = interrupts[0].value action_requests = hitl_payload.get("action_requests", []) print("\n=== 需要人工审批的工具调用 ===") decisions: list[dict[str, str]] = [] for idx, ar in enumerate(action_requests): name = ar.get("name") args = ar.get("args") print(f"[{idx}] 工具 {name} 参数: {args}") while True: choice = input(" 决策 (a=approve, r=reject): ").strip().lower() if choice in ("a", "r"): break decisions.append({"type": "approve" if choice == "a" else "reject"}) # 下一轮调用改为 resume,同一轮用户回合继续往下跑 pending_resume = {"decisions": decisions} break # 兼容 dict state 和 AgentState dataclass messages = state.get("messages", []) if isinstance(state, dict) else getattr(state, "messages", []) for msg in messages: msg_id = getattr(msg, "id", None) if msg_id is not None and msg_id in printed_msg_ids: continue if msg_id is not None: printed_msg_ids.add(msg_id) msg_type = getattr(msg, "type", None) if msg_type == "human": # 用户输入已经在命令行里,不再重复打印 continue if msg_type == "ai": tool_calls = getattr(msg, "tool_calls", None) or [] if tool_calls: # 这是发起工具调用的 AI 消息(TOOL CALL) for tc in tool_calls: tool_name = tc.get("name") args = tc.get("args") print(f"TOOL CALL [{tool_name}]: {args}") # 如果 AI 同时带有自然语言内容,也一起打印 if getattr(msg, "content", None): print(f"AI: {msg.content}") continue if msg_type == "tool": # 工具执行结果(TOOL RESPONSE) tool_name = getattr(msg, "name", None) or "tool" print(f"TOOL RESPONSE [{tool_name}]: {msg.content}") continue # 兜底:其它类型直接打印出来便于调试 print(f"[{msg_type}]: {getattr(msg, 'content', None)}") # 如果没有新的中断需要 resume,则整轮结束,等待下一轮用户输入 if pending_resume is None: break