feat(Agent): 新增维基百科搜索和温度查询工具并实现web界面

- 添加search_wikipedia和get_current_temperature工具函数
- 实现基于Streamlit的web交互界面
- 更新requirements.txt添加相关依赖
- 修复PROMPT_TEMPLATE变量名拼写错误
- 移除不再使用的工具函数
- 添加web界面截图到文档
This commit is contained in:
KMnO4-zx
2025-06-20 12:14:19 +08:00
parent cdf10fea16
commit 28636a0f9b
10 changed files with 249 additions and 41 deletions

View File

@@ -1,5 +1,5 @@
from src.core import Agent
from src.tools import add, count_letter_in_string, compare, get_current_datetime
from src.tools import add, count_letter_in_string, compare, get_current_datetime, search_wikipedia, get_current_temperature
from openai import OpenAI
@@ -13,7 +13,7 @@ if __name__ == "__main__":
agent = Agent(
client=client,
model="Qwen/Qwen2.5-32B-Instruct",
tools=[get_current_datetime, add, compare, count_letter_in_string],
tools=[get_current_datetime, search_wikipedia, get_current_temperature],
)
while True:

View File

@@ -1,4 +1,55 @@
json
openai
datetime
pprint
altair==5.5.0
annotated-types==0.7.0
anyio==4.9.0
attrs==25.3.0
beautifulsoup4==4.13.4
blinker==1.9.0
cachetools==6.1.0
certifi==2025.6.15
charset-normalizer==3.4.2
click==8.2.1
datetime==5.5
distro==1.9.0
gitdb==4.0.12
gitpython==3.1.44
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
idna==3.10
jinja2==3.1.6
jiter==0.10.0
jsonschema==4.24.0
jsonschema-specifications==2025.4.1
markupsafe==3.0.2
narwhals==1.43.1
numpy==2.3.0
openai==1.88.0
packaging==25.0
pandas==2.3.0
pillow==11.2.1
protobuf==6.31.1
pyarrow==20.0.0
pydantic==2.11.7
pydantic-core==2.33.2
pydeck==0.9.1
python-dateutil==2.9.0.post0
pytz==2025.2
referencing==0.36.2
requests==2.32.4
rpds-py==0.25.1
setuptools==80.9.0
six==1.17.0
smmap==5.0.2
sniffio==1.3.1
soupsieve==2.7
streamlit==1.46.0
tenacity==9.1.2
toml==0.10.2
tornado==6.5.1
tqdm==4.67.1
typing-extensions==4.14.0
typing-inspection==0.4.1
tzdata==2025.2
urllib3==2.5.0
wikipedia==1.4.0
zope-interface==7.2

View File

