如何在5分钟内打造自己的巴菲特代理

如何在5分钟内打造自己的巴菲特代理

如果你可以随时向沃伦-巴菲特请教有关股票、市场趋势或长期投资的问题,你会怎么做?有报道称,巴菲特可能很快卸任伯克希尔-哈撒韦公司首席执行官一职,现在正是反思他的原则的持久价值的好时机。数十年来,巴菲特一直是投资界的坚定代言人,他以注重价值、耐心和了解自己所拥有的资产而闻名。在本指南中,我将向你展示如何将这些原则转化为对话式巴菲特代理,通过他的视角评估公司,并使用实时股票数据和新闻进行互动。我们的目标不是再现巴菲特,而是建立一个聊天机器人,帮助你以巴菲特的方式思考。

项目目标和架构

我们的目标很明确:创建一个能像巴菲特一样进行互动的巴菲特代理。它应该讨论投资理念,用巴菲特的核心原则分析股票,并利用实时数据。

主要组成部分包括

  1. 语言模型(OpenAI):提供对话能力和角色遵从性。
  2. 语言链(LangChain):作为框架,连接语言模型、工具和内存。
  3. 股票数据 API(雅虎财经):获取当前股票价格和基本面数据。
  4. 新闻 API (SerpAPI):检索最近的新闻头条以了解上下文。
  5. Streamlit为用户交互构建基于网络的聊天界面。

第 1 步:准备环境

编码前,请确保您的计算机已准备就绪。

  • 安装 Python:您需要 Python 3.8 或更新版本。
  • 获取 API 密钥:从 OpenAI 获取语言功能的 API 密钥。从 SerpAPI 获取另一个密钥,用于新闻搜索。妥善保管这些密钥。
  • 安装库:打开电脑终端或命令提示符。运行以下命令安装必要的
  • Python 软件包:
pip install langchain langchain-openai langchain-community openai yfinance google-search-results streamlit python-dotenv streamlit-chat
  • 创建 .env 文件(可选):在保存脚本的目录下,创建一个名为 .env 的文件。像这样添加键值:
OPENAI_API_KEY="sk-YOUR_KEY_HERE"
SERPAPI_API_KEY="YOUR_KEY_HERE"

第 2 步:启动脚本并导入库

创建一个新的 Python 文件(如 buffett_chatbot.py)。首先在顶部导入所需的模块:

import streamlit as st
import os
import json
import yfinance as yf
from dotenv import load_dotenv
# LangChain components
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain.memory import ConversationBufferMemory
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.messages import SystemMessage # No need for HumanMessage/AIMessage here anymore
from langchain.tools import Tool
from langchain_community.utilities import SerpAPIWrapper
# --- Load .env file (as a fallback) ---
load_dotenv()

这些导入包括用于界面的 Streamlit、用于环境变量的 os、用于数据处理的 json、用于股票的 yfinance、用于密钥加载的 dotenv 以及用于代理逻辑的各种 LangChain 组件。

第 3 步:设置Streamlit界面

配置基本应用布局,为 API 密钥创建侧边栏输入:

# --- Page Config ---
st.set_page_config(page_title="Warren Buffett Bot", layout="wide")
st.title("Warren Buffett Investment Chatbot 📈")
st.caption("Ask me about investing, stocks, or market wisdom - in the style of Warren Buffett.")
# --- API Key Input in Sidebar ---
st.sidebar.header("API Configuration")
# Initialize session state for keys if they don't exist
if 'openai_api_key' not in st.session_state:
   st.session_state.openai_api_key = ""
if 'serpapi_api_key' not in st.session_state:
   st.session_state.serpapi_api_key = ""
# Create text input fields for keys, storing values in session state
input_openai_key = st.sidebar.text_input(
   "OpenAI API Key", type="password", value=st.session_state.openai_api_key, key="openai_input"
)
input_serpapi_key = st.sidebar.text_input(
   "SerpAPI API Key", type="password", value=st.session_state.serpapi_key, key="serpapi_input"
)
# Update session state with current input values
st.session_state.openai_api_key = input_openai_key
st.session_state.serpapi_key = input_serpapi_key
# Determine which keys are active (user input takes priority)
active_openai_key = st.session_state.openai_api_key or os.getenv("OPENAI_API_KEY")
active_serpapi_key = st.session_state.serpapi_api_key or os.getenv("SERPAPI_API_KEY")
# --- Display API Status ---
st.sidebar.header("API Status")
# (Add the if/else blocks using st.sidebar.success/error/warning as in the provided code)
if active_openai_key: st.sidebar.success(...) else: st.sidebar.error(...)
# Check and display SerpAPI status similarly

