【征文计划】基于Rokid 眼镜 的AI天气应用+GPS定位+AI旅游规划

【征文计划】基于Rokid 眼镜 的AI天气应用+GPS定位+AI旅游规划

文章目录

本文我将解决这三件事,将天气应用升级为AI旅游规划助手,基于Rokid 眼镜 的AI天气应用+GPS定位+AI旅游规划的实现。🙏

本文应用基于Rokid灵珠智能体/CXR SDK开发,开发指南https://forum.rokid.com/index

在这里插入图片描述

本文选用的技术包括:

GPS 自动定位:说「这里天气」自动获取位置,不用报城市名
多轮对话:说「上海呢」「那边呢」「再查一次」接续上轮查询
AI 旅游规划:接入 Claude API,天气播报后自动生成个性化旅游建议和行程规划
可直接复制的 Kotlin 代码(LocationHelper、ConversationContext、AiTravelPlanHelper)
踩坑经验:直辖市 adcode、续播语义识别、LLM 延迟控制


一、主要流程

本篇在 AiWeatherActivity(AI 语音查天气)基础上扩展,整体数据流如下:

新增三个辅助类,原有文件做对应改造:

新建文件职责
LocationHelper.ktGPS + 高德逆地理编码
ConversationContext.kt多轮对话上下文(含5分钟TTL)
AiTravelPlanHelper.ktClaude API 旅游规划
文件创新点
AiIntentParser.kt+ GPS触发词 + 续播意图解析 + 城市库扩充
WeatherViewHelper.kt+ tv_travel_plan 控件 + generateTravelPlanUpdateJson()
AiWeatherActivity.kt串联 GPS / Context / TravelPlan 完整调用链
---

二、功能 A:GPS 自动定位

2.1 实现路径

用户说完“这里的天气”不想等 5 秒。缓存位置最多偏差几公里,对天气查询完全够用。

2.2 核心代码:LocationHelper.kt

