동적 `Query` 처리 및 향상된 정보 검색을 위해 LangChain 에이전트를 사용하여 Neo4j Knowledge Graph, 기본 `Vector` 검색 및 Cypher LangChain 템플릿을 결합합니다.
정보 검색에서 중요한 문제는 `Query`를 지능적으로 처리하고 라우팅할 수 있는 효율적인 에이전트가 부족하다는 것이에요. 기존 시스템은 특히 데이터베이스에 저장된 방대하고 복잡한 데이터를 처리할 때 복잡한 `Query`를 동적으로 처리하는 데 어려움을 겪는 경우가 많죠. Neo4j `Vector` 및 `Graph Database` 말이에요. 이러한 제한은 최적이 아닌 검색 결과로 이어질 수 있으며, 정보에 근거한 응답을 종합하고 생성하는 AI 애플리케이션의 잠재력을 최대한 방해할 수 있어요.
이 솔루션은 Neo4j `Graph` 체인의 LangChain 템플릿, 특히 `Vector` 및 `Graph` Cypher 템플릿을 효과적으로 통합하여 이러한 체인을상담원이 정보에 입각한 결정을 내릴 수 있도록 해줍니다.
The Neo4j `Vector` 체인 정밀한 `Embedding`의 균형을 유지하고 고급 검색 전략을 구현하여 컨텍스트를 유지할 수 있습니다.
The Neo4j Cypher 체인 템플릿자연어 질문을 Cypher `Query`(Neo4j 데이터베이스에서 데이터를 가져오는 데 사용됨)로 변환하고, `Query`를 실행하고, `Query` 결과를 기반으로 자연어 응답을 제공합니다.
이 접근 방식을 통해 상담원은 질문에 따라 사용할 체인이나 도구를 지능적으로 결정할 수 있어요. Neo4j의 `Vector` 및 `Graph` 기능의 장점을 결합하여 시스템은 `Query` 처리를 최적화하고 정보 검색의 전반적인 품질을 향상시킵니다.
LangChain — 에이전트 및 체인
LangChain 에이전트는 `Large Language Model`을 사용하여 작업을 동적으로 선택하고 순서를 지정하며 AI 애플리케이션에서 지능적인 의사 결정자 역할을 합니다. 정교한 추론을 기반으로 하는 이러한 에이전트는 입력과 컨텍스트를 평가하여 가장 효과적인 조치 과정을 결정하고 AI 기반 프로세스의 기능과 적응성을 향상시킬 수 있어요.
또한 체인은 구성 요소에 대한 일련의 호출로, 워크플로에서 구성 가능한 모듈식 요소로 작동합니다. `Large Language Model`을 다른 구성 요소와 통합할 수 있어 복잡한 애플리케이션 시나리오에서 다양성과 효율성을 제공합니다.
이 프로젝트에서는 두 가지를 통합하여 성능을 향상시키는 것이 흥미로운 도전이었어요. Retrieval-Augmented Generation(RAG) 애플리케이션 말이죠. 보다 구체적으로 상담원은 사용자 질문을 평가하고 최선의 조치를 결정합니다. 에이전트는 일련의 작업과 기능으로 개념화된 체인을 도구로 사용할 수 있어요.
고급 검색 전략
기존 RAG 접근 방식에서 순진한 `Vector` 유사성 검색의 주요 문제는 대규모 문서에서 컨텍스트와 특정 개념을 정확하게 식별하는 능력이 제한되어 있다는 것이에요.
이 때문에 일반화되거나 관련 없는 정보가 검색되는 경우가 많아서 대응 효율성이 떨어지기도 해요. 고급 RAG 전략은 데이터를 더 의미 있는 단위로 나눠서 상황을 인식하고, 개념적으로 정확한 대상 검색을 가능하게 해서 이런 문제를 해결하죠.
1. 일반적인 RAG:
– 인덱싱된 정확한 데이터가 검색된 데이터인 기존 방식이에요.
2. 부모 리트리버:
– 전체 문서를 인덱싱하는 대신, 데이터는 상위 및 하위 문서라고 하는 더 작은 덩어리로 나뉘어요.
– 하위 문서는 특정 개념을 더 잘 표현하기 위해 인덱싱되고, 상위 문서는 컨텍스트 보존을 위해 검색돼요.
3. 가정적인 질문:
– 문서는 답변할 수 있는 잠재적인 질문을 생성하기 위해 처리돼요.
– 이러한 질문은 특정 개념을 더 잘 표현하기 위해 인덱싱되고, 상위 문서는 컨텍스트 유지를 보장하기 위해 검색돼요.
4. 요약:
– 전체 문서를 인덱싱하는 대신, 문서의 요약을 생성해서 인덱싱해요.
– 마찬가지로 상위 문서는 RAG 애플리케이션에서 검색돼요.
고급 검색 전략에 대해 더 자세히 알고 싶다면 이 블로그를 참고해 보세요.
이러한 전략은 특히 벡터 RAG 체인 구현에 통합되어 사용돼요.
LangChain 템플릿
LangChain 템플릿은 체인 및 에이전트의 생성 및 커스터마이징을 단순화하는 배포 가능한 참조 아키텍처 모음을 제공해요.
이 템플릿들은 LangServe와 쉽게 통합될 수 있도록 표준 형식으로 설계되었어요. 덕분에 프로덕션에 바로 사용할 수 있는 API를 빠르게 배포하고 모니터링(LangSmith 사용)할 수 있죠. 이 프레임워크는 개발 프로세스를 가속화하고, 더 많은 사용자가 AI 기능을 쉽게 공유하고, 유지 관리하고, 개선할 수 있도록 도와줘요.
예를 들어, 저는 neo4j-advanced-rag 템플릿을 사용해서 애플리케이션을 구축했어요. 이 템플릿을 사용하면 최근 LangChain에 통합된 고급 검색 전략을 구현해서 정확한 Vector Embedding과 컨텍스트 유지를 균형 있게 맞출 수 있답니다. 토마즈 브라타닉님 덕분이죠.
마찬가지로 제 템플릿, 특히 제 에이전트를 사용해서 이를 기반으로 구축하거나 기존 애플리케이션에 통합할 수도 있어요.
구현
여기서는 중요한 코드만 보여드리고, 중요하지 않은 코드는 추상화할 거예요. 제 레퍼런스 GitHub 저장소에서 전체 코드를 확인하고 LangServe에 배포할 수 있어요.
메인 방향의 agi/지식 그래프/neo4j-rag/my-app · sauravjoshi23/towards-agi
retriever.py
이 파일에는 다양한 검색 전략을 사용해서 Neo4j 벡터 인덱스에서 데이터를 검색하기 위한 코드가 들어 있어요. 이 코드는 체인에서 사용되는데, 프롬프트가 검색기(대화 기록 및 사용자 질문 포함)에 전달되면 특정 검색 전략의 인덱스에서 데이터를 검색하는 역할을 하죠.
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Neo4jVector
# Typical RAG retriever
typical_rag = Neo4jVector.from_existing_index(
OpenAIEmbeddings(), index_name="typical_rag"
)
# Parent retriever
parent_query = """
MATCH (node)<-[:HAS_CHILD]-(parent)
WITH parent, max(score) AS score // deduplicate parents
RETURN parent.text AS text, score, {} AS metadata LIMIT 1
"""
parent_vectorstore = Neo4jVector.from_existing_index(
OpenAIEmbeddings(),
index_name="parent_document",
retrieval_query=parent_query,
)
# Hypothetic questions retriever
hypothetic_question_query = """
MATCH (node)<-[:HAS_QUESTION]-(parent)
WITH parent, max(score) AS score // deduplicate parents
RETURN parent.text AS text, score, {} AS metadata
"""
hypothetic_question_vectorstore = Neo4jVector.from_existing_index(
OpenAIEmbeddings(),
index_name="hypothetical_questions",
retrieval_query=hypothetic_question_query,
)
# Summary retriever
summary_query = """
MATCH (node)<-[:HAS_SUMMARY]-(parent)
WITH parent, max(score) AS score // deduplicate parents
RETURN parent.text AS text, score, {} AS metadata
"""
summary_vectorstore = Neo4jVector.from_existing_index(
OpenAIEmbeddings(),
index_name="summary",
retrieval_query=summary_query,
)
History.py
이 파일에는 Neo4j Graph Database에 대화 기록을 저장하는 대화형 메모리 모듈이 있어요. 대화 메모리는 각 사용자 세션마다 고유하게 유지되어서 개인화된 상호 작용이 보장되죠. 이러한 함수를 하나로 병합하려고 했지만, index의 차이로 인해 이러한 함수에 대한 입력에 약간 복잡한 부분이 포함되었어요.
from typing import Any, Dict, List, Union
from langchain.graphs import Neo4jGraph
from langchain.memory import ChatMessageHistory
from langchain.schema import AIMessage, HumanMessage
graph = Neo4jGraph()
def convert_messages(input: List[Dict[str, Any]]) -> ChatMessageHistory:
history = ChatMessageHistory()
for item in input:
history.add_user_message(item["result"]["question"])
history.add_ai_message(item["result"]["answer"])
return history
def get_vector_history(input: Dict[str, Any]) -> List[Union[HumanMessage, AIMessage]]:
# Lookback conversation window
window = 3
data = graph.query(
"""
MATCH (u:User {id:$user_id})-[:HAS_SESSION]->(s:Session {id:$session_id}),
(s)-[:LAST_MESSAGE]->(last_message)
MATCH p=(last_message)<-[:NEXT*0.."""
+ str(window)
+ """]-()
WITH p, length(p) AS length
ORDER BY length DESC LIMIT 1
UNWIND reverse(nodes(p)) AS node
MATCH (node)-[:HAS_ANSWER]->(answer)
RETURN {question:node.text, answer:answer.text} AS result
""",
params=input,
)
history = convert_messages(data)
return history.messages
def save_vector_history(input: Dict[str, Any]) -> str:
input["context"] = [el.page_content for el in input["context"]]
# print(input)
has_history = bool(input.pop("chat_history"))
# store history to database
if has_history:
graph.query(
"""
MATCH (u:User {id: $user_id})-[:HAS_SESSION]->(s:Session{id: $session_id}),
(s)-[l:LAST_MESSAGE]->(last_message)
CREATE (last_message)-[:NEXT]->(q:Question
{text:$question, rephrased:$rephrased_question, date:datetime()}),
(q)-[:HAS_ANSWER]->(:Answer {text:$output}),
(s)-[:LAST_MESSAGE]->(q)
DELETE l
WITH q
UNWIND $context AS c
MATCH (n) WHERE elementId(n) = c
MERGE (q)-[:RETRIEVED]->(n)
""",
params=input,
)
else:
graph.query(
"""MERGE (u:User {id: $user_id})
CREATE (u)-[:HAS_SESSION]->(s1:Session {id:$session_id}),
(s1)-[:LAST_MESSAGE]->(q:Question
{text:$question, rephrased:$rephrased_question, date:datetime()}),
(q)-[:HAS_ANSWER]->(:Answer {text:$output})
WITH q
UNWIND $context AS c
MATCH (n) WHERE elementId(n) = c
MERGE (q)-[:RETRIEVED]->(n)
""",
params=input,
)
# Return LLM response to the chain
return input["output"]
def get_graph_history(input: Dict[str, Any]) -> ChatMessageHistory:
input.pop("question")
# Lookback conversation window
window = 3
data = graph.query(
"""
MATCH (u:User {id:$user_id})-[:HAS_SESSION]->(s:Session {id:$session_id}),
(s)-[:LAST_MESSAGE]->(last_message)
MATCH p=(last_message)<-[:NEXT*0.."""
+ str(window)
+ """]-()
WITH p, length(p) AS length
ORDER BY length DESC LIMIT 1
UNWIND reverse(nodes(p)) AS node
MATCH (node)-[:HAS_ANSWER]->(answer)
RETURN {question:node.text, answer:answer.text} AS result
""",
params=input,
)
history = convert_messages(data)
return history.messages
def save_graph_history(input):
input.pop("response")
# store history to database
graph.query(
"""MERGE (u:User {id: $user_id})
WITH u
OPTIONAL MATCH (u)-[:HAS_SESSION]->(s:Session{id: $session_id}),
(s)-[l:LAST_MESSAGE]->(last_message)
FOREACH (_ IN CASE WHEN last_message IS NULL THEN [1] ELSE [] END |
CREATE (u)-[:HAS_SESSION]->(s1:Session {id:$session_id}),
(s1)-[:LAST_MESSAGE]->(q:Question {text:$question, cypher:$query, date:datetime()}),
(q)-[:HAS_ANSWER]->(:Answer {text:$output}))
FOREACH (_ IN CASE WHEN last_message IS NOT NULL THEN [1] ELSE [] END |
CREATE (last_message)-[:NEXT]->(q:Question
{text:$question, cypher:$query, date:datetime()}),
(q)-[:HAS_ANSWER]->(:Answer {text:$output}),
(s)-[:LAST_MESSAGE]->(q)
DELETE l) """,
params=input,
)
# Return LLM response to the chain
return input["output"]
Neo4j_Vector.py
이 코드는 Neo4j 벡터 인덱스를 사용해서 RAG를 구현하는 LangChain의 독립형 템플릿이에요.
제 프로젝트에서는 이 체인을 결국 의사 결정을 위해 에이전트에게 전달되는 도구처럼 다루고 있어요. 이 도구에 대한 설명은 간단하죠. 'Dune에 대한 특정 컨텍스트를 검색하는 데 유용한 도구'라고요.
- 체인은 먼저 메모리에서 데이터를 검색하고 사용자 질문을 사용해서 CONDENSE_QUESTION_PROMPT 프롬프트 템플릿으로 원래 질문을 다시 표현해요.
- 다음으로, 변형된 질문을 사용해서 Neo4j 벡터 인덱스에서 데이터를 검색하고 이를 ANSWER_PROMPT에 컨텍스트로 전달하죠.
- 마지막으로 시스템 메시지 지침, 컨텍스트, 대화 기록, 사용자 질문이 포함된 ANSWER_PROMPT를 사용해서 상황에 맞는 답변을 출력하는 거예요.
from operator import itemgetter
from typing import Optional, Type
from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate, PromptTemplate, MessagesPlaceholder
from langchain.pydantic_v1 import BaseModel
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import ConfigurableField, RunnablePassthrough
from neo4j_advanced_rag.history import get_vector_history, save_vector_history
from langchain.tools import BaseTool
from pydantic import BaseModel, BaseSettings
from typing import Type, Any
from neo4j_advanced_rag.retrievers import (
hypothetic_question_vectorstore,
parent_vectorstore,
summary_vectorstore,
typical_rag,
)
class Question(BaseModel):
question: str
user_id: str
session_id: str
class VectorTool(BaseTool, BaseSettings):
name = "vector_tool"
description = "Useful Tool for retrieving specific context about Dune"
args_schema: Type[Question] = Question
def _run(self, question, user_id, session_id):
retriever = typical_rag.as_retriever().configurable_alternatives(
ConfigurableField(id="strategy"),
default_key="typical_rag",
parent_strategy=parent_vectorstore.as_retriever(),
hypothetical_questions=hypothetic_question_vectorstore.as_retriever(),
summary_strategy=summary_vectorstore.as_retriever(),
)
# Define LLM
llm = ChatOpenAI()
# Condense a chat history and follow-up question into a standalone question
condense_template = """Given the following conversation and a follow up question, rephrase the follow up question to be a standalone question, in its original language.
Make sure to include all the relevant information.
Chat History:
{chat_history}
Follow Up Input: {question}
Standalone question:""" # noqa: E501
CONDENSE_QUESTION_PROMPT = PromptTemplate.from_template(condense_template)
# RAG answer synthesis prompt
answer_template = """Answer the question based only on the following context:
<context>
{context}
</context>"""
ANSWER_PROMPT = ChatPromptTemplate.from_messages(
[
("system", answer_template),
MessagesPlaceholder(variable_name="chat_history"),
("user", "{question}"),
]
)
chain = (
RunnablePassthrough.assign(chat_history=get_vector_history)
| RunnablePassthrough.assign(rephrased_question=CONDENSE_QUESTION_PROMPT | llm | StrOutputParser())
| RunnablePassthrough.assign(context=itemgetter("rephrased_question") | retriever)
| RunnablePassthrough.assign(output=ANSWER_PROMPT | llm | StrOutputParser())
| save_vector_history
).with_types(input_type=Question)
return chain.invoke(
{
"question": question,
"user_id": user_id,
"session_id": session_id,
},
{"configurable": {"strategy": "typical_rag"}} #todo
)
Neo4j_cypher.py
이 도구에 사용된 설명은 "Dune과 관련된 구조적, 상호 연결성 및 관계형 지식을 검색하는 데 유용한 도구"랍니다.
하지만 체인에서 흥미로운 구성 요소가 하나 있어요. 바로 CypherQueryCorrector인데요, 이건 기본적으로 주어진 스키마를 기반으로 Cypher 문의 관계 방향을 검증하고 수정하는 역할을 해요.
from langchain.chains.graph_qa.cypher_utils import CypherQueryCorrector, Schema
from langchain.chat_models import ChatOpenAI
from langchain.graphs import Neo4jGraph
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.pydantic_v1 import BaseModel
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import RunnablePassthrough
from typing import Type
from neo4j_advanced_rag.history import get_graph_history, save_graph_history
from langchain.tools import BaseTool
class Question(BaseModel):
question: str
user_id: str
session_id: str
class GraphTool(BaseTool):
name = "graph_tool"
description = "Useful Tool for retrieving structural, interconnected and relational knowledge related to Dune"
args_schema: Type[Question] = Question
def _run(self, question, user_id, session_id):
# Connection to Neo4j
graph = Neo4jGraph()
# Cypher validation tool for relationship directions
corrector_schema = [
Schema(el["start"], el["type"], el["end"])
for el in graph.structured_schema.get("relationships")
]
cypher_validation = CypherQueryCorrector(corrector_schema)
# LLMs
cypher_llm = ChatOpenAI(model_name="gpt-4", temperature=0.0)
qa_llm = ChatOpenAI(model_name="gpt-3.5-turbo", temperature=0.0)
# Generate Cypher statement based on natural language input
cypher_template = """This is important for my career.
Based on the Neo4j graph schema below, write a Cypher query that would answer the user's question:
{schema}
Question: {question}
Cypher query:""" # noqa: E501
cypher_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"Given an input question, convert it to a Cypher query. No pre-amble.",
),
MessagesPlaceholder(variable_name="history"),
("human", cypher_template),
]
)
cypher_response = (
RunnablePassthrough.assign(schema=lambda _: graph.get_schema, history=get_graph_history)
| cypher_prompt
| cypher_llm.bind(stop=["nCypherResult:"])
| StrOutputParser()
)
# Generate natural language response based on database results
response_template = """Based on the the question, Cypher query, and Cypher response, write a natural language response:
Question: {question}
Cypher query: {query}
Cypher Response: {response}""" # noqa: E501
response_prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"Given an input question and Cypher response, convert it to a "
"natural language answer. No pre-amble.",
),
("human", response_template),
]
)
chain = (
RunnablePassthrough.assign(query=cypher_response)
| RunnablePassthrough.assign(
response=lambda x: graph.query(cypher_validation(x["query"])),
)
| RunnablePassthrough.assign(
output=response_prompt | qa_llm | StrOutputParser(),
)
| save_graph_history
).with_types(input_type=Question)
return chain.invoke(
{
"question": question,
"user_id": user_id,
"session_id": session_id,
}
)
Agent.py
이제 vector와 graph 도구를 정의했으니, agent 정의가 간단해졌어요.
저희는 OpenAI의 효율적인 함수 호출 기능을 활용하기 위해 이러한 도구를 OpenAI 함수로 변환할 거예요. 함수 호출은 Pydantic 클래스를 전달하고 구조화된 출력을 반환할 수 있다는 점에서 정말 흥미로운 기능이죠. 이는 추가 처리에 훨씬 더 좋아요 (LLM이 매번 다른 구조로 출력을 반환한다고 상상해 보세요! 처리하기가 정말 어렵겠죠?).
from pydantic import BaseModel, Field
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain.pydantic_v1 import BaseModel
from langchain.chat_models import ChatOpenAI
from langchain.agents import AgentExecutor
from langchain.tools.render import format_tool_to_openai_function
from langchain.agents.format_scratchpad import format_to_openai_function_messages
from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser
from langchain.tools import tool
from langchain.agents import AgentExecutor
from neo4j_advanced_rag.neo4j_vector import VectorTool
from neo4j_advanced_rag.neo4j_cypher import GraphTool
from langchain.schema.runnable import ConfigurableField
from neo4j_advanced_rag.retrievers import (
hypothetic_question_vectorstore,
parent_vectorstore,
summary_vectorstore,
typical_rag,
)
class AgentInput(BaseModel):
input: str
user_id: str
session_id: str
llm = ChatOpenAI()
vector_tool = VectorTool()
graph_tool = GraphTool()
tools = [vector_tool, graph_tool]
llm_with_tools = llm.bind(functions=[format_tool_to_openai_function(t) for t in tools])
assistant_system_message = """You are a helpful assistant.
Use one of the tools provided to you if necessary."""
prompt = ChatPromptTemplate.from_messages(
[
("system", assistant_system_message),
("user", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
]
)
agent = (
{
"input": lambda x: x["input"],
"user_id": lambda x: x["user_id"],
"session_id": lambda x: x["session_id"],
"agent_scratchpad": lambda x: format_to_openai_function_messages(
x["intermediate_steps"]
)
}
| prompt
| llm_with_tools
| OpenAIFunctionsAgentOutputParser()
)
agent_executor = AgentExecutor(agent=agent, tools=tools).with_types(input_type=AgentInput)
agent_executor = agent_executor | (lambda x: x["output"])
결과
애플리케이션을 실행하고 LangServe에서 실행되는 것을 보려면 다음을 실행하세요. langchain serve 명령을 프로젝트 디렉토리의 루트에서 실행하면 돼요.
이 접근 방식은 vector 및 graph index에 다양한 데이터가 저장되어 있거나, agent가 호출할 도구에 대한 훌륭한 설명이 있는 경우 효과적일 수 있어요. 다만, 비슷한 프로젝트를 진행했을 때 빠르게 공감할 수 있도록 전체 프로젝트를 잘 이해하는 게 목표였어요.
데모 목적으로 도구 중 하나를 호출하기 위해 agent executor에게 두 가지 질문을 던져봤어요. 다음은 샘플 응답이에요.
How many tools have you been provided?
Can you please use this tool and answer what is the plot of Dune?
미래의 일
user_id와session_id를 agent에서 chain 도구에 인수로 전달하려고 열심히 노력했지만 실패했어요.- vector index에 대한 검색기의 동적 선택이 다시 여기에 있네요. 선택한 검색기를 vector 도구에 전달하는 것과 동일한 문제에 직면했어요.
요약
이 시스템은 고급 검색 전략을 사용하여 vector 및 graph database에서 추출된 정보의 정확성과 관련성을 향상시키죠. 이는 대화형 메모리 모듈을 갖추고 있어 각 사용자 상호 작용이 상황에 맞는 정보를 얻을 수 있도록 해줘요.
이러한 도구를 갖춘 agent는 query를 기반으로 사용할 검색 방법에 대해 정보에 입각한 결정을 내릴 수 있어요. 이 접근 방식은 특정 데이터 검색과 전체 context 유지 간의 균형을 최적화하는 데 도움이 되죠.
구현은 간단하며 다양한 데이터 유형에 대한 실용적인 유용성과 적응성에 중점을 둬요. 이 프로젝트는 AI 기반 데이터 검색 및 처리의 효율성과 정확성을 향상시키는 것을 목표로 하고 있어요.
참고 자료
- Neo4j로 고급 RAG 전략 구현
- LangChain 템플릿
- Neo4j 및 LangChain을 사용하여 구조화되지 않은 Knowledge Graph를 통합하는 향상된 QA
- 구조화되지 않은 텍스트에서 Knowledge Graph 구성
- OpenAI 기능을 사용하여 텍스트에서 Knowledge Graph 구성: Knowledge Graph를 활용하여 LangChain 애플리케이션 강화
- LangChain
- LangChain 에이전트
- OpenAI
에이치시스템즈의 LogTree는 Neo4j 기반 GraphRAG 플랫폼으로, 데이터를 자동으로 지식그래프화하고 자연어 질의로 즉시 답을 제공합니다.
'Agent AI' 카테고리의 다른 글
| 리테일 혁신: Neo4j, Oneture, 그리고 AWS로 구현하는 멀티 에이전트 스토어 어드바이저 (2) | 2026.04.14 |
|---|---|
| LangGraph와 MCP로 ReAct Agent, 초고속 구축하기 (1) | 2026.04.14 |
| Neo4j, GenAI에 1억 달러 투자 및 핵심 Agentic AI 혁신 출시 (0) | 2026.04.13 |
| Needle StarterKit 2.0: 템플릿, 챗봇, 그리고 더 많은 기능으로 시작하세요! (0) | 2026.04.13 |
| 에이전트 기억, 어떻게 모델링할까? (0) | 2026.04.13 |
