langchain-ladybug 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ """langchain-ladybug: LangChain integration for the Ladybug graph database."""
2
+
3
+ from langchain_ladybug.chains.ladybug_qa import LadybugQAChain
4
+ from langchain_ladybug.graphs.ladybug_graph import LadybugGraph
5
+
6
+ __all__ = [
7
+ "LadybugGraph",
8
+ "LadybugQAChain",
9
+ ]
@@ -0,0 +1,13 @@
1
+ """Chain and prompt classes for the Ladybug integration."""
2
+
3
+ from langchain_ladybug.chains.ladybug_qa import LadybugQAChain
4
+ from langchain_ladybug.chains.prompts import (
5
+ CYPHER_QA_PROMPT,
6
+ LADYBUG_GENERATION_PROMPT,
7
+ )
8
+
9
+ __all__ = [
10
+ "LadybugQAChain",
11
+ "CYPHER_QA_PROMPT",
12
+ "LADYBUG_GENERATION_PROMPT",
13
+ ]
@@ -0,0 +1,217 @@
1
+ """Question answering over a graph."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any, Dict, List, Optional
7
+
8
+ from langchain_classic.chains.base import Chain
9
+ from langchain_classic.chains.llm import LLMChain
10
+ from langchain_core.callbacks import CallbackManagerForChainRun
11
+ from langchain_core.language_models import BaseLanguageModel
12
+ from langchain_core.prompts import BasePromptTemplate
13
+ from pydantic import Field
14
+
15
+ from langchain_ladybug.chains.prompts import CYPHER_QA_PROMPT, LADYBUG_GENERATION_PROMPT
16
+ from langchain_ladybug.graphs.ladybug_graph import LadybugGraph
17
+
18
+
19
+ def remove_prefix(text: str, prefix: str) -> str:
20
+ """Remove a prefix from a text string.
21
+
22
+ Args:
23
+ text: Text to remove the prefix from.
24
+ prefix: Prefix to remove from the text.
25
+
26
+ Returns:
27
+ Text with the prefix removed, or the original text if it does not start
28
+ with the prefix.
29
+ """
30
+ if text.startswith(prefix):
31
+ return text[len(prefix):]
32
+ return text
33
+
34
+
35
+ def extract_cypher(text: str) -> str:
36
+ """Extract Cypher code from a text string.
37
+
38
+ Looks for Cypher code enclosed in triple backticks.
39
+
40
+ Args:
41
+ text: Text to extract Cypher code from.
42
+
43
+ Returns:
44
+ Cypher code extracted from the first triple-backtick block, or the
45
+ original text if no such block is found.
46
+ """
47
+ pattern = r"```(.*?)```"
48
+ matches = re.findall(pattern, text, re.DOTALL)
49
+ return matches[0] if matches else text
50
+
51
+
52
+ class LadybugQAChain(Chain):
53
+ """Question-answering against a graph by generating Cypher statements for Ladybug.
54
+
55
+ *Security note*: Make sure that the database connection uses credentials
56
+ that are narrowly-scoped to only include necessary permissions.
57
+ Failure to do so may result in data corruption or loss, since the calling
58
+ code may attempt commands that would result in deletion, mutation
59
+ of data if appropriately prompted or reading sensitive data if such
60
+ data is present in the database.
61
+ The best way to guard against such negative outcomes is to (as appropriate)
62
+ limit the permissions granted to the credentials used with this tool.
63
+
64
+ See https://python.langchain.com/docs/security for more information.
65
+ """
66
+
67
+ graph: LadybugGraph = Field(exclude=True)
68
+ cypher_generation_chain: LLMChain
69
+ qa_chain: LLMChain
70
+ input_key: str = "query"
71
+ output_key: str = "result"
72
+
73
+ allow_dangerous_requests: bool = False
74
+ """Forced user opt-in to acknowledge that the chain can make dangerous requests.
75
+
76
+ *Security note*: Make sure that the database connection uses credentials
77
+ that are narrowly-scoped to only include necessary permissions.
78
+ Failure to do so may result in data corruption or loss, since the calling
79
+ code may attempt commands that would result in deletion, mutation
80
+ of data if appropriately prompted or reading sensitive data if such
81
+ data is present in the database.
82
+ The best way to guard against such negative outcomes is to (as appropriate)
83
+ limit the permissions granted to the credentials used with this tool.
84
+
85
+ See https://python.langchain.com/docs/security for more information.
86
+ """
87
+
88
+ def __init__(self, **kwargs: Any) -> None:
89
+ """Initialize the chain.
90
+
91
+ Raises:
92
+ ValueError: If `allow_dangerous_requests` is not `True`.
93
+ """
94
+ if not kwargs.get("allow_dangerous_requests"):
95
+ raise ValueError(
96
+ "In order to use this chain, you must acknowledge that it can make "
97
+ "dangerous requests by setting `allow_dangerous_requests` to `True`. "
98
+ "You must narrowly scope the permissions of the database connection "
99
+ "to only include necessary permissions. Failure to do so may result "
100
+ "in data corruption or loss or reading sensitive data if such data is "
101
+ "present in the database. "
102
+ "Only use this chain if you understand the risks and have taken the "
103
+ "necessary precautions. "
104
+ "See https://python.langchain.com/docs/security for more information."
105
+ )
106
+ super().__init__(**kwargs)
107
+
108
+ @property
109
+ def input_keys(self) -> List[str]:
110
+ """Return the input keys."""
111
+ return [self.input_key]
112
+
113
+ @property
114
+ def output_keys(self) -> List[str]:
115
+ """Return the output keys."""
116
+ return [self.output_key]
117
+
118
+ @classmethod
119
+ def from_llm(
120
+ cls,
121
+ llm: Optional[BaseLanguageModel] = None,
122
+ *,
123
+ qa_prompt: BasePromptTemplate = CYPHER_QA_PROMPT,
124
+ cypher_prompt: BasePromptTemplate = LADYBUG_GENERATION_PROMPT,
125
+ cypher_llm: Optional[BaseLanguageModel] = None,
126
+ qa_llm: Optional[BaseLanguageModel] = None,
127
+ **kwargs: Any,
128
+ ) -> LadybugQAChain:
129
+ """Initialize from an LLM.
130
+
131
+ Args:
132
+ llm: A language model to use for both Cypher generation and QA when
133
+ separate models are not provided.
134
+ qa_prompt: Prompt template for the QA step.
135
+ cypher_prompt: Prompt template for the Cypher generation step.
136
+ cypher_llm: A language model dedicated to Cypher generation. If
137
+ provided, `llm` is used only for QA unless `qa_llm` is also given.
138
+ qa_llm: A language model dedicated to the QA step. If provided,
139
+ `llm` is used only for Cypher generation unless `cypher_llm` is
140
+ also given.
141
+ **kwargs: Additional keyword arguments passed to the chain constructor.
142
+
143
+ Returns:
144
+ A configured `LadybugQAChain` instance.
145
+
146
+ Raises:
147
+ ValueError: If neither `llm` nor `cypher_llm` is provided, neither
148
+ `llm` nor `qa_llm` is provided, or all three of `llm`,
149
+ `cypher_llm`, and `qa_llm` are provided simultaneously.
150
+ """
151
+ if not cypher_llm and not llm:
152
+ raise ValueError("Either `llm` or `cypher_llm` parameters must be provided")
153
+ if not qa_llm and not llm:
154
+ raise ValueError(
155
+ "Either `llm` or `qa_llm` parameters must be provided along with"
156
+ " `cypher_llm`"
157
+ )
158
+ if cypher_llm and qa_llm and llm:
159
+ raise ValueError(
160
+ "You can specify up to two of 'cypher_llm', 'qa_llm'"
161
+ ", and 'llm', but not all three simultaneously."
162
+ )
163
+
164
+ qa_chain = LLMChain(
165
+ llm=qa_llm or llm, # type: ignore[arg-type]
166
+ prompt=qa_prompt,
167
+ )
168
+ cypher_generation_chain = LLMChain(
169
+ llm=cypher_llm or llm, # type: ignore[arg-type]
170
+ prompt=cypher_prompt,
171
+ )
172
+
173
+ return cls(
174
+ qa_chain=qa_chain,
175
+ cypher_generation_chain=cypher_generation_chain,
176
+ **kwargs,
177
+ )
178
+
179
+ def _call(
180
+ self,
181
+ inputs: Dict[str, Any],
182
+ run_manager: Optional[CallbackManagerForChainRun] = None,
183
+ ) -> Dict[str, str]:
184
+ """Generate a Cypher statement, query the graph, and answer the question.
185
+
186
+ Args:
187
+ inputs: Dictionary containing the input question under `input_key`.
188
+ run_manager: Optional callback manager for tracing and logging.
189
+
190
+ Returns:
191
+ Dictionary with the answer under `output_key`.
192
+ """
193
+ _run_manager = run_manager or CallbackManagerForChainRun.get_noop_manager()
194
+ callbacks = _run_manager.get_child()
195
+ question = inputs[self.input_key]
196
+
197
+ generated_cypher = self.cypher_generation_chain.run(
198
+ {"question": question, "schema": self.graph.get_schema}, callbacks=callbacks
199
+ )
200
+ generated_cypher = remove_prefix(extract_cypher(generated_cypher), "cypher")
201
+
202
+ _run_manager.on_text("Generated Cypher:", end="\n", verbose=self.verbose)
203
+ _run_manager.on_text(
204
+ generated_cypher, color="green", end="\n", verbose=self.verbose
205
+ )
206
+ context = self.graph.query(generated_cypher)
207
+
208
+ _run_manager.on_text("Full Context:", end="\n", verbose=self.verbose)
209
+ _run_manager.on_text(
210
+ str(context), color="green", end="\n", verbose=self.verbose
211
+ )
212
+
213
+ result = self.qa_chain(
214
+ {"question": question, "context": context},
215
+ callbacks=callbacks,
216
+ )
217
+ return {self.output_key: result[self.qa_chain.output_key]}
@@ -0,0 +1,53 @@
1
+ # flake8: noqa
2
+ from langchain_core.prompts.prompt import PromptTemplate
3
+
4
+ CYPHER_GENERATION_TEMPLATE = """Task:Generate Cypher statement to query a graph database.
5
+ Instructions:
6
+ Use only the provided relationship types and properties in the schema.
7
+ Do not use any other relationship types or properties that are not provided.
8
+ Schema:
9
+ {schema}
10
+ Note: Do not include any explanations or apologies in your responses.
11
+ Do not respond to any questions that might ask anything else than for you to construct a Cypher statement.
12
+ Do not include any text except the generated Cypher statement.
13
+
14
+ The question is:
15
+ {question}"""
16
+
17
+ LADYBUG_EXTRA_INSTRUCTIONS = """
18
+ Instructions:
19
+ Generate the Ladybug dialect of Cypher with the following rules in mind:
20
+ 1. Do not omit the relationship pattern. Always use `()-[]->()` instead of `()->()`.
21
+ 2. Do not include triple backticks ``` in your response. Return only Cypher.
22
+ 3. Do not return any notes or comments in your response.
23
+ \n"""
24
+
25
+ LADYBUG_GENERATION_TEMPLATE = CYPHER_GENERATION_TEMPLATE.replace(
26
+ "Generate Cypher", "Generate Ladybug Cypher"
27
+ ).replace("Instructions:", LADYBUG_EXTRA_INSTRUCTIONS)
28
+
29
+ LADYBUG_GENERATION_PROMPT = PromptTemplate(
30
+ input_variables=["schema", "question"], template=LADYBUG_GENERATION_TEMPLATE
31
+ )
32
+
33
+ CYPHER_QA_TEMPLATE = """You are an assistant that helps to form nice and human understandable answers.
34
+ The information part contains the provided information that you must use to construct an answer.
35
+ The provided information is authoritative, you must never doubt it or try to use your internal knowledge to correct it.
36
+ Make the answer sound as a response to the question. Do not mention that you based the result on the given information.
37
+ Here is an example:
38
+
39
+ Question: Which managers own Neo4j stocks?
40
+ Context:[manager:CTL LLC, manager:JANE STREET GROUP LLC]
41
+ Helpful Answer: CTL LLC, JANE STREET GROUP LLC owns Neo4j stocks.
42
+
43
+ Follow this example when generating answers.
44
+ If the provided information is empty, say that you don't know the answer.
45
+ Information:
46
+ {context}
47
+
48
+ Question: {question}
49
+ Helpful Answer:"""
50
+
51
+ CYPHER_QA_PROMPT = PromptTemplate(
52
+ input_variables=["context", "question"], template=CYPHER_QA_TEMPLATE
53
+ )
@@ -0,0 +1,11 @@
1
+ """Graph classes for the Ladybug integration."""
2
+
3
+ from langchain_community.graphs.graph_document import GraphDocument, Node, Relationship
4
+ from langchain_ladybug.graphs.ladybug_graph import LadybugGraph
5
+
6
+ __all__ = [
7
+ "LadybugGraph",
8
+ "GraphDocument",
9
+ "Node",
10
+ "Relationship",
11
+ ]
@@ -0,0 +1,258 @@
1
+ from hashlib import md5
2
+ from typing import Any, Dict, List, Tuple
3
+
4
+ from langchain_community.graphs.graph_document import GraphDocument, Relationship
5
+
6
+
7
+ class LadybugGraph:
8
+ """Ladybug wrapper for graph operations.
9
+
10
+ *Security note*: Make sure that the database connection uses credentials
11
+ that are narrowly-scoped to only include necessary permissions.
12
+ Failure to do so may result in data corruption or loss, since the calling
13
+ code may attempt commands that would result in deletion, mutation
14
+ of data if appropriately prompted or reading sensitive data if such
15
+ data is present in the database.
16
+ The best way to guard against such negative outcomes is to (as appropriate)
17
+ limit the permissions granted to the credentials used with this tool.
18
+
19
+ See https://python.langchain.com/docs/security for more information.
20
+ """
21
+
22
+ def __init__(
23
+ self, db: Any, database: str = "ladybug", allow_dangerous_requests: bool = False
24
+ ) -> None:
25
+ """Initializes the Ladybug graph database connection.
26
+
27
+ Args:
28
+ db: The Ladybug database instance.
29
+ database: The name of the database.
30
+ allow_dangerous_requests: Must be set to `True` to acknowledge that
31
+ this class can execute arbitrary queries on the database.
32
+
33
+ Raises:
34
+ ValueError: If `allow_dangerous_requests` is not `True`, or if the
35
+ `ladybug` package is not installed.
36
+ """
37
+ if allow_dangerous_requests is not True:
38
+ raise ValueError(
39
+ "The LadybugGraph class is a powerful tool that can be used to execute "
40
+ "arbitrary queries on the database. To enable this functionality, "
41
+ "set the `allow_dangerous_requests` parameter to `True` when "
42
+ "constructing the LadybugGraph object."
43
+ )
44
+
45
+ try:
46
+ import ladybug as lb
47
+ except ImportError as e:
48
+ raise ImportError(
49
+ "Could not import Ladybug python package. "
50
+ "Please install Ladybug with `pip install ladybug`."
51
+ ) from e
52
+
53
+ self.db = db
54
+ self.conn = lb.Connection(self.db)
55
+ self.database = database
56
+ self.refresh_schema()
57
+
58
+ @property
59
+ def get_schema(self) -> str:
60
+ """Returns the schema of the Ladybug database."""
61
+ return self.schema
62
+
63
+ def query(self, query: str, params: dict = {}) -> List[Dict[str, Any]]:
64
+ """Query the Ladybug database.
65
+
66
+ Args:
67
+ query: The Cypher query string to execute.
68
+ params: Optional parameter bindings for the query.
69
+
70
+ Returns:
71
+ A list of result rows, each represented as a dict of column name to value.
72
+ """
73
+ result = self.conn.execute(query, params)
74
+ column_names = result.get_column_names()
75
+ return_list = []
76
+ while result.has_next():
77
+ row = result.get_next()
78
+ return_list.append(dict(zip(column_names, row)))
79
+ return return_list
80
+
81
+ def refresh_schema(self) -> None:
82
+ """Refreshes the Ladybug graph schema information."""
83
+ node_properties = []
84
+ node_table_names = self.conn._get_node_table_names()
85
+ for table_name in node_table_names:
86
+ current_table_schema = {"properties": [], "label": table_name}
87
+ properties = self.conn._get_node_property_names(table_name)
88
+ for property_name in properties:
89
+ property_type = properties[property_name]["type"]
90
+ list_type_flag = ""
91
+ if properties[property_name]["dimension"] > 0:
92
+ if "shape" in properties[property_name]:
93
+ for s in properties[property_name]["shape"]:
94
+ list_type_flag += f"[{s}]"
95
+ else:
96
+ for i in range(properties[property_name]["dimension"]):
97
+ list_type_flag += "[]"
98
+ property_type += list_type_flag
99
+ current_table_schema["properties"].append(
100
+ (
101
+ property_name,
102
+ property_type,
103
+ )
104
+ )
105
+ node_properties.append(current_table_schema)
106
+
107
+ relationships = []
108
+ rel_tables = self.conn._get_rel_table_names()
109
+ for table in rel_tables:
110
+ relationships.append(
111
+ f"(:{table['src']})-[:{table['name']}]->(:{table['dst']})"
112
+ )
113
+
114
+ rel_properties = []
115
+ for table in rel_tables:
116
+ table_name = table["name"]
117
+ current_table_schema = {"properties": [], "label": table_name}
118
+ query_result = self.conn.execute(
119
+ f"CALL table_info('{table_name}') RETURN *;"
120
+ )
121
+ while query_result.has_next():
122
+ row = query_result.get_next()
123
+ prop_name = row[1]
124
+ prop_type = row[2]
125
+ current_table_schema["properties"].append((prop_name, prop_type))
126
+ rel_properties.append(current_table_schema)
127
+
128
+ self.schema = (
129
+ f"Node properties: {node_properties}\n"
130
+ f"Relationships properties: {rel_properties}\n"
131
+ f"Relationships: {relationships}\n"
132
+ )
133
+
134
+ def _create_chunk_node_table(self) -> None:
135
+ self.conn.execute(
136
+ """
137
+ CREATE NODE TABLE IF NOT EXISTS Chunk (
138
+ id STRING,
139
+ text STRING,
140
+ type STRING,
141
+ PRIMARY KEY(id)
142
+ );
143
+ """
144
+ )
145
+
146
+ def _create_entity_node_table(self, node_label: str) -> None:
147
+ self.conn.execute(
148
+ f"""
149
+ CREATE NODE TABLE IF NOT EXISTS {node_label} (
150
+ id STRING,
151
+ type STRING,
152
+ PRIMARY KEY(id)
153
+ );
154
+ """
155
+ )
156
+
157
+ def _create_entity_relationship_table(self, rel: Relationship) -> None:
158
+ self.conn.execute(
159
+ f"""
160
+ CREATE REL TABLE IF NOT EXISTS {rel.type} (
161
+ FROM {rel.source.type} TO {rel.target.type}
162
+ );
163
+ """
164
+ )
165
+
166
+ def add_graph_documents(
167
+ self,
168
+ graph_documents: List[GraphDocument],
169
+ allowed_relationships: List[Tuple[str, str, str]],
170
+ include_source: bool = False,
171
+ ) -> None:
172
+ """Add a list of `GraphDocument` objects to the Ladybug graph.
173
+
174
+ Args:
175
+ graph_documents: A list of `GraphDocument` objects that contain the
176
+ nodes and relationships to be added to the graph.
177
+ allowed_relationships: A list of allowed relationships in the graph.
178
+ Each tuple contains ``(source_node_type, relationship_type,
179
+ target_node_type)``. Required for Ladybug because the relationship
180
+ table names must pre-exist and are derived from these tuples.
181
+ include_source: If `True`, stores the source document and links it to
182
+ nodes in the graph via the `MENTIONS` relationship. Merges source
183
+ documents by the `id` field in source metadata when available;
184
+ otherwise uses the MD5 hash of `page_content`.
185
+ """
186
+ node_labels = list(
187
+ {node.type for document in graph_documents for node in document.nodes}
188
+ )
189
+
190
+ for document in graph_documents:
191
+ if include_source:
192
+ self._create_chunk_node_table()
193
+ if not document.source.metadata.get("id"):
194
+ document.source.metadata["id"] = md5(
195
+ document.source.page_content.encode("utf-8")
196
+ ).hexdigest()
197
+
198
+ self.conn.execute(
199
+ f"""
200
+ MERGE (c:Chunk {{id: $id}})
201
+ SET c.text = $text,
202
+ c.type = "text_chunk"
203
+ """, # noqa: F541
204
+ parameters={
205
+ "id": document.source.metadata["id"],
206
+ "text": document.source.page_content,
207
+ },
208
+ )
209
+
210
+ for node_label in node_labels:
211
+ self._create_entity_node_table(node_label)
212
+
213
+ for node in document.nodes:
214
+ self.conn.execute(
215
+ f"""
216
+ MERGE (e:{node.type} {{id: $id}})
217
+ SET e.type = "entity"
218
+ """,
219
+ parameters={"id": node.id},
220
+ )
221
+ if include_source:
222
+ self._create_chunk_node_table()
223
+ ddl = "CREATE REL TABLE GROUP IF NOT EXISTS MENTIONS ("
224
+ table_names = list(
225
+ {f"FROM Chunk TO {label}" for label in node_labels}
226
+ )
227
+ ddl += ", ".join(table_names)
228
+ ddl += ", label STRING, triplet_source_id STRING)"
229
+ if ddl:
230
+ self.conn.execute(ddl)
231
+
232
+ if node.type in node_labels:
233
+ self.conn.execute(
234
+ f"""
235
+ MATCH (c:Chunk {{id: $id}}),
236
+ (e:{node.type} {{id: $node_id}})
237
+ MERGE (c)-[m:MENTIONS]->(e)
238
+ SET m.triplet_source_id = $id
239
+ """,
240
+ parameters={
241
+ "id": document.source.metadata["id"],
242
+ "node_id": node.id,
243
+ },
244
+ )
245
+
246
+ for rel in document.relationships:
247
+ self._create_entity_relationship_table(rel)
248
+ self.conn.execute(
249
+ f"""
250
+ MATCH (e1:{rel.source.type} {{id: $source_id}}),
251
+ (e2:{rel.target.type} {{id: $target_id}})
252
+ MERGE (e1)-[:{rel.type}]->(e2)
253
+ """,
254
+ parameters={
255
+ "source_id": rel.source.id,
256
+ "target_id": rel.target.id,
257
+ },
258
+ )
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-ladybug
3
+ Version: 0.3.0
4
+ Summary: LangChain integration for the Ladybug graph database.
5
+ Project-URL: Homepage, https://github.com/stevereiner/langchain-ladybug
6
+ Project-URL: Source, https://github.com/stevereiner/langchain-ladybug
7
+ License: MIT
8
+ Requires-Python: <4.0.0,>=3.10.0
9
+ Requires-Dist: ladybug>=0.16.1
10
+ Requires-Dist: langchain-classic<2.0.0,>=1.0.0
11
+ Requires-Dist: langchain-community<1.0.0,>=0.4.1
12
+ Requires-Dist: langchain-core<2.0.0,>=1.0.1
13
+ Requires-Dist: pydantic<3.0.0,>=2.0.0
14
+ Provides-Extra: dev
15
+ Requires-Dist: langchain-core; extra == 'dev'
16
+ Provides-Extra: lint
17
+ Requires-Dist: ruff<1.0.0,>=0.13.1; extra == 'lint'
18
+ Provides-Extra: test
19
+ Requires-Dist: langchain-tests<2.0.0,>=1.0.0; extra == 'test'
20
+ Requires-Dist: pytest-asyncio<1.0.0,>=0.20.3; extra == 'test'
21
+ Requires-Dist: pytest-cov<7.0.0,>=6.2.1; extra == 'test'
22
+ Requires-Dist: pytest-mock<4.0.0,>=3.10.0; extra == 'test'
23
+ Requires-Dist: pytest<9.0.0,>=8.4.1; extra == 'test'
24
+ Provides-Extra: typing
25
+ Requires-Dist: langchain-core; extra == 'typing'
26
+ Requires-Dist: mypy<2.0.0,>=1.17.1; extra == 'typing'
27
+ Description-Content-Type: text/markdown
28
+
29
+ # langchain-ladybug
30
+
31
+ LangChain integration for the [Ladybug](https://github.com/ladybug-ai/ladybug) graph database.
32
+
33
+ This package provides:
34
+
35
+ - **`LadybugGraph`** — a LangChain graph wrapper for Ladybug that supports schema introspection, Cypher queries, and document ingestion via `add_graph_documents`.
36
+ - **`LadybugQAChain`** — a question-answering chain that generates Ladybug-dialect Cypher queries from natural language and returns answers grounded in the graph.
37
+ - Prompt templates (`LADYBUG_GENERATION_PROMPT`, `CYPHER_QA_PROMPT`) tuned for the Ladybug Cypher dialect.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ uv pip install langchain-ladybug
43
+ ```
44
+
45
+ The `ladybug` Python package must also be installed:
46
+
47
+ ```bash
48
+ uv pip install ladybug
49
+ ```
50
+
51
+ ## Quick Start
52
+
53
+ ```python
54
+ import ladybug as lb
55
+ from langchain_ladybug import LadybugGraph, LadybugQAChain
56
+ from langchain_openai import ChatOpenAI
57
+
58
+ db = lb.Database("/path/to/my.db")
59
+
60
+ graph = LadybugGraph(db)
61
+
62
+ llm = ChatOpenAI(model="gpt-4o", temperature=0)
63
+
64
+ chain = LadybugQAChain.from_llm(
65
+ llm=llm,
66
+ graph=graph,
67
+ )
68
+
69
+ answer = chain.invoke({"query": "Who acted in The Godfather?"})
70
+ print(answer["result"])
71
+ ```
72
+
73
+ ## Development
74
+
75
+ ```bash
76
+ # Install dependencies
77
+ uv sync
78
+
79
+ # Run unit tests
80
+ make test
81
+
82
+ # Lint
83
+ make lint
84
+
85
+ # Format
86
+ make format
87
+ ```
88
+
89
+ ## Acknowledgements
90
+
91
+ Started from the Kuzu → Ladybug LangChain support port by [@adsharma](https://github.com/adsharma) ([PR #438](https://github.com/langchain-ai/langchain-community/pull/438)) — a proposed LadybugDB (formerly Kuzu) integration into the upstream langchain-community repo
92
+
93
+
94
+ ## Project Structure
95
+
96
+ ```
97
+ langchain_ladybug/
98
+ ├── graphs/
99
+ │ ├── graph_document.py # Node, Relationship, GraphDocument data classes
100
+ │ └── ladybug_graph.py # LadybugGraph wrapper
101
+ └── chains/
102
+ ├── prompts.py # Ladybug-dialect Cypher prompt templates
103
+ └── ladybug_qa.py # LadybugQAChain
104
+ tests/
105
+ ├── unit_tests/ # No network calls
106
+ └── integration_tests/ # Requires ladybug
107
+ ```
@@ -0,0 +1,9 @@
1
+ langchain_ladybug/__init__.py,sha256=wOcKWn4j43o3svAsMSLND8QHC-h4yw_DYKc7wurMmLU,273
2
+ langchain_ladybug/chains/__init__.py,sha256=7qAERP7NPj2uX3A_7HYJMUCs1fAnG93kepbzDVrvFWI,333
3
+ langchain_ladybug/chains/ladybug_qa.py,sha256=MxX_qPOALz4t74Kg58IpCndEuWj-qwnHdUeV32PxsKg,8668
4
+ langchain_ladybug/chains/prompts.py,sha256=I_EMxMAas7Z7aWBytFxUPtY-BJaiXQqqBMIiKO5Y2IM,2235
5
+ langchain_ladybug/graphs/__init__.py,sha256=Vnl7_uaAp0R_ou_kWQLfLMzSSNlVubsbOH7BpRDG6sg,301
6
+ langchain_ladybug/graphs/ladybug_graph.py,sha256=bAiz0dfUZYMAP7sRUeCIzR0a74p-n78VoPyn2GX2moc,10509
7
+ langchain_ladybug-0.3.0.dist-info/METADATA,sha256=uCuaKcHOAkSbNfjaKkiVUyr8vwlNkCc_lW4Y89K6HZw,3183
8
+ langchain_ladybug-0.3.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
9
+ langchain_ladybug-0.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any