最近在DataWhale參加Agents開發的組隊學習,雖然課程才剛滿兩週,但我這顆想搞事的心早就按捺不住了!今天,我終於要正式“出道”,成為一名智能體系統構建者啦~
我最近調研發現某部門的小夥伴們得頻繁巡檢機房設備,工作量巨大不説,機房那噪音、輻射、還有不明氣體……簡直是對健康的“全方位關愛” 😅
於是他們悄悄問我:能不能讓AI來幫忙巡檢?我一聽,這不就是我學《從零開始構建智能體》之後練手的好機會嘛!靈感瞬間爆發,火速整理思路,項目火速上線👇
項目名稱:機房巡檢助手
問題分析:當前機房巡檢工作面臨幾個核心痛點:人工巡檢效率低下,工作環境對人員不友好,機房內的噪音、輻射和有害氣體影響健康。傳統巡檢模式存在盲區,易出錯等問題。
核心功能:通過API工具採集機房環境數據,智能分析與決策模塊 負責數據處理與決策。實現根因分析:當多指標異常時,能智能推斷故障根源,輔助快速定位問題。人機交互支持聊天對話。
預期成果:實現聊天對話框內巡檢機房環境數據,並智能分析總結,輔助快速定位問題。
項目地址:https://gitee.com/yisheng163/hello-agents-ys由於從 GitHub 拉取/推送代碼的網絡延遲較高,為提升協作效率,我將項目同步開源至 Gitee。
效果圖:
開發環境準備
1,下載 vs code 安裝 。
2,# 安裝HelloAgents
pip install hello-agents
測試 python -c "import hello_agents; print(dir(hello_agents))"
安裝gradio庫,低代碼搭建聊天對話界面。
pip install gradio
編寫代碼
import os
from openai import OpenAI
from dotenv import load_dotenv
from typing import List, Dict
# 加載 .env 文件中的環境變量
load_dotenv()
class HelloAgentsLLM:
"""
為本書 "Hello Agents" 定製的LLM客户端。
它用於調用任何兼容OpenAI接口的服務,並默認使用流式響應。
"""
def __init__(self, model: str = None, apiKey: str = None, baseUrl: str = None, timeout: int = None):
"""
初始化客户端。優先使用傳入參數,如果未提供,則從環境變量加載。
"""
self.model = model or os.getenv("LLM_MODEL_ID")
apiKey = apiKey or os.getenv("LLM_API_KEY")
baseUrl = baseUrl or os.getenv("LLM_BASE_URL")
timeout = timeout or int(os.getenv("LLM_TIMEOUT", 60))
if not all([self.model, apiKey, baseUrl]):
raise ValueError("模型ID、API密鑰和服務地址必須被提供或在.env文件中定義。")
self.client = OpenAI(api_key=apiKey, base_url=baseUrl, timeout=timeout)
def think(self, messages: List[Dict[str, str]], temperature: float = 0) -> str:
"""
調用大語言模型進行思考,並返回其響應。
"""
print(f"🧠 正在調用 {self.model} 模型...")
try:
response = self.client.chat.completions.create(
model=self.model,
messages=messages,
temperature=temperature,
stream=True,
)
# 處理流式響應
print("✅ 大語言模型響應成功:")
collected_content = [] # 創建空列表用於收集所有響應片段
for chunk in response: # 遍歷流式響應中的每個數據塊
content = chunk.choices[0].delta.content or "" # 提取當前數據塊中的文本內容,如果沒有則為空字符串
print(content, end="", flush=True) # 實時打印內容,不換行並立即刷新輸出緩衝區
collected_content.append(content) # 將當前內容添加到收集列表中
print() # 在流式輸出結束後換行
return "".join(collected_content) # 將收集的所有內容拼接成完整字符串並返回
except Exception as e:
print(f"❌ 調用LLM API時發生錯誤: {e}")
return None
def invoke(self, messages: List[Dict[str, str]], **kwargs) -> str:
"""
兼容SimpleAgent的調用方法
"""
temperature = kwargs.get("temperature", 0)
return self.think(messages, temperature)
# --- 客户端使用示例 ---
if __name__ == '__main__':
try:
llmClient = HelloAgentsLLM()
exampleMessages = [
{"role": "system", "content": "You are a helpful assistant that writes Python code."},
{"role": "user", "content": "寫一個快速排序算法"}
]
print("--- 調用LLM ---")
responseText = llmClient.think(exampleMessages)
if responseText:
print("\n\n--- 完整模型響應 ---")
print(responseText)
except ValueError as e:
print(e)
HelloAgentsLLM.py
# my_llm.py
import os
from typing import Optional
from openai import OpenAI
from hello_agents import HelloAgentsLLM
class MyLLM(HelloAgentsLLM):
def __init__(
self,
model: Optional[str] = None,
api_key: Optional[str] = None,
base_url: Optional[str] = None,
provider: Optional[str] = "auto",
**kwargs
):
# 檢查provider是否為我們想處理的'modelscope'
if provider == "modelscope":
print("正在使用自定義的 ModelScope Provider")
self.provider = "modelscope"
# 解析 ModelScope 的憑證
self.api_key = api_key or os.getenv("MODELSCOPE_API_KEY")
self.base_url = base_url or "https://api-inference.modelscope.cn/v1/"
# 驗證憑證是否存在
if not self.api_key:
raise ValueError("ModelScope API key not found. Please set MODELSCOPE_API_KEY environment variable.")
# 設置默認模型和其他參數
self.model = model or os.getenv("LLM_MODEL_ID") or "Qwen/Qwen2.5-VL-72B-Instruct"
self.temperature = kwargs.get('temperature', 0.7)
self.max_tokens = kwargs.get('max_tokens')
self.timeout = kwargs.get('timeout', 60)
# 使用獲取的參數創建OpenAI客户端實例
self._client = OpenAI(api_key=self.api_key, base_url=self.base_url, timeout=self.timeout)
else:
# 如果不是 modelscope, 則完全使用父類的原始邏輯來處理
super().__init__(model=model, api_key=api_key, base_url=base_url, provider=provider, **kwargs)
my_llm.py
配置.env文件
編寫通過API查詢機房環境信息的類
import os
import requests
import hashlib
import json
from typing import Optional, Union
class api_Temperature:
def __init__(self):
self.session = requests.Session()
self.base_url = "http://192.19.20.5:3000"
self.base_url = os.getenv("ROOM_API_URL")
# 獲取加鹽值
salt_url = f"{self.base_url}/api/Plugin.Base/Log/Salt"
try:
response = self.session.get(salt_url)
response.raise_for_status()
re_body = response.json()
# print(f"re_body: {re_body}")
fixed_salt = re_body.get("data", {}).get("FixedSalt")
temp_salt = re_body.get("data", {}).get("TempSalt")
# print(f"fixed_salt: {fixed_salt}")
# print(f"temp_salt: {temp_salt}")
# 登錄
login_url = f"{self.base_url}/api/Plugin.Base/Log/In"
account = os.getenv("ROOM_API_USERNAME")
password = os.getenv("ROOM_API_USERPASS")
remember_me = "true"
# 計算密文 md5(md5({賬號}+{固定加鹽值}+{密碼})+{臨時加鹽值})
password_step1 = self.md5_hash(account + fixed_salt + password)
encrypted_password = self.md5_hash(password_step1 + temp_salt)
post_data = {
"Account": account,
"Password": encrypted_password,
"RememberMe": remember_me
}
login_response = self.session.post(login_url, data=post_data)
login_response.raise_for_status()
# 輸出登陸結果
print("=" * 50)
# print("登錄結果詳情:")
# print(f"請求URL: {login_url}")
# print(f"請求方法: POST")
# print(f"請求體: {login_response.request.body}")
print(f"響應狀態碼: {login_response.status_code}")
print(f"響應內容: {login_response.text}")
print("=" * 50)
except requests.exceptions.RequestException as e:
print(f"初始化失敗: {e}")
@staticmethod
def md5_hash(text: str) -> str:
"""MD5哈希計算"""
return hashlib.md5(text.encode('utf-8')).hexdigest()
def get_temperature_value(self, object_id: str) -> float:
"""取單個温度值"""
re_value = 0.0
url = f"{self.base_url}/api/Plugin.Data/Property/RealTimeData"
params = {
"Clazz": "Plugin.Env.Model.Device",
"objectId": object_id,
"provider": "Plugin.Env.Providers.Data.Device.Property_Point",
"propertyId": "HT_Temp"
}
try:
response = self.session.get(url, params=params)
response.raise_for_status()
re_body = response.json()
# 屏幕打印日誌
#print(f"[{self.get_current_time()}] {json.dumps(re_body)}")
ht_temp_value = re_body.get("data", {}).get("value")
if ht_temp_value:
re_value = float(ht_temp_value)
except (requests.exceptions.RequestException, ValueError) as e:
print(f"獲取温度值失敗: {e}")
return re_value
def get_leak_judge_value(self, object_id: str, property_id: str) -> bool:
"""取單個漏水判定"""
url = f"{self.base_url}/api/Plugin.Data/Property/RealTimeData"
params = {
"Clazz": "Plugin.Env.Model.Device",
"objectId": object_id,
"provider": "Plugin.Env.Providers.Data.Device.Property_Point",
"propertyId": property_id
}
try:
response = self.session.get(url, params=params)
response.raise_for_status()
re_body = response.json()
#print(json.dumps(re_body))
mk_judge_value = re_body.get("data", {}).get("value")
re_value = float(mk_judge_value)
return re_value == 1
except (requests.exceptions.RequestException, ValueError) as e:
print(f"獲取漏水判定失敗: {e}")
return False
def get_leak_locate_value(self, object_id: str, property_id: str) -> float:
"""取單個漏水位置"""
re_value = 0.0
url = f"{self.base_url}/api/Plugin.Data/Property/RealTimeData"
params = {
"Clazz": "Plugin.Env.Model.Device",
"objectId": object_id,
"provider": "Plugin.Env.Providers.Data.Device.Property_Point",
"propertyId": property_id
}
try:
response = self.session.get(url, params=params)
response.raise_for_status()
re_body = response.json()
#print(json.dumps(re_body))
mk_locate_value = re_body.get("data", {}).get("value")
re_value = float(mk_locate_value)
except (requests.exceptions.RequestException, ValueError) as e:
print(f"獲取漏水位置失敗: {e}")
return re_value
def get_all_value(self) -> str:
"""獲取所有值"""
# 分院信息
SD_Temp_Main = 0.0
SD_Temp_UPS = 0.0
# 獲取温度值
SD_Temp_Main = self.get_temperature_value("30.wsd5")
SD_Temp_UPS = self.get_temperature_value("30.wsd1")
# 獲取漏水判斷值
SD_Leak_Judge_Main1 = self.get_leak_judge_value("30.ls1", "Leak_Judge")
SD_Leak_Judge_Main2 = self.get_leak_judge_value("30.ls2", "Leak_Judge")
SD_Leak_Locate_Main1 = 0.0
SD_Leak_Locate_Main2 = 0.0
# 處理漏水定位邏輯[1](@ref)
if SD_Leak_Judge_Main1:
SD_Leak_Locate_Main1 = self.get_leak_locate_value("30.ls1", "Leak_Locate")
if SD_Leak_Judge_Main2:
SD_Leak_Locate_Main2 = self.get_leak_locate_value("30.ls2", "Leak_Locate")
if SD_Leak_Locate_Main2 > SD_Leak_Locate_Main1:
SD_Leak_Locate_Main1 = SD_Leak_Locate_Main2
#獲取總院信息
LY_Temp_Main = 0.0
LY_Temp_UPS = 0.0
# 獲取温度值[2](@ref)
LY_Temp_Main = self.get_temperature_value("32.wsd3")
LY_Temp_UPS = self.get_temperature_value("32.wsd1")
# 獲取漏水判斷值
LY_Leak_Judge_Main = self.get_leak_judge_value("32.mk", "Path0")
LY_Leak_Judge_UPS = self.get_leak_judge_value("32.mk", "Path1")
# 構建分院報告
msgBody = "分院院區"
if SD_Temp_Main == 0:
msgBody += ",中心機房温度探測器故障"
if SD_Temp_UPS == 0:
msgBody += ",電源機房温度探測器故障"
if SD_Temp_Main > 0:
msgBody += f",中心機櫃温度{SD_Temp_Main}"
if SD_Temp_UPS > 0:
msgBody += f",電源機櫃温度{SD_Temp_UPS}"
# 漏水狀態判斷
if not SD_Leak_Judge_Main1 and not SD_Leak_Judge_Main2:
msgBody += ",中心機櫃無漏水"
if SD_Leak_Judge_Main1 or SD_Leak_Judge_Main2:
msgBody += f",中心機櫃漏水,漏水位置傳感繩第{SD_Leak_Locate_Main1}米處"
msgBody += "。"
msgBody += "\n總院院區"
# 構建總院院區報告
if LY_Temp_Main == 0:
msgBody += ",中心機櫃温度探測器故障"
if LY_Temp_UPS == 0:
msgBody += ",電源機櫃温度探測器故障"
if LY_Temp_Main > 0:
msgBody += f",中心機櫃温度{LY_Temp_Main}"
if LY_Temp_UPS > 0:
msgBody += f",電源機櫃温度{LY_Temp_UPS}"
# 漏水狀態判斷
if not LY_Leak_Judge_Main:
msgBody += ",中心機櫃無漏水"
if LY_Leak_Judge_Main:
msgBody += ",中心機櫃漏水"
if not LY_Leak_Judge_UPS:
msgBody += ",電源機櫃無漏水"
if LY_Leak_Judge_UPS:
msgBody += ",電源機櫃漏水"
msgBody += "。"
return msgBody
if __name__ == "__main__":
# 創建API實例(會自動登錄)
api = api_Temperature()
# 獲取所有值
getall = api.get_all_value()
print(getall)
api_Temperature.py
創建包含機房巡檢的工具註冊表類
# my_calculator_tool2.py # 導入所需的模塊和庫
import ast # 抽象語法樹模塊,用於解析表達式
import operator # 操作符模塊,提供基本的數學操作函數
import math # 數學模塊,提供數學函數
from hello_agents import ToolRegistry # 從hello_agents導入ToolRegistry類
from api_Temperature import api_Temperature #取機房數據
def my_room_temp(expression: str) -> str:
"""機房巡檢"""
if not expression.strip(): # 檢查表達式是否為空
return "巡項項目不能為空" # 如果為空則返回錯誤信息
try:
if "機房" in expression or "温度" in expression:
room_api = api_Temperature()
msgBody = room_api.get_all_value()
# 確保返回有效信息
if msgBody:
return msgBody
else:
return "未能獲取到機房信息"
if "漏水" in expression:
room_api = api_Temperature()
msgBody = room_api.get_all_value()
# 確保返回有效信息
if msgBody:
return msgBody
else:
return "未能獲取到漏水信息"
except Exception as e:
return f"查詢失敗: {str(e)}" # 捕獲異常並返回錯誤信息
def _eval_node(node, operators, functions):
"""簡化的表達式求值"""
if isinstance(node, ast.Constant): # 如果節點是常量類型
return node.value # 直接返回常量值
elif isinstance(node, ast.BinOp): # 如果節點是二元運算類型
left = _eval_node(node.left, operators, functions) # 遞歸計算左子樹
right = _eval_node(node.right, operators, functions) # 遞歸計算右子樹
op = operators.get(type(node.op)) # 獲取對應的操作符函數
return op(left, right) # 執行運算並返回結果
elif isinstance(node, ast.Call): # 如果節點是函數調用類型
func_name = node.func.id # 獲取函數名
if func_name in functions: # 檢查函數是否受支持
args = [_eval_node(arg, operators, functions) for arg in node.args] # 遞歸計算參數值
return functions[func_name](*args) # 調用函數並返回結果
elif isinstance(node, ast.Name): # 如果節點是名稱類型(如變量或常量)
if node.id in functions: # 檢查名稱是否在函數列表中
return functions[node.id] # 返回對應的值
def create_calculator_registry():
"""創建包含機房巡檢的工具註冊表"""
registry = ToolRegistry() # 創建ToolRegistry實例
# 註冊計算器函數
registry.register_function(
name="my_room_temp", # 工具名稱
description="機房巡檢工具,支持温度,漏水檢測", # 工具描述
func=my_room_temp # 對應的函數
)
return registry # 返回註冊表實例
# 使用示例
if __name__ == "__main__":
msgBody=my_room_temp("機房巡檢")
print(msgBody)
my_room_temp_tool.py
主界面,通過gradio庫,實現聊天對話界面。
import gradio as gr
from hello_agents import SimpleAgent
from HelloAgentsLLM import HelloAgentsLLM
from my_room_temp_tool import create_calculator_registry
# 創建LLM實例
llm = HelloAgentsLLM()
# 創建工具註冊表
registry = create_calculator_registry()
# 創建智能體
agent = SimpleAgent(
name="機房巡檢助手",
llm=llm,
system_prompt="你是一個機房巡檢助手,專門負責監控和報告機房的温度及漏水情況。當用户詢問機房相關信息時,你應該使用my_room_temp工具來獲取實時數據。"
)
# 重寫agent的run方法,使其能夠調用我們的工具
def agent_run_with_tools(input_text):
# 檢查是否需要調用工具
if "機房" in input_text or "温度" in input_text or "漏水" in input_text:
# 調用工具獲取數據
tool_result = registry.execute_tool("my_room_temp", input_text)
# 構造最終回覆
messages = [
{"role": "user", "content": f"根據以下機房巡檢結果,用自然語言回答用户問題:{input_text}\n\n巡檢結果:{tool_result}"}
]
response = llm.think(messages)
return response
else:
# 直接調用agent.run
return agent.run(input_text)
def agent_chat(message, history):
"""
與agent進行對話
"""
try:
# 運行agent
response = agent_run_with_tools(message)
# 確保返回的是字符串而不是None
return response or "抱歉,我沒有收到有效的回覆。"
except Exception as e:
return f"發生錯誤: {str(e)}"
# 創建Gradio界面
with gr.Blocks(title="機房巡檢助手") as demo:
gr.Markdown("# 機房巡檢助手 -- 《Hello-Agents從零開始構建智能體》之畢業設計")
gr.Markdown("這是一個聊天界面,可以與機房巡檢助手進行交互,並自動調用my_room_temp工具。")
chatbot = gr.Chatbot(label="對話歷史", type="messages")
msg = gr.Textbox(label="輸入消息", placeholder="請輸入您的問題,例如:機房温度是多少?")
clear = gr.Button("清除對話")
designer = gr.Markdown("設計:西湖誼生")
def add_user_message(history, message):
"""添加用户消息到歷史記錄"""
return history + [{"role": "user", "content": message}]
def bot_response(history):
"""生成機器人回覆並添加到歷史記錄"""
user_message = history[-1]["content"]
bot_reply = agent_chat(user_message, history)
history.append({"role": "assistant", "content": bot_reply})
return history, ""
msg.submit(add_user_message, [chatbot, msg], [chatbot]).then(
bot_response, [chatbot], [chatbot, msg]
)
clear.click(lambda: [], None, chatbot, queue=False)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7861, share=False)
app.py
編寫README.md,將代碼提交到遠程倉庫。