@@ -2,11 +2,11 @@ from openai import OpenAI
import json
from typing import List, Dict, Any
from src.utils import function_to_json
from src.tools import get_current_datetime, add, compare, count_letter_in_string
from src.tools import get_current_datetime, add, compare, count_letter_in_string, search_wikipedia, get_current_temperature
import pprint
SYSREM_PROMPT = """
SYSTEM_PROMPT = """
你是一个叫不要葱姜蒜的人工智能助手。你的输出应该与用户的语言保持一致。
当用户的问题需要调用工具时,你可以从提供的工具列表中调用适当的工具函数。
"""
@@ -17,7 +17,7 @@ class Agent:
self.tools = tools
self.model = model
self.messages = [
{"role": "system", "content": SYSREM_PROMPT},
{"role": "system", "content": SYSTEM_PROMPT},
]
self.verbose = verbose

View File

@@ -1,12 +1,14 @@
from datetime import datetime
import datetime
import wikipedia
import requests
# 获取当前日期和时间
def get_current_datetime() -> str:
"""
获取当前日期和时间。
获取真实的当前日期和时间。
:return: 当前日期和时间的字符串表示。
"""
current_datetime = datetime.now()
current_datetime = datetime.datetime.now()
formatted_datetime = current_datetime.strftime("%Y-%m-%d %H:%M:%S")
return formatted_datetime
@@ -54,3 +56,76 @@ def count_letter_in_string(a: str, b: str):
count = string.count(letter)
return(f"The letter '{letter}' appears {count} times in the string.")
def search_wikipedia(query: str) -> str:
"""
在维基百科中搜索指定查询的前三个页面摘要。
:param query: 要搜索的查询字符串。
:return: 包含前三个页面摘要的字符串。
"""
page_titles = wikipedia.search(query)
summaries = []
for page_title in page_titles[: 3]: # 取前三个页面标题
try:
# 使用 wikipedia 模块的 page 函数,获取指定标题的维基百科页面对象。
wiki_page = wikipedia.page(title=page_title, auto_suggest=False)
# 获取页面摘要
summaries.append(f"页面: {page_title}\n摘要: {wiki_page.summary}")
except (
wikipedia.exceptions.PageError,
wikipedia.exceptions.DisambiguationError,
):
pass
if not summaries:
return "维基百科没有搜索到合适的结果"
return "\n\n".join(summaries)
def get_current_temperature(latitude: float, longitude: float) -> str:
"""
获取指定经纬度位置的当前温度。
:param latitude: 纬度坐标。
:param longitude: 经度坐标。
:return: 当前温度的字符串表示。
"""
# Open Meteo API 的URL
open_meteo_url = "https://api.open-meteo.com/v1/forecast"
# 请求参数
params = {
'latitude': latitude,
'longitude': longitude,
'hourly': 'temperature_2m',
'forecast_days': 1,
}
# 发送 API 请求
response = requests.get(open_meteo_url, params=params)
# 检查响应状态码
if response.status_code == 200:
# 解析 JSON 响应
results = response.json()
else:
# 处理请求失败的情况
raise Exception(f"API Request failed with status code: {response.status_code}")
# 获取当前 UTC 时间
current_utc_time = datetime.datetime.now(datetime.UTC)
# 将时间字符串转换为 datetime 对象
time_list = [datetime.datetime.fromisoformat(time_str).replace(tzinfo=datetime.timezone.utc) for time_str in
results['hourly']['time']]
# 获取温度列表
temperature_list = results['hourly']['temperature_2m']
# 找到最接近当前时间的索引
closest_time_index = min(range(len(time_list)), key=lambda i: abs(time_list[i] - current_utc_time))
# 获取当前温度
current_temperature = temperature_list[closest_time_index]
# 返回当前温度的字符串形式
return f'现在温度是 {current_temperature}°C'

View File

@@ -0,0 +1,62 @@
import streamlit as st
from src.core import Agent
from src.tools import add, count_letter_in_string, compare, get_current_datetime, search_wikipedia, get_current_temperature
from openai import OpenAI
# --- 页面配置 ---
st.set_page_config(
page_title="Tiny Agent Demo", # 页面标题
page_icon="🤖", # 页面图标
layout="centered", # 页面布局
initial_sidebar_state="auto", # 侧边栏初始状态
)
# --- OpenAI客户端初始化 ---
client = OpenAI(
api_key="sk-quovvfgjdmmrvwiljusggiwvxfiekzicwjgtdvpfqhpmbpqu",
base_url="https://api.siliconflow.cn/v1",
)
# --- Agent初始化 ---
@st.cache_resource
def load_agent():
"""创建并缓存Agent实例。"""
return Agent(
client=client,
model="Qwen/Qwen2.5-32B-Instruct", # 使用的模型
tools=[get_current_datetime, search_wikipedia, get_current_temperature], # Agent可以使用的工具
)
agent = load_agent() # 加载Agent
# --- UI组件 ---
st.title("🤖 Happy-LLM Tiny Agent") # 设置页面标题
st.markdown("""欢迎来到 Tiny Agent web 界面!
在下方输入您的提示,查看 Agent 的实际操作。
""") # 显示Markdown格式的欢迎信息
# 初始化聊天记录
if "messages" not in st.session_state:
st.session_state.messages = []
# 在应用重新运行时显示历史聊天记录
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# 响应用户输入
if prompt := st.chat_input("我能为您做些什么?"):
# 在聊天消息容器中显示用户消息
st.chat_message("user").markdown(prompt)
# 将用户消息添加到聊天记录中
st.session_state.messages.append({"role": "user", "content": prompt})
with st.spinner('思考中...'):
response = agent.get_completion(prompt) # 获取Agent的响应
# 在聊天消息容器中显示助手响应
with st.chat_message("assistant"):
st.markdown(response)
# 将助手响应添加到聊天记录中
st.session_state.messages.append({"role": "assistant", "content": response})

View File

@@ -13,7 +13,6 @@ from copy import copy
from typing import Dict, List, Optional, Tuple, Union
import numpy as np
os.environ['CURL_CA_BUNDLE'] = ''
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

View File

@@ -11,7 +11,7 @@ import os
from typing import Dict, List, Optional, Tuple, Union
PROMPT_TEMPLATE = dict(
RAG_PROMPT_TEMPALTE="""使用以上下文来回答用户的问题。如果你不知道答案,就说你不知道。总是使用中文回答。
RAG_PROMPT_TEMPLATE="""使用以上下文来回答用户的问题。如果你不知道答案,就说你不知道。总是使用中文回答。
问题: {question}
可参考的上下文:
···
@@ -19,7 +19,7 @@ PROMPT_TEMPLATE = dict(
···
如果给定的上下文无法让你做出回答,请回答数据库中没有这个内容,你不知道。
有用的回答:""",
InternLM_PROMPT_TEMPALTE="""先对上下文进行内容总结,再使用上下文来回答用户的问题。如果你不知道答案,就说你不知道。总是使用中文回答。
InternLM_PROMPT_TEMPLATE="""先对上下文进行内容总结,再使用上下文来回答用户的问题。如果你不知道答案,就说你不知道。总是使用中文回答。
问题: {question}
可参考的上下文:
···
@@ -65,7 +65,7 @@ class InternLMChat(BaseModel):
self.load_model()
def chat(self, prompt: str, history: List = [], content: str='') -> str:
prompt = PROMPT_TEMPLATE['InternLM_PROMPT_TEMPALTE'].format(question=prompt, context=content)
prompt = PROMPT_TEMPLATE['InternLM_PROMPT_TEMPLATE'].format(question=prompt, context=content)
response, history = self.model.chat(self.tokenizer, prompt, history)
return response

