AIVideo与Unity3D集成:游戏剧情视频自动生成

核心内容摘要

伪代码避坑大全:为什么你的算法描述总被同事吐槽?附LaTeX排版技巧
SpringSecurity之跨域

合规使用“ODM/OEM”产品,避免外贸订单引发TRO

本文详细介绍了LangChain大模型应用框架涵盖其五大核心概念、快速上手指南及核心组件

使用方法。

通过构建智能文档问答Web应用的实战项目展示了如何整合LangChain的各项功能包括模型管理、数据检索、链式调用和记忆功能。

文章提供完整代码实现、部署方法和扩展练习帮助读者从入门到实战掌握LangChain应用开发。

分LangChain基础教程

什么是LangChain为什么你需要它想象一下你想让AI帮你分析一份PDF文档提取关键信息然后用这些信息回答用户问题。

单独使用GPT-4的话你需要手动处理文档解析、信息存储、上下文管理等步骤。

而LangChain就像一个AI应用胶水框架它能把这些零散的功能模块串联起来让你快速构建复杂的LLM应用。

核心概念速览 *Model I/O统一接口对接各类大模型OpenAI、Claude、本地模型等 *Retrieval智能检索外部数据文档、数据库、API等 *Chains将多个组件串联成工作流 *Agents让AI自主决策使用哪些工具 *Memory为对话添加记忆能力

5分钟快速上手

环境准备# 创建项目目录 mkdir langchain_tutorial cd langchain_tutorial # 安装核心库 pip install langchain openai chromadb tiktoken # 设置API密钥以OpenAI为例 export OPENAI_API_KEYyour-api-key-here

第一个Hello Worldfrom langchain.llms import OpenAI from langchain.prompts import PromptTemplate # 初始化模型 llm OpenAI(temperature

0.

# 创建提示模板 prompt PromptTemplate( input_variables[product], template请为{product}写一段吸引人的广告文案要求包含emoji ) # 生成内容 result llm(prompt.format(product智能咖啡杯)) print(result) # 输出示例☕️ 智能咖啡杯 - 你的私人咖啡师自动控温APP远程操控让每一口都恰到好处

核心组件详解

模型管理Model I/Ofrom langchain.llms import OpenAI, HuggingFaceHub from langchain.chat_models import ChatOpenAI # 文本生成模型 text_llm OpenAI(model_nametext-davinci-

# 对话模型 chat_model ChatOpenAI(model_namegpt-

5-turbo) # 本地模型 local_llm HuggingFaceHub( repo_idgoogle/flan-t5-xl, model_kwargs{temperature:

5} )

提示工程Prompt Engineeringfrom langchain.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate # 系统用户双角色提示 system_msg SystemMessagePromptTemplate.from_template( 你是一个专业的{role}请用{style}的语气回答问题 ) human_msg HumanMessagePromptTemplate.from_template({query}) chat_prompt ChatPromptTemplate.from_messages([system_msg, human_msg]) # 格式化输入 formatted_prompt chat_prompt.format_messages( role美食评论家, style幽默风趣, query如何评价北京豆汁儿 )

数据检索Retrievalfrom langchain.document_loaders import TextLoader from langchain.text_splitter import CharacterTextSplitter from langchain.embeddings import OpenAIEmbeddings from langchain.vectorstores import Chroma # 加载文档 loader TextLoader(company_handbook.txt) documents loader.load() # 分割文本 text_splitter CharacterTextSplitter(chunk_size500, chunk_overlap

docs text_splitter.split_documents(documents) # 创建向量数据库 embeddings OpenAIEmbeddings() db Chroma.from_documents(docs, embeddings) # 相似度检索 query 公司的年假政策是什么 results db.similarity_search(query, k

for doc in results: print(f相关段落{doc.page_content[:200]}...)

链式调用Chainsfrom langchain.chains import LLMChain, SimpleSequentialChain # 单链翻译润色 translate_chain LLMChain( llmllm, promptPromptTemplate( input_variables[text], template将以下文本翻译成英文\n{text}\n然后润色使其更专业 ) ) # 顺序链先翻译再

