alibaba / higress

🤖 AI Gateway | AI Native API Gateway
https://higress.io
Apache License 2.0
2.83k stars 469 forks source link

Higress AI 格式化文档/代码生成 #1214

Closed Suchun-sv closed 3 weeks ago

Suchun-sv commented 4 weeks ago

1. 背景

目前,LLM 的响应往往是非正式且非结构化的。这使得在需要基于 LLM 响应进行开发时,通常需要使用复杂的工具如 LangChain 等思维链操作,以确保输出符合预期。例如,在根据用户提问生成代码的场景中,开发者常常需要手动分离 LLM 的文本回复和生成的代码。这不仅增加了开发的复杂性,还难以确保最终效果的稳定性。 为了简化这一流程,OpenAI 最近基于 “gpt-4o-2024-08-06” 模型推出了 JSON 结构化输出接口(Structured Outputs - OpenAI API)。通过定义 JSON Schema 来严格约束 LLM 的响应格式,开发者可以确保输出更加规范和可控,大大降低了开发的难度。 一个简单的Case如下:

思维链数学辅导的结构化输出

首先定义需要响应的JSON Schema:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "MathReasoning",
  "type": "object",
  "properties": {
    "steps": {
      "type": "array",
      "items": {
        "$ref": "#/definitions/Step"
      }
    },
    "final_answer": {
      "type": "string"
    }
  },
  "required": ["steps", "final_answer"],
  "definitions": {
    "Step": {
      "type": "object",
      "properties": {
        "explanation": {
          "type": "string"
        },
        "output": {
          "type": "string"
        }
      },
      "required": ["explanation", "output"]
    }
  }
}

其次,问题为:帮我解方程:8x + 7 = -23 最后的回答为:

"Answer": {
  "steps": [
    {
      "explanation": "从方程 8x + 7 = -23 开始。",
      "output": "8x + 7 = -23"
    },
    {
      "explanation": "两边同时减去 7,以隔离含有变量的项。",
      "output": "8x = -23 - 7"
    },
    {
      "explanation": "简化方程右边。",
      "output": "8x = -30"
    },
    {
      "explanation": "两边同时除以 8,解出 x。",
      "output": "x = -30 / 8"
    },
    {
      "explanation": "简化分数。",
      "output": "x = -15 / 4"
    }
  ],
  "final_answer": "x = -15 / 4"
}

这样不仅使得对回答要求的格式更为明确,并且回复的结果能可以简单的作为后续程序处理的输入。

2. 需求

2.1 在 AI-Proxy 的代码基础上增加对 JSON Schema 接口的支持

AI-Proxy 已经支持了众多 LLM 的接口,在其上增加支持 JSON Sechma 的接口可以让现有使用其的用户或依赖 AI-Proxy 开发的组件方便的扩展对 JSON Sechma 接口的支持。

2.2 格式化文档/代码生成

由于 JSON Sechma 的编写和维护并不适合所有基础的用户,单独维护一个格式化文档/代码生成的插件也有一定的价值。

johnlanni commented 4 weeks ago

很棒的idea,2.2我觉得可以独立一个插件,JSON schema 跟 Higress 的结合点很多,例如插件的配置格式化、API文档的辅助生成(swagger/oas3也都是基于 JSON schema 扩展而来的)

Suchun-sv commented 3 weeks ago

简介

Note

需要数据面的proxy wasm版本大于等于0.2.100

编译时,需要带上版本的tag,例如:tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags="custommalloc nottinygc_finalizer proxy_wasm_version_0_2_100" ./

需搭配AI-Proxy插件,并且需要配置 provider = openai, model=gpt-4o-2024-08-06 以支持结构化输出接口

AI 结构化Json文档生成和验证,可根据自然语言描述生成Json文档或者Json Schema,或根据预先定义的JsonSchema验证Json文档是否正确,并给出解释。

