本文以通义千问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 这类低质量量化,容易产生大量幻觉和错误回答。
若要本地部署可以参考以下参数量范围:
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 osimport sysfrom gguf import GGUFReaderif __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:.3 f} 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:.2 f} 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:.2 f} 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 jsonimport urllib.requestimport urllib.errorclass 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 jsonimport urllib.requestimport urllib.errorfrom 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) resp = llm.generate(prompt) memory.add_assistant(resp) print (f"AI:{resp} \n" )
执行以下终端命令运行脚本:
CMD> python main.py 你:你好,我叫王瑞 AI:你好,王瑞,很高兴认识你。 你:我叫什么,你知道吗 AI:你好,王瑞,很高兴认识你。 你:简单介绍下你自己 AI:我是来自阿里的智能机器人。我叫“小助手”。我可以回答各种各样的问题,提供你想要的信息。你有什么问题想问我吗?
实现工具调用 原生本地大模型存在天然能力短板:无法获取实时系统时间、无法精准运算复杂数学公式、无外部数据获取能力、静态知识存在滞后性,无法适配实用化落地场景。本模块基于前文记忆对话框架,拓展自定义工具调用系统,内置时间查询、数学计算器两大高频实用工具,通过正则匹配解析模型输出的标准化工具调用指令,自动执行本地工具函数、获取结果后二次调用模型,生成贴合用户需求的最终自然语言回复,完整实现「模型决策调用工具-本地执行工具-结果反馈模型」的闭环能力。
本工具框架高度可拓展,开发者可基于现有代码,快速新增天气查询、文件读取、文本翻译、代码运行、联网搜索等各类自定义工具,无需重构核心逻辑。同时兼容Qwen模型原生工具调用模板。
import jsonimport urllib.requestimport urllib.errorimport refrom typing import List , Dict , Any from datetime import datetimeclass 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岁。