总结 summary_chain LLMChain( llmllm, promptPromptTemplate( input_variables[text], template用三句话

总结以下内容\n{text} ) ) overall_chain SimpleSequentialChain( chains[translate_chain, summary_chain], verboseTrue ) result overall_chain.run(人工智能是计算机科学的一个分支...)

记忆功能Memoryfrom langchain.memory import ConversationBufferMemory memory ConversationBufferMemory() # 第一轮对话 response1 chat_model.predict( input我叫小明今年25岁, memorymemory ) print(response

# 你好小明很高兴认识你... # 第二轮对话自动携带历史 response2 chat_model.predict( input我刚才说了什么, memorymemory ) print(response

# 你之前提到你叫小明今年25岁

分实战项目 - 智能文档问答Web应用

项目概览我们将把上面学到的LangChain知识全部整合起来构建一个功能完整的智能文档问答系统具有以下特性 支持PDF/TXT/DOCX文档上传 智能语义检索与问答 多轮对话记忆功能️ 实时参数调节面板 响应式Web界面

项目结构langchain_tutorial/ ├── app.py # 主应用文件 ├── requirements.txt # 依赖列表 ├── run.py # 启动脚本 ├── README.md # 使用说明 ├── chroma_db/ # 向量数据库目录自动创建 └── utils/ # 工具模块 ├── __init__.py └── document_processor.py

完整代码实现

主应用文件 (app.py)import streamlit as st import os import tempfile import shutil from pathlib import Path from langchain.chains import ConversationalRetrievalChain from langchain.memory import ConversationBufferWindowMemory from langchain.vectorstores import Chroma from langchain.embeddings import OpenAIEmbeddings from langchain.chat_models import ChatOpenAI from langchain.prompts import PromptTemplate from utils.document_processor import process_documents # 页面配置 st.set_page_config( page_title智能文档问答系统 - LangChain实战, page_icon, layoutwide, initial_sidebar_stateexpanded ) # 确保chroma_db目录存在 CHROMA_DB_DIR ./chroma_db Path(CHROMA_DB_DIR).mkdir(exist_okTrue) # 自定义CSS样式 def apply_custom_css(): st.markdown( style .main { background-color: #f5f7fa ; } .stButtonbutton { background-color: #4CAF50 ; color: white; border-radius: 20px; padding: 10px 24px; } .chat-message { padding:

5rem; border-radius:

8rem; margin-bottom: 1rem; display: flex; } .user-message { background-color: #e3f2fd ; justify-content: flex-end; } .assistant-message { background-color: #ffffff ; box-shadow: 0 2px 4px rgba(0,0,0,

0.

; } .source-box { background-color: #f8f9fa ; border-left: 4px solid #4285f4 ; padding: 1rem; margin-top:

5rem; border-radius: 0 8px 8px 0; } /style , unsafe_allow_htmlTrue) apply_custom_css() # 初始化会话状态 if messages not in st.session_state: st.session_state.messages [] if vector_db not in st.session_state: st.session_state.vector_db None if memory not in st.session_state: st.session_state.memory ConversationBufferWindowMemory( k3, return_messagesTrue, memory_keychat_history ) # 侧边栏配置 with st.sidebar: st.title(⚙️ 系统配置) # API密钥输入 api_key st.text_input(OpenAI API Key, typepassword, help在此输入您的OpenAI API密钥) if api_key: os.environ[OPENAI_API_KEY] api_key # 模型参数调节 st.subheader(模型参数) temperature st.slider(Temperature,

0,

0,

7,

0.

max_tokens st.slider(Max Tokens, 256, 2048, 1024,

# 记忆配置 st.subheader(记忆设置) memory_window st.slider(记忆窗口大小, 1, 10,

# 检索设置 st.subheader(检索设置) top_k st.slider(检索文档数量, 1, 10,

# 文档管理 st.subheader(文档管理) uploaded_files st.file_uploader( 上传文档 (PDF/TXT/DOCX), type[pdf, txt, docx], accept_multiple_filesTrue ) if uploaded_files and api_key: with st.spinner(正在处理文档...): try: # 处理文档 documents process_documents(uploaded_files) if documents: # 重新初始化向量数据库 if os.path.exists(CHROMA_DB_DIR): shutil.rmtree(CHROMA_DB_DIR) st.session_state.vector_db Chroma.from_documents( documentsdocuments, embeddingOpenAIEmbeddings(), persist_directoryCHROMA_DB_DIR ) st.success(f✅ 成功处理 {len(documents)} 个文档片段) else: st.error(❌ 未能处理任何文档) except Exception as e: st.error(f❌ 处理文档时出错: {str(e)}) # 清空对话按钮 if st.button(️ 清空对话历史): st.session_state.messages [] st.session_state.memory ConversationBufferWindowMemory( kmemory_window, return_messagesTrue, memory_keychat_history ) st.rerun() # 主界面 st.title( 智能文档问答系统) st.caption(基于LangChain Streamlit构建) # 显示LangChain概念说明 with st.expander( LangChain核心概念): st.markdown( ###

Model I/O - 使用 ChatOpenAI 作为大语言模型 - 通过 temperature 和 max_tokens 控制模型行为 ###

Retrieval - 使用 Chroma 向量数据库存储文档嵌入 - 通过 as_retriever() 方法创建检索器 ###

Chains - 使用 ConversationalRetrievalChain 组合检索和生成 ###

Memory - 使用 ConversationBufferWindowMemory 维护对话历史 ###

Prompts - 自定义提示模板指导AI回答 ) # 显示聊天历史 for message in st.session_state.messages: with st.container(): if message[role] user: st.markdown(f div classchat-message user-message div stylebackground-color: #2196F3 ; color: white; padding: 10px 15px; border-radius: 18px; max-width: 80%; {message[content]} /div /div , unsafe_allow_htmlTrue) else: st.markdown(f div classchat-message assistant-message div stylebackground-color: white; padding: 10px 15px; border-radius: 18px; max-width: 80%; box-shadow: 0 1px 3px rgba(0,0,0,

0.

; {message[content]} /div /div , unsafe_allow_htmlTrue) # 显示来源文档 if sources in message and message[sources]: with st.expander( 参考文档): for i, source in enumerate(message[sources]): source_name source.metadata.get(source, 未知) content_preview source.page_content[:200].replace(\n, ) st.markdown(f**来源 {i1}:** {source_name}) st.markdown(f**内容:** {content_preview}...) # 用户输入 if prompt : st.chat_input(请输入您的问题...): if not api_key: st.error(❌ 请先在侧边栏输入OpenAI API密钥) st.stop() if st.session_state.vector_db is None: st.warning(⚠️ 请先上传并处理文档) st.stop() # 添加用户消息 st.session_state.messages.append({role: user, content: prompt}) # 显示用户消息 st.markdown(f div classchat-message user-message div stylebackground-color: #2196F3 ; color: white; padding: 10px 15px; border-radius: 18px; max-width: 80%; {prompt} /div /div , unsafe_allow_htmlTrue) # 生成回答 with st.spinner( AI正在思考中...): try: # 创建自定义提示模板 custom_prompt PromptTemplate( template 你是一个专业的文档分析助手请根据提供的上下文信息回答问题。

如果上下文中没有相关信息请明确说明无法回答不要编造答案。

上下文信息: {context} 对话历史: {chat_history} 问题: {question} 回答要求:

答案要准确、简洁

使用中文回答 , input_variables[context, chat_history, question] ) # 创建QA链 qa_chain ConversationalRetrievalChain.from_llm( llmChatOpenAI( temperaturetemperature, max_tokensmax_tokens, model_namegpt-

5-turbo ), retrieverst.session_state.vector_db.as_retriever( search_kwargs{k: top_k} ), memoryst.session_state.memory, combine_docs_chain_kwargs{prompt: custom_prompt}, return_source_documentsTrue, verboseFalse ) # 获取回答 response qa_chain({question: prompt}) # 添加助手消息 assistant_message { role: assistant, content: response[answer], sources: response.get(source_documents, []) } st.session_state.messages.append(assistant_message) # 显示助手消息 st.markdown(f div classchat-message assistant-message div stylebackground-color: white; padding: 10px 15px; border-radius: 18px; max-width: 80%; box-shadow: 0 1px 3px rgba(0,0,0,

0.