这段代码用于设置 Streamlit 聊天机器人应用程序的可视化部分。它使用 st.session_state 来记住用户在会话期间输入的 API 密钥。

第 4 步:定义核心设置和巴菲特角色

为人工智能模型建立常量,并定义塑造聊天机器人个性的详细指令(系统提示):

# --- Constants & Prompt ---
MODEL_NAME = "gpt-4o" # Specify the OpenAI model
TEMPERATURE = 0.5     # Controls AI creativity (lower is more predictable)
MEMORY_KEY = "chat_history" # Key for storing conversation history
BUFFETT_SYSTEM_PROMPT = """
You are a conversational AI assistant modeled after Warren Buffett, the legendary value investor. Embody his persona accurately. 
**Your Core Principles:**
*   **Value Investing:** Focus on finding undervalued companies with solid fundamentals (earnings, low debt, strong management). Judge businesses, not stock tickers.
*   **Long-Term Horizon:** Think in terms of decades, not days or months. Discourage short-term speculation and market timing.
*   **Margin of Safety:** Only invest when the market price is significantly below your estimate of intrinsic value. Be conservative.
*   **Business Moats:** Favor companies with durable competitive advantages (strong brands, network effects, low-cost production, regulatory advantages).
*   **Understand the Business:** Only invest in companies you understand. "Risk comes from not knowing what you're doing."
*   **Management Quality:** Assess the integrity and competence of the company's leadership.
*   **Patience and Discipline:** Wait for the right opportunities ("fat pitches"). Avoid unnecessary activity. Be rational and unemotional.
*   **Circle of Competence:** Stick to industries and businesses you can reasonably understand. Acknowledge what you don't know.
**Your Communication Style:**
*   **Wise and Folksy:** Use simple language, analogies, and occasional humor, much like Buffett does in his letters and interviews.
*   **Patient and Calm:** Respond thoughtfully, avoiding hype or panic.
*   **Educational:** Explain your reasoning clearly, referencing your core principles.
*   **Prudent:** Be cautious about making specific buy/sell recommendations without thorough analysis based on your principles. Often, you might explain *how* you would analyze it rather than giving a direct 'yes' or 'no'.
*   **Quote Yourself:** Occasionally weave in famous Buffett quotes where appropriate (e.g., "Price is what you pay; value is what you get.", "Be fearful when others are greedy and greedy when others are fearful.").
*   **Acknowledge Limitations:** If asked about something outside your expertise (e.g., complex tech you wouldn't invest in, short-term trading), politely state it's not your area.
**Interaction Guidelines:**
*   When asked for stock recommendations, first use your tools to gather fundamental data (P/E, earnings, debt if possible) and recent news.
*   Analyze the gathered information through the lens of your core principles (moat, management, valuation, long-term prospects).
*   Explain your thought process clearly.
*   If a company seems to fit your criteria, express cautious optimism, emphasizing the need for further due diligence by the investor.
*   If a company doesn't fit (e.g., too speculative, high P/E without justification, outside circle of competence), explain why based on your principles.
*   If asked for general advice, draw upon your well-known philosophies.
*   Maintain conversational context using the provided chat history. Refer back to previous points if relevant.
Remember: You are simulating Warren Buffett. Your goal is to provide insights consistent with his philosophy and communication style, leveraging the tools for data when needed. Do not give definitive financial advice, but rather educate and explain the *Buffett way* of thinking about investments.
"""

第 5 步:创建数据获取工具

实现聊天机器人获取外部股票和新闻数据的功能。

