Unity 工具 之 Azure 微软 【GPT4o】HttpClient 异步流式请求的简单封装
目录
Unity 工具 之 Azure 微软 【GPT4o】HttpClient 异步流式请求的简单封装
一、简单介绍
Unity 工具类,自己整理的一些游戏开发可能用到的模块,单独独立使用,方便游戏开发。
本节介绍,这里在使用微软的Azure 进行语音合成的两个方法的做简单整理,这里简单说明,如果你有更好的方法,欢迎留言交流。
官网注册:
面向学生的 Azure - 免费帐户额度 | Microsoft Azure
官网技术文档网址:
Azure OpenAI 一些模型介绍:
Azure OpenAI 服务模型 - Azure OpenAI | Microsoft Learn
GPT-4o 和 GPT-4 Turbo
GPT-4o 是 OpenAI 的最新模型。 GPT-4o 在单个模型中集成文本和图像,从而能够同时处理多个数据类型。 这种多模式方法提高了人机交互的准确性和响应能力。 GPT-4o 在英语文本和编码任务方面与 GPT-4 Turbo 相当,但在非英语语言和视觉任务方面具有更优越的性能,为 AI 功能设定了新的基准。
如何访问 GPT-4o 模型?
GPT-4o 可用于标准和全球标准模型部署。
需要在该模型可用的受支持标准或全球标准区域中创建或使用现有资源。
创建资源后,可以部署 GPT-4o 模型。
发起请求的json 格式:
{ "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": [ { "type": "text", "text": "Describe this picture:" }, { "type": "image_url", "image_url": { "url": "<image URL>" } } ] } ], "max_tokens": 100, "stream": false }
返回的格式根据是否是流式返回,略有所不同:
非流式的:
{ "id": "chatcmpl-8X0uY6Xbv4XpU4zo0KXhVk4iTldJf", "object": "chat.completion", "created": 1702879018, "model": "gpt-4v", "prompt_filter_results": [{ "prompt_index": 0, "content_filter_results": { "hate": { "filtered": false, "severity": "safe" }, "self_harm": { "filtered": false, "severity": "safe" }, "sexual": { "filtered": false, "severity": "safe" }, "violence": { "filtered": false, "severity": "safe" } } }], "choices": [{ "finish_details": { "type": "stop", "stop": "<|fim_suffix|>" }, "index": 0, "message": { "role": "assistant", "content": "这张图片展示了一辆银灰色的跑车,停靠在一个内饰精致的展厅里。车身呈流线型,前脸上有显著的品牌标志和数字“10”。车身上的漆面光洁明亮,反射着展厅内柔和的灯光。背景中可以看到其他几辆停放整齐的高端车辆,以及墙上挂着的一些装饰性画作。整个场景显得高端大气,彰显了跑车的豪华品质。" }, "content_filter_results": { "hate": { "filtered": false, "severity": "safe" }, "self_harm": { "filtered": false, "severity": "safe" }, "sexual": { "filtered": false, "severity": "safe" }, "violence": { "filtered": false, "severity": "safe" } } }], "usage": { "prompt_tokens": 826, "completion_tokens": 175, "total_tokens": 1001 } }
流式的根据数据内容不同,data 数据不同:
data: {"choices":[],"created":0,"id":"","model":"","object":"","prompt_filter_results":[{"prompt_index":0,"content_filter_result":{"jailbreak":{"filtered":false,"detected":false},"custom_blocklists":{"filtered":false,"details":[]}}},{"prompt_index":1,"content_filter_result":{"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"},"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"custom_blocklists":{"filtered":false,"details":[]}}}]} data: {"choices":[{"content_filter_results":{},"delta":{"content":"","role":"assistant"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"镜"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"腿"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"\n"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"眼"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"镜"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"框"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"\n"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"控制"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"电"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"路"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"\n"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"连接"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"电"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"路"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"\n"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"前"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"顶"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"盖"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"\n"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"摄"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"像"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"头"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"\n"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"投"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"影"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"模块"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"\n"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"鼻"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"梁"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"\n"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"显示"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"光"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"学"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"元"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"件"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"\n"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"麦"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"克"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"风"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: {"choices":[{"content_filter_results":{},"delta":{},"finish_reason":"stop","index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} data: [DONE]
二、实现原理
1、官网申请得到对应使用 GPT4o 模型的相关key 等信息(和之前申请GPT3.5、GPT4 类似)
2、使用 HttpClinet 进行网络 Post 请求,且使用异步请求 ,使用 cancellationToken 用于取消请求
// 发送请求并获取响应 HttpResponseMessage response = await client.SendAsync(request, cancellationToken);
3、根据请求流式和非流式的标志 “stream”,false 为非流式,true 为流式返回数据
4、其中 HttpClient 的异步请求也是可以打断的
cancellationTokenSource.Cancel();
三、注意实现
1、GPT4o 支持文本和图片数据发起 Post 请求访问,其中 图片数据,可以是 url ,也可以是 Base64 ,其中 Base64 数据注意添加上前缀,例如 png 图片,前缀:data:image/png;base64,
string IMAGE_png_EXTRA_PREFIXES_BASE64 = "data:image/png;base64,"; string base64 = await ImageLoader.Instance.LoadImageAndConvertToBase64(); base64 = IMAGE_png_EXTRA_PREFIXES_BASE64 + base64;
2、这里没有添加历史数据,需要的话可以自动添加历史数据,添加到请求的 public List<Message> messages; 列表中即可,不过,提问要放到最后
public class JsonRequestStruct { public int max_tokens; public bool stream; public float temperature; public List<Message> messages; } public class Message { public string role; public object content; //这个是不规则的json 列表 }
四、简单效果预览
五、案例简单实现步骤
1、新建 Unity 工程
2、创建接口脚本,定义一些基本功能和回调
3、创建基类,HttpClient 实现数据的请求,取消请求的基本功能
4、创建实现了,填写自己的 GPT4o key 相关信息
5、测试接口,先测试非流式的文字请求
6、测试流式,图片请求的接口
六、关键代码
1、IAzureGpt4o
using System; using System.Threading.Tasks; public interface IAzureGpt4o { /// <summary> /// 请求失败的回调 /// </summary> Action<string> OnSendRequestFailed { get; set; } /// <summary> /// 响应的流式数据 /// </summary> Action<string> OnResponsingData { get; set; } /// <summary> /// 响应接收完的数据 /// </summary> Action<string> OnResponseFinished { get; set; } /// <summary> /// 发送请求 /// </summary> /// <param name="askContent"></param> /// <param name="imgData">图片数据,支持 url/base64</param> /// <returns></returns> Task<string> SendRequestAsync(string askContent, string imgData); /// <summary> /// 发送请求(流式返回数据) /// </summary> /// <param name="askContent"></param> /// <param name="imgData">图片数据,支持 url/base64</param> /// <returns></returns> Task<string> SendRequestStreamResponseAsync(string askContent, string imgData); /// <summary> /// Helper function to cancel the ongoing request /// </summary> public void CancelRequest(); }
2、BaseAzureGpt4o
using Newtonsoft.Json; using System.Collections.Generic; using System.IO; using System.Net; using System.Text.RegularExpressions; using System.Text; using UnityEngine; using System; using System.Threading.Tasks; using System.Threading; using System.Net.Http; /// <summary> /// GPT 4o 基类 /// </summary> /// <typeparam name="T"></typeparam> public class BaseAzureGpt4o : IAzureGpt4o { #region Data /// <summary> /// TAG /// </summary> protected virtual string TAG { get; } = "[BaseAzureGpt4o]"; /// <summary> /// API_BASE /// </summary> protected virtual string API_BASE { get; } = "https://gpt4o-version.openai.azure.com/"; /// <summary> /// DEPLOYMENT_NAME /// </summary> protected virtual string DEPLOYMENT_NAME { get; } = "Your_Deplyment_Name"; /// <summary> /// API_KEY /// </summary> protected virtual string API_KEY { get; } = "YOur_API_Key"; /// <summary> /// 对应模型接口的发行版本 /// </summary> protected virtual string MODEL_VERSION { get; } = "2024-05-01-preview"; /// <summary> /// 组织基础的 Url /// </summary> protected virtual string m_BaseUrl { get{ return $"{API_BASE}/openai/deployments/{DEPLOYMENT_NAME}"; } } /// <summary> /// 组成最后访问的 Url /// </summary> protected virtual string m_EndPointend { get {return $"{m_BaseUrl}/chat/completions?api-version={MODEL_VERSION}"; } } /// <summary> /// 最大的 Tokens /// </summary> protected virtual int MAX_TOKENS { get; } = 100; /// <summary> /// 请求失败的回调 /// </summary> public Action<string> OnSendRequestFailed { get; set; } /// <summary> /// 响应的流式数据 /// </summary> public Action<string> OnResponsingData { get; set; } /// <summary> /// 响应接收完的数据 /// </summary> public Action<string> OnResponseFinished { get; set; } /// <summary> /// CancellationTokenSource /// </summary> private CancellationTokenSource cancellationTokenSource; /// <summary> /// HttpClient /// </summary> private static readonly HttpClient client = new HttpClient(); #endregion #region Inteface function /// <summary> /// 发送请求(正常返回数据) /// </summary> /// <param name="askContent"></param> /// <param name="imgData">图片数据,支持 url/base64</param> /// <returns></returns> public virtual async Task<string> SendRequestAsync(string askContent, string imgData) { Debug.Log(TAG + "Start HttpWebRequest m_EndPointend : " + m_EndPointend); cancellationTokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = cancellationTokenSource.Token; // json 数据 string jsonData = GetJsonStr(askContent, imgData); /// 设置请求头 client.DefaultRequestHeaders.Clear(); client.DefaultRequestHeaders.Add("api-key", API_KEY); // 创建 HTTP 内容 var content = new StringContent(jsonData, Encoding.UTF8, "application/json"); try { // 创建请求 var request = new HttpRequestMessage(HttpMethod.Post, m_EndPointend) { Content = content }; // 发送请求并获取响应 HttpResponseMessage response = await client.SendAsync(request, cancellationToken); // 检查响应状态码 if (response.IsSuccessStatusCode) { // 读取整个响应内容 string responseText = await response.Content.ReadAsStringAsync(); if (cancellationToken.IsCancellationRequested) { Debug.Log(TAG + "Operation was canceled."); OnSendRequestFailed?.Invoke("Operation was canceled."); return null; // 或抛出异常,根据需要处理 } Debug.Log(TAG + " PostRequestStreamToStringAsync whole stream data : " + responseText); string jsonString = DecodeUnicode(responseText); string parseWholeRlt; // 使用 Newtonsoft.Json 将 JSON 字符串映射到对应的对象 JsonResponseStruct data = JsonConvert.DeserializeObject<JsonResponseStruct>(jsonString); if (data != null) { parseWholeRlt = data.choices[0].message.content.ToString(); Debug.Log(TAG + " PostRequestStreamToStringAsync whole stream data : " + parseWholeRlt); } else { Debug.Log(TAG + " PostRequestStreamToStringAsync whole stream data : " + jsonString); parseWholeRlt = jsonString; } if (OnResponseFinished != null) { Debug.Log(TAG + " PostRequestStreamToStringAsync OnResponseFinished is not null "); OnResponseFinished?.Invoke(parseWholeRlt); } else { Debug.Log(TAG + " PostRequestStreamToStringAsync OnResponseFinished is null "); } return parseWholeRlt; } else { // 打印错误状态码 Debug.LogError(TAG + "Error: " + response.StatusCode); OnSendRequestFailed?.Invoke(response.Content.ToString()); return null; } } catch (OperationCanceledException e) { Debug.Log(TAG + "Operation was canceled. " + e.Message); OnSendRequestFailed?.Invoke(e.Message); return null; // 或抛出异常,根据需要处理 } catch (WebException e) { Debug.Log(TAG + " e.Message: " + e.Message); OnSendRequestFailed?.Invoke(e.Message); return null; } } /// <summary> /// 发送请求(流式返回数据) /// </summary> /// <param name="askContent"></param> /// <param name="imgData">图片数据,支持 url/base64</param> /// <returns></returns> public virtual async Task<string> SendRequestStreamResponseAsync(string askContent, string imgData) { Debug.Log(TAG + "Start HttpWebRequest m_EndPointend : " + m_EndPointend); cancellationTokenSource = new CancellationTokenSource(); CancellationToken cancellationToken = cancellationTokenSource.Token; // json 数据 string jsonData = GetJsonStr(askContent, imgData, true); /// 设置请求头 client.DefaultRequestHeaders.Clear(); client.DefaultRequestHeaders.Add("api-key", API_KEY); // 创建 HTTP 内容 var content = new StringContent(jsonData, Encoding.UTF8, "application/json"); try { // 创建请求 var request = new HttpRequestMessage(HttpMethod.Post, m_EndPointend) { Content = content }; // 发送请求并获取响应头 HttpResponseMessage response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); // 检查响应状态码 if (response.IsSuccessStatusCode) { // 以流的方式读取响应内容 using (var stream = await response.Content.ReadAsStreamAsync()) { using (var reader = new StreamReader(stream)) { StringBuilder sb = new StringBuilder(); string line; while ((line = await reader.ReadLineAsync()) != null) { string parseRlt = HandleStreamData(line); if (string.IsNullOrEmpty(parseRlt) == false) { OnResponsingData?.Invoke(parseRlt); } sb.AppendLine(line); // 逐行读取并累积响应数据 Debug.Log(TAG + " PostRequestStreamToStringAsync getting steam data : " + sb.ToString()); // Check for cancellation if (cancellationToken.IsCancellationRequested) { Debug.Log(TAG + "Operation was canceled."); return null; // or throw an exception, handle as needed } } Debug.Log(TAG + " PostRequestStreamToStringAsync whole stream data : " + sb.ToString()); string jsonString = DecodeUnicode(sb.ToString()); string parseWholeRlt = HandleStreamData(jsonString); Debug.Log(TAG + " PostRequestStreamToStringAsync whole stream data : " + parseWholeRlt); if (OnResponseFinished != null) { Debug.Log(TAG + " PostRequestStreamToStringAsync OnResponseFinished is not null "); OnResponseFinished?.Invoke(parseWholeRlt); } else { Debug.Log(TAG + " PostRequestStreamToStringAsync OnResponseFinished is null "); } return parseWholeRlt; } } } else { // 打印错误状态码 Debug.LogError(TAG + "Error: " + response.StatusCode); OnSendRequestFailed?.Invoke(response.Content.ToString()); return null; } } catch (OperationCanceledException e) { Debug.Log(TAG + "Operation was canceled. " + e.Message); OnSendRequestFailed?.Invoke(e.Message); return null; // or throw an exception, handle as needed } catch (WebException e) { Debug.Log(TAG + " e.Message: " + e.Message); OnSendRequestFailed?.Invoke(e.Message); return null; } } /// <summary> /// Helper function to cancel the ongoing request /// </summary> public virtual void CancelRequest() { if (cancellationTokenSource != null && !cancellationTokenSource.IsCancellationRequested) { cancellationTokenSource.Cancel(); } } #endregion #region Protected function /// <summary> /// 将 Unicode 转义字符解码为 UTF-8 字符串的方法 /// </summary> /// <param name="input"></param> /// <returns></returns> protected string DecodeUnicode(string input) { return Regex.Replace(input, @"\\u([0-9a-fA-F]{4})", match => { string hex = match.Groups[1].Value; int codePoint = Convert.ToInt32(hex, 16); return char.ConvertFromUtf32(codePoint); }); } /// <summary> /// 组织提问和图片数据 /// </summary> /// <param name="askContent"></param> /// <param name="imgBase64OrUrlg"></param> /// <param name="isStreamResponse">是否流式响应返回数据</param> /// <returns></returns> protected string GetJsonStr(string askContent, string imgBase64OrUrlg, bool isStreamResponse = false) { List<object> contentList = new List<object>(); contentList.Add(new { type = "text", text = askContent, }); if (imgBase64OrUrlg != null) { // 创建一个包含指定数据结构的对象列表 contentList.Add(new { type = "image_url", image_url = new { url = imgBase64OrUrlg }, }); } // 将对象列表转换为 JSON 字符串 string jsonString = JsonConvert.SerializeObject(contentList, Formatting.Indented); Message messageS = new Message() { role = "system", content = "You are a helpful assistant." }; Message message = new Message() { role = "user", content = contentList }; List<Message> messages = new List<Message>(); messages.Add(messageS); messages.Add(message); JsonRequestStruct jsonRequest = new JsonRequestStruct() { messages = messages, max_tokens = MAX_TOKENS, stream = isStreamResponse }; jsonString = JsonConvert.SerializeObject(jsonRequest, Formatting.Indented); Debug.Log(jsonString); return jsonString; } #region Stream Response 数据解析 /* data: {"choices":[{"content_filter_results":{"hate":{"filtered":false,"severity":"safe"},"self_harm":{"filtered":false,"severity":"safe"},"sexual":{"filtered":false,"severity":"safe"},"violence":{"filtered":false,"severity":"safe"}},"delta":{"content":"克"},"finish_reason":null,"index":0,"logprobs":null}],"created":1718690676,"id":"chatcmpl-9bMFIE4gPNKU6Z1QyKvFsjwqNTOtw","model":"gpt-4o-2024-05-13","object":"chat.completion.chunk","system_fingerprint":"fp_abc28019ad"} */ /// <summary> /// 流式处理数据 /// </summary> /// <param name="data"></param> /// <returns></returns> string HandleStreamData(string data) { try { string[] dataLines = data.Split(new string[] { "data: " }, StringSplitOptions.RemoveEmptyEntries); StringBuilder sb = new StringBuilder(); foreach (var line in dataLines) { sb.Append(ProcessDataLine(line)); } return sb.ToString(); } catch (Exception e) { return null; } } /// <summary> /// 处理单行数据,解析获取 content,finish_reason 数据 /// content : 回复内容 /// finish_reason : 判断流式回答是否结束 /// </summary> /// <param name="dataLine"></param> /// /// <returns></returns> string ProcessDataLine(string dataLine) { string content = ExtractField(dataLine, "\"content\":\"", "\"},"); string finishReason = ExtractField(dataLine, "\"finish_reason\":", ","); Debug.Log(TAG + "ProcessDataLine():Content: " + content); content = content == null ? "" : content; // 排除这个情况 "delta":{"content":"","role":"assistant"},"finish_reason":null," if(content.Contains("\"role\":\"assistant")) content=""; if (finishReason!=null && finishReason == "\"stop\"") { Debug.Log(TAG+ "ProcessDataLine(): Finish reason is stop. Stopping further processing."); return ""; // 如果 finish_reason 是 stop,停止进一步处理 } else { return content; } } /// <summary> /// 抽取块数据 /// </summary> /// <param name="dataLine">行数据</param> /// <param name="fieldName">块名称</param> /// <param name="delimiter">分割符</param> /// <returns></returns> string ExtractField(string dataLine, string fieldName, string delimiter) { int fieldIndex = dataLine.IndexOf(fieldName); if (fieldIndex == -1) { return null; } int startIndex = fieldIndex + fieldName.Length; int endIndex = dataLine.IndexOf(delimiter, startIndex); if (endIndex == -1) { return dataLine.Substring(startIndex).Trim(' ', '}', ']'); } return dataLine.Substring(startIndex, endIndex - startIndex); } #endregion #endregion #region JsonRequestStruct /* json 字符串 { "messages": [ { "role": "system", "content": "You are a helpful assistant." }, # Content can be a string, OR { "role": "user", "content": [ # It can be an array containing strings and images. "描述一下这张图,请用中文回答", { "image": "img_base64" } # Images are represented like this. ] } ], "max_tokens": 100 } */ /* 更新 json 字符串 { "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": [ { "type": "text", "text": "Describe this picture:" }, { "type": "image_url", "image_url": { "url": "<image URL>" } } ] } ], "max_tokens": 100, "stream": false } */ public class JsonRequestStruct { public int max_tokens; public bool stream; public float temperature; public List<Message> messages; } public class Message { public string role; public object content; //这个是不规则的json 列表 } #endregion #region JsonResponseStruct /* json 字符串 { "id": "chatcmpl-8X0uY6Xbv4XpU4zo0KXhVk4iTldJf", "object": "chat.completion", "created": 1702879018, "model": "gpt-4v", "prompt_filter_results": [{ "prompt_index": 0, "content_filter_results": { "hate": { "filtered": false, "severity": "safe" }, "self_harm": { "filtered": false, "severity": "safe" }, "sexual": { "filtered": false, "severity": "safe" }, "violence": { "filtered": false, "severity": "safe" } } }], "choices": [{ "finish_details": { "type": "stop", "stop": "<|fim_suffix|>" }, "index": 0, "message": { "role": "assistant", "content": "这张图片展示了一辆银灰色的跑车,停靠在一个内饰精致的展厅里。车身呈流线型,前脸上有显著的品牌标志和数字“10”。车身上的漆面光洁明亮,反射着展厅内柔和的灯光。背景中可以看到其他几辆停放整齐的高端车辆,以及墙上挂着的一些装饰性画作。整个场景显得高端大气,彰显了跑车的豪华品质。" }, "content_filter_results": { "hate": { "filtered": false, "severity": "safe" }, "self_harm": { "filtered": false, "severity": "safe" }, "sexual": { "filtered": false, "severity": "safe" }, "violence": { "filtered": false, "severity": "safe" } } }], "usage": { "prompt_tokens": 826, "completion_tokens": 175, "total_tokens": 1001 } } */ public class JsonResponseStruct { public string id { get; set; } public string @object { get; set; } public long created { get; set; } public string model { get; set; } public Usage usage { get; set; } public List<Choice> choices { get; set; } public List<PromptFilterResult> prompt_filter_results { get; set; } } public class Usage { public int prompt_tokens { get; set; } public int completion_tokens { get; set; } public int total_tokens { get; set; } } public class Choice { public RMessage message { get; set; } public FinishDetails finish_details { get; set; } public int index { get; set; } public ContentFilterResult content_filter_results { get; set; } } public class RMessage { public string role; public string content; //这个是不规则的json 列表 } public class FinishDetails { public string type { get; set; } public string stop { get; set; } } public class PromptFilterResult { public int index { get; set; } public ContentFilterResult content_filter_results { get; set; } } public class ContentFilterResult { public FilterReuslt hate { get; set; } public FilterReuslt self_harm { get; set; } public FilterReuslt sexual { get; set; } public FilterReuslt violence { get; set; } } public class FilterReuslt { public bool filtered { get; set; } public string severity { get; set; } } #endregion }
3、AzureGPT4o
public class AzureGPT4o : BaseAzureGpt4o { /// <summary> /// TAG /// </summary> protected override string TAG { get; } = "[AzureGPT4o]"; /// <summary> /// API_BASE /// </summary> protected override string API_BASE { get; } = "https://gpt4o-version.openai.azure.com/"; /// <summary> /// DEPLOYMENT_NAME /// </summary> protected override string DEPLOYMENT_NAME { get; } = "Your_Deployment_Name"; /// <summary> /// API_KEY /// </summary> protected override string API_KEY { get; } = "Your_API_Key"; /// <summary> /// 对应模型接口的发行版本 /// </summary> protected override string MODEL_VERSION { get; } = "2024-05-01-preview"; /// <summary> /// 最大的 Tokens /// </summary> protected override int MAX_TOKENS { get; } = 300; }
4、TestAzureGPT4o
using System.Collections; using System.Collections.Generic; using UnityEngine; public class TestAzureGPT4o : MonoBehaviour { string TAG = "[TestAzureGPT4o] "; IAzureGpt4o m_AzureGpt4o; // Start is called before the first frame update void Start() { //TestGPT4o_Text(); TestGPT4o_Image(); } // Update is called once per frame void Update() { if (Input.GetKeyDown(KeyCode.Space)) { m_AzureGpt4o?.CancelRequest(); } } async void TestGPT4o_Text() { m_AzureGpt4o = new AzureGPT4o(); m_AzureGpt4o.OnSendRequestFailed = (err) => { Debug.Log(TAG + "TestGPT4o_Text(): OnSendRequestFailed err " + err); }; m_AzureGpt4o.OnResponseFinished = (str) => { Debug.Log(TAG + "TestGPT4o_Text(): OnResponseFinished str " + str); }; m_AzureGpt4o.OnResponsingData = (str) => { Debug.Log(TAG + "TestGPT4o_Text(): OnResponsingData str " + str); }; string rlt = await m_AzureGpt4o.SendRequestAsync( "你好呀,你是谁?", null); Debug.Log(TAG + "TestGPT4o_Text(): rlt " + rlt); } async void TestGPT4o_Image() { m_AzureGpt4o = new AzureGPT4o(); m_AzureGpt4o.OnSendRequestFailed = (err) => { Debug.Log(TAG + "TestGPT4o_Image(): OnSendRequestFailed err " + err); }; m_AzureGpt4o.OnResponseFinished = (str) => { Debug.Log(TAG + "TestGPT4o_Image(): OnResponseFinished str " + str); }; m_AzureGpt4o.OnResponsingData = (str) => { Debug.Log(TAG + "TestGPT4o_Image(): OnResponsingData str " + str); }; string rlt = await m_AzureGpt4o.SendRequestStreamResponseAsync( "描述一下", "https://d1.faiusr.com/4/AAEIABAEGAAggOra9AUooNDeiAEwoAY45gM.png"); Debug.Log(TAG + "TestGPT4o_Image(): rlt " + rlt); } }