package mcp import ( "context" "yunyan/lego/core" "yunyan/lego/core/cbase" "yunyan/lego/sys/log" "yunyan/sys/amap" "yunyan/utils" "fmt" "runtime/debug" "sync" "github.com/mark3labs/mcp-go/mcp" ) // 天气卡片数据 结构统一化输出 type NavigationCard struct { Origin string `json:"location"` //起始点 Destination string `json:"destination"` //目标点 Start string `json:"start"` //起始点 End string `json:"end"` //目标点 Start_longitude string `json:"start_longitude"` //起始点经度 Start_latitude string `json:"start_latitude"` //起始点纬度 End_longitude string `json:"end_longitude"` //目标点经度 End_latitude string `json:"end_latitude"` //目标点纬度 Itmes []NavigationItem `json:"items"` //导航信息 } type NavigationItem struct { Ntype string `json:"ntype"` //类型 Enable bool `json:"enable"` //启用 Image string `json:"image"` //图片 Distance string `json:"distance"` //距离 Duration string `json:"duration"` //时间 Path []struct { Action string `json:"action"` //动作 右转 Distance string `json:"distance"` //距离 距离 } `json:"path"` //路径 Transits []struct { Name string `json:"name"` //搅动方式 Distance string `json:"distance"` //距离 } `json:"transits"` //路径 } // 天气查询 type tool_gaode_map_route_navigation struct { cbase.ModuleCompBase module *Mcp } // 组件初始化接口 func (this *tool_gaode_map_route_navigation) Init(service core.IService, module core.IModule, comp core.IModuleComp, opt core.IModuleOptions) (err error) { this.ModuleCompBase.Init(service, module, comp, opt) this.module = module.(*Mcp) return } func (this *tool_gaode_map_route_navigation) Start() (err error) { err = this.ModuleCompBase.Start() if this.module.AddTool(ToolGroup_CHINA, this.Tool(), this.Handl) { if err := amap.OnInit(this.module.service.GetSettings().Sys["amap"]); err != nil { panic(fmt.Sprintf("init sys.amap err: %s", err.Error())) } else { log.Infof("init sys.amap success!") } } return } func (this *tool_gaode_map_route_navigation) Tool() (tool mcp.Tool) { return mcp.NewTool("maps_route_navigation", mcp.WithDescription("根据用户提供的起点和终点以及出行方式,提供导航服务"), mcp.WithString("origins_longitude", mcp.Description("起点经度"), mcp.Required(), ), mcp.WithString("origins_latitude", mcp.Description("起点纬度"), mcp.Required(), ), mcp.WithString("destination_address", mcp.Description("终点地址"), mcp.Required(), ), mcp.WithString("destination_longitude", mcp.Description("终点经度"), mcp.Required(), ), mcp.WithString("destination_latitude", mcp.Description("终点纬度"), mcp.Required(), ), mcp.WithString("type", mcp.Description("交通类型,0:不指定交通工具,1驾车,2骑行,3公交,4步行"), mcp.DefaultNumber(0), ), ) } // 工具处理接口 // 参数: // - ctx: 上下文 // - request: 工具调用请求,包含起点终点坐标与交通类型 // 返回值: // - result: 工具调用结果,包含导航卡片或失败提示 // - err: 处理过程中的错误,当导航失败时返回非空错误 // 异常: // - 当参数缺失、定位/路径规划失败或内部错误时返回错误 func (this *tool_gaode_map_route_navigation) Handl(ctx context.Context, request mcp.CallToolRequest) (result *mcp.CallToolResult, err error) { var ( o_longitude, o_latitude, d_longitude, d_latitude, origins, destination, destination_address, city1, city2, cityCode1, cityCode2, originddress, destinationaddress string location *amap.LocationResponse geocoderesult *amap.ReverseGeocodeResponse georesult *amap.GeocodeResponse direction_driving *amap.RootResponse direction_walking *amap.RootResponse direction_bicycling *amap.RootResponse direction_tsransit *amap.TsransitResponse card *NavigationCard context string errs map[string]error errlock sync.Mutex ok bool ) this.module.Debug("tool_gaode_map_route_navigation", log.Field{Key: "request.Params.Arguments", Value: request.GetRawArguments()}, ) if o_longitude, err = request.RequireString("origins_longitude"); err != nil || o_longitude == "" { err = fmt.Errorf("必要参数[origins_longitude]丢失") context = "获取目标地址失败" return } if o_latitude, err = request.RequireString("origins_latitude"); err != nil || o_latitude == "" { err = fmt.Errorf("必要参数[origins_latitude]丢失") context = "获取目标地址失败" return } if destination_address, err = request.RequireString("destination_address"); err != nil || destination_address == "" { err = fmt.Errorf("必要参数[destination_address]丢失") context = "获取目标地址失败" return } if d_longitude, err = request.RequireString("destination_longitude"); err != nil || d_longitude == "" { err = fmt.Errorf("必要参数[destination_longitude]丢失") context = "获取目标地址失败" return } if d_latitude, err = request.RequireString("destination_latitude"); err != nil || d_latitude == "" { err = fmt.Errorf("必要参数[destination_latitude]丢失") context = "获取目标地址失败" return } origins = fmt.Sprintf("%s,%s", o_longitude, o_latitude) destination = fmt.Sprintf("%s,%s", d_longitude, d_latitude) log.Debug("tool_navigation", log.Field{Key: "origins", Value: origins}, log.Field{Key: "destination", Value: destination}, ) ok = false if geocoderesult, err = amap.Geocode(origins); err == nil { city1 = fmt.Sprintf("%s%s", geocoderesult.Regeocode.AddressComponent.Province, geocoderesult.Regeocode.AddressComponent.City) originddress = fmt.Sprintf("%s", geocoderesult.Regeocode.AddressComponent.StreetNumber.Street) cityCode1 = geocoderesult.Regeocode.AddressComponent.Citycode if geocoderesult, err = amap.Geocode(destination); err == nil { city2 = fmt.Sprintf("%s%s", geocoderesult.Regeocode.AddressComponent.Province, geocoderesult.Regeocode.AddressComponent.City) destinationaddress = fmt.Sprintf("%s", geocoderesult.Regeocode.AddressComponent.StreetNumber.Street) cityCode2 = geocoderesult.Regeocode.AddressComponent.Citycode var wg sync.WaitGroup errs = make(map[string]error) wg.Add(4) go func() { defer func() { if err := recover(); err != nil { // 打印堆栈信息用于调试 if stack := debug.Stack(); len(stack) > 0 { log.Errorf("Stack trace: %s", string(stack)) } } }() defer wg.Done() if direction_driving, err = amap.DirectionForDriving(origins, destination); err != nil { errlock.Lock() errs["driving"] = err errlock.Unlock() } }() go func() { defer func() { if err := recover(); err != nil { // 打印堆栈信息用于调试 if stack := debug.Stack(); len(stack) > 0 { log.Errorf("Stack trace: %s", string(stack)) } } }() defer wg.Done() if direction_walking, err = amap.DirectionForWalking(origins, destination); err != nil { errlock.Lock() errs["walking"] = err errlock.Unlock() } }() go func() { defer func() { if err := recover(); err != nil { // 打印堆栈信息用于调试 if stack := debug.Stack(); len(stack) > 0 { log.Errorf("Stack trace: %s", string(stack)) } } }() defer wg.Done() if direction_bicycling, err = amap.DirectionForBicycling(origins, destination); err != nil { errlock.Lock() errs["bicycling"] = err errlock.Unlock() } }() go func() { defer func() { if err := recover(); err != nil { // 打印堆栈信息用于调试 if stack := debug.Stack(); len(stack) > 0 { log.Errorf("Stack trace: %s", string(stack)) } } }() defer wg.Done() if direction_tsransit, err = amap.DirectionForTsransit(origins, destination, cityCode1, cityCode2); err != nil { errlock.Lock() errs["transit"] = err errlock.Unlock() } }() wg.Wait() // 任意一种路径规划成功则认为成功 if direction_driving != nil || direction_walking != nil || direction_bicycling != nil || direction_tsransit != nil { ok = true } } } if !ok { context = "导航失败,请检查起点和终点是否正确" this.module.Error("导航失败,请检查起点和终点是否正确", log.Field{Key: "city1", Value: city1}, log.Field{Key: "location", Value: location}, log.Field{Key: "city2", Value: city2}, log.Field{Key: "address", Value: destinationaddress}, log.Field{Key: "georesult", Value: georesult}, log.Field{Key: "err", Value: err}, ) // 返回非空错误避免上层对nil结果解引用造成panic err = fmt.Errorf("导航失败") return } else { card = &NavigationCard{ Origin: originddress, Destination: destination_address, Start: origins, End: destination, Start_longitude: o_longitude, Start_latitude: o_latitude, End_longitude: d_longitude, End_latitude: d_latitude, Itmes: make([]NavigationItem, 4), } card.Itmes[0] = pathTransNavigationItem("driving", direction_driving) card.Itmes[1] = pathTransNavigationItem("walking", direction_walking) card.Itmes[2] = pathTransNavigationItem("cycling", direction_bicycling) card.Itmes[3] = transitTransNavigationItem("transit", direction_tsransit) context = utils.ToString(map[string]interface{}{ "iscard": true, "broadcast": false, "card_navigation": card, "result": fmt.Sprintf("正在为您导航到:%s", originddress), }) this.module.Debug("导航成功", log.Field{Key: "origin", Value: originddress}, log.Field{Key: "address", Value: destinationaddress}, log.Field{Key: "card", Value: card}, ) } result = &mcp.CallToolResult{ Content: []mcp.Content{ mcp.TextContent{ Type: "text", Text: context, }, }, } return } func pathTransNavigationItem(ntype string, root *amap.RootResponse) (item NavigationItem) { item.Ntype = ntype if root == nil || root.Route == nil || root.Route.Paths == nil || len(root.Route.Paths) == 0 { return } path := root.Route.Paths[0] item = NavigationItem{ Ntype: ntype, Enable: true, Image: path.Image, Distance: path.Distance, Duration: path.Cost.Duration, } if ntype == "cycling" { // 骑行类型 数据和其他的不一样 item.Duration = path.Duration // 骑行的耗时 } item.Path = make([]struct { Action string `json:"action"` //动作 右转 Distance string `json:"distance"` //距离 距离 }, len(path.Steps)) for i, v := range path.Steps { item.Path[i].Action = v.Navi.Action item.Path[i].Distance = fmt.Sprintf("%v", v.StepDistance) } return } func transitTransNavigationItem(ntype string, root *amap.TsransitResponse) (item NavigationItem) { item.Ntype = ntype if root == nil || root.Route == nil || root.Route.Transits == nil || len(root.Route.Transits) == 0 { return } path := root.Route.Transits[0] item = NavigationItem{ Ntype: ntype, Enable: true, Image: path.Image, Distance: path.Distance, Duration: path.Cost.Duration, } item.Transits = make([]struct { Name string `json:"name"` //搅动方式 Distance string `json:"distance"` //距离 }, len(path.Segments)) for i, v := range path.Segments { if v.Bus != nil { item.Transits[i].Name = v.Bus.Buslines[0].Name } else if v.Walking != nil { item.Transits[i].Name = "步行" item.Transits[i].Distance = v.Walking.Distance } } return }