公司当前项目使用的是Apifox作为API调试/文档工具,所以以这个为例。市面上的工具应该都逐步开放了AI能力,需要关注一下,只要能提供原始数据就行,不能的话那也没办法喂给AI。
上一篇已经让AI写UI了,这一篇让AI写接口请求。首先思考一下可能碰到的问题。
一、可能遇到的问题
默认网络请求用的retrofit,其它也是一通百通。
1、问题一:适配请求头
项目中网络请求的拦截器已经定义了请求头,读取的原始数据如何关联?答案就是在skill中告诉AI,并让它模仿你的代码写。
2、问题二:适配请求参数中有一些公共部分
项目中网络请求的拦截器已经定义了一些公共请求参数,读取的原始数据如何关联?例如:
kotlin
{
"app": {
"locale": "{{locale}}",
"tid": "{{terminal_id}}",
"platform": "{{platform}}"
},
"data": {
//每个接口只有这里有差异
}
}
只有data块中有差异。答案其实同上,skill中告诉AI,并让它模仿你的代码。
3、问题三:返回数据脱壳
例如项目中的壳是这样:
kotlin
data class ApiResponse<T>(
val code: Int,
val msg: String,
val data: T
) : BaseResponse<T>() {...}
那我们需要的解析的Data Class可能只需要脱壳的部分,如何让AI知道?答案也同上,skill中告诉AI,并让它模仿你的代码。
4、问题四:viewmodel中网络请求如何写
道理同上。
5、问题五:代码放在哪个文件
这个问题的解答要看情况了,我们先看下面只给代码的示例,最后再探讨一下这个问题。
二、获取接口原始数据
这一步依赖MCP的开放能力,后端兄弟接口怎么定义的,字段怎么写的,Android如何封装Data Class都依赖这些原始数据。接下来看通过Apifox如何办到的。
Apifox的MCP现阶段还不能集成到Codex中使用,启动的时候会报错:
kotlin
⚠ MCP client for `apifox_api_docs` failed to start: MCP startup failed: handshaking with MCP server failed: connection closed: initialize response
⚠ MCP startup incomplete (failed: apifox_api_docs)
原因是Apifox的MCP返回不符合Codex定义的规范。
那换其他方式,打开Apifox终端(当前V版本2.7.58 (2.7.58)),选择需要生成代码的接口:

如上操作,可以得到复制的一段信息如下:
kotlin
请访问以下链接获取接口"天气信息V3"的接口定义信息:https://api.apifox.cn/temp-links/api/%E5%A4%A9%E6%B0%6%81%AFv3-253061517?t=e0be3ab5-e8bf-4ec2-be81-f1489
这样我们就拿到了接口的原始数据链接。
三、如何编辑skills
这一步跟项目强关联,因为每个项目写法都不一样,文件不一样,所以skill的写法因项目而异,下面贴一份剔除敏感信息后的skill,抛砖引玉:
kotlin
---
name: api_to_request
description: 请访问链接获取接口定义信息。
---
## 请访问链接获取接口定义信息,给出如下要求的代码:
### 1、代码一:生成retrofit接口定义
`java/api` 文件夹内是定义的retrofit api 请求的接口,例如其中的一例:
/**
* 获取Camera Event事件
*/
@POST("/v1/iot/camera/discoverXXX")
suspend fun discoverXxx(@Body request: Request): ApiResponse<Response?>
请按照链接中的请求类型给出类似上面的接口定义。
### 2、代码二:给出请求的@Body
一般的请求数据示例是下面这样的:
{
"data": {
"dev_id": "xxx"
},
"app": {
"locale": "en_AU",
"tid": "8AE4EC1E36904B869D03FC9BABE4C087",
"alias": "Kevin 的 A52",
"platform": "foxx"
}
}
为了避免每次都给出重复的参数,我在retrofit添加了一个拦截器,请参考`AddPublicParamsInterceptor.kt`文件,这里是添加公共请求参数的地方。所以@Body中只需要给出data节点的内容即可,如果data节点内的内容为空,那就不需要给出任何参数,因为`AddPublicParamsInterceptor.kt`中会默认添加`{}`.
### 3、代码三:给出返回的response
返回的数据使用`ApiResponse.kt`剥壳,所以给出的response数据应该是剥壳后的数据,一般是一个data class类。请参考`java/data`文件夹内其他response的写法给出。
### 4、代码四:viewmodel中实际请求的代码
viewmodel中实际请求的代码请参考下面的代码给出:
/**
* 获取设备同步授权码
*/
fun syncAppDevice(
buffer: MeshBanBindBuffer,
success: (Any?) -> Unit,
fail: (ApiHttpExceptionResponse<Any?>) -> Unit,
error: (AppException) -> Unit
) {
val request = SyncAppDeviceRequest(...)
requestWithHttpException({ apiService.syncAppDevice(request) }, success = {
success(it)
}, fail = {
fail(it)
}, error = {
error(it)
})
}
根据项目情况可以写的更丰富,更详细。
Apifox复制链接的内容因为匹配skill的description,所以给出链接就可以触发上面的skill。Codex生成的代码如下(剔除了敏感信息):
kotlin
• 使用 api_to_request 技能,因为需要从接口文档生成 Retrofit 请求、请求体、响应体与 ViewModel 调用代码。(api.apifox.cn (https://api.apifox.cn/temp-links/api/
%E5%A4%A9%E6%F%A1%E6%81%AFv3-253061517?t=e0be3ab5-e8bf-4ec2-be81-f148d9))
代码一:Retrofit 接口定义(api.apifox.cn (https://api.apifox.cn/temp-links/api/%E5%A4%A9%E6%A1%E6%81%AFv3-253061517?t=e0be3ab5-e8bf-4ec2-be81-
f14569))
/**
* 天气信息V3
*/
@POST("/v1/iot/weather/getCurrentInfo")
suspend fun getCurrentInfoV3(@Body request: WeatherInfoV3Request): ApiResponse<WeatherInfoV3Response?>
代码二:@Body(仅 data 节点)(api.apifox.cn (https://api.apifox.cn/temp-links/api/%E5%A4%A9%E6%%E6%81%AFv3-253061517?t=e0be3ab5-e8bf-4ec2-be81-
f8569))
data class WeatherInfoV3Request(
var lon: Double,
var lat: Double,
var unit: String = "IMPERIAL" // METRIC / IMPERIAL
)
代码三:Response(剥壳后的 data)(api.apifox.cn (https://api.apifox.cn/temp-links/api/%E5%A4%A9%E6%F%A1%E6%81%AFv3-253061517?t=e0be3ab5-e8bf-4ec2-
be81-f18569))
data class WeatherInfoV3Response(
var day: Boolean?,
var weather: WeatherV3?,
var temperature: Double?,
var wind: Double?,
var pressure: Double?,
var humidity: Double?,
var aqi: String?
)
data class WeatherV3(
var condition: String?,
var icon: String?
)
代码四:ViewModel 中实际请求代码(api.apifox.cn (https://api.apifox.cn/temp-links/api/%E5%A4%A9%%A1%E6%81%AFv3-253061517?t=e0be3ab5-e8bf-4ec2-
be81-f1469))
/**
* 获取天气信息V3
*/
fun getCurrentInfoV3(
lon: Double,
lat: Double,
unit: String = "IMPERIAL",
success: (WeatherInfoV3Response?) -> Unit,
fail: (ApiHttpExceptionResponse<Any?>) -> Unit,
error: (AppException) -> Unit
) {
val request = WeatherInfoV3Request(
lon = lon,
lat = lat,
unit = unit
)
requestWithHttpException({ homeManagementApiService.getCurrentInfoV3(request) }, success = {
success(it)
}, fail = {
fail(it)
}, error = {
error(it)
})
}
生成的代码可用度极高,很大程度上解放了生产力。
四、AI的放权
AI生成的代码可用度这么高,那是不是应该放权直接让AI写文件呢?
1、如果是一个新项目,定义好项目架构后就大胆的让AI生成文件自己写代码吧,能"摸鱼"就别动手,人生苦短我用AI。
2、如果是一个成熟度高的项目,建议以小颗粒度的形式让AI生成或修改文件代码,这样影响的范围可控,git提交的时候请仔细审查AI生成的代码。
3、如果是一个成熟度高的项目,修改的需求复杂,牵扯面广,或者公司规定不让AI写代码只让辅助参考,那还是开只读模式自己写吧。
回到最上面的问题五:代码放哪个文件?
答案就简单了。
- 项目架构清晰,让AI干活,可以完全让AI自主决策,后续自己调整。
- 可以在skill中添加提示加以引导,让AI放哪个文件/目录。
- 只读模式,自己处理AI生成的代码,前期可以开只读熟悉AI的使用,等熟练后就可以让AI自己写文件了。