生成Json文档或者Json Schema约束 image 验证给定文档是否满足给定Json Schema约束 image

配置说明

Name Type Requirement Default Description
model string optional "gpt-4o-2024-08-06" 指定的模型服务,注意需选择支持结构化输出接口的模型
enable_swagger bool optional false 是否启用swagger验证文档
enable_oas3 bool optional true 是否启用oas3验证文档
custom_askjson object optional “” 生成Json文档时使用的JsonSchema约束,此设置会覆盖默认设置
custom_askjsonschema object optional “” 生成Json Schema约束时使用的JsonSchema约束,此设置会覆盖默认设置
custom_askverify object optional “” 当验证Json文档不符合给定的Json Schema约束时,传入后续模型服务使用的JsonSchema约束,此设置会覆盖默认设置

配置示例

Model: "gpt-4o-2024-08-06"
EnableOas3: true

请求格式

Name Type Requirement Description
desc string required 自然语言描述,需要用户提供,单独提供可以根据默认Json Schema生成Json文档
json_doc string optional json文档,和 desc 搭配可以生成相应的Json Schema约束
json_schema string optional [type=val] 用于验证 json_doc 是否满足约束 [type=gen] 和 json_doc 一起提示LLM生成相应的Json Schema约束
type string required 指定为 gen 或者 val ,分别代表生成和验证模式

返回参数

Name Type Requirement Description
reason string required LLM后端的自然语言回答
json string optional 返回的json文档获json schema约束
listOfCases string optional 如果生成Json文档(API文档),相应给出案例说明

请求示例

1. 根据自然语言描述生成Json文档,并给出解释 (type=gen)

curl -X POST "http://loalhost:8001/v1/chat/completions" \
-H "Content-Type: application/json" \
-d '{
  "model": "gpt-4o-2024-08-06",
  "desc": "帮我设计API接口:在请求的变量上加5,返回加和",
  "type": "gen"
}'

默认使用 templates\askjson.go 中定义的json schema来生成Json文档,可以使用配置中的 custom_askjson 来替换

返回示例:

{
  "json": {
    "api_version": "1.0",
    "endpoint": "/addFive",
    "method": "POST",
    "parameters": [
      {
        "name": "number",
        "type": "integer"
      }
    ],
    "response": {
      "message": "The sum of the input number and 5.",
      "status": "success"
    }
  },
  "listOfCases": [
    {
      "caseDescription": "Add 5 to the positive integer 10",
      "caseName": "Adding to positive integer",
      "input": "number=10",
      "output": "15"
    },
    {
      "caseDescription": "Add 5 to zero",
      "caseName": "Adding to zero",
      "input": "number=0",
      "output": "5"
    },
    {
      "caseDescription": "Add 5 to negative integer -3",
      "caseName": "Adding to negative integer",
      "input": "number=-3",
      "output": "2"
    }
  ],
  "reason": "This API is designed to take an integer input, add the number 5 to it, and return the result. It demonstrates a simple arithmetic operation applied to an API input parameter."
}

2. 根据Json文档生成Json Schema,并给出解释 (type=gen)

curl -X POST "http://localhost:8001/v1/chat/completions" \
-H "Content-Type: application/json" \
-d '{
  "model": "gpt-4o-2024-08-06",
  "desc": "我希望实现一个 JSON Schema,用于约束 Kibana 配置文件的结构。请根据以下测试用例帮助我编写该 JSON Schema。",
  "json_doc": "{ \"attributes\": { \"title\": \"Sample Dashboard\", \"description\": \"This is a sample dashboard.\", \"panelsJSON\": \"[{\\\"panelIndex\\\":\\\"1\\\",\\\"gridData\\\":{\\\"x\\\":0,\\\"y\\\":0,\\\"w\\\":24,\\\"h\\\":15},\\\"type\\\":\\\"visualization\\\",\\\"id\\\":\\\"1\\\"}]\", \"optionsJSON\": \"{\\\"darkTheme\\\":false}\", \"version\": 1, \"timeRestore\": false, \"kibanaSavedObjectMeta\": { \"searchSourceJSON\": \"{\\\"query\\\":{\\\"query\\\":\\\"\\\",\\\"language\\\":\\\"lucene\\\"},\\\"filter\\\":[]}\" } }, \"type\": \"dashboard\" }",
  "type": "gen"
}'

默认使用 templates\askjson.go 中定义的json schema来生成Json文档,可以使用配置中的 custom_askjsonschema 来替换,注意由于现有的结构化输出接口不支持所有的Json Schema语法,所以这里生成的Json Schema仅为String类型,用户使用时需要根据实际情况进行验证并调整

{
  "json": "{\n  \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n  \"type\": \"object\",\n  \"properties\": {\n    \"attributes\": {\n      \"type\": \"object\",\n      \"properties\": {\n        \"title\": {\n          \"type\": \"string\"\n        },\n        \"description\": {\n          \"type\": \"string\"\n        },\n        \"panelsJSON\": {\n          \"type\": \"string\",\n          \"description\": \"Serialized JSON string of panels configuration\"\n        },\n        \"optionsJSON\": {\n          \"type\": \"string\",\n          \"description\": \"Serialized JSON string of dashboard options\"\n        },\n        \"version\": {\n          \"type\": \"integer\"\n        },\n        \"timeRestore\": {\n          \"type\": \"boolean\"\n        },\n        \"kibanaSavedObjectMeta\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"searchSourceJSON\": {\n              \"type\": \"string\",\n              \"description\": \"Serialized JSON string of search source configuration\"\n            }\n          },\n          \"required\": [\"searchSourceJSON\"]\n        }\n      },\n      \"required\": [\"title\", \"panelsJSON\", \"version\", \"kibanaSavedObjectMeta\"]\n    },\n    \"type\": {\n      \"type\": \"string\",\n      \"enum\": [\"dashboard\"]\n    }\n  },\n  \"required\": [\"attributes\", \"type\"]\n}",
  "reason": "The JSON schema defines the required structure for a Kibana configuration file based on the provided example, ensuring correct data types and required fields."
}

3. 给定Json Schema检验Json文档是否正确,并给出解释 (type=val)

curl -X POST "http://localhost:8001/v1/chat/completions" \
-H "Content-Type: application/json" \
-d '{
  "desc": "请帮我检查一下这个case和对应的jsonschema是否匹配请检查以下提供的案例及其对应的JSON Schema是否匹配。",
  "json_doc": "{ \"attributes\": { \"title\": \"Sample Dashboard\", \"description\": \"This is a sample dashboard.\", \"panelsJSON\": \"[{\\\"panelIndex\\\":\\\"1\\\",\\\"gridData\\\":{\\\"x\\\":0,\\\"y\\\":0,\\\"w\\\":24,\\\"h\\\":15},\\\"type\\\":\\\"visualization\\\",\\\"id\\\":\\\"1\\\"}]\", \"optionsJSON\": \"{\\\"darkTheme\\\":false}\", \"version\": 1, \"timeRestore\": false, \"kibanaSavedObjectMeta\": { \"searchSourceJSON\": \"{\\\"query\\\":{\\\"query\\\":\\\"\\\",\\\"language\\\":\\\"lucene\\\"},\\\"filter\\\":[]}\" } }, \"type\": \"dashboard\" }",
  "json_schema": "{ \"type\": \"object\", \"properties\": { \"attributes\": { \"type\": \"object\", \"properties\": { \"title\": { \"type\": \"string\" }, \"description\": { \"type\": \"string\" }, \"panelsJSON\": { \"type\": \"string\" }, \"optionsJSON\": { \"type\": \"string\" }, \"version\": { \"type\": \"integer\" }, \"timeRestore\": { \"type\": \"boolean\" }, \"kibanaSavedObjectMeta\": { \"type\": \"object\", \"properties\": { \"searchSourceJSON\": { \"type\": \"string\" } }, \"required\": [\"searchSourceJSON\"] } }, \"required\": [\"title\", \"description\", \"panelsJSON\", \"optionsJSON\", \"version\", \"timeRestore\", \"kibanaSavedObjectMeta\"] }, \"type\": { \"type\": \"string\" } }, \"required\": [\"attributes\", \"type\"] }",
  "type": "val"
}'

