Metadata-Version: 2.4
Name: llm-requests
Version: 1.0.0
Summary: An LLM development tool like requests.
Author-email: nanfu <nanfu2001@gmail.com>
License: MIT
Project-URL: Homepage, https://gitee.com/ysu-xzh/llm_requests
Project-URL: Bug Tracker, https://gitee.com/ysu-xzh/llm_requests/issues
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: openai>=2.35.1
Dynamic: license-file

# 为什么世界还缺少一个 llm_requests

如果你是一名LLM开发者，你可能遇到过这样的困扰：明明在软件开发领域自己有了一个想法之后可以借助各种开发工具快速搭建一个模型来验证自己的想法或者向别人展示。但是到了LLM开发领域却有许多的脏活需要处理。依据API文档慢慢实现模型请求的代码？好在后来有了API网关解决了这个问题。想快速构建一个应用？似乎LangChain可以解决这个问题，但繁杂的文档却要求你先花一段学习如何使用它。而当你想实现一些特殊需求时，其高度封装的API成为了另一个阻碍。

如果你是一名LLM研究者，你可能遇到过这样的困扰：自己的方法需要在大量的模型上对比大量的方法，可能不同的方法上还要去考虑不同的推理配置对结果的影响，如解码超参数、是否开启推理等。而且pass@k等指标的计算还要求多次重复实验。大模型推理很贵，你希望能够缓存模型的输出结果，以减少实验的消耗。前者需要能够方便地在全局设置LLM请求的一些参数，后者需要一个工具来管理模型的输出结果。

——在LangChain之下、LLM网关之上，LLM开发领域似乎还缺少一个类似requests的工具，它仅仅封装模型请求的脏活累活，而把其他一些内容暴露给使用者，使开发者能够在其之上便捷地构建下游应用。

于是便有了llm_requests。它通过以请求体为中心的设计，把模型（Model）、推理平台（Infrastructure）、推理设置（policy）和推理参数（Params）作为一等公民，让使用者能够在不同的对象间自由组合、快速配置。

# QuikStart

在模型请求前，应在`.config/llmrequest/{infra-name}.json`中配置请求网址和key：

```json
{
    "api_key": "none",
    "base_url": "http://127.0.0.1:8080/"
}
```

## 单次请求

```python
from llm_requests import LLMRequester
from llm_requests.infras.llamacpp import gemma4_e2b

requester = LLMRequester()
print(requester.request(gemma4_e2b, "介绍一下你自己。"))
```
## 批次请求

批次请求用于并行进行多次请求，并行度取决于平台支持的上限。此外，为了防止重复请求，该方式可以接收一个缓存文件。
当缓存文件中包含当次请求的输入和参数时直接调用缓存的结果。

```python
from llm_requests import LLMRequester
from llm_requests.infras.llamacpp import gemma4_e2b

requester = LLMRequester()
print(requester.request_batch(gemma4_e2b, "介绍一下你自己。", batch_size=10, cachefile=".test.json"))
```

我们把模型的输出结果封装在了LLMRequestResult类中，它包含一次推理涉及的所有信息：

```
model: Model(name='gemma4-e2b', infra_name='llama.cpp', released_date='2026-04-02', support_reasoning=True, inference_params=InferenceParams(enable_reason=True, max_tokens=20000, reasoning_effort=1))
contexts: [{'role': 'user', 'content': [{'type': 'text', 'text': '介绍一下你自己。'}]}]
output: 您好！很高兴向您介绍我自己。 ...
input_length: 20
output_length: 627
reasoning_output: Thinking Process:  ...
reasoning_length: 0
inference_time: 11.822667500004172
is_ooc: False
is_stop_by_length: False
appendix: {}
response_msgs: ChatCompletionMessage(...)
tool_calls: None
```

## 自定义模型

我们在每个推理设施中封装了一些常用的模型。除此之外，我们还提供自定义模型：

```python
from llm_requests import LLMRequester, Model, InferenceParams

inference_param = InferenceParams(enable_think=True, max_tokens=5)
model = Model(name="gemma4-e2b", infra_name="llama.cpp", released_date="2026-04-02",
              support_reasoning=True, inference_params=inference_param)
requester = LLMRequester()
print(requester.request(gemma4_e2b, "介绍一下你自己。"))
```

# Callback

Callback功能用于对模型的输出结果进行后处理。结果会放在LLMRequestResult.appendix中。在实验中，callback经常用于对模型的输出结果打分。在开发中，可以通过此功能追踪LLM的用量信息或对模型的输出内容添加审查。

