使用 Ollama 和 LiteLLM 在 Python 中构建交互式控制台 LLM 聊天应用
随着人们对本地大型语言模型(LLM)及其集成到自定义应用程序中的兴趣日益增长,仅仅运行模型已不足够,创建一个功能齐全的交互式界面变得至关重要。本文作为我们系列文章的延续,将重点介绍如何将一个用于单次 LLM 查询的基础脚本,转换为一个功能完善的 Python 控制台聊天应用。我们将利用 Ollama 和 LiteLLM 的强大组合,涵盖代码结构化、系统提示词实现、添加交互性以及错误处理等关键步骤。我们的目标是展示如何从简单的命令执行演变为构建一个能够实时用户交互的健壮且易于管理的应用,并为包括对话上下文管理在内的进一步功能扩展奠定基础。
从一次性脚本到交互式应用
我们指南的上一部分展示了如何使用 LiteLLM 库将通过 Ollama 托管的本地 LLM 与 Python 代码进行基本集成。其结果是一个能够向模型发送单个预定义查询并显示响应,然后程序终止的脚本。虽然这足以作为技术入门,但对于任何实际应用来说,这种方法都是不切实际的。一个真正的聊天机器人需要提供持续的交互:接受用户输入,处理输入,从模型接收响应,然后等待下一个问题,直到用户决定结束会话。这种根本性的转变正是我们接下来要实现的,将一个静态脚本转变为一个动态的控制台聊天应用。
本部分将引入的关键改进包括:
- 将 LLM 查询逻辑提取到一个独立的、可重用的函数中。
- 利用系统提示词来定义助手的角色和行为。
- 组织一个对话循环,允许用户连续提出多个问题。
- 实现响应时间测量以评估性能。
- 基本的异常处理,以提高应用程序的健壮性。
理解 LLM API 的消息格式对于有效交互至关重要。模型并非处理单行文本;它们处理的是一个消息列表,每个消息都具有特定的角色:
system(系统):这是给模型的指令,定义其个性、响应风格、限制或重要的上下文数据。用户不会直接看到此消息;它是在代码层面生成的。user(用户):源自用户的消息或问题。assistant(助手):模型自身生成的响应。在未来的迭代中,之前的模型响应将被添加到此处以维护上下文。
Python 控制台 LLM 聊天应用的架构
现在,让我们进入实现阶段。我们将修改 main.py 文件,以封装 LLM 交互逻辑并组织用户输入循环。以下是您可以用来替换 main.py 内容的更新代码。
# -*- coding: utf-8 -*-
import time
from typing import Optional
from litellm import completion
MODEL = "ollama_chat/qwen2.5:3b"
API_BASE = "http://localhost:11434"
SYSTEM_PROMPT = "You are a helpful assistant. Answer concisely and to the point."
def send_request_to_llm(user_message: str) -> Optional[str]:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
]
try:
start_time = time.time()
response = completion(
model=MODEL,
messages=messages,
api_base=API_BASE,
request_timeout=120,
)
duration = time.time() - start_time
print(f"\n⏱ 生成时间: {duration:.2f} 秒")
return response.choices[0].message.content
except Exception as e:
print(f"\n❌ 请求过程中发生错误: {e}")
return None
def main() -> None:
print("🤖 本地AI助手已启动。")
print("请输入您的问题,或输入 'exit' 退出。\n")
while True:
user_input = input("👤 您: ").strip()
if user_input.lower() in ("退出", "exit", "quit"):
print("再见!")
break
if not user_input:
print("⚠️ 请输入一个问题。")
continue
print("\n🤔 模型正在思考...")
answer = send_request_to_llm(user_input)
if answer is not None:
print(f"\n🦙 AI: {answer}\n")
else:
print("\n⚠️ 未能获取到响应。请检查 Ollama 是否正在运行。\n")
if __name__ == "__main__":
main()
分析关键代码组件
让我们仔细看看这段脚本的每个部分是如何工作的。
1. 配置常量:
在文件开头,定义了常量:MODEL、API_BASE 和 SYSTEM_PROMPT。这种方法允许轻松修改配置参数,例如使用不同的 LLM 模型或调整助手的行为,而无需深入主代码的核心逻辑。在这里,SYSTEM_PROMPT 扮演着至关重要的角色,它为模型设置了初始指令,影响其响应的整体语气和风格。
2. send_request_to_llm 函数:
这个函数(def send_request_to_llm(user_message: str) -> Optional[str]:)是与 LLM 交互的核心元素。它接收用户消息,构建一个完整的消息列表(包括 SYSTEM_PROMPT 和 user_message),并通过 litellm.completion 将其发送到本地 Ollama 模型。
- 封装性: 发送请求和接收响应的逻辑被完全封装起来,使得主
main循环更清晰、更易懂。 - 时间测量: 内置的时间测量(
time.time())允许评估模型性能,显示生成响应所需的时间(以秒为单位)。这在尝试不同模型或参数时特别有用。 - 错误处理:
try...except块提供了基本的容错能力。如果在 LLM 请求过程中发生错误(例如,Ollama 不可用),函数将返回None而不是引发关键错误,从而允许main函数优雅地处理这种情况并继续聊天。
3. main 函数和交互循环:
main 函数管理用户界面和整个聊天流程。
- 无限循环(
while True): 创建一个持续的聊天会话,直到用户明确决定退出。 - 用户输入:
input("👤 您: ").strip()等待用户输入。.strip()方法会移除开头和结尾的空白字符,防止发送空查询或格式错误的查询。 - 退出命令:
user_input.lower() in ("退出", "exit", "quit")检查允许用户使用各种退出命令选项来终止程序。 - 空输入:
if not user_input:检查可防止将空消息发送给模型,并提示用户输入问题。 - 交互:
main函数使用用户的输入调用send_request_to_llm,并处理接收到的响应,将其打印到控制台或报告错误。main(界面管理)和send_request_to_llm(模型交互)之间的职责分离是良好软件设计的基本原则。
通过系统提示词控制 LLM 行为
在使用 LLM 时,最强大的工具之一就是系统提示词。它允许您为模型分配特定的角色、沟通风格或一系列约束,从而在不改变模型本身或其参数的情况下显著改变其响应。通过实验 SYSTEM_PROMPT 是理解如何巧妙地为特定任务定制助手行为的绝佳方式。让我们看几个例子,说明如何通过改变一行代码来显著影响对话:
- `
SYSTEM_PROMPT = "You are a sarcastic assistant. Respond with irony.` —— 模型将以幽默和机智的方式回应。 - `
SYSTEM_PROMPT = "You are a strict teacher. Correct errors in questions.` —— 助手不仅会回答问题,还会指出查询中的语法或事实错误。 - `
SYSTEM_PROMPT = "Respond only in English. Always provide a code example.` —— 模型将严格遵守英语,并尝试在其响应中包含代码示例(如果适用)。
这些例子表明,系统提示词如何将一个静态模型转变为一个能够适应各种用例的灵活工具。这对于创建专业聊天机器人至关重要,无论它们是技术助手、创意伙伴还是教育工具。
关键要点
在此开发阶段,我们的控制台 LLM 聊天应用已取得了以下重要成果:
- 交互性: 用户可以进行持续对话,实时提问并接收答案。
- 行为可控: 系统提示词允许轻松配置模型的角色和沟通风格。
- 代码模块化: LLM 交互逻辑与用户界面逻辑分离,简化了后续开发和调试。
- 性能: 内置的响应生成时间测量有助于监控模型的速度。
- 可靠性: 基本的错误处理可防止因连接或模型问题导致的程序崩溃。
常见挑战及解决方案
在使用本地 LLM 和控制台应用程序时,可能会出现一些常见问题:
- 输入问题后程序冻结: 这通常是因为 Ollama 未运行或模型尚未完全加载到内存中。请确保 Ollama 正在积极运行(在您的终端中输入
ollama list),并给模型一些时间进行初始化。 - 模型响应过于简洁: 如果
SYSTEM_PROMPT指定“简洁回答”,模型将遵循此指令。如需更详细的响应,请修改系统提示词,移除此约束或明确要求“提供带有示例的详细回答”。 KeyboardInterrupt(键盘中断)在按下 Ctrl+C 时: 这是 Python 的标准行为。虽然对于学习项目来说不关键,但在生产环境中,您可以将main()包装在try/except KeyboardInterrupt块中,以实现更优雅的关闭。- 编码问题(Windows 中的乱码): 这可以通过将终端编码设置为 UTF-8 来解决,例如,在 PowerShell 中使用命令
$OutputEncoding = [System.Text.Encoding]::UTF8。
下一步:迈向功能完备的对话
尽管我们的控制台聊天应用功能已显著增强,但它目前缺乏“记忆”。每个请求都是独立处理的,模型不记得之前的对话轮次。这意味着如果您问“你能做同样的事情,但更简洁吗?”,模型将不理解“同样的事情”指的是什么。在下一部分中,我们将探讨如何实现消息历史记录,在每个新请求中传递整个对话上下文,这将使其转变为一个真正智能的对话伙伴。
— Editorial Team
暂无评论。