View File

@@ -0,0 +1,14 @@
openai
zhipuai
numpy
python-dotenv
torch
torchvision
torchaudio
transformers
tqdm
PyPDF2
markdown
html2text
tiktoken
beautifulsoup4

View File

@@ -17,7 +17,6 @@
2. **工具使用评测集**
- **BFCL V2**:用于评测模型在复杂工具使用任务中的表现,特别是在执行多步骤操作时的正确性和效率。这些任务通常涉及与数据库交互或执行特定指令,以模拟实际工具使用场景。
- **Nexus**:用于测试模型在多步骤操作中的工具使用能力,主要评估其在多任务操作中的协调性和任务管理能力,如进行文件操作、数据整合等复杂流程。
3. **数学评测集**
- **GSM8K**GSM8K是一个包含小学数学问题的数据集用于测试模型的数学推理和逻辑分析能力。具体任务包括算术运算、简单方程求解、数字推理等。GSM8K中的问题虽然看似简单但模型需要理解问题语义并进行正确的数学运算体现了逻辑推理和语言理解的双重挑战。
@@ -481,29 +480,6 @@ def get_current_datetime() -> str:
formatted_datetime = current_datetime.strftime("%Y-%m-%d %H:%M:%S")
return formatted_datetime
def add(a: float, b: float):
"""
计算两个浮点数的和。
:param a: 第一个浮点数。
:param b: 第二个浮点数。
:return: 两个浮点数的和。
"""
return str(a + b)
def compare(a: float, b: float):
"""
比较两个浮点数的大小。
:param a: 第一个浮点数。
:param b: 第二个浮点数。
:return: 比较结果的字符串表示。
"""
if a > b:
return f'{a} is greater than {b}'
elif a < b:
return f'{b} is greater than {a}'
else:
return f'{a} is equal to {b}'
def count_letter_in_string(a: str, b: str):
"""
统计字符串中某个字母的出现次数。
@@ -513,6 +489,28 @@ def count_letter_in_string(a: str, b: str):
"""
return str(a.count(b))
def search_wikipedia(query: str) -> str:
"""
在维基百科中搜索指定查询的前三个页面摘要。
:param query: 要搜索的查询字符串。
:return: 包含前三个页面摘要的字符串。
"""
page_titles = wikipedia.search(query)
summaries = []
for page_title in page_titles[: 3]: # 取前三个页面标题
try:
# 使用 wikipedia 模块的 page 函数,获取指定标题的维基百科页面对象。
wiki_page = wikipedia.page(title=page_title, auto_suggest=False)
# 获取页面摘要
summaries.append(f"页面: {page_title}\n摘要: {wiki_page.summary}")
except (
wikipedia.exceptions.PageError,
wikipedia.exceptions.DisambiguationError,
):
pass
if not summaries:
return "维基百科没有搜索到合适的结果"
return "\n\n".join(summaries)
# ... (可能还有其他工具函数)
```
@@ -552,7 +550,7 @@ from utils import function_to_json
# 导入定义好的工具函数
from tools import get_current_datetime, add, compare, count_letter_in_string
SYSREM_PROMPT = """
SYSTEM_PROMPT = """
你是一个叫不要葱姜蒜的人工智能助手。你的输出应该与用户的语言保持一致。
当用户的问题需要调用工具时,你可以从提供的工具列表中调用适当的工具函数。
"""
@@ -669,6 +667,11 @@ if __name__ == "__main__":
运行 `python demo.py`你可以开始提问。如果问题需要调用工具Agent 会自动处理。
<div align='center'>
<img src="https://raw.githubusercontent.com/datawhalechina/happy-llm/main/docs/images/7-images/7-3-streamlit-demo.png" alt="alt text" width="80%">
<p>图7.10 Agent 工作流程</p>
</div>
**示例交互:**
```bash
@@ -691,6 +694,10 @@ Assistant: 当前的时间是2025年4月26日17:01:33。不过我注意到
User: exit
```
另外,我们也准备了一份可以展示的 Streamlit 应用,可以运行在本地,展示 Agent 的功能。`streamlit run web_demo.py` 来运行,以下为 Agent 运行效果。
**参考文献**
[1] Hugging Face. (2023). *Open LLM Leaderboard: 开源大语言模型基准测试平台*. https://huggingface.co/spaces/open-llm-leaderboard/open_llm_leaderboard

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB