U2INVEST / agent_graph.py
DasbootU9607
feat: initial clean commit
0001f12
import os
import sqlite3
from typing import Annotated, TypedDict
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage # Added for persona
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
from langgraph.checkpoint.sqlite import SqliteSaver
from tools import tools
from runtime_config import CHECKPOINTS_DB_PATH
load_dotenv()
DEEPSEEK_MODEL = os.getenv("DEEPSEEK_MODEL", "deepseek-chat")
DEEPSEEK_BASE_URL = os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com")
# 1. Define State
class AgentState(TypedDict):
messages: Annotated[list, add_messages]
# 2. Setup LLM & Tools
llm = ChatOpenAI(
model=DEEPSEEK_MODEL,
openai_api_key=os.getenv("DEEPSEEK_API_KEY"),
openai_api_base=DEEPSEEK_BASE_URL,
temperature=0
)
llm_with_tools = llm.bind_tools(tools)
# 3. Define Nodes
def call_model(state: AgentState):
"""
Node for LLM reasoning with a professional persona and structured logic.
"""
# Define the core instructions for the AI
# 在 call_model 函数中修改 SystemMessage
system_message = SystemMessage(content="""You are the 'U2CHAT bot', a professional stock analysis assistant.
Your goal is to provide objective and analytical financial guidance.
Core Directives:
1. Language: Respond exclusively in English.
2. Brevity & Structure: Be concise. Use clear headings (###), bullet points, and short paragraphs. Avoid "walls of text".
3. Clean Formatting: Strictly DO NOT use double asterisks (**) for emphasis within sentences or around single words. Use standard Markdown headers for organization only.
4. Data Visualization: Whenever analyzing a stock (e.g., K-line trends, price history), you MUST provide the data in a structured JSON block wrapped in ```json-chart``` tags.
Format:
```json-chart
{
"type": "line",
"title": "Title of the Chart",
"labels": ["Jan", "Feb", "Mar"],
"data": [100, 120, 110]
}
```
Supported types: "line", "bar". Ensure 'labels' and 'data' arrays have the same length.
Response Template:
### Market Overview: (1-2 sentences on current status)
### Key Data: (Include the json-chart block here if applicable, or a Markdown Table)
### Technical Analysis: (Brief bullet points on trends/indicators)
### Visual Trend: (A simple ASCII-based visual or directional emoji chart)
Tone: Professional, objective, and analytical.""")
# Inject the system message at the start of the conversation
messages = [system_message] + state["messages"]
response = llm_with_tools.invoke(messages)
return {"messages": [response]}
def route_logic(state: AgentState):
"""Router to decide if tools are needed."""
last_msg = state["messages"][-1]
return "tools_executor" if last_msg.tool_calls else END
# 4. Persistence Setup
# Connection remains open for the lifecycle of the app
conn = sqlite3.connect(str(CHECKPOINTS_DB_PATH), check_same_thread=False)
memory = SqliteSaver(conn)
# 5. Build Graph
workflow = StateGraph(AgentState)
workflow.add_node("llm_reasoning", call_model)
workflow.add_node("tools_executor", ToolNode(tools))
workflow.set_entry_point("llm_reasoning")
workflow.add_conditional_edges("llm_reasoning", route_logic)
workflow.add_edge("tools_executor", "llm_reasoning")
# 6. Compile with Checkpointer
stock_agent_app = workflow.compile(checkpointer=memory)