# --- Tool Definitions ---
# 1. Stock Data Tool (Yahoo Finance) - No changes needed here
@st.cache_data(show_spinner=False) # Add caching for yfinance calls
def get_stock_info(symbol: str) -> str:
   # ... (keep the existing get_stock_info function code) ...
   """
   Fetches key financial data for a given stock symbol using Yahoo Finance...
   """
   try:
       ticker = yf.Ticker(symbol)
       info = ticker.info
       if not info or info.get('regularMarketPrice') is None and info.get('currentPrice') is None and info.get('previousClose') is None:
           hist = ticker.history(period="5d")
           if hist.empty:
                return f"Error: Could not retrieve any data for symbol {symbol}."
           last_close = hist['Close'].iloc[-1] if not hist.empty else 'N/A'
           current_price = info.get("currentPrice") or info.get("regularMarketPrice") or last_close
       else:
           current_price = info.get("currentPrice") or info.get("regularMarketPrice") or info.get("previousClose", "N/A")
       data = {
           "symbol": symbol, "companyName": info.get("longName", "N/A"),
           "currentPrice": current_price, "peRatio": info.get("trailingPE") or info.get("forwardPE", "N/A"),
           "earningsPerShare": info.get("trailingEps", "N/A"), "marketCap": info.get("marketCap", "N/A"),
           "dividendYield": info.get("dividendYield", "N/A"), "priceToBook": info.get("priceToBook", "N/A"),
           "sector": info.get("sector", "N/A"), "industry": info.get("industry", "N/A"),
           "summary": info.get("longBusinessSummary", "N/A")[:500] + ("..." if len(info.get("longBusinessSummary", "")) > 500 else "")
       }
       if data["currentPrice"] == "N/A": return f"Error: Could not retrieve current price for {symbol}."
       return json.dumps(data)
   except Exception as e: return f"Error fetching data for {symbol} using yfinance: {str(e)}."
stock_data_tool = Tool(
   name="get_stock_financial_data",
   func=get_stock_info,
   description="Useful for fetching fundamental financial data for a specific stock symbol (ticker)..." # Keep description
)
# 2. News Search Tool (SerpAPI) - Now uses active_serpapi_key
def create_news_search_tool(api_key):
   if api_key:
       try:
           params = {"engine": "google_news", "gl": "us", "hl": "en", "num": 5}
           search_wrapper = SerpAPIWrapper(params=params, serpapi_api_key=api_key)
           # Test connectivity during creation (optional, can slow down startup)
           # search_wrapper.run("test query")
           return Tool(
               name="search_stock_news",
               func=search_wrapper.run,
               description="Useful for searching recent news articles about a specific company or stock symbol..." # Keep description
           )
       except Exception as e:
           print(f"SerpAPI Tool Creation Warning: {e}")
           # Fallback to a dummy tool if key is provided but invalid/error occurs
           return Tool(
               name="search_stock_news",
               func=lambda x: f"News search unavailable (SerpAPI key configured, but error occurred: {e}).",
               description="News search tool (currently unavailable due to configuration error)."
           )
   else:
       # Dummy tool if no key is available
       return Tool(
           name="search_stock_news",
           func=lambda x: "News search unavailable (SerpAPI key not provided).",
           description="News search tool (unavailable - API key needed)."
       )
news_search_tool = create_news_search_tool(active_serpapi_key)
tools = [stock_data_tool, news_search_tool]

这些函数将成为股票数据分析机器人的“感官”,使其能够访问当前信息。将它们封装为工具对象后,LangChain 就可以使用它们了。

第 6 步:组装 LangChain 代理

配置核心人工智能逻辑:语言模型、提示结构、内存管理以及将它们联系在一起的代理执行器。这通常发生在脚本的主要部分,也就是条件检查中。

# --- Main App Logic ---
# Check if the essential OpenAI key is provided
if not active_openai_key:
   st.warning("Please enter your OpenAI API Key in the sidebar...", icon="🔑")
   st.stop() # Stop if no key
# --- LangChain Agent Setup (conditional on key) ---
try:
   # Initialize the OpenAI LLM
   llm = ChatOpenAI(
       model=MODEL_NAME, temperature=TEMPERATURE, openai_api_key=active_openai_key
   )
   # Create the prompt template
   prompt_template = ChatPromptTemplate.from_messages(
       [
           SystemMessage(content=BUFFETT_SYSTEM_PROMPT),
           MessagesPlaceholder(variable_name=MEMORY_KEY),
           ("human", "{input}"),
           MessagesPlaceholder(variable_name="agent_scratchpad"),
       ]
   )
   # Initialize or re-initialize agent components in session state
   reinitialize_agent = False
   # (Add the logic to check if 'agent_executor' exists or if keys changed)
   # ...
   if reinitialize_agent:
       # Initialize memory
       st.session_state['memory'] = ConversationBufferMemory(memory_key=MEMORY_KEY, return_messages=True)
       # Create the agent
       agent = create_openai_functions_agent(llm, tools, prompt_template)
       # Create the executor
       st.session_state['agent_executor'] = AgentExecutor(
           agent=agent, tools=tools, memory=st.session_state['memory'], verbose=True, # Set verbose=False for production
           handle_parsing_errors=True, max_iterations=5
       )
       # Store keys used for this agent instance
       st.session_state.agent_openai_key = active_openai_key
       st.session_state.agent_serpapi_key = active_serpapi_key
       # st.experimental_rerun() # Rerun to apply changes
   # Continue with chat history initialization and display...

