1부
랭체인(LangChain) 정리 (LLM 로컬 실행 및 배포 & RAG 실습)
2부
오픈소스 LLM으로 RAG 에이전트 만들기 (랭체인, Ollama, Tool Calling 대체)
계획, 메모리, 도구 사용 등의 기능을 포함한 AI
외부 도구나 함수를 호출하여 작업을 수행하는 기능
https://python.langchain.com/v0.2/docs/integrations/chat/
eeve = ChatOllama(model="EEVE-Korean-Instruct-10.8B-v1.0:latest", temperature=0)
qwen2 = ChatOllama(model="qwen2:latest", temperature=0)
embeddings = HuggingFaceEmbeddings(
model_name="BAAI/bge-m3",
model_kwargs = {'device': 'cpu'}, # 모델이 CPU에서 실행되도록 설정. GPU를 사용할 수 있는 환경이라면 'cuda'로 설정할 수도 있음
encode_kwargs = {'normalize_embeddings': True}, # 임베딩 정규화. 모든 벡터가 같은 범위의 값을 갖도록 함. 유사도 계산 시 일관성을 높여줌
)
# 로컬 DB 불러오기
MY_NEWS_INDEX = "MY_NEWS_INDEX"
vectorstore1 = FAISS.load_local(MY_NEWS_INDEX,
embeddings,
allow_dangerous_deserialization=True)
retriever1 = vectorstore1.as_retriever(search_type="similarity", search_kwargs={"k": 3}) # 유사도 높은 3문장 추출
MY_PDF_INDEX = "MY_PDF_INDEX"
vectorstore2 = FAISS.load_local(MY_PDF_INDEX,
embeddings,
allow_dangerous_deserialization=True)
retriever2 = vectorstore2.as_retriever(search_type="similarity", search_kwargs={"k": 3}) # 유사도 높은 3문장 추출
from langchain.tools.retriever import create_retriever_tool
retriever_tool1 = create_retriever_tool(
retriever1,
name="saved_news_search",
description="""
다음과 같은 정보를 검색할 때에는 이 도구를 사용해야 한다:
- 엔비디아의 스타트업 인수 관련 내용
- 퍼플렉시티 관련 내용 (회사가치, 투자 등)
- 라마3 관련 내용
""",
)
retriever_tool2 = create_retriever_tool(
retriever2,
name="pdf_search",
description="""
다음과 같은 정보를 검색할 때에는 이 도구를 사용해야 한다:
- 생성형 AI 신기술 도입에 따른 선거 규제 연구 관련 내용
- 생성 AI 규제 연구 관련 내용
- 생성 AI 연구 관련 내용
"""
)
tools = [retriever_tool1, retriever_tool2]
tools
prompt_for_extract_actions = hub.pull("kwonempty/extract-actions-for-ollama")
def get_tools(query) -> str:
"""
사용 가능한 도구들의 이름과 설명을 JSON 문자열 형식으로 변환하여 반환
"""
# tools 리스트에서 각 도구의 이름, 설명을 딕셔너리 형태로 추출
tool_info = [{"tool_name": tool.name, "tool_description": tool.description} for tool in tools]
print(f"get_tools / tool_info: {tool_info}")
# tool_info 리스트를 JSON 문자열 형식으로 변환하여 반환
return json.dumps(tool_info, ensure_ascii=False)
chain_for_extract_actions = (
{"tools": get_tools, "question": RunnablePassthrough()}
| prompt_for_extract_actions
| qwen2
| StrOutputParser()
)
chain_for_extract_actions.invoke("3+4 계산해줘")
query = "라마3 성능은 어떻게 돼? 그리고 생성형 AI 도입에 따른 규제 연구 책임자는 누구야?"
actions_json = chain_for_select_actions.invoke(query)
actions_json
def get_documents_from_actions(actions_json: str, tools: List[Tool]) -> List[Document]:
"""
주어진 JSON 문자열을 파싱하여 해당 액션에 대응하는 검색기를 찾아서
액션을 실행 후 검색된 문서를 반환
:param actions_json: 액션과 그 입력이 포함된 JSON 문자열
:param tools: 사용 가능한 도구들의 리스트
:return: 액션을 통해 검색된 문서들의 리스트
"""
print(f"get_documents_from_actions / actions_json: {actions_json}")
# JSON 문자열을 파싱
try:
actions = json.loads(actions_json)
except json.JSONDecodeError:
raise ValueError("유효하지 않은 JSON 문자열")
# 파싱된 객체가 리스트인지 확인
if not isinstance(actions, list):
raise ValueError("제공된 JSON은 액션 리스트를 나타내야 함")
documents = []
# 도구 이름으로 검색기를 가져오는 함수
def get_retriever_by_tool_name(name: str) -> VectorStoreRetriever:
for tool in tools:
if tool.name == name:
return tool.func.keywords['retriever']
return None
# 각 액션을 처리
for action in actions:
if not isinstance(action, dict) or 'action' not in action or 'action_input' not in action:
continue # 유효하지 않은 액션은 건너뜀
tool_name = action['action']
action_input = action['action_input']
print(f"get_documents_from_actions / tool_name: {tool_name} / action_input: {action_input}")
if tool_name == "None": # 사용할 도구 없음. 바로 빈 document 리턴
print(f"get_documents_from_actions / 사용할 도구 없음. 바로 빈 document 리턴")
return []
retriever = get_retriever_by_tool_name(tool_name)
if retriever:
# 액션 입력으로 검색기 실행
retrieved_docs = retriever.invoke(action_input)
documents.extend(retrieved_docs)
print(f"get_documents_from_actions / len(documents): {len(documents)}")
return documents
get_documents_from_actions(actions_json, tools)
agent_prompt = ChatPromptTemplate.from_messages([
("system", """
너는 정확하고 신뢰할 수 있는 답변을 제공하는 유능한 업무 보조자야.
아래의 context를 사용해서 question에 대한 답변을 작성해줘.
다음 지침을 따라주세요:
1. 답변은 반드시 한국어로 작성해야 해.
2. context에 있는 정보만을 사용해서 답변해야 해.
3. 정답을 확실히 알 수 없다면 "주어진 정보로는 답변하기 어렵습니다."라고만 말해.
4. 답변 시 추측하거나 개인적인 의견을 추가하지 마.
5. 가능한 간결하고 명확하게 답변해.
# question:
{question}
# context:
{context}
# answer:
"""
),
])
default_prompt = ChatPromptTemplate.from_messages([
("system", """
너는 정확하고 신뢰할 수 있는 답변을 제공하는 유능한 업무 보조자야.
다음 질문에 최선을 다해서 대답해줘.
# question:
{question}
# answer:
"""
),
])
retrieved_docs = []
def get_page_contents_with_metadata(docs) -> str:
"""
문서 리스트를 받아 각 문서의 본문 내용과 출처를 포함한 문자열을 생성
"""
global retrieved_docs
retrieved_docs = docs
result = ""
for i, doc in enumerate(docs):
if i > 0:
result += "\n\n"
result += f"## 본문: {doc.page_content}\n### 출처: {doc.metadata['source']}"
return result
def get_retrieved_docs_string(query) -> str:
"""
쿼리에 따라 문서를 검색하고, 해당 문서들의 본문 내용과 출처를 포함한 문자열을 반환
"""
actions_json = chain_for_extract_actions.invoke(query)
docs = get_documents_from_actions(actions_json, tools)
if len(docs) <= 0:
return ""
return get_page_contents_with_metadata(docs)
def get_metadata_sources(docs) -> str:
"""
문서 리스트에서 각 문서의 출처 추출해서 문자열로 반환
"""
sources = set()
for doc in docs:
source = doc.metadata['source']
is_pdf = source.endswith('.pdf')
if (is_pdf):
file_path = doc.metadata['source']
file_name = os.path.basename(file_path)
source = f"{file_name} ({int(doc.metadata['page']) + 1}페이지)"
sources.add(source)
return "\n".join(sources)
def check_context(inputs: dict) -> bool:
"""
context 존재 여부 확인
:return: 문자열이 비어있지 않으면 True, 비어있으면 False
"""
result = bool(inputs['context'].strip())
print(f"check_context / result: {result}")
return result
def parse(ai_message: AIMessage) -> str:
"""
AI 메시지 파싱해서 내용에 출처 추가
"""
return f"{ai_message.content}\n\n[출처]\n{get_metadata_sources(retrieved_docs)}"
with_context_chain = (
RunnablePassthrough()
| RunnableLambda(lambda x: {
"context": x["context"],
"question": x["question"]
})
| agent_prompt
| eeve
| parse
)
without_context_chain = (
RunnablePassthrough()
| RunnableLambda(lambda x: {"question": x["question"]})
| default_prompt
| eeve
| StrOutputParser()
)
agent_chain = (
{"context": get_retrieved_docs_string, "question": RunnablePassthrough()}
| RunnableBranch(
(lambda x: check_context(x), with_context_chain),
without_context_chain # default
)
)
agent_chain.invoke("12+34 계산해줘")
물론이죠, 도와드리겠습니다!
12 + 34 = 46입니다.
agent_chain.invoke("라마3 성능은 어떻게 돼? 그리고 생성형 AI 도입에 따른 규제 연구 책임자는 누구야?")
라마3의 성능은 객관식 문제(MMLU)와 코딩(HumanEval)에서 강점을 보이며, 특히 인간 선호도 측면에서 경쟁 모델을 앞서고 있습니다. 그러나 수학 단어 문제(MATH) 해결이나 대학원생 수준의 객관식 문제(GPQA)에서는 제미나이 프로 1.5에 비해 뒤처집니다. 라마3는 공개 후 몇 시간 만에 LLM 리더보드에서 1위를 차지하며 역대 가장 빠른 기록을 세웠습니다.
라마3의 인간 평가 결과, 허깅페이스는 공개 후 몇 시간 만에 LLM 리더보드에서 1위에 올랐다고 언급했습니다. 또한 이전 라마 1과 2를 기반으로 한 모델이 3만 개 이상 출시되었으며, 라마 2 모델은 17억 번 다운로드되었다고 합니다. 그러나 라마 3는 완전한 오픈 소스가 아니며, 연구 및 상업적 용도로 사용 가능하지만 개발자가 다른 생성 모델을 훈련하는 것을 금지하고 있습니다.
라마3의 안전성과 책임 있는 사용을 보장하기 위해 다양한 안전장치를 마련했습니다. 부적절한 답변 가능성을 최소화하기 위해 전문가와 자동화된 도구를 활용한 레드팀 테스트를 실시했습니다. 라마3는 라마 2보다 두 배 큰 컨텍스트 길이(8000 토큰)를 지원하며, 훈련 규모를 확대하고 지시 미세조정 과정을 거쳤습니다.
생성형 AI 도입에 따른 규제 연구 책임자는 국립부경대학교 김주희 교수입니다. 공동연구자로는 차재권, 김현정, 조성복 교수가 있으며, 연구보조원은 박서현과 권수민입니다. 이 연구는 2023년도 중앙선거관리위원회 정책연구용역 과제로서, 생성형 AI 기술이 선거에 미치는 영향과 잠재적 문제점을 분석하고 규제 방안을 제안하고 있습니다.
[출처]
https://www.aitimes.com/news/articleView.html?idxno=158943
생성형AI신기술도입에따른선거규제연구결과보고서.pdf (1페이지)
생성형AI신기술도입에따른선거규제연구결과보고서.pdf (9페이지)
생성형AI신기술도입에따른선거규제연구결과보고서.pdf (2페이지)
항상 좋은 글 잘보고 갑니다 :)