目前的地图软件比如高德地图,百度地图的交互的智能的能力不够,于是便想着用LLM加地图做一个关于旅游的agent,帮助我们实现智能交互。
最终的demo的样子:
agent4train
一. 准备工作
我们模型用openai的gpt-3.5-turbo,前端用streamlit
pip install openai streamlit streamlit-chat
地图用百度地图,这里需要在百度地图开放平台申请两个key
具体过程为,链接-> 右上角控制台-> 左边应用管理-> 我的应用 ->创建应用
应用类型为 浏览器端和服务端(两个),然后得到两个key,之后会用到
还有gpt-3.5-turbo的key,可以去openai官网充值,也可以去淘宝买,如果是转接服务器的key,还需要转接服务器提供的url 作为base url。
二. 地图在streamlit中的使用
streamlit 中提供了诸多组件可以便利的使用,地图可以用streamlit-folium 里面提供了开源的地图,和众多可以交互的功能,但是由于涉及到诸如路径规划,地理编码等问题,所以采用了百度地图的api与百度地图结合。
这里采用streamlit来渲染百度地图的html。(这里的ak是浏览器端的ak)
import streamlit as st
html_temp = r"""
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
body, html,#allmap {{width: 100%;height: 100%;overflow: hidden;margin:0;font-family:"微软雅黑";}}
</style>
<script src="//mapopen.bj.bcebos.com/github/BMapGLLib/TrackAnimation/src/TrackAnimation.min.js"></script>
<script type="text/javascript" src="//api.map.baidu.com/api?type=webgl&v=1.0&ak={ak}"></script>
<title>视角动画</title>
</head>
<body>
<div id="allmap"></div>
</body>
</html>
<script type="text/javascript">
// GL版命名空间为BMapGL
// 按住鼠标右键,修改倾斜角和角度
var bmap = new BMapGL.Map("allmap"); // 创建Map实例
bmap.centerAndZoom(new BMapGL.Point{start}, 17); // 初始化地图,设置中心点坐标和地图级别
bmap.enableScrollWheelZoom(true); // 开启鼠标滚轮缩放
var path = {path};
var point = [];
for (var i = 0; i < path.length; i++) {{
point.push(new BMapGL.Point(path[i].lng, path[i].lat));
}}
var pl = new BMapGL.Polyline(point);
setTimeout('start()', 3000);
function start () {{
trackAni = new BMapGLLib.TrackAnimation(bmap, pl, {{
overallView: true,
tilt: 30,
duration: 20000,
delay: 300
}});
trackAni.start();
}}
</script>
"""
"""
lng: 经度
lat: 纬度
start 格式 (lng,lat)
path 格式
{
'lng': 116.297611,
'lat': 40.047363
}, {
'lng': 116.302839,
'lat': 40.048219
},
"""
ak = "你的浏览器端的ak"
# start = -> 按照上面格式填写
# path = -> 按照上面格式填写
html_component = st.components.v1.html(html_temp.format(ak=ak,start=start,path=path), height=600)
定义BaiduAPI, 这里的ak是服务端的ak
class BaiduAPI:
def __init__(self, ak):
self.ak = ak
def get_address_encode(self, address):
url = 'http://api.map.baidu.com/geocoding/v3/'
params = {
'address': address,
'output': 'json',
'ak': ak
}
response = requests.get(url, params=params)
if response.status_code == 200:
result = response.json()
# 打印结果
print(result)
# 提取经纬度
if result['status'] == 0:
lat = result['result']['location']['lat']
lng = result['result']['location']['lng']
return lat, lng
else:
print('Error:', result.get('message', 'Unknown error'))
else:
print('Failed to get geocode:', response.status_code)
def transform(self, address):
s, e = self.get_address_encode(address)
result = str(s) + "," + str(e)
return result
def is_lat_lng(self, text) -> bool:
if "," in text:
return True
return False
def path_planing(self, start_point:str, end_point:str) -> Dict:
if not self.is_lat_lng(end_point):
start_point = self.transform(start_point)
end_point = self.transform(end_point)
url = 'https://api.map.baidu.com/directionlite/v1/driving?'
# 请求参数
params = {
'ak': ak,
'output': 'json',
'origin': start_point,
'destination': end_point
}
# 发送HTTP GET请求
response = requests.get(url, params=params)
result = {}
if response.status_code == 200:
# 解析JSON格式的响应内容
result = response.json()
# print(result)
return result
def parse_path_planing_result(self, result) -> List[List[SData]]:
middle_point = []
# 解析数据
if result["message"] == 'ok':
for i in result['result']['routes']:
route = []
for j in i['steps']:
k = SData(path=j["path"],instruction=j["instruction"])
route.append(k)
middle_point.append(route)
else:
print('Error:', result.get('message', 'Unknown error'))
return middle_point
def parse(self, start_point, end_point):
result = self.path_planing(start_point,end_point)
start_point = (result['result']['origin']['lng'], result['result']['origin']['lat'])
end_point = (result['result']['destination']['lng'], result['result']['destination']['lat'])
parse_result = self.parse_path_planing_result(result)
return start_point,end_point,parse_result
def get_html(self,start,end):
start, end, middle = self.parse(start, end)
browser_ak = "你的浏览器端的ak"
html = Html(browser_ak, start, middle)
html_temp = html.html
return html_temp
class Html:
def __init__(self,browser_ak,start: Tuple,path:List[List[SData]]):
# html_temp = ... 上文有,此处省略
self.html = html_temp.format(ak=browser_ak,start=str(start),path=self.transform_parse(path))
def transform_parse(self,parse:List[List[SData]]):
# SData.path : List[Tuple]
parse = parse[0]
point = []
for i in parse:
for j in i.path:
point.append({ "lng":j[1],"lat":j[0]})
return str(point)
class SData: # Single data
def __init__(self, path: str, instruction: str = None):
self.path: List[Tuple] = self.parse_data(path)
self.instruction = instruction
def parse_data(self, path) -> List[Tuple]:
p = path.split(';')
new = []
for i in range(len(p)):
s = p[i].split(',')
new.append((eval(s[1]), eval(s[0])))
return new
def __str__(self):
s = "Path: \033[91m" + str(self.path[0]).strip("(").strip(")") + "\033[0m->...->\033[91m" + str(self.path[-1]).strip("(").strip(")") + "\033[0m Instruction: " + (self.instruction if self.instruction is not None else "None")
return s
其中,路径规划可以调用parse这个函数来实现,只需要传入开始点和终止点。get_html, 可以直接得到需要渲染的html。
至于其中的解析过程,参考传回来的数据格式(从清华大学到北京大学的路径规划)
{
"message": "ok",
"result": {
"destination": {
"lat": 39.764275186905,
"lng": 116.36376803804
},
"origin": {
"lat": 40.00964515987,
"lng": 116.33337396094
},
"routes": [
{
"distance": 31456,
"duration": 3266,
"restriction_info": {
"status": 0
},
"route_md5": "409a52192c4950c7c4323aecd17d440d",
"steps": [
{
"direction": 6,
"distance": 12,
"duration": 0,
"end_location": {
"lat": "40.009573455204",
"lng": "116.33362746276"
},
"instruction": "\u4ece<b>\u8d77\u70b9</b>\u5411\u6b63\u5357\u65b9\u5411\u51fa\u53d1,\u884c\u9a7610\u7c73,<b>\u5de6\u8f6c</b>",
"leg_index": 0,
"path": "116.33360734072,40.009673689662;116.33362746276,40.009573455204",
"road_type": 6,
"road_types": "6",
"start_location": {
"lat": "40.009673689662",
"lng": "116.33360734072"
},
"traffic_condition": [
{
"geo_cnt": 1,
"status": 0
}
],
"turn": 7
},
# 此处省略多个类似上面的json格式.....
],
"toll": 2,
"traffic_condition": 1
}
]
},
"status": 0
}
其他相关功能的html可以在百度地图开放平台JS API示例查看
三. 利用streamlit和openai做一个问答界面
model.py 定义openai的模型
class Open_AI:
def __init__(self, api_key,base_url):
self.openai = OpenAI(
base_url=base_url,
api_key=api_key,
# http_client=self.http_client,
)
self.api_key = api_key
self.client = self.openai
def get_streaming_completion(self,system_prompt,prompt,messages):
# 调用 chat.completions.create 并启用流式输出
message_list = messages + [
{"role": "system", "content": system_prompt},
{"role": "user", "content": prompt}
]
response = self.client.chat.completions.create(
model="gpt-3.5-turbo",
messages=message_list,
stream=True # 启用流式输出
)
return response
agent.py 定义agent
class BaseAgent:
def __init__(self):
pass
class DialogPrompt(BasePrompt):
def __init__(self):
self.pre_prompt = """
请通过对话来给对话者制定旅游路线,以下是你可以利用的工具。
-----------------------------------------------
1. [路线规划](<出发地>, <目的地>)
2. [坐标转换](<地点>)
-----------------------------------------------
其中方括号[]包裹的是工具的名称,尖括号<>包裹的是参数的名称。
!!!请注意,你的输出应该严格按照下面的方式:
工具名称(参数名称)
例如:
1.
user: 我想知道从哈尔滨工业大学威海到山东大学威海应该怎么走。
you: 路线规划("哈尔滨工业大学威海","山东大学威海")
2.
user: 我想从哈尔滨工业大学威海到山东大学威海去玩。
you: 路线规划("哈尔滨工业大学威海", "山东大学威海")
!!! 如果对话者给定信息不全,请提醒用户给出全面的信息。
例如:
user: 我想去山东大学威海,应该怎么走。
you: 您提供的信息不足,请给出出发点。
user: 出发点是哈尔滨工业大学威海。
you:路线规划("哈尔滨工业大学威海","山东大学威海")
请开始对话:
"""
self.sys_prompt = "你是优秀的旅游向导,你能通过使用工具来为你的对话者制定旅游路线。"
super(DialogPrompt, self).__init__(pre_prompt=self.pre_prompt,sys_prompt=self.sys_prompt)
class DialogAgent(BaseAgent,DialogPrompt):
def __init__(self):
super(BaseAgent,self).__init__()
def parse_output(self,output:str): # 解析agent的输出
args=[]
func = None
positions = []
for k,v in tools_dict.items():
if k in output:
func = tools_dict[k]
if not func:
return False
for index, i in enumerate(output):
if i == "\"":
positions.append(index)
try:
for index in range(0,len(positions),2):
print(output[positions[index]:positions[index+1]].strip("\""))
args.append(output[positions[index]:positions[index+1]].strip("\""))
except Exception as e:
print("输出的\"的数量有问题")
if len(args) == 0:
return False
return func(*args)
tools.py -> 这里采用tool的名称和其方法直接对应
from baiduAPI import BaiduAPI,ak
tools_dict = {
"路线规划": BaiduAPI(ak).get_html,
"坐标转换": BaiduAPI(ak).transform
}
main.py
import streamlit as st
st.title(" Agent for train")
response = None
client = Open_AI(api_key="你的api_key",base_url="服务器提供的url")
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("What is up?"):
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
if prompt != None:
agent = DialogAgent()
stream = client.get_streaming_completion(system_prompt=agent.sys_prompt,prompt=agent.pre_prompt+"\n"+prompt,messages=st.session_state.messages)
response = st.write_stream(stream)
if response is not None:
out = agent.parse_output(response)
if out:
st.write("路径规划如下")
# 渲染地图
html_component = st.components.v1.html(out, height=600)
else:
stream = None
if prompt is not None:
st.session_state.messages.append({"role": "user", "content": prompt})
if response is not None:
st.session_state.messages.append({"role": "assistant", "content": response})
最后,一个帮助旅游的ai就做出来了
当然,这只是一个小的demo,可以帮助大家去快速上手streamlit+llm+train。还有其他的点子,比如在地图上进行标记,做可交互的地图,或者做基于LLM的大群聊,类似下面的。
User: @刘公岛,我要去你那里玩了。 刘公岛: 好啊,好啊!欢迎! User: @刘公岛,发个位置给我。 刘公岛: ok ok。 [位置地图] User: 威海还有什么好玩的。 刘公岛: @威高,我哥叫你呢。 威高: 来了来了,😊😊。 User: 哪里看日出比较好。 半月湾: 我这我这。 成头山: 别去他那,来我这。我这有。。。。。,@半月湾,看看实力? 成头山: [图片], @半月湾,O.o。
github 链接 SongWWWWWW/Agent4train at master (github.com)
参考链接:
[毕设记录]streamlit调用folium地图点击后返回坐标_streamlit-folium-CSDN博客
485

被折叠的 条评论
为什么被折叠?