这是 LangChain 聊天机器人开发的核心部分。它使用角色、工具和记忆设置代理,通过集成 OpenAI API 实现智能对话。在这里使用 st.session_state 对于在用户交互过程中保持代理的记忆至关重要。

第 7 步:实现聊天交互循环

添加代码,以便通过代理显示对话和处理用户输入。

# --- Chat History and Interaction ---
   # Initialize chat history if it doesn't exist
   if "messages" not in st.session_state:
       st.session_state["messages"] = [
           {"role": "assistant", "content": "Greetings! ..."} # Initial message
       ]
   # Display existing chat messages
   for msg in st.session_state.messages:
       st.chat_message(msg["role"]).write(msg["content"])
   # Get new user input
   if prompt := st.chat_input("Ask Buffett Bot..."):
       # Display user message
       st.session_state.messages.append({"role": "user", "content": prompt})
       st.chat_message("user").write(prompt)
       # Prepare input for the agent
       agent_input = {"input": prompt}
       # Invoke the agent executor
       try:
           with st.spinner("Buffett is pondering..."):
               # Get the executor instance from session state
               agent_executor_instance = st.session_state['agent_executor']
               response = agent_executor_instance.invoke(agent_input)
           # Display assistant response
           output = response.get('output', "Sorry, an error occurred.")
           st.session_state.messages.append({"role": "assistant", "content": output})
           st.chat_message("assistant").write(output)
       except Exception as e:
           # Handle errors during agent execution
           error_message = f"An error occurred: {str(e)}"
           st.error(error_message, icon="🔥")
           # Add error to chat display
           st.session_state.messages.append({"role": "assistant", "content": f"Sorry... {e}"})
           st.chat_message("assistant").write(f"Sorry... {e}")
   # Optional: Add the button to clear history
   if st.sidebar.button("Clear Chat History"):
       # (Code to clear st.session_state.messages and st.session_state.memory)
       st.rerun()

这部分使 Streamlit 聊天机器人应用程序具有交互性。它读取用户输入,将其发送给 LangChain 代理执行器,并显示用户的询问和机器人生成的回复。

第 8 步:运行巴菲特代理

保存完整的 Python 脚本。在脚本目录下打开终端并运行:

streamlit run buffett_chatbot.py

在终端运行该文件,浏览器就会打开应用程序,您就可以输入 API 密钥并与聊天机器人互动了。

分析输出

让我们用一些问题来测试巴菲特 AI 代理。您可以从此处获取同样的文件。

测试巴菲特 AI 代理

我们的 streamlit 应用程序看起来是这样的,在这里我们可以选择填写自己的 OpenAI 密钥和 SerpAPI 密钥。现在让我们测试一下机器人…

问题 1:“Mr. Buffett, could you explain your core investment philosophy in simple terms?”

巴菲特核心投资理念

问题 2:“Analyze Apple (AAPL) based on its current fundamentals. Would you consider it a good long-term investment based on your principles?”

AAPL长期投资项目

问题 3:“What are your thoughts on Microsoft (MSFT) considering its recent news and developments?”

MSFT看法

根据上述输出结果,我们可以看出机器人表现良好,并利用其所有功能实现了最终输出。它正在使用我们之前定义的沃伦-巴菲特角色来回答所有问题。机器人利用 yfinance 获取最新的股票价格和市盈率。SerpAPI 用于获取股票的最新消息。

小结

对于任何希望通过永恒原则的视角探索价值投资的人来说,巴菲特的这本参考书都是一个有用的伴侣。无论你是刚刚起步还是正在完善自己的投资方法,这款代理都能帮助你更清晰、更耐心地思考市场,就像巴菲特一样。

你可以在这里试用它:BuffettBot on Hugging Face

有问题想让代理回答吗?请在评论中留言,我很乐意听听你的问题和代理是如何回答的。

评论留言