ChatGPT Plugin开发setup - Java(Spring Boot) Python(fastapi)

记录一下快速模板,整体很简单,如果不接auth,只需要以下:

  • 提供一个/.well-known/ai-plugin.json接口,返回openAI所需要的格式
  • 提供openAPI规范的文档
  • CORS设置

其他的和普通的web开发类似.

本地开发就直接使用localhost即可,前几天官方localhost无法联通,最近应该修复了.

要让GPT更好理解接口内容,接口需要写详细的文档,在文档内写清楚各个参数作用和可选值以及示例.

Spring Boot

增加对文档的依赖

1
2
3
4
5
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>

增加一个bean配置:

1
2
3
4
5
6
7
@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(new Info().title("html fetcher")
.description("get content from url")
.version("1.0"));
}

文档的地址为/v3/api-docs

增加ai-plugin.json接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@GetMapping(value = "/.well-known/ai-plugin.json", produces = "application/json")
public String aiPlugin() {
return """
{
"schema_version": "v1",
"name_for_human": "html fetcher Plugin",
"name_for_model": "html_fetcher",
"description_for_human": "Plugin for getting content from url",
"description_for_model": "Plugin for getting content from url",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "http://localhost:8080/v3/api-docs",
"is_user_authenticated": false
},
"logo_url": "http://localhost:8080/logo.png",
"contact_email": "support@example.com",
"legal_info_url": "http://www.example.com/legal"
}
""";
}

logo直接放到\resources\static
内容根据自己插件修改,本地开发直接写localhost,部署写对应的网站地址.

CORS设置

测试的时候允许可以写*, 后续上线更改为openai.com:

1
2
3
4
5
6
7
8
9
10
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}

要写自定义文档可以用spring-doc的相关注解.
比如写在接口上用@Operation, 字段上使用@Schema注解.
@Schema内可以用allowableValuesexample等来约束openai的查询.

fastapi

fastapi自带openAPI集成,只需要把json dump成yaml即可, setup比较简单, 这里直接全部放一起了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
app = FastAPI()

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

# static 文件夹用来做静态资源host 放logo
app.mount("/static", StaticFiles(directory="static"), name="static")

# json -> yaml
@app.get("/openapi.yaml")
async def get_openapi():
return Response(content=yaml.dump(app.openapi()), media_type="application/yaml")


@app.get("/.well-known/ai-plugin.json")
async def openai_api_plugin():
return {
"schema_version": "v1",
"name_for_human": "",
"name_for_model": "",
"description_for_human": "",
"description_for_model": "",
"auth": {
"type": "none"
},
"api": {
"type": "openapi",
"url": "http://localhost:8000/openapi.yaml",
"is_user_authenticated": False
},
"logo_url": "http://localhost:8000/static/logo.png",
"contact_email": "support@example.com",
"legal_info_url": "http://www.example.com/legal"
}

自定义文档内容对于接口的可以用summary,response_description参数, query参数可以用Annotationd, 一个例子:

1
2
3
4
5
6
@app.get("/api/query_profit_data", summary='query profit data by company code, year and quarter', response_description="""
return profit data in format {"key":{"0":"value"}}, panda's dataframe""")
async def query_profit_data(code: Annotated[str, Query(description="the company code", example="sh.600000")],
year: Annotated[int, Query(description="year to get profit", example=2023)],
quarter: Annotated[
int, Query(description="quarter to get profit. allow values:1,2,3,4", example=1)]):

参考资料

chatgpt plugin: https://openai.com/blog/chatgpt-plugins

spring doc: https://springdoc.org/v2/

fastapi: https://fastapi.tiangolo.com/