; {response[answer]} /div /div , unsafe_allow_htmlTrue) # 显示来源文档 if response.get(source_documents): with st.expander( 参考文档): for i, doc in enumerate(response[source_documents]): source_name doc.metadata.get(source, 未知) content_preview doc.page_content[:200].replace(\n, ) st.markdown(f**来源 {i1}:** {source_name}) st.markdown(f**内容:** {content_preview}...) except Exception as e: error_msg f❌ 生成回答时出错: {str(e)} st.error(error_msg) st.session_state.messages.append({ role: assistant, content: error_msg }) # 显示使用统计 if st.session_state.messages: col1, col2, col3 st.columns(

with col1: st.metric(对话轮数, len(st.session_state.messages) //

with col2: if st.session_state.vector_db: try: doc_count len(st.session_state.vector_db.get()[ids]) st.metric(文档片段数, doc_count) except: st.metric(文档片段数, N/A) with col3: st.metric(记忆窗口, f{memory_window} 轮)

文档处理器 (utils/document_processor.py)import os import tempfile import shutil from pathlib import Path from langchain.document_loaders import ( PyPDFLoader, TextLoader, Docx2txtLoader ) from langchain.text_splitter import RecursiveCharacterTextSplitter def process_documents(uploaded_files): 处理上传的文档并返回分割后的文本块 all_docs [] # 创建临时目录 temp_dir tempfile.mkdtemp() try: for file in uploaded_files: # 保存临时文件 temp_path Path(temp_dir) / file.name with open(temp_path, wb) as f: f.write(file.getbuffer()) try: # 根据文件类型选择加载器 file_extension temp_path.suffix.lower() if file_extension .pdf: loader PyPDFLoader(str(temp_path)) elif file_extension .txt: loader TextLoader(str(temp_path)) elif file_extension .docx: loader Docx2txtLoader(str(temp_path)) else: continue # 加载文档 documents loader.load() # 添加文件来源信息 for doc in documents: doc.metadata[source] file.name doc.metadata[file_type] file_extension all_docs.extend(documents) except Exception as e: print(f处理文件 {file.name} 时出错: {str(e)}) continue if all_docs: # 分割文本 text_splitter RecursiveCharacterTextSplitter( chunk_size1000, chunk_overlap200, length_functionlen, add_start_indexTrue, ) split_docs text_splitter.split_documents(all_docs) return split_docs return [] finally: # 清理临时目录 if os.path.exists(temp_dir): shutil.rmtree(temp_dir)

空初始化文件 (utils/init.py)# 空文件使utils成为Python包

依赖文件 (requirements.txt)streamlit

1.

2

0 langchain

0.

340 openai

1.

0 chromadb

0.

15 PyPDF

23.

1 python-docx

0.

11 tiktoken

0.

5.

启动脚本 (run.py)#!/usr/bin/env python3 LangChain 智能文档问答系统启动脚本 import subprocess import sys import os def check_dependencies(): 检查依赖是否安装 required_packages [ streamlit, langchain, openai, chromadb, PyPDF2, python-docx, tiktoken ] missing_packages [] for package in required_packages: try: __import__(package.replace(-, _)) except ImportError: missing_packages.append(package) if missing_packages: print(f❌ 缺少以下依赖包: {, .join(missing_packages)}) print(请运行: pip install -r requirements.txt) return False return True def main(): 主函数 print( LangChain 智能文档问答系统) print( *

# 检查依赖 if not check_dependencies(): sys.exit(

# 检查项目结构 required_files [ app.py, requirements.txt, utils/__init__.py, utils/document_processor.py ] missing_files [] for file in required_files: if not os.path.exists(file): missing_files.append(file) if missing_files: print(f❌ 缺少以下文件: {, .join(missing_files)}) sys.exit(

print(✅ 所有文件和依赖检查通过) print( 启动应用...) print( 应用将在浏览器中打开: http://localhost:

print(⏹️ 按 CtrlC 停止应用) print( *

# 启动Streamlit应用 try: subprocess.run([ sys.executable, -m, streamlit, run, app.py, --server.port8501, --server.addresslocalhost ]) except KeyboardInterrupt: print(\n 应用已停止) except Exception as e: print(f❌ 启动应用时出错: {str(e)}) sys.exit(

if __name__ __main__: main()

使用说明 (README.md)# LangChain 智能文档问答系统 这是一个基于 LangChain Streamlit 构建的智能文档问答系统支持 PDF、TXT、DOCX 文档的上传和智能问答。

## 功能特性 - 支持多种文档格式 (PDF, TXT, DOCX) - 智能语义检索与问答 - 多轮对话记忆功能 - ️ 可调节的模型参数 - 响应式 Web 界面## 安装和运行

安装依赖pip install -r requirements.txt###

设置 OpenAI API 密钥- 在 [OpenAI官网](https://platform.openai.com/api-keys) 注册账号并获取 API 密钥 - 或在运行时通过界面输入###

运行应用方式1: 使用启动脚本python run.py方式2: 直接使用streamlitstreamlit run app.py###

访问应用打开浏览器访问: http://localhost:8501 ## 使用说明

**设置 API 密钥**: 在左侧边栏输入您的 OpenAI API 密钥

**上传文档**: 点击上传文档按钮选择 PDF/TXT/DOCX 文件

**等待处理**: 系统会自动处理文档并建立索引

**开始问答**: 在底部输入框中输入问题AI 会基于文档内容回答## 项目结构langchain_tutorial/ ├── app.py # 主应用文件 ├── requirements.txt # 依赖列表 ├── run.py # 启动脚本 ├── chroma_db/ # 向量数据库目录自动创建 └── utils/ # 工具模块 ├── init .py └── document_processor.py## 故障排除###

常见问题

**ModuleNotFoundError**: 运行 pip install -r requirements.txt

**API 密钥错误**: 检查 OpenAI API 密钥是否正确设置

**文档处理失败**: 确保文档格式正确且未损坏

**内存不足**: 减少上传文档的大小或数量 ### 支持的文档格式 - PDF (.pdf) - 纯文本 (.txt) - Word 文档 (.docx) ## 技术栈 - **LangChain**: 大语言模型应用框架 - **Streamlit**: Web 应用框架 - **ChromaDB**: 向量数据库 - **OpenAI GPT**: 大语言模型 - **Python**: 编程语言 ## 许可证 MIT License

部署与运行

本地运行# 创建项目目录 mkdir langchain_tutorial cd langchain_tutorial # 创建所有文件复制上面的代码 # 安装依赖 pip install -r requirements.txt # 启动应用 python run.py

云部署Streamlit Cloud将代码推送到GitHub仓库访问 share.streamlit.io连接GitHub仓库并部署在设置中添加OpenAI API密钥作为secrets

从教程到实战的完美结合核心概念在实战中的应用LangChain概念在实战项目中的应用具体实现Model I/O使用ChatOpenAI处理用户输入ChatOpenAI(temperature

7, max_tokens

Retrieval从文档中检索相关信息Chroma向量数据库 as_retriever()Chains组合检索和生成过程ConversationalRetrievalChainMemory维护对话历史ConversationBufferWindowMemoryPrompts指导AI回答的格式和内容自定义PromptTemplate

扩展练习建议添加Agents功能让用户可以选择使用网络搜索工具支持更多文件格式添加Excel、PowerPoint等格式支持多语言支持添加英文等其他语言的问答能力用户认证添加登录系统支持多用户使用API接口添加REST API支持第三方集成

七、

常见问题解答Q: 如何处理大型文档A: 使用更小的chunk_size或者实现分层检索策略Q: 如何提高检索准确性A: 调整chunk_size、chunk_overlap或者使用不同的embedding模型Q: 如何降低API成本A: 使用本地embedding模型只在必要时调用LLM API

总结通过这篇完整的教程我们从LangChain的基础概念开始逐步深入到实际的Web应用开发。

这个项目不仅展示了LangChain的各个核心组件还提供了一个可以直接使用的智能文档问答系统。

重要提示确保已安装所有依赖包需要有效的OpenAI API密钥首次运行可能需要较长时间处理文档建议使用较小的文档进行测试如何系统的学习大模型 AI 由于新岗位的生产效率要优于被取代岗位的生产效率所以实际上整个社会的生产效率是提升的。

但是具体到个人只能说是“最先掌握AI的人将会比较晚掌握AI的人有竞争优势”。

这句话放在计算机、互联网、移动互联网的开局时期都是一样的道理。

我在一线互联网企业工作十余年里指导过不少同行后辈。

帮助很多人得到了学习和成长。

我意识到有很多经验和知识值得分享给大家也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑所以在工作繁忙的情况下还是坚持各种整理和分享。

但苦于知识传播途径有限很多互联网行业朋友无法获得正确的资料得到学习提升故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

一直在更新更多的大模型学习和面试资料已经上传带到CSDN的官方了有需要的朋友可以扫描下方二维码免费领取【保证100%免费】

大模型风口已至月薪30K的AI岗正在批量诞生2025年大模型应用呈现爆发式增长根据工信部最新数据国内大模型相关岗位缺口达47万初级工程师平均薪资28K数据来源BOSS直聘报告70%企业存在能用模型不会调优的痛点真实案例某二本机械专业学员通过4个月系统学习成功拿到某AI医疗公司大模型优化岗offer薪资直接翻3倍

大模型 AI 学习和面试资料1️⃣ 提示词工程把ChatGPT从玩具变成生产工具2️⃣ RAG系统让大模型精准输出行业知识3️⃣ 智能体开发用AutoGPT打造24小时数字员工熬了三个大夜整理的《AI进化工具包》送你✔️ 大厂内部LLM落地手册含58个真实案例✔️ 提示词设计模板库覆盖12大应用场景✔️ 私藏学习路径图0基础到项目实战仅需90天第一阶段10天初阶应用该阶段让大家对大模型 AI有一个最前沿的认识对大模型 AI 的理解超过 95% 的人可以在相关讨论时发表高级、不跟风、又接地气的见解别人只会和 AI 聊天而你能调教 AI并能用代码将大模型和业务衔接。

大模型 AI 能干什么大模型是怎样获得「智能」的用好 AI 的核心心法大模型应用业务架构大模型应用技术架构代码示例向 GPT-

5 灌入新知识提示工程的意义和核心思想Prompt 典型构成指令调优方法论思维链和思维树Prompt 攻击和防范…第二阶段30天高阶应用该阶段我们正式进入大模型 AI 进阶实战学习学会构造私有知识库扩展 AI 的能力。

快速开发一个完整的基于 agent 对话机器人。

掌握功能最强的大模型开发框架抓住最新的技术进展适合 Python 和 JavaScript 程序员。

为什么要做 RAG搭建一个简单的 ChatPDF检索的基础概念什么是向量表示Embeddings向量数据库与向量检索基于向量检索的 RAG搭建 RAG 系统的扩展知识混合检索与 RAG-Fusion 简介向量模型本地部署…第三阶段30天模型训练恭喜你如果学到这里你基本可以找到一份大模型 AI相关的工作自己也能训练 GPT 了通过微调训练自己的垂直大模型能独立训练开源多模态大模型掌握更多技术方案。

到此为止大概2个月的时间。

你已经成为了一名“AI小子”。

那么你还想往下探索吗为什么要做 RAG什么是模型什么是模型训练求解器 损失函数简介小实验2手写一个简单的神经网络并训练它什么是训练/预训练/微调/轻量化微调Transformer结构简介轻量化微调实验数据集的构建…第四阶段20天商业闭环对全球大模型从性能、吞吐量、成本等方面有一定的认知可以在云端和本地等多种环境下部署大模型找到适合自己的项目/创业方向做一名被 AI 武装的产品经理。

硬件选型带你了解全球大模型使用国产大模型服务搭建 OpenAI 代理热身基于阿里云 PAI 部署 Stable Diffusion在本地计算机运行大模型大模型的私有化部署基于 vLLM 部署大模型案例如何优雅地在阿里云私有部署开源大模型部署一套开源 LLM 项目内容安全互联网信息服务算法备案…学习是一个过程只要学习就会有挑战。

天道酬勤你越努力就会成为越优秀的自己。

如果你能在15天内完成所有的任务那你堪称天才。

然而如果你能完成

% 的内容你就已经开始具备成为一名大模型 AI 的正确特征了。

这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

萨姆依555488最新消息视频-萨姆依555488最新消息视频应用

百度百家号客服电话人工服务

123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123