当检验通过

{"reason": "case is valid"}

当检验不通过

这里的 json 代表尝试更正后的 json_doc , reason 代表对不匹配原因的解释

{
  "json": "{\n  \"attributes\": { \n    \"title\": \"Sample Dashboard\", \n    \"description\": \"This is a sample dashboard.\", \n    \"panelsJSON\": \"[{\\\"panelIndex\\\":\\\"1\\\",\\\"gridData\\\":{\\\"x\\\":0,\\\"y\\\":0,\\\"w\\\":24,\\\"h\\\":15},\\\"type\\\":\\\"visualization\\\",\\\"id\\\":\\\"1\\\"}]\", \n    \"optionsJSON\": \"{\\\"darkTheme\\\":false}\", \n    \"version\": 1, \n    \"timeRestore\": false, \n    \"kibanaSavedObjectMeta\": { \n      \"searchSourceJSON\": \"{\\\"query\\\":{\\\"query\\\":\\\"\\\",\\\"language\\\":\\\"lucene\\\"},\\\"filter\\\":[]}\" \n    } \n  }, \n  \"type\": \"dashboard\" \n}",
  "reason": "The JSON case does not match the schema because a key in the 'attributes' object is incorrectly named. In the JSON case, 'tile' is used instead of 'title', which is the required key name as per the JSON schema. To fix this, we need to rename 'tile' to 'title', ensuring that all required keys in the schema are present and correctly named. After this correction, all keys in the JSON case will align with those specified in the JSON schema."
}

当提供的JsonSchema格式不正确

{"reason": "failed to compile json schema, please check the json schema you provided"}
Suchun-sv commented 3 weeks ago

[ai-json-resp] LLM响应结构化插件,用于根据默认或用户配置的Json Schema对AI的响应进行结构化

简介

Note

需要数据面的proxy wasm版本大于等于0.2.100

编译时,需要带上版本的tag,例如:tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags="custommalloc nottinygc_finalizer proxy_wasm_version_0_2_100" ./

需要配合 ai-proxy 插件使用

LLM响应结构化插件,用于根据默认或用户配置的Json Schema对AI的响应进行结构化,以便后续插件处理。注意目前只支持 非流式响应

配置说明

Name Type Requirement Default Description
serviceName str required - 网关服务名称
serviceDomain str required - 网关服务域名/IP地址
servicePort int required - 网关服务端口
serviceTimeout int optional 50000 默认请求超时时间
maxRetry int optional 3 若回答无法正确提取格式化时重试次数
contentPath str optional "choices.0.message.content” 从LLM回答中提取响应结果的gpath路径
jsonSchema str (json) optional APITemp, details in the “./templates.go” 验证请求所参照的jsonSchema
enableSwagger bool optional false 是否启用Swagger协议进行验证
enableOas3 bool optional true 是否启用Oas3协议进行验证

请求和返回参数说明

请求示例

curl -X POST "http://localhost:8001/v1/chat/completions" \
-H "Content-Type: application/json" \
-d '{
  "model": "gpt-4",
  "messages": [
    {"role": "user", "content": "give me a api doc for add the variable x to x+5"}
  ]
}'

返回Json为

{
  "apiVersion": "1.0",
  "request": {
    "endpoint": "/add_to_five",
    "method": "POST",
    "port": 8080,
    "headers": {
      "Content-Type": "application/json"
    },
    "body": {
      "x": 7
    }
  }
}