ChatGPT应用:函数调用(function_calling)配合流式输出示例代码

文章讲述了如何使用OpenAI的GPT-4进行流式对话,涉及函数调用和参数传递,以及一个具体使用场景的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

function_call + 流式输出

pip install openai==1.9.0

# -*- coding: utf-8 -*-
# Author: 薄荷你玩
# Date: 2024/01/26

from openai import OpenAI
import json

api_key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
client = OpenAI(api_key=api_key)


def get_user_name(user_id: int):
    """
    根据用户id获取用户名(模拟)
    :param user_id: 用户ID
    :return:
    """
    if user_id == 1001:
        return "张三"
    elif user_id == 1002:
        return "李四"
    else:
        return "-"


def get_weather(city: str, date: str = None):
    """
    获取城市当天天气信息(模拟)
    :param city: 城市名
    :param date: 日期(暂时不使用)
    :return:
    """
    data = {}
    if city == "杭州":
        data['weather'] = "晴天"
        data['temperature'] = "20℃"
    elif city == "北京":
        data['weather'] = "中雨"
        data['temperature'] = "15℃"

    return json.dumps(data)


def gpt_ask_with_stream(messages: list[dict], tools: list[dict]):
    response = client.chat.completions.create(
        model="gpt-4",
        messages=messages,
        tools=tools,
        tool_choice="auto",
        stream=True
    )
    tool_calls = []
    for chunk in response:
        if chunk.choices:
            choice = chunk.choices[0]
            if choice.delta.tool_calls:  # function_calling
                for idx, tool_call in enumerate(choice.delta.tool_calls):
                    tool = choice.delta.tool_calls[idx]
                    if len(tool_calls) <= idx:
                        tool_calls.append(tool)
                        continue
                    if tool.function.arguments:
                        # function参数为流式响应,需要拼接
                        tool_calls[idx].function.arguments += tool.function.arguments
            elif choice.finish_reason:
                # print(f"choice.finish_reason: {choice.finish_reason}")
                break
            else:  # 普通回答
                yield ""  # 第一条返回无意义,便于后续检测回复消息类型
                yield choice.delta.content

    # 如果是function_call请求,返回数据
    if tool_calls:
        yield tool_calls


def run_conversation(content: str):
    messages = [{"role": "user", "content": content}]
    tools = [
        {
            "type": "function",
            "function": {
                "name": "get_weather",
                "description": "获取城市今天的天气信息",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "city": {
                            "type": "string",
                            "enum": ["杭州", "北京"],
                            "description": "城市名",
                        },
                        "date": {
                            "type": "string",
                            "description": "查询的日期,格式:%Y-%m-%d。默认为当天日期",
                        }
                    },
                    "required": ["city"],
                },
            },
        },
        {
            "type": "function",
            "function": {
                "name": "get_user_name",
                "description": "根据用户id获取用户名",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "user_id": {
                            "type": "integer",
                            "description": "用户id",
                        }
                    },
                    "required": ["user_id"],
                },
            },
        }
    ]

    response = gpt_ask_with_stream(messages, tools)
    tool_calls = []
    for res in response:
        # 如果响应数据第一条是list则表明是function_call,如果是str就是普通回答
        if type(res) == list:
            tool_calls = res
        break
    # 判断是否是 function_call 请求
    while tool_calls:
        available_functions = {
            "get_weather": get_weather,
            "get_user_name": get_user_name
        }
        # 手动构建gpt返回消息,tool_calls暂时置空,后面循环调用function的时候再赋值
        assistant_message = {
            "role": "assistant",
            "tool_calls": [],
            "content": None
        }
        messages.append(assistant_message)
        # send the info for each function call and function response to the model
        for tool_call in tool_calls:
            print("tool_calls:", tool_call)
            assistant_message['tool_calls'].append({
                "id": tool_call.id,
                "type": tool_call.type,
                "function": {
                    "name": tool_call.function.name,
                    "arguments": tool_call.function.arguments
                }
            })
            function_name = tool_call.function.name
            function_to_call = available_functions[function_name]
            function_args = json.loads(tool_call.function.arguments)
            if function_name == 'get_weather':
                function_response = function_to_call(
                    city=function_args.get("city"),
                    date=None,
                )
            else:
                function_response = function_to_call(
                    user_id=function_args.get("user_id"),
                )
            messages.append({
                "tool_call_id": tool_call.id,
                "role": "tool",
                "name": function_name,
                "content": function_response,
            })
        # 携带 function 返回的结果再请求一次GPT
        response = gpt_ask_with_stream(messages, tools)
        # 和 while 前面写法一样,支持多轮tools调用
        tool_calls = []
        for res in response:
            if type(res) == list:
                tool_calls = res
            break
    # 返回普通回答的流式响应
    return response