classLocationHelper(privateval context: Context){interface LocationCallback {funonCityCode(adcode: String, cityName: String, districtName: String)funonError(reason: String)}fungetCurrentCityCode(callback: LocationCallback){if(!hasLocationPermission()){ callback.onError("缺少定位权限")return}val manager = context.getSystemService(Context.LOCATION_SERVICE)as LocationManager val lastKnown =getLastKnownLocation(manager)if(lastKnown !=null){reverseGeocode(lastKnown.latitude, lastKnown.longitude, callback)}else{requestSingleUpdate(manager, callback)}}@SuppressLint("MissingPermission")privatefungetLastKnownLocation(manager: LocationManager): Location?=listOf(GPS_PROVIDER, NETWORK_PROVIDER, PASSIVE_PROVIDER).mapNotNull{ runCatching { manager.getLastKnownLocation(it)}.getOrNull()}.maxByOrNull{ it.time }privatefunreverseGeocode(lat: Double, lon: Double, callback: LocationCallback){// 注意:高德 API 格式是 "经度,纬度"(lon在前)val url ="$REGEO_URL?location=$lon,$lat&key=$API_KEY&extensions=base&output=JSON"// OkHttp 调用 ...// 解析响应val component = json .getJSONObject("regeocode").getJSONObject("addressComponent")val adcode = component.optString("adcode")// 坑:直辖市 city 字段为空,需取 provinceval city = component.optString("city").ifEmpty{ component.optString("province")}val district = component.optString("district") callback.onCityCode(adcode, city, district)}}

2.3 意图识别:我们添加 GPS 的关键词

在 AiIntentParser 里加一批触发词,识别「天气」类意图:

privateval LOCATION_KEYWORDS =listOf("这里","附近","当前","我这","这边","当前位置","我在哪","这里的")// 返回特殊常量 INTENT_LOCATION,交给 Activity 分支处理constval INTENT_LOCATION ="__LOCATION__"funisLocationIntent(text: String): Boolean {val hasLocation = LOCATION_KEYWORDS.any{ text.contains(it)}val hasWeather = text.contains("天气")|| WEATHER_KEYWORDS.any{ text.contains(it)}return hasLocation && hasWeather }

Activity 侧处理分支:

privatefunprocessRecognizedText(text: String){val intent = intentParser.parseWeatherIntent(text, conversationContext)when{ intent ==null->{updateStatus("未识别到查询意图,请说「XXX天气」或「这里天气」")notifyAiError()} intent == AiIntentParser.INTENT_LOCATION ->handleLocationIntent()else->queryWeather(intent, intentParser.getCityNameByCode(intent))}}privatefunhandleLocationIntent(){ checkLocationPermission { locationHelper.getCurrentCityCode(object: LocationHelper.LocationCallback{overridefunonCityCode(adcode: String, cityName: String, districtName: String){val name =if(districtName.isNotBlank())"$cityName$districtName"else cityName queryWeather(adcode, name)}overridefunonError(reason: String){notifyAiError()}})}}

三、功能 B:对话上下文工程

3.1 核心数据结构

dataclassConversationContext(val lastCityCode: String?=null,val lastCityName: String?=null,val turnCount: Int =0,val lastQueryTimeMs: Long =0L){companionobject{privateconstval CONTEXT_TTL_MS =5*60*1000L// 5分钟}funisValid(): Boolean = lastCityCode !=null &&(System.currentTimeMillis()- lastQueryTimeMs) < CONTEXT_TTL_MS funadvance(cityCode: String, cityName: String): ConversationContext =copy( lastCityCode = cityCode, lastCityName = cityName, turnCount = turnCount +1, lastQueryTimeMs = System.currentTimeMillis())}

为什么设 5 分钟 TTL? 其实就是经验估计:5 分钟内的续问大概率是连续对话;超过 5 分钟放下手机再拿起来,基本是新话题,不应复用旧上下文。

3.2 续播意图的两种形态

privateval CONTINUATION_KEYWORDS =listOf("那呢","那边","那里呢","那边呢","再查","继续","再来一次","重新查")privatefunparseContinuationIntent(text: String, ctx: ConversationContext): String?{// 形态1:续播词 → 直接复用上次城市if(CONTINUATION_KEYWORDS.any{ text.contains(it)})return ctx.lastCityCode // 形态2:只有城市名,没有天气关键词(「福州呢」)→ 切换城市val hasWeather = WEATHER_KEYWORDS.any{ text.contains(it)}if(!hasWeather){val cityCode =extractCityCode(text)if(cityCode !=null)return cityCode }returnnull}

三种典型场景对照:

用户说解析结果
「福州呢」形态2:切换到福州
「那边呢」形态1:复用上次城市
「再查一次」形态1:同城市重查
「明天北京天气」正常解析:北京(不走续播)

Activity 侧每次成功查询后更新上下文:

// queryWeather 成功回调中: conversationContext = conversationContext.advance(cityCode, cityName)

四、功能 C:AI 旅游规划

4.1 为什么用 LLM, 而不是规则

用规则也能生成简单建议:

if(temp <10)"适合室内景点"elseif(weather.contains("雨"))"建议带伞,推荐室内博物馆"else"户外景点和公园都适合"

问题在于这是死的。同样是 25 度、晴天:北京故宫需要建议避开人流高峰;杭州西湖需要推荐骑行路线;三亚应该提醒防晒。LLM 能感知城市的旅游特色、气候背景,给出有地域差异的个性化旅游建议,这是规则系统做不到的。

4.2 核心代码:AiTravelPlanHelper.kt

class AiTravelPlanHelper {companionobject{privateconstval API_URL ="https://api.anthropic.com/v1/messages"privateconstval CLAUDE_API_KEY ="YOUR_CLAUDE_API_KEY"privateconstval MODEL ="claude-haiku-4-5-20251001"}interface TravelPlanCallback {funonTravelPlan(plan: String)funonError(reason: String)}fungetTravelPlan( city: String, temp: String, weather: String, wind: String, humidity: String, callback: TravelPlanCallback ){val systemPrompt ="你是一个专业的旅游规划助手,根据天气数据为用户生成简洁的中文旅游建议。"+"要求:语气自然友好,不超过80字,直接给建议,包含1-2个当地特色景点推荐,不要重复天气数据。"val userMessage ="城市:$city,气温:${temp}°C,天气:$weather,"+"风力:$wind,湿度:${humidity}%,请给出旅游建议。"val requestBody =JSONObject().apply{put("model", MODEL)put("max_tokens",300)put("system", systemPrompt)put("messages",JSONArray().apply{put(JSONObject().apply{put("role","user")put("content", userMessage)})})}.toString()val request = Request.Builder().url(API_URL).addHeader("x-api-key", CLAUDE_API_KEY).addHeader("anthropic-version","2023-06-01").addHeader("content-type","application/json").post(requestBody.toRequestBody("application/json".toMediaType())).build() client.newCall(request).enqueue(object: Callback {overridefunonResponse(call: Call, response: Response){val body = response.body?.string()?:returnval plan =JSONObject(body).getJSONArray("content").getJSONObject(0).optString("text")?.trim()if(plan !=null) callback.onTravelPlan(plan)else callback.onError("解析失败")}overridefunonFailure(call: Call, e: IOException){ callback.onError("网络请求失败: ${e.message}")}})}}

4.3 Prompt 设计要点

「不要重复天气数据」这条约束很关键——用户刚听完 TTS 播报了天气,建议里再说「当前北京25度晴天,推荐去故宫」是纯粹的信息冗余。选 claude4.5而不是更强的模型,是因为这个场景对「聪明程度」要求不高,对延迟的要求更高:用户说完天气查询,天气 TTS 结束后 2 秒内最好就能听到旅游建议。

4.4 与天气查询的串联时序

privatefunqueryWeather(cityCode: String, cityName: String){ weatherApiHelper.getWeatherForecast(cityCode,object: WeatherApiHelper.WeatherCallback{overridefunonSuccess(response: WeatherApiResponse){val live = response.lives?.firstOrNull()val forecast = response.forecasts?.firstOrNull()// 1. 打开眼镜端 Custom View(旅游规划区初始显示「规划获取中...」)openGlassCustomView(weatherViewHelper.generateWeatherViewJson(live, forecast))// 2. TTS 播报天气摘要sendWeatherTts(weatherViewHelper.generateWeatherTtsText(live, forecast))// 3. 更新多轮上下文 conversationContext = conversationContext.advance(cityCode, cityName)// 4. 异步获取 AI 旅游规划(不阻塞天气播报)if(live !=null)fetchAiTravelPlan(live, cityName)}overridefunonError(error: String){notifyAiError()}})}privatefunfetchAiTravelPlan(live: Live, cityName: String){val wind ="${live.winddirection ?:""}${live.windpower ?:""}".trim() travelPlanHelper.getTravelPlan( city = cityName, temp = live.temperature ?:"--", weather = live.weather ?:"--", wind = wind, humidity = live.humidity ?:"--", callback =object: AiTravelPlanHelper.TravelPlanCallback{overridefunonTravelPlan(plan: String){// 更新眼镜端旅游规划控件updateGlassCustomView(weatherViewHelper.generateTravelPlanUpdateJson(plan))// 延迟 2 秒播报,避免与天气 TTS 重叠Handler(Looper.getMainLooper()).postDelayed({sendGlobalTtsContent(plan)},2000L)}overridefunonError(reason: String){updateGlassCustomView( weatherViewHelper.generateTravelPlanUpdateJson("旅游规划暂时无法获取"))}})}

4.5 眼镜端 Custom View 新增旅游规划区

WeatherViewHelper 在原有天气卡片末尾追加分割线和旅游规划控件:

// 分割线 children.put(createTextView( id ="tv_divider", text ="─────────────────", textSize ="10sp", textColor ="#FF444444", marginTop ="12dp", marginBottom ="8dp"))// AI 旅游规划占位(成功后 updateCustomView 更新) children.put(createTextView( id = ViewIds.TV_TRAVEL_PLAN, text ="规划获取中...", textSize ="14sp", textColor ="#FFFFCC00"// 金色,区别于普通信息))

仅更新旅游规划的方法:

fungenerateTravelPlanUpdateJson(plan: String): String {val updates =JSONArray() updates.put(createUpdateAction(ViewIds.TV_TRAVEL_PLAN,"text", plan))return updates.toString()}

五、踩坑与排错速查

直辖市逆地理编码返回城市名为空

高德 regeo 接口,北京/上海/天津/重庆的 city 字段是空字符串,城市信息在 province 里:

// 错误写法:val city = component.optString("city")// 北京返回 ""// 正确写法:val city = component.optString("city").ifEmpty{ component.optString("province")}

续播语义识别错误

判断关键是「有没有天气关键词」:

  • 有天气关键词(「北京天气」)→ 走正常解析,不走续播
  • 无天气关键词(「北京呢」)+ 有城市名 → 走续播,切换城市
  • 续播词(「那边呢」)→ 复用上次城市

AI 旅游规划延迟太长/播报重叠

Claude Haiku 响应通常在 1-2 秒。fetchAiTravelPlan 在天气查询成功后立即异步发起,规划播报延迟 2 秒,基本不会与天气 TTS 重叠。如果网络慢可以加 OkHttp 超时:

OkHttpClient.Builder().readTimeout(10, TimeUnit.SECONDS).build()

requestSingleUpdate 废弃警告

LocationManager.requestSingleUpdate() 在 API 30+ 被标记废弃,但本项目 minSdk=28,功能完全正常,用 @Suppress(“DEPRECATION”) 压警告即可。


六、完整调用示意

用户:「这里天气」 → isLocationIntent → INTENT_LOCATION → checkLocationPermission → LocationHelper.getCurrentCityCode → 高德 regeo → adcode=110105(朝阳区) → queryWeather("110105", "北京市朝阳区") → openCustomView(天气卡片,旅游规划区显示「获取中...」) → sendTtsContent(「北京市朝阳区当前天气,温度25度,晴...」) → context.advance("110105", "北京市朝阳区") → AiTravelPlanHelper.getTravelPlan → Claude API → updateCustomView(「今天天气舒适,推荐去朝阳公园散步,傍晚可以去三里屯逛逛」) → 2秒后 sendGlobalTtsContent(「今天天气舒适,推荐去朝阳公园散步,傍晚可以去三里屯逛逛」) 用户:「福州呢」 → parseContinuationIntent → 形态2,切换到福州 → queryWeather("310101", "福州") ...(同上流程) 用户:「那边呢」 → parseContinuationIntent → 形态1,复用福州 → queryWeather("310101", "福州") ... 

七、其他功能

做完这篇,其实有一个更大的问题浮现:眼镜应该做什么?

手机是工具——你主动去用它。眼镜是助手——它在你需要的时候说一句话,然后闭上嘴。

天气+旅游是最安全的起点:不打扰、有明确答案、TTS 一句话说完。但如果你想继续探索,以下方向都在这套框架上可以直接延伸:

  • 景点导览:到达景点后自动识别位置,推送景点介绍和历史背景
  • 行程提醒:结合日历,提前推送目的地天气和出行建议
  • 实时路况:结合地图数据,提供出行路线和实时交通信息
  • 多日规划:「那明天呢」处理预报字段,生成多天旅游行程
  • 美食推荐:结合当地特色美食,根据天气推荐适合的餐厅
    🙏🙏🙏🙏

Read more

编程小白也能玩转AI!零基础入门指南

编程小白也能玩转AI!零基础入门指南

🎁个人主页:User_芊芊君子 🎉欢迎大家点赞👍评论📝收藏⭐文章 🔍系列专栏:AI 【前言】 你是不是也经常听到“AI时代来了”,却总觉得那是程序员、算法工程师的专属领域?自己连代码都没写过,是不是就只能旁观?别急——2025年,AI早已不是高不可攀的黑科技,而是一套普通人也能上手的实用工具。本文专为零编程基础的小白量身打造,不讲公式、不谈理论,只教你怎么用最简单的方式,把大模型变成你的“智能外挂”:写周报、回邮件、分析数据、做方案……从注册账号到跑通第一段代码,手把手带你迈出第一步。你会发现,会打字,就能玩转AI。 一、别被“AI”吓到!你不需要成为科学家 很多人一听“人工智能”,立刻想到: * 要懂数学?要会微积分? * 要会写复杂代码? * 要有GPU服务器? 错!2025年了,AI已经像Word、

SpringBoot 整合LangChain4j 集成 Tavily 实现联网搜索,如何获取Tavily API_KEY(一篇文章解决AI联网搜索全部问题)

SpringBoot 整合LangChain4j 集成 Tavily 实现联网搜索,如何获取Tavily API_KEY(一篇文章解决AI联网搜索全部问题)

关于LangChain4j+LangGraph4j的完整项目已经基本完成,教程中所有代码均有使用,可以前往KuiCoding ,了解更多LangChain4j相关应用。希望您不吝惜您的starred给新人一点创作鼓励。 要实现联网搜索功能,获取API Key是必不可少的关键步骤。 建议按以下步骤获取Tavily API密钥: 1. 访问Tavily官网注册账号 2. 进入API Playground填写申请信息 3. 返回Overview页面即可查看生成的API密钥我们可以先去Tavily官网 获取一个API密钥,注册一个自己的账号,然后点击API Playground 后面填写申请内容,回到Overview就能看见我们申请好的API密钥了 获得API密钥后,即可在项目中完成相应配置。 可以通过max-results 来控制最多搜索结果数量 langchain4j:open-ai:chat-model:base-url: https://dashscope.aliyuncs.com/compatible-mode/v1 api-key: ${QWEN_API_KEY}

8款高效科研绘图工具推荐:从流程图到专业结构图,AI助力学术可视化

8款高效科研绘图工具推荐:从流程图到专业结构图,AI助力学术可视化

在现代科研工作中,数据的可视化表达已成为论文撰写、项目汇报、成果展示中不可或缺的一环。一张清晰、专业、美观的图表不仅能提升论文的可读性与说服力,更能帮助研究者更直观地梳理逻辑、传达思想。然而,对于许多非设计背景的研究人员而言,使用传统绘图软件(如Visio、PPT、Adobe Illustrator)制作高质量科研图表往往耗时费力、学习成本高、易出错。 幸运的是,随着人工智能和自动化技术的发展,一批专为科研人员打造的智能绘图工具应运而生。它们支持通过自然语言描述自动生成各类图表,涵盖流程图、机制图、结构图、时序图等常见类型,极大降低了科研可视化的门槛。 本文将为您详细介绍8款当前主流且实用的科研绘图工具,涵盖不同学科领域与应用场景。其中,我们将重点解析“PaperXie AI科研绘图模块”,并结合其界面截图进行详细功能说明,确保内容真实、贴合产品实际,避免任何夸大或误导性描述。 一、PaperXie AI科研绘图模块 —— 面向多学科的专业级智能绘图助手 官网地址:点击直达https://www.paperxie.cn/tools/drawing 核心亮点:

我把OpenClaw调教成了能替我干活的AI员工,以后上班可以摸鱼了!

我把OpenClaw调教成了能替我干活的AI员工,以后上班可以摸鱼了!

手把手教你一键部署OpenClaw(Clawdbot),2分钟搞定! 网上教你装OpenClaw(小龙虾)的文章满大街都是,但装好后怎么让它真正派上用场,几乎没人讲透。 这半个月我为了折腾搜索、浏览器、文件同步和人格配置,前前后后烧掉不少钱,最后理出了这份实战清单。 文章挺长,建议把配置方法直接丢给你家的AI,让它手把手带你弄。 动手之前,咱们先得换个思路:别把小龙虾当成ChatGPT那种助手,把它想成你刚招的一个远程员工。 既然是员工,你就得给人家配电脑、开网络、装工具,还得讲清楚你是谁、你平时干活的习惯是什么。 下面分享的,就是我给这位“远程同事”搭工位的全过程。 手把手教你一键部署OpenClaw(Clawdbot),2分钟搞定! 一、 准备工作:电脑、大脑、部署 1、 搞台云服务器 首先,小龙虾得有个安身立命的电脑。 虽然很多人喜欢部署在Mac mini或者闲置本子上,但我更建议买云服务器。 服务器24小时不关机,你随时随地都能用,不像家里的电脑关了就断了。 这里有两个坑得避开: 第一,