Python 原生封装 Llama.cpp 大模型推理接口
本文以通义千问Qwen量化GGUF模型为案例,基于Llama.cpp框架展示本地大模型调用与功能开发的完整实现原理及流程。全文采用原生Python编写代码案例,无需依赖重型AI框架,依次实现模型元数据解析、本地接口单次对话调用、多轮对话持久化记忆、自定义工具调用拓展等功能实现逻辑,针对直接调用大模型上下文断裂、功能单一、拓展性不足等问题,通过轻量化对话记忆与工具解析执行框架完成优化实现,拆解本地对话交互运行原理,帮助开发者掌握大模型的基础调用原理。
大模型基础内容
什么是GGUF格式
首先 GGUF 是 llama.cpp 项目作者 Georgi Gerganov 于 2023 年 8 月 21 日推出的大模型文件存储格式标准,用于替代老旧的 GGML 格式。原始大模型(如 Llama、Qwen、Mistral 等)由各厂商完成训练,并以 Hugging Face PyTorch 格式发布原版权重。开发者需要借助 llama.cpp 提供的转换工具,将 HF 原版权重转换导出为 GGUF 文件,模型才能够在 llama.cpp 生态中完成本地推理运行。
与 GGML 格式不同 GGUF 格式将模型权重、分词器、各类模型配置参数全部封装打包到单个.gguf文件当中,相比前代格式,GGUF 拥有更广的兼容性、更快的模型加载速度、更低的内存开销以及更强的推理稳定性,现已成为大模型本地轻量化部署的主流优选格式。
针对 Ollama 框架,其底层推理引擎直接复用 llama.cpp,同时在 llama.cpp 的基础上封装扩展了模型拉取、版本管理、模型调用等一系列能力,降低使用门槛,便于开发者快速搭建私有化的大模型推理服务。
模型参数规模
简单理解参数量代表模型的规模,在同一模型系列、训练质量相近的前提下,参数量越大,知识储备、逻辑推理、复杂任务处理能力越强;但代价也同步上升,比如模型文件体积更大,运行需要的内存/显存更多,推理速度会变慢;但参数量也不是唯一评判标准,不同架构、不同训练数据的模型,即使参数量相同,实际效果差距也可能很大;部分经过精细微调的小模型,表现也有可能优于普通大参数量模型。
通常情况下大模型参数以Billion(亿)为单位,1B ≈10 亿参数,能够接触到并最常见的大模型参数量范围如下:
- 1B = 1 Billion ≈10 亿参数
- 2B = 2 Billion ≈20 亿参数
- 3B = 3 Billion ≈30 亿参数
- 4B = 4 Billion ≈40 亿参数
- 7B = 7 Billion ≈70 亿参数
- 8B = 8 Billion ≈80 亿参数
- 14B = 14 Billion ≈140 亿参数
- 20B = 20 Billion ≈200 亿参数
- 34B = 34 Billion ≈340 亿参数
- 70B = 70 Billion ≈700 亿参数
模型量化仅对权重数值做压缩处理,不会改变模型本身网络结构,模型原生能力上限保持不变,仅实际推理精度受到损耗。量化属于权衡取舍,用可接受的少量效果损失,换取更小的存储占用、更低硬件门槛以及更快推理速度。实际使用时,优先选择 Q4_K_M、Q5_K_M、Q6_K、Q8_0 这类成熟量化版本,不建议日常使用 Q2、Q3 这类低质量量化,容易产生大量幻觉和错误回答。
-
非量化(FP16 / BF16 原版):原始精度权重,每个参数用 16 位浮点数保存。
- 优点:效果最好,几乎无损,推理质量最高
- 缺点:体积巨大,吃内存显存,硬件要求高。比如 8B 模型 FP16 大约 16GB。
-
量化 GGUF(Q4_K_M、Q5_K_M、Q8_0 这类):把模型权重数值压缩,用更少比特存储一个参数。
- 优点:文件体积大幅缩小,内存 / 显存占用降低,普通电脑也能跑;速度更快。
- 缺点:会损失一小部分精度;量化等级越低(Q2、Q3)压缩越强,模型回答越容易胡言乱语。
若要本地部署可以参考以下参数量范围:
- 1‑4B:低配电脑、笔记本,适合简单问答、文本摘要等轻量任务
- 显存:最低 2‑3GB;核显也可运行
- 系统内存:最低 8GB,推荐 16GB
- 参考显卡:GTX1650、RTX3050,轻薄本核显可跑
- 7‑8B:家用电脑主流选择,推理效果与硬件消耗较为均衡
- 显存:最低 4‑6GB,推荐 8‑12GB
- 系统内存:最低 12GB,推荐 16GB
- 参考显卡:RTX3060 12G、RTX4060;8G 显存显卡可跑
- 14‑20B:需要较好显存支持,逻辑推理能力相比小模型有明显提升
- 显存:最低 9‑10GB,推荐 16GB 以上显存
- 系统内存:最低 24GB,推荐 32GB
- 参考显卡:RTX4060Ti‑16G、RTX3090 12G
- 34B 及以上:硬件门槛较高,适合高性能台式机运行
- 显存:Q4_K_M 最低 18‑20GB,推荐 24GB 及以上
- 系统内存:最低 32GB,推荐 64GB
- 参考显卡:RTX3090/4090(24G)、RTX5090(32G)
查询模型信息
gguf 是适配 Llama.cpp 生态的官方Python解析工具模块,专门用于读取、解析 GGUF 格式量化大模型文件,是本地离线部署GGUF模型的基础依赖库,执行以下终端命令完成依赖一键安装:
CMD> pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gguf
下文提供全自动Python解析脚本,无需手动配置参数,可一键读取本地GGUF模型文件,完整输出模型基础信息、网络超参、分词器配置、张量统计、量化参数等全维度数据,适配所有主流GGUF格式大模型。
import os
import sys
from gguf import GGUFReader
if __name__ == "__main__":
if len(sys.argv) < 2:
sys.exit(1)
model_path = sys.argv[1]
if not os.path.exists(model_path):
print(f"错误:文件不存在 -> {model_path}")
sys.exit(1)
reader = GGUFReader(model_path)
meta = {}
for k, field in reader.fields.items():
meta[k] = field.contents()
file_size_bytes = os.path.getsize(model_path)
file_size_gb = file_size_bytes / (1024 ** 3)
arch = meta.get("general.architecture")
GGML_TYPE_MAP = {
0: "F32",
1: "F16",
2: "Q4_0",
3: "Q4_1",
6: "Q5_0",
7: "Q5_1",
8: "Q8_0",
9: "Q8_1",
10: "Q8_K",
11: "Q6_K",
12: "Q4_K",
13: "Q5_K",
14: "Q6_K",
15: "Q2_K",
16: "Q3_K",
17: "IQ3_K_S",
18: "IQ3_K_M",
19: "IQ3_K_L",
20: "IQ2_K_S",
21: "IQ2_K_M",
}
model_filename = os.path.basename(model_path)
total_param_count = 0
for tensor in reader.tensors:
cnt = 1
for dim in tensor.shape:
cnt *= dim
total_param_count += cnt
total_param_b = total_param_count / 1e9
print("-" * 80)
print("[GGUF 文件整体信息]")
print("-" * 80)
print(f"文件名称 : {model_filename}")
print(f"文件大小 : {file_size_gb:.3f} GB ({file_size_bytes:,} bytes)")
print(f"GGUF版本 : {meta.get('general.file_version')}")
print(f"GGUF内部version : {meta.get('GGUF.version')}")
print(f"文件类型(量化) : {meta.get('general.file_type')}")
print(f"量化版本 : {meta.get('general.quantization_version')}")
print(f"模型原始总参数量 : {total_param_count:,} ≈ {total_param_b:.2f} B")
print("\n" + "-" * 80)
print("[模型基础通用信息 general.*]")
print("-" * 80)
print(f"模型名称 : {meta.get('general.name')}")
print(f"模型厂商 : {meta.get('general.author')}")
print(f"参数量标签 : {meta.get('general.size_label')}")
print(f"模型架构 : {meta.get('general.architecture')}")
print(f"模型版本 : {meta.get('general.version')}")
print(f"许可 License : {meta.get('general.license')}")
print(f"来源URL : {meta.get('general.source_url')}")
print(f"来源HuggingFace : {meta.get('general.source_hf')}")
print(f"描述 description : {meta.get('general.description')}")
print(f"量化工具 : {meta.get('general.quantization_tool')}")
print(f"量化工具版本 : {meta.get('general.quantization_tool_version')}")
print("\n" + "-" * 80)
print(f"[模型网络超参 {arch}.*]")
print("-" * 80)
print(f"block_count(层数) : {meta.get(f'{arch}.block_count')}")
print(f"embedding_length(隐层dim) : {meta.get(f'{arch}.embedding_length')}")
print(f"feed_forward_length(FFN) : {meta.get(f'{arch}.feed_forward_length')}")
print(f"context_length(训练上下文) : {meta.get(f'{arch}.context_length')}")
print(f"head_count(总注意力头) : {meta.get(f'{arch}.attention.head_count')}")
print(f"key_value_head_count(KV头) : {meta.get(f'{arch}.attention.head_count_kv')}")
print(f"rope.dimension_count(RoPE维度): {meta.get(f'{arch}.rope.dimension_count')}")
print(f"rope.freq_base(RoPE theta) : {meta.get(f'{arch}.rope.freq_base')}")
print(f"rope.scaling_type : {meta.get(f'{arch}.rope.scaling_type')}")
print(f"rope.scaling_factor : {meta.get(f'{arch}.rope.scaling_factor')}")
print(f"rope.ext_factor : {meta.get(f'{arch}.rope.ext_factor')}")
print(f"rope.attn_factor : {meta.get(f'{arch}.rope.attn_factor')}")
print(f"rope.finetune : {meta.get(f'{arch}.finetune')}")
print(f"norm_epsilon(RMS‑eps) : {meta.get(f'{arch}.attention.layer_norm_rms_epsilon')}")
print(f"attention.dropout : {meta.get(f'{arch}.attention.dropout')}")
attn_head = meta.get(f"{arch}.attention.head_count")
attn_kv_head = meta.get(f"{arch}.attention.head_count_kv")
attn_norm_eps = meta.get(f"{arch}.attention.layer_norm_rms_epsilon")
print(f"attention.head_count : {attn_head}")
print(f"attention.head_count_kv : {attn_kv_head}")
print(f"attention.layer_norm_rms_epsilon : {attn_norm_eps}")
print(f"\n>>> MoE混合专家(普通模型为None)")
print(f"expert_count(专家总数) : {meta.get(f'{arch}.expert_count')}")
print(f"expert_used_count(激活数) : {meta.get(f'{arch}.expert_used_count')}")
print("\n" + "-" * 80)
print("[Tokenizer 分词器配置 tokenizer.ggml.*]")
print("-" * 80)
print(f"vocab_size(词表大小) : {meta.get('tokenizer.ggml.vocab_size')}")
print(f"BOS token id : {meta.get('tokenizer.ggml.bos_token_id')}")
print(f"EOS token id : {meta.get('tokenizer.ggml.eos_token_id')}")
print(f"PAD token id : {meta.get('tokenizer.ggml.pad_token_id')}")
print(f"UNK token id : {meta.get('tokenizer.ggml.unk_token_id')}")
print(f"SEP token id : {meta.get('tokenizer.ggml.sep_token_id')}")
print(f"CLS token id : {meta.get('tokenizer.ggml.cls_token_id')}")
print(f"MASK token id : {meta.get('tokenizer.ggml.mask_token_id')}")
chat_template = meta.get("tokenizer.ggml.chat_template")
chat_template_alt = meta.get("tokenizer.chat_template")
if chat_template:
print("\n>>> Chat Template(Jinja2对话模板‑ggml)")
print(chat_template)
elif chat_template_alt:
print("\n>>> Chat Template(Jinja2对话模板‑tokenizer)")
print(chat_template_alt)
special_tokens_list = meta.get("tokenizer.ggml.special_tokens")
if special_tokens_list is not None:
print(f"\n>>> 特殊token列表数量: {len(special_tokens_list)}")
print("\n" + "-" * 80)
print("[张量统计信息]")
print("-" * 80)
total_tensors = len(reader.tensors)
print(f"张量总数量: {total_tensors}")
dtype_counter = {}
total_weights_elements = 0
for tensor in reader.tensors:
dtype_counter[tensor.tensor_type] = dtype_counter.get(tensor.tensor_type, 0) + 1
ele_cnt = 1
for s in tensor.shape:
ele_cnt *= s
total_weights_elements += ele_cnt
print(f"权重元素总个数: {total_weights_elements:,}")
print("各张量类型计数(原始数字 + 可读类型):")
for dt, cnt in dtype_counter.items():
dt_name = GGML_TYPE_MAP.get(dt, "UNKNOWN")
print(f" {dt} ({dt_name:<8}) : {cnt} 个张量")
print("\n>>> 前8个张量信息展示:")
for t in reader.tensors[:8]:
shape_str = str(t.shape)
dt_name = GGML_TYPE_MAP.get(t.tensor_type, "UNKNOWN")
print(f" {t.name:<55} shape:{shape_str:<25} dtype:{t.tensor_type:2d}({dt_name})")
n_layer = meta.get(f"{arch}.block_count")
n_embd = meta.get(f"{arch}.embedding_length")
ff_dim = meta.get(f"{arch}.feed_forward_length")
if n_layer and n_embd and ff_dim:
print("\n>>> 简易Transformer层估算(不含embedding/output层,仅作对比)")
est_params = n_layer * (2 * n_embd * n_embd + ff_dim * n_embd)
est_billion = est_params / 1e9
print(f"估算参数量(仅transformer层): {est_params:,} ≈ {est_billion:.2f} B")
print("\n" + "-" * 80)
print("[元数据字段总览]")
print("-" * 80)
all_meta_keys = list(meta.keys())
print(f"GGUF文件内元数据总字段数量:{len(all_meta_keys)}")
print("\n全部元数据key列表:")
for key in sorted(all_meta_keys):
print(f" {key}")
将上述代码保存文件,放置于本地模型文件同级目录,将命令中的模型路径替换为本地GGUF模型绝对路径,执行以下终端命令运行脚本:
CMD> python main.py C://llamacpp/qwen2.5-1.5b-instruct-q4_k_m.gguf
执行接口调用
1、承接《Windows 环境下 llama.cpp 编译运行指南》部分内容,通过命令工具llama‑server启动服务,端口监听127.0.0.1:11433,并加载 qwen2.5‑1.5b‑instruct‑q4_k_m.gguf 模型,确保本地服务已经启动。
CMD> llama-server.exe -m qwen2.5-1.5b-instruct-q4_k_m.gguf --host 127.0.0.1 --port 11433 -c 4096
CMD>
init: llama threadpool init, n_threads = 12
load_model: initializing, n_slots = 4, n_ctx_slot = 4096, kv_unified = 'true'
llama_server: model loaded
llama_server: listening on http://127.0.0.1:11433
2、完成模型解析与环境校验后,即可基于Llama.cpp本地服务实现基础对话功能。本模块全程使用Python原生urllib+json库开发,无任何第三方框架依赖,通过向本地11433端口服务发起POST请求实现模型推理。
import json
import urllib.request
import urllib.error
class Config:
LLM_BASE_URL = "http://127.0.0.1:11433"
LLM_MODEL = "qwen2.5-1.5b-instruct-q4_k_m.gguf"
LLM_TEMPERATURE = 0.8
LLM_MAX_TOKENS = 1024
LLM_CTX_SIZE = 4096
LLM_STOP_WORDS = ["<|im_end|>"]
TIMEOUT_SECONDS = 30
class LlamaCppClient:
def __init__(self):
self.base_url = Config.LLM_BASE_URL
self.model = Config.LLM_MODEL
self.temperature = Config.LLM_TEMPERATURE
self.max_tokens = Config.LLM_MAX_TOKENS
self.ctx_size = Config.LLM_CTX_SIZE
self.stop = Config.LLM_STOP_WORDS
self.timeout = Config.TIMEOUT_SECONDS
def generate(self, prompt: str) -> str:
url = f"{self.base_url}/completion"
headers = {"Content-Type": "application/json"}
payload = {
"model": self.model,
"prompt": prompt,
"temperature": self.temperature,
"max_tokens": self.max_tokens,
"ctx_size": self.ctx_size,
"stop": self.stop,
"stream": False
}
try:
req = urllib.request.Request(
url,
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
headers=headers,
method="POST"
)
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
data = json.loads(resp.read().decode("utf-8"))
return data.get("content", "").strip()
except urllib.error.URLError as e:
return f"[错误] llama.cpp服务连接失败:{str(e)}"
except json.JSONDecodeError:
return "[错误] 返回非合法JSON"
except Exception as e:
return f"[错误] {str(e)}"
if __name__ == "__main__":
llm = LlamaCppClient()
# 拼接提示词
system_text = "你是一个专业的助手,能够回答用户的问题。"
user_input = "你好,简单介绍一下自己"
prompt = (
f"<|im_start|>system\n{system_text}<|im_end|>\n"
f"<|im_start|>user\n{user_input}<|im_end|>\n"
f"<|im_start|>assistant\n"
)
# 调用模型生成回复
reply = llm.generate(prompt)
print(f"AI:{reply}\n")
执行以下终端命令运行脚本:
CMD> python main.py
AI:我是阿里云开发的一款超大规模语言模型,我叫通义千问。
实现内存记忆
上一模块的基础单次对话为无状态推理,模型无法记忆历史对话内容,多轮交互时上下文断裂、对话逻辑脱节,无法满足日常人机交互需求。
本模块自定义轻量化ChatMemory对话内存类,通过列表结构化存储系统提示词、用户提问、助手回复等完整对话记录,自动拼接标准化完整提示词,实现永久上下文记忆、手动清空记忆、持续多轮对话功能,完美模拟在线AI对话交互逻辑。
该内存方案无需数据库存储,纯内存运行、轻量化无冗余,适配本地离线部署场景,同时预留拓展接口,可后续新增文件持久化记忆功能
import json
import urllib.request
import urllib.error
from typing import List, Dict
class Config:
LLM_BASE_URL = "http://127.0.0.1:11433"
LLM_MODEL = "qwen2.5-1.5b-instruct-q4_k_m.gguf"
LLM_TEMPERATURE = 0.8
LLM_MAX_TOKENS = 1024
LLM_CTX_SIZE = 4096
LLM_STOP_WORDS = ["<|im_end|>"]
TIMEOUT_SECONDS = 30
class LlamaCppClient:
def __init__(self):
self.base_url = Config.LLM_BASE_URL
self.model = Config.LLM_MODEL
self.temperature = Config.LLM_TEMPERATURE
self.max_tokens = Config.LLM_MAX_TOKENS
self.ctx_size = Config.LLM_CTX_SIZE
self.stop = Config.LLM_STOP_WORDS
self.timeout = Config.TIMEOUT_SECONDS
def generate(self, prompt: str) -> str:
url = f"{self.base_url}/completion"
headers = {"Content-Type": "application/json"}
payload = {
"model": self.model,
"prompt": prompt,
"temperature": self.temperature,
"max_tokens": self.max_tokens,
"ctx_size": self.ctx_size,
"stop": self.stop,
"stream": False
}
try:
req = urllib.request.Request(
url,
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
headers=headers,
method="POST"
)
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
data = json.loads(resp.read().decode("utf-8"))
return data.get("content", "").strip()
except urllib.error.URLError as e:
return f"[错误] llama.cpp服务连接失败:{str(e)}"
except json.JSONDecodeError:
return "[错误] 返回非合法JSON"
except Exception as e:
return f"[错误] {str(e)}"
# 对话内存实现方式:基于列表的简单记忆
class ChatMemory:
def __init__(self):
self.history: List[Dict[str, str]] = []
def add_user(self, content: str):
self.history.append({"role":"user","content":content})
def add_assistant(self, content: str):
self.history.append({"role":"assistant","content":content})
def clear(self):
self.history.clear()
def build_prompt(self, system_content:str) -> str:
prompt = f"<|im_start|>system\n{system_content}<|im_end|>\n"
for msg in self.history:
prompt += f"<|im_start|>{msg['role']}\n{msg['content']}<|im_end|>\n"
prompt += "<|im_start|>assistant\n"
return prompt
if __name__ == "__main__":
llm = LlamaCppClient()
memory = ChatMemory()
system_prompt = "你是一个专业的助手,能够回答用户的问题。"
while True:
user_in = input("你:").strip()
if not user_in:
continue
if user_in in ("q","quit","exit","退出"):
break
if user_in == "clear":
memory.clear()
print("已清空对话记忆\n")
continue
memory.add_user(user_in)
# 构建提示词
prompt = memory.build_prompt(system_prompt)
# 调用LLM生成回复
resp = llm.generate(prompt)
# 写入内存
memory.add_assistant(resp)
print(f"AI:{resp}\n")
执行以下终端命令运行脚本:
CMD> python main.py
你:你好,我叫王瑞
AI:你好,王瑞,很高兴认识你。
你:我叫什么,你知道吗
AI:你好,王瑞,很高兴认识你。
你:简单介绍下你自己
AI:我是来自阿里的智能机器人。我叫“小助手”。我可以回答各种各样的问题,提供你想要的信息。你有什么问题想问我吗?
实现工具调用
原生本地大模型存在天然能力短板:无法获取实时系统时间、无法精准运算复杂数学公式、无外部数据获取能力、静态知识存在滞后性,无法适配实用化落地场景。本模块基于前文记忆对话框架,拓展自定义工具调用系统,内置时间查询、数学计算器两大高频实用工具,通过正则匹配解析模型输出的标准化工具调用指令,自动执行本地工具函数、获取结果后二次调用模型,生成贴合用户需求的最终自然语言回复,完整实现「模型决策调用工具-本地执行工具-结果反馈模型」的闭环能力。
本工具框架高度可拓展,开发者可基于现有代码,快速新增天气查询、文件读取、文本翻译、代码运行、联网搜索等各类自定义工具,无需重构核心逻辑。同时兼容Qwen模型原生工具调用模板。
import json
import urllib.request
import urllib.error
import re
from typing import List, Dict, Any
from datetime import datetime
class Config:
LLM_BASE_URL = "http://127.0.0.1:11433"
LLM_MODEL = "qwen2.5-1.5b-instruct-q4_k_m.gguf"
LLM_TEMPERATURE = 0.7
LLM_MAX_TOKENS = 1024
LLM_CTX_SIZE = 4096
LLM_STOP_WORDS = ["<|im_end|>"]
TIMEOUT_SECONDS = 30
class LlamaCppClient:
def __init__(self):
self.base_url = Config.LLM_BASE_URL
self.model = Config.LLM_MODEL
self.temperature = Config.LLM_TEMPERATURE
self.max_tokens = Config.LLM_MAX_TOKENS
self.ctx_size = Config.LLM_CTX_SIZE
self.stop = Config.LLM_STOP_WORDS
self.timeout = Config.TIMEOUT_SECONDS
def generate(self, prompt: str) -> str:
url = f"{self.base_url}/completion"
headers = {"Content-Type": "application/json"}
payload = {
"model": self.model,
"prompt": prompt,
"temperature": self.temperature,
"max_tokens": self.max_tokens,
"ctx_size": self.ctx_size,
"stop": self.stop,
"stream": False
}
try:
req = urllib.request.Request(
url,
data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
headers=headers,
method="POST"
)
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
data = json.loads(resp.read().decode("utf-8"))
return data.get("content", "").strip()
except urllib.error.URLError as e:
return f"[错误] llama.cpp服务连接失败:{str(e)}"
except json.JSONDecodeError:
return "[错误] 返回非合法JSON"
except Exception as e:
return f"[错误] {str(e)}"
class ChatMemory:
def __init__(self):
self.history: List[Dict[str, Any]] = []
def add_user(self, content: str):
self.history.append({"role": "user", "content": content})
def add_assistant(self, content: str):
self.history.append({"role": "assistant", "content": content})
def add_tool_response(self, content: str):
self.history.append({"role": "tool", "content": content})
def clear(self):
self.history.clear()
def build_prompt(self, system_content: str, tools: List[Dict] = None) -> str:
prompt_parts = []
if tools:
tool_str = json.dumps(tools, ensure_ascii=False, indent=2)
system_block = (
f"{system_content}\n\n# 工具\n\n"
"你可以调用一个或多个函数来协助回答用户的问题。\n\n"
"工具定义放在 <tools></tools> 标签内:\n"
f"<tools>\n{tool_str}\n</tools>\n\n"
"调用工具时,请把函数名和参数的JSON放在 <tool_call></tool_call> 标签内部:\n"
"<tool_call>\n{\"name\": \"函数名\", \"arguments\": {参数对象}}\n</tool_call>"
)
prompt_parts.append(f"<|im_start|>system\n{system_block}<|im_end|>\n")
else:
prompt_parts.append(f"<|im_start|>system\n{system_content}<|im_end|>\n")
for msg in self.history:
role = msg["role"]
content = msg["content"]
if role == "tool":
prompt_parts.append(f"<|im_start|>user\n<tool_response>\n{content}\n</tool_response><|im_end|>\n")
else:
prompt_parts.append(f"<|im_start|>{role}\n{content}<|im_end|>\n")
prompt_parts.append("<|im_start|>assistant\n")
return "".join(prompt_parts)
# ---------------- 工具定义与本地实现 ----------------
TOOLS_DEFINITION = [
{
"type": "function",
"function": {
"name": "get_datetime",
"description": "获取系统当前日期和时间,不需要参数",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "calculator",
"description": "计算器,执行数学运算",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string", "description": "数学表达式,例如 100+200"}
},
"required": ["expression"]
}
}
}
]
def tool_get_datetime() -> str:
"""获取系统当前日期时间"""
now = datetime.now()
return f"当前系统时间:{now.strftime('%Y‑%m‑%d %H:%M:%S')}"
def tool_calculator(expression: str) -> str:
"""简易计算器,做表达式计算"""
try:
res = eval(expression)
return f"计算结果:{res}"
except Exception as e:
return f"计算错误:{str(e)}"
TOOL_MAP = {
"get_datetime": tool_get_datetime,
"calculator": tool_calculator
}
def parse_tool_calls(assistant_text: str) -> List[Dict]:
pattern = r"<tool_call>(.*?)</tool_call>"
matches = re.findall(pattern, assistant_text, flags=re.DOTALL)
calls = []
for m in matches:
m = m.strip()
try:
obj = json.loads(m)
calls.append(obj)
except json.JSONDecodeError:
continue
return calls
def execute_tool_calls(tool_calls: List[Dict]) -> str:
outputs = []
for call in tool_calls:
fn_name = call.get("name")
args = call.get("arguments", {})
if fn_name in TOOL_MAP:
fn = TOOL_MAP[fn_name]
result = fn(**args)
outputs.append(f"[工具:{fn_name}] {result}")
else:
outputs.append(f"[工具:{fn_name}] 工具不存在")
return "\n".join(outputs)
if __name__ == "__main__":
llm = LlamaCppClient()
memory = ChatMemory()
system_prompt = "你是一个专业的助手,能够回答用户的问题,需要时调用工具。"
while True:
user_in = input("你:").strip()
if user_in in ("q", "quit", "exit", "退出"):
break
if user_in == "clear":
memory.clear()
print("已清空对话记忆\n")
continue
if not user_in:
continue
memory.add_user(user_in)
prompt = memory.build_prompt(system_prompt, tools=TOOLS_DEFINITION)
resp = llm.generate(prompt)
tool_calls = parse_tool_calls(resp)
if tool_calls:
print(f"AI(调用工具):\n{resp}\n")
memory.add_assistant(resp)
tool_result = execute_tool_calls(tool_calls)
print(f"工具返回:\n{tool_result}\n")
memory.add_tool_response(tool_result)
prompt2 = memory.build_prompt(system_prompt, tools=TOOLS_DEFINITION)
final_resp = llm.generate(prompt2)
memory.add_assistant(final_resp)
print(f"AI:{final_resp}\n")
else:
memory.add_assistant(resp)
print(f"AI:{resp}\n")
执行以下终端命令运行脚本:
你:当前日期时间是多少
AI(调用工具):
<tool_call>
{"name": "get_datetime", "arguments": {}}
</tool_call>
工具返回:
[工具:get_datetime] 当前系统时间:2026‑08‑25 14:51:35
AI:当前系统时间是2026‑08‑25 14:51:35。
你:计算100+100*10
AI(调用工具):
<tool_call>
{"name": "calculator", "arguments": {"expression": "100+100*10"}}
</tool_call>
工具返回:
[工具:calculator] 计算结果:1100
AI:计算结果是1100。
你:我是王瑞,今年28岁,男性
AI:好的,王瑞先生,今年28岁,男性。
你:简单介绍下我自己
AI:王瑞,男,28岁。
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

浙公网安备 33010602011771号