if __name__ == '__main__':
    for chunk in run_conversation("杭州今天天气怎么样? ID 1002的用户名?"):
        print(chunk, end='')
    # 输出:
    # tool_calls: ChoiceDeltaToolCall(index=0, id='call_5jSbeoAcUqDmZRXS70iVKbrL', function=ChoiceDeltaToolCallFunction(arguments='{\n  "city": "杭州"\n}', name='get_weather'), type='function')
    # tool_calls: ChoiceDeltaToolCall(index=0, id='call_ppgzqcbHAeDnfklMznrOG7lh', function=ChoiceDeltaToolCallFunction(arguments='{\n  "user_id": 1002\n}', name='get_user_name'), type='function')
    # 杭州今天是晴天,温度为20℃。ID 1002的用户名是李四。

更多示例代码:https://github.com/yaokui2018/chatgpt_example

### 实现 Spring AI 中的函数调用流式输出 在 Spring AI 应用程序中,为了实现函数调用并支持流式输出,主要依赖于 `Function Calling` 特性和特定配置来处理异步数据传输。当应用程序接收到请求时,会解析输入 JSON 并将其转换为适合目标方法的形式。对于需要持续更新的应用场景,比如实时日志分析或长时间运行的任务进度报告,则可以通过设置回调机制或是采用服务器发送事件(Server-Sent Events, SSE)的方式来进行流式输出。 下面是一个基于 Spring WebFlux 和 Server-Sent Events (SSE) 技术栈的例子,展示了如何创建一个能够接收来自大模型指令并通过 HTTP 流返回执行结果的服务端点: #### 添加 Maven 依赖项 首先,在项目的 pom.xml 文件里加入必要的库文件以启用反应式编程的支持: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency> ``` #### 创建控制器类 接着定义 RESTful API 控制器用于暴露服务接口,并在此基础上集成大模型驱动的功能调用逻辑: ```java @RestController @RequestMapping("/api/functions") public class FunctionController { private final MyCustomService myCustomService; @Autowired public FunctionController(MyCustomService myCustomService){ this.myCustomService = myCustomService; } /** * 处理 POST 请求,接受由大模型生成的 JSON 输入, * 调用相应的业务逻辑并将结果作为 SSE 发送回客户端。 */ @PostMapping(value="/invoke", produces=MediaType.TEXT_EVENT_STREAM_VALUE) Flux<String> invoke(@RequestBody Map<String,Object> payload){ // 将传入的数据映射到具体的方法参数上... return myCustomService.execute(payload).map(result -> "data:" + result); } } ``` 上述代码片段中的 `MyCustomService` 是假设存在的服务层组件,负责实际操作的具体实施细节;而 `execute()` 方法则代表了被封装起来的核心算法或者是对外部系统的访问过程[^1]。 #### 配置 SSE 支持 为了让浏览器或其他类型的消费者能正确解释这些连续的消息推送,还需要确保设置了合适的 MIME 类型——即 `"text/event-stream"` ——这已经在上面例子中的 `produces` 参数指定了。此外,如果希望进一步优化性能表现的话,还可以考虑调整一些默认的行为选项,例如最大缓存大小、心跳间隔时间等。 最后值得注意的是,虽然这里展示了一个较为通用的设计模式,但在真实世界里的应用场景可能会更加复杂多变,因此建议开发者们根据具体的业务需求灵活调整架构设计[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

薄荷你玩_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值