```python
from llm_requests import LLMRequester, register_callback, LLMRequestResult
from llm_requests.infras.llamacpp import gemma4_e2b
from llm_requests.utils import extract_code_block
from forging_utils.code_runner import execute_code, LANG_TYPE

@register_callback("code")
def run_code(r: LLMRequestResult) -> dict:
    code = extract_code_block(r.output)
    result = execute_code(code, LANG_TYPE.Python)
    return {"code": result.stdout}


requester = LLMRequester(callbacks=[run_code])
print(requester.request(gemma4_e2b, "请你编写一个Python代码输出三三乘法表。").appendix["code"])
--------------------------------
2026-09-11 21:05:21.390 | INFO     | forging_utils.code_runner:_init_client:435 - SingletonCodeRunner client initialized
2026-09-11 21:05:22.961 | INFO     | forging_utils.code_runner:try_to_launch:210 - podman run -d --name bench-web-1789131921 --workdir /home/ubuntu/code_runner -P --memory="2g" --memory-swap="2g" --cpus="1" --pids-limit=10000 localhost/bench:1.3.2 /home/ubuntu/env_code_runner/bin/python3 -m gunicorn --workers=1 --threads=4 --bind=0.0.0.0:8000 --timeout 120 server:app
2026-09-11 21:05:22.962 | INFO     | forging_utils.code_runner:try_to_launch:211 - 783d1e
2026-09-11 21:05:24.189 | INFO     | forging_utils.code_runner:start_server:164 - [783d1e]start at http://192.168.239.131:34763/
1 * 1 = 1
1 * 2 = 2; 2 * 2 = 4
1 * 3 = 3; 2 * 3 = 6; 3 * 3 = 9
2026-09-11 21:05:26.476 | INFO     | forging_utils.code_runner:terminal:263 - [783d1e] terminal, has del it
2026-09-11 21:05:26.476 | INFO     | forging_utils.code_runner:_shutdown_client:444 - CodeRunner client shutdown
```

# 全局开关

全局开关用于设置一些不方便通过参数设置的内容，如requester已经被框架封装在最里面时。以下代码常用于确保模型请求时读取缓存文件，而不是去发起新的请求。所有可配置的开关可参考`llm_requests.policy`。

```Python
from llm_requests import LLMRequester
from llm_requests.infras.llamacpp import gemma4_e2b
from llm_requests.policy import change_policy

requester = LLMRequester()
with change_policy(requests_allow_real=False):
    print(requester.request(gemma4_e2b, "请你介绍一下你自己。。").output)
--------------
...
    raise dtypes.NotAllowedRequestError()
llm_requests.dtypes.NotAllowedRequestError
```



# 工具调用

我们的框架支持简单地配置工具，你可以使用log_tool_calls开关来观察模型调用工具的情况。

```python
from llm_requests import LLMRequester
from llm_requests.infras.llamacpp import gemma4_e2b
from llm_requests.dtypes import ToolParams
from llm_requests.policy import change_policy
from llm_requests.tools import print_to, ask

requester = LLMRequester()
tool_params = ToolParams(tools=[print_to, ask])
with change_policy(log_tool_calls=True):
    requester.request(gemma4_e2b, "请你用工具和我聊三句话。", tool_params=tool_params)
```

# 添加推理平台

我们把推理平台抽象为Infrastructure类，只需要实现相应平台的接口即可接入新的平台。具体内容可以参考`llm_requests.infras`下的文件。

# 利用内容块管理模型输入

我们使用内容块封装不同类型（文本、图片、音频和视频）的输入，以屏蔽平台间的web请求协议的差异。目前我们支持文字、图片、音频、视频四种输入类型。

对于流式输入类型（如音频、视频），我们支持对内容块进行切片：

```python
from llm_requests import LLMRequester, dtypes
from llm_requests.infras.llamacpp import gemma4_e2b

requester = LLMRequester()
block = dtypes.VidioContentBlock.from_bilibili(your_url)

print(requester.request_content(gemma4_e2b, contents=[
    dtypes.SystemContentBlock(), dtypes.TextContentBlock("这个视频里的男人说了什么？"),
    block.slice(1, 30),
    ]).output)
```

对于视频类型，我们支持获取其音频内容块：

`dtypes.VidioContentBlock.from_bilibili(your_url).to_audio()`。

