标准/工程化写法

from langchain_openai import (
    ChatOpenAI,
)  # 用 OpenAI 兼容接口和模型对话(阿里云等也兼容这个接口)
import os  # Python 自带:用来读「环境变量」(如 API 密钥)

# load_dotenv:从项目根目录的 .env 文件里,把变量加载到「环境」里,之后用 os.getenv("变量名") 就能读到。
# 把密钥写在 .env 里而不是代码里,既安全(不把密钥提交到 Git),又方便换环境(开发/生产用不同 .env)。
from dotenv import load_dotenv

# LangChainException:LangChain 在调用模型失败时会抛出的异常类型。
# 在 main() 里用 except LangChainException 单独接住这类错误,就能打出「模型调用失败」的日志,和配置错误、其他未知错误区分开。
from langchain_core.exceptions import LangChainException

# 真正执行「从 .env 加载到环境」;encoding='utf-8' 避免 .env 里有中文时乱码。
load_dotenv(encoding="utf-8")

# ----- 日志配置 -----
# logging 是 Python 自带的日志库,不用 pip 安装。用 logger.info() / logger.error() 代替 print,方便区分「普通信息」和「错误」,且可统一格式、写文件等。
# 通过环境变量 LOG_LEVEL 控制输出多少:开发时用 INFO(看得到调试信息),生产时在 .env 里设 LOG_LEVEL=WARNING,就只打警告和错误,减少刷屏。
import logging

_log_level = os.getenv("LOG_LEVEL", "INFO").upper()
logging.basicConfig(
    level=getattr(logging, _log_level, logging.INFO),
    format="%(asctime)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)  # 当前模块的 logger,后面用 logger.info(...) 即可

# ==========  LLM 客户端初始化(封装为函数,便于多处复用) ==========
# 把「创建可对话的客户端」封装成一个函数,以后在别处也能直接调 init_llm_client(),不用重复写一长串配置。

def init_llm_client() -> ChatOprnAI:
    """
    Returns:
        ChatOpenAI: 初始化好的「对话客户端」,可以对其调用 .invoke(问题) 或 .stream(问题)。
    """
    api_key = os.getenv("QWEN_API_KEY")
    if not api_key:
        raise ValueError("环境变量 QWEN_API_KEY 未配置,请检查 .env 文件")
    llm = ChatOpenAI(
         model="deepseek-v3.2",  # 模型名称(这里演示的是“DeepSeek 模型 + 阿里百炼兼容接口”)
        api_key=api_key,
        base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",  # 阿里云提供的兼容 OpenAI 的地址
        temperature=0.7,  # 控制「随机程度」:0 更确定、重复性高;1 更随机、更有创意。一般 0.5~0.8 即可。
        max_tokens=2048,  # 单次回复最多生成多少个 token(约等于字数),防止回复过长或超限。
    )
    return llm

# ==========   主逻辑:invoke(一次性) + stream(流式)两种调用方式 ==========
# 这里把「问问题、拿回答、打日志」都放在 main() 里,并用 try/except 把可能出现的错误分开处理,避免程序一报错就崩掉、且能打出清晰错误信息。

def main():
    try:
        llm = init_llm_client();
        logger.info("LLM客户端初始化成功")
        
        # 方式一 invoke()
        # 发一个问题,程序会等模型全部答完,再一次性把 response 给你。适合短问答。
        question = "你是谁"
        response = llm.invoke(question)
        logger.info(f"问题:{question},回答:{response.content}")
        
        # 方式二 stream()
        question = "介绍下 langchain,300字以内"
        response_stream = llm.stream(question)
        for chunk in response_stream:
            print(chunk.content, end="") # end="" 表示不换行,紧挨着打
        print()     # 流式结束后补一个换行,避免和后续输出粘在一起
        
        
# ----- 异常处理:根据错误类型打不同日志,方便排查 -----
# try 里面的代码一旦报错,会跳到下面某个 except;若都不匹配,再往上抛。
except ValueError as e:        
        # 例如:.env 里没配 QWEN_API_KEY,init_llm_client 里会 raise ValueError
        logger.error(f"配置错误:{str(e)}")
except LangChainException as e:
        # 例如:网络失败、API 限流、模型返回异常等,LangChain 会抛出 LangChainException
        logger.error(f"模型调用失败:{str(e)}")
except Exception as e:
        # 其他没预料到的错误都归到这里,避免程序静默崩溃
        logger.error(f"未知错误:{str(e)}")
        
        
        
# ========== 脚本入口 ==========
# __name__ 是 Python 给每个模块自动设置的内置变量,表示「当前模块的名字」:
#   - 直接运行本文件时(如 python StandardDesc.py),Python 会把 __name__ 设为字符串 "__main__"(前后各两个下划线),
#     于是下面的条件为真,会执行 main();
#   - 被别的文件 import 时,__name__ 是模块名(如 "01_helloworld.StandardDesc"),不等于 "__main__",不会执行 main(),
#     避免一导入就自动跑一遍问问题。
#
# 注意:必须写 "__main__" 不能写成 "main"。Python 规定「主程序」的 __name__ 就是 "__main__",
# 若写成 if __name__ == "main": 则条件永远为假(因为 __name__ 实际是 "__main__"),直接运行脚本时 main() 也不会被调用。
#
# 这里直接写 main() 即可,因为本文件的 main 是普通函数(def main),调用即执行。
# 若 main 是异步函数(async def main),则必须写 asyncio.run(main()),否则协程不会真正运行。
if __name__ == "__main__":
    main()

 

posted @ 2026-04-28 16:47  幻影之舞  阅读(3)  评论(0)    收藏  举报