diff --git a/README.md b/README.md index 0bf527b..6351451 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Updates](https://pyup.io/repos/github/cubenlp/openai_api_call/shield.svg)](https://pyup.io/repos/github/cubenlp/openai_api_call/) --> -A simple wrapper for OpenAI API, which can be used to send requests and get responses. +A Python wrapper for OpenAI API, supporting multi-turn dialogue, proxy, and asynchronous data processing. ## Installation @@ -20,165 +20,74 @@ pip install openai-api-call --upgrade ## Usage -### Set API Key +### Set API Key and Base URL -```py -import openai_api_call as apicall -apicall.api_key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +Method 1, write in Python code: + +```python +import openai_api_call +openai_api_call.api_key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +openai_api_call.base_url = "https://api.example.com" ``` -Or set `OPENAI_API_KEY` in `~/.bashrc` to avoid setting the API key every time: +Method 2, set environment variables in `~/.bashrc` or `~/.zshrc`: ```bash -# Add the following code to ~/.bashrc export OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +export OPENAI_BASE_URL="https://api.example.com" ``` -Also, you might set different `api_key` for each `Chat` object: - -```py -from openai_api_call import Chat -chat = Chat("hello") -chat.api_key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -``` - -### Set Proxy (Optional) - -```py -from openai_api_call import proxy_on, proxy_off, proxy_status -# Check the current proxy -proxy_status() - -# Set proxy(example) -proxy_on(http="127.0.0.1:7890", https="127.0.0.1:7890") - -# Check the updated proxy -proxy_status() - -# Turn off proxy -proxy_off() -``` - -Alternatively, you can use a proxy URL to send requests from restricted network, as shown below: - -```py -from openai_api_call import request - -# set request url -request.base_url = "https://api.example.com" -``` - -You can set `OPENAI_BASE_URL` in `~/.bashrc` as well. - -### Basic Usage - -Example 1, send prompt and return response: - -```python -from openai_api_call import Chat, show_apikey - -# Check if API key is set -show_apikey() - -# Check if proxy is enabled -proxy_status() - -# Send prompt and return response -chat = Chat("Hello, GPT-3.5!") -resp = chat.getresponse(update=False) # Not update the chat history, default to True -``` - -Example 2, customize the message template and return the information and the number of consumed tokens: - -```python -import openai_api_call as apicall - -# Customize the sending template -apicall.default_prompt = lambda msg: [ - {"role": "system", "content": "帮我翻译这段文字"}, - {"role": "user", "content": msg} -] -chat = Chat("Hello!") -# Set the number of retries to Inf -# The timeout for each request is 10 seconds -response = chat.getresponse(temperature=0.5, max_requests=-1, timeout=10) -print("Number of consumed tokens: ", response.total_tokens) -print("Returned content: ", response.content) - -# Reset the default template -apicall.default_prompt = None -``` +## Examples -Example 3, continue chatting based on the last response: +Example 1, simulate multi-turn dialogue: ```python -# first call +# first chat chat = Chat("Hello, GPT-3.5!") -resp = chat.getresponse() # update chat history, default is True -print(resp.content) +resp = chat.getresponse() -# continue chatting +# continue the chat chat.user("How are you?") next_resp = chat.getresponse() -print(next_resp.content) -# fake response +# add response manually chat.user("What's your name?") chat.assistant("My name is GPT-3.5.") -# get the last result -print(chat[-1]) +# save the chat history +chat.save("chat.json", mode="w") # default to "a" -# save chat history -chat.save("chat_history.log", mode="w") # default to "a" - -# print chat history +# print the chat history chat.print_log() ``` -### Advance usage - -Save the chat history to a file: - -```python -checkpoint = "tmp.log" -# chat 1 -chat = Chat() -chat.save(checkpoint, mode="w") # default to "a" -# chat 2 -chat = Chat("hello!") -chat.save(checkpoint) -# chat 3 -chat.assistant("你好, how can I assist you today?") -chat.save(checkpoint) -``` - -Load the chat history from a file: - -```python -# load chats(default) -chats = load_chats(checkpoint) -assert chats == [Chat(log) for log in chat_logs] -``` - -In general, one can create a function `msg2chat` and use `process_chats` to process the data: +Example 2, process data in batch, and use a checkpoint file `checkpoint`: ```python +# write a function to process the data def msg2chat(msg): chat = Chat(api_key=api_key) chat.system("You are a helpful translator for numbers.") chat.user(f"Please translate the digit to Roman numerals: {msg}") chat.getresponse() -checkpath = "tmp.log" -# first part of the data -msgs = ["1", "2", "3"] -chats = process_chats(msgs, msg2chat, checkpath, clearfile=True) -assert len(chats) == 3 -assert all([len(chat) == 3 for chat in chats]) -# continue the process -msgs = msgs + ["4", "5", "6"] -continue_chats = process_chats(msgs, msg2chat, checkpath) +checkpoint = "chat.jsonl" +msgs = ["%d" % i for i in range(1, 10)] +# process the data +chats = process_chats(msgs[:5], msg2chat, checkpoint, clearfile=True) +# process the rest data, and read the cache from the last time +continue_chats = process_chats(msgs, msg2chat, checkpoint) +``` + +Example 3, process data in batch (asynchronous), print hello using different languages, and use two coroutines: + +```python +from openai_api_call import async_chat_completion + +chatlogs = [ + {"role":"user", "content":"print hello using %s" % lang} + for lang in ["python", "java", "Julia", "C++"]] +async_chat_completion(chatlogs, chkpoint="async_chat.jsonl", ncoroutines=2) ``` ## License @@ -187,6 +96,9 @@ This package is licensed under the MIT license. See the LICENSE file for more de ## update log +Current version `1.0.0` is a stable version, with the redundant feature `function call` removed, and the asynchronous data processing tool added. + +### Beta version - Since version `0.2.0`, `Chat` type is used to handle data - Since version `0.3.0`, you can use different API Key to send requests. - Since version `0.4.0`, this package is mantained by [cubenlp](https://github.com/cubenlp). diff --git a/README_zh_CN.md b/README_zh_CN.md index 8a398d1..288bc36 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -2,12 +2,13 @@ [![PyPI version](https://img.shields.io/pypi/v/openai_api_call.svg)](https://pypi.python.org/pypi/openai_api_call) [![Tests](https://github.com/cubenlp/openai_api_call/actions/workflows/test.yml/badge.svg)](https://github.com/cubenlp/openai_api_call/actions/workflows/test.yml/) [![Documentation Status](https://img.shields.io/badge/docs-github_pages-blue.svg)](https://apicall.wzhecnu.cn) +[![Coverage](https://codecov.io/gh/cubenlp/openai_api_call/branch/master/graph/badge.svg)](https://codecov.io/gh/cubenlp/openai_api_call.jl) -OpenAI API 的简单封装,用于发送 prompt 并返回 response。 +OpenAI API 的 Python 封装工具,支持多轮对话,代理,以及异步处理数据等。 ## 安装方法 @@ -17,162 +18,73 @@ pip install openai-api-call --upgrade ## 使用方法 -### 设置 API 密钥 +### 设置密钥和代理链接 -```py -import openai_api_call as apicall -apicall.api_key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +方法一,在代码中修改 +```python +import openai_api_call +openai_api_call.api_key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +openai_api_call.base_url = "https://api.example.com" ``` -或者直接在 `~/.bashrc` 中设置 `OPENAI_API_KEY`,每次启动终端可以自动设置: +方法二,设置环境变量,在 `~/.bashrc` 或者 `~/.zshrc` 中追加 ```bash -# 在 ~/.bashrc 中添加如下代码 export OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +export OPENAI_BASE_URL="https://api.example.com" ``` -当然,你也可以为每个 `Chat` 对象设置不同的 `api_key`: - -```py -from openai_api_call import Chat -chat = Chat("hello") -chat.api_key = "sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -``` - -### 设置代理(可选) - -```py -from openai_api_call import proxy_on, proxy_off, proxy_status -# 查看当前代理 -proxy_status() - -# 设置代理,这里 IP 127.0.0.1 代表本机 -proxy_on(http="127.0.0.1:7890", https="127.0.0.1:7890") - -# 查看更新后的代理 -proxy_status() - -# 关闭代理 -proxy_off() -``` - -或者,你也可以使用代理 URL 来发送请求,如下所示: - -```py -from openai_api_call import request - -# 设置代理 URL -request.bash_url = "https://api.example.com" -``` - -同样地,可以在环境变量指定 `OPENAI_BASE_URL` 来自动设置。 - -### 基本使用 - -示例一,发送 prompt 并返回信息: -```python -from openai_api_call import Chat, show_apikey, proxy_status - -# 检查 API 密钥是否设置 -show_apikey() - -# 查看是否开启代理 -proxy_status() - -# 发送 prompt 并返回 response -chat = Chat("Hello, GPT-3.5!") -resp = chat.getresponse(update=False) # 不更新对话历史,默认为 True -print(resp.content) -``` - -示例二,自定义消息模板,并返回信息和消耗的 tokens 数量: - -```python -import openai_api_call - -# 自定义发送模板 -openai_api_call.default_prompt = lambda msg: [ - {"role": "system", "content": "帮我翻译这段文字"}, - {"role": "user", "content": msg} -] -chat = Chat("Hello!") +### 示例 -# 设置重试次数为 Inf,超时时间为 10s -response = chat.getresponse(temperature=0.5, max_requests=-1, timeout=10) -print("Number of consumed tokens: ", response.total_tokens) -print("Returned content: ", response.content) - -# 重置默认 Prompt -apicall.default_prompt = None -``` - -示例三,多轮对话: +示例1,模拟多轮对话: ```python # 初次对话 chat = Chat("Hello, GPT-3.5!") -resp = chat.getresponse() # 更新对话历史,默认为 True -print(resp.content) +resp = chat.getresponse() # 继续对话 chat.user("How are you?") next_resp = chat.getresponse() -print(next_resp.content) -# 假装对话历史 +# 人为添加返回内容 chat.user("What's your name?") chat.assistant("My name is GPT-3.5.") -# 打印最后一次谈话 -print(chat[-1]) - # 保存对话内容 -chat.save("chat_history.log", mode="w") # 默认为 "a" +chat.save("chat.json", mode="w") # 默认为 "a" # 打印对话历史 chat.print_log() ``` -### 进阶用法 -将对话历史保存到文件中: +示例二,批量处理数据(串行),并使用缓存文件 `checkpoint`: ```python -checkpoint = "tmp.log" -# chat 1 -chat = Chat() -chat.save(checkpoint, mode="w") # 默认为 "a" -# chat 2 -chat = Chat("hello!") -chat.save(checkpoint) -# chat 3 -chat.assistant("你好, how can I assist you today?") -chat.save(checkpoint) -``` - -从文件中加载对话历史: +# 编写处理函数 +def msg2chat(msg): + chat = Chat() + chat.system("You are a helpful translator for numbers.") + chat.user(f"Please translate the digit to Roman numerals: {msg}") + chat.getresponse() -```python -# 加载 Chat 对象(默认) -chats = load_chats(checkpoint) -assert chats == [Chat(log) for log in chat_logs] +checkpoint = "chat.jsonl" +msgs = ["%d" % i for i in range(10)] +# 处理前5个数据,将结果缓存到 checkpoint 文件中 +chats = process_chats(msgs[:5], msg2chat, checkpoint, clearfile=True) +# 处理所有数据,并从上一次结果中读取缓存 +continue_chats = process_chats(msgs, msg2chat, checkpoint) ``` -一般来说,你可以定义函数 `msg2chat` 并使用 `process_chats` 来处理数据: +示例三,批量处理数据(异步并行),用不同语言打印 hello,并使用两个协程: ```python -def msg2chat(msg): - chat = Chat(api_key=api_key) - chat.system("You are a helpful translator for numbers.") - chat.user(f"Please translate the digit to Roman numerals: {msg}") - chat.getresponse() +from openai_api_call import async_chat_completion -checkpath = "tmp.log" -# 处理数据的第一部分 -msgs = ["1", "2", "3"] -chats = process_chats(msgs, msg2chat, checkpath, clearfile=True) -# 继续处理数据 -msgs = msgs + ["4", "5", "6"] -continue_chats = process_chats(msgs, msg2chat, checkpath) +chatlogs = [ + {"role":"user", "content":"print hello using %s" % lang} + for lang in ["python", "java", "Julia", "C++"]] +async_chat_completion(chatlogs, chkpoint="async_chat.jsonl", ncoroutines=2) ``` ## 开源协议 @@ -181,6 +93,9 @@ continue_chats = process_chats(msgs, msg2chat, checkpath) ## 更新日志 +当前版本 `1.0.0` 为稳定版,删除关于 `function call` 的冗余功能,增加异步处理数据的工具。 + +### 测试版本 - 版本 `0.2.0` 改用 `Chat` 类型作为中心交互对象 - 版本 `0.3.0` 开始不依赖模块 `openai.py` ,而是直接使用 `requests` 发送请求 - 支持对每个 `Chat` 使用不同 API 密钥 diff --git a/openai_api_call/__init__.py b/openai_api_call/__init__.py index f0d4d98..caf48e3 100644 --- a/openai_api_call/__init__.py +++ b/openai_api_call/__init__.py @@ -8,7 +8,7 @@ from .chattool import Chat, Resp from .checkpoint import load_chats, process_chats from .proxy import proxy_on, proxy_off, proxy_status -from .async_process import async_chat_completion +from .asynctool import async_chat_completion from . import request # read API key from the environment variable diff --git a/openai_api_call/async_process.py b/openai_api_call/asynctool.py similarity index 90% rename from openai_api_call/async_process.py rename to openai_api_call/asynctool.py index 37d6bcf..21592e9 100644 --- a/openai_api_call/async_process.py +++ b/openai_api_call/asynctool.py @@ -3,6 +3,7 @@ from typing import List, Dict, Union from openai_api_call import Chat, Resp, load_chats import openai_api_call +from tqdm.asyncio import tqdm async def async_post( session , sem @@ -85,6 +86,7 @@ async def chat_complete(ind, locker, chatlog, chkpoint, **options): , max_requests=max_requests , timeinterval=timeinterval , timeout=timeout) + if response is None:return False resp = Resp(json.loads(response)) if not resp.is_valid(): warnings.warn(f"Invalid response: {resp.error_message}") @@ -108,7 +110,7 @@ async def chat_complete(ind, locker, chatlog, chkpoint, **options): , chatlog=chatlog , chkpoint=chkpoint , **options))) - responses = await asyncio.gather(*tasks) + responses = await tqdm.gather(*tasks) return responses def async_chat_completion( chatlogs:List[List[Dict]] @@ -121,6 +123,7 @@ def async_chat_completion( chatlogs:List[List[Dict]] , timeout:int=0 , timeinterval:int=0 , clearfile:bool=False + , notrun:bool=False , **options ): """Asynchronous chat completion @@ -149,15 +152,19 @@ def async_chat_completion( chatlogs:List[List[Dict]] chat_url = openai_api_call.request.normalize_url(chat_url) # run async process assert ncoroutines > 0, "ncoroutines must be greater than 0!" - responses = asyncio.run( - async_process_msgs( chatlogs=chatlogs - , chkpoint=chkpoint - , api_key=api_key - , chat_url=chat_url - , max_requests=max_requests - , ncoroutines=ncoroutines - , timeout=timeout - , timeinterval=timeinterval - , model=model - , **options)) - return responses \ No newline at end of file + args = { + "chatlogs": chatlogs, + "chkpoint": chkpoint, + "api_key": api_key, + "chat_url": chat_url, + "max_requests": max_requests, + "ncoroutines": ncoroutines, + "timeout": timeout, + "timeinterval": timeinterval, + "model": model, + **options + } + if notrun: # when use in Jupyter Notebook + return async_process_msgs(**args) # return the async object + else: + return asyncio.run(async_process_msgs(**args)) \ No newline at end of file diff --git a/tests/test_async.py b/tests/test_async.py index ba93de4..de0f319 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -1,6 +1,6 @@ import openai_api_call, time from openai_api_call import Chat, process_chats -from openai_api_call.async_process import async_chat_completion +from openai_api_call.asynctool import async_chat_completion openai_api_call.api_key="free-123" openai_api_call.base_url = "https://api.wzhecnu.cn"