-
Notifications
You must be signed in to change notification settings - Fork 86
feat(llm): property embedding #240
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 22 commits
74cf3c5
e9941ad
d2e4e2e
c5ec3ea
12221c9
8343d3c
0036b2c
173b9b2
c9ef9ed
c933c58
263758b
8a0e6cd
a60887d
43cdcae
b4f1a51
142a44e
9f245a0
87cb1cf
961f582
4cbb47a
36116e6
5db4e0c
bf19a84
026c65c
f2ee374
0e98879
a3b864f
ac7e137
8ace5a8
a9f92c4
18744d2
89b2d47
f14087a
ef37855
c24d210
f1fdbdb
2953dbc
cf279f5
182ecba
b7e7425
52c40cb
10e76cd
923502a
739479a
9acaa96
86e6098
b27d925
765e93f
b5f31ff
50ec338
7d9d67c
c918e73
7b7260a
3754211
7a2cf2b
7b3e5e2
cb31b35
9778c37
cf6d2e4
eb5e9f1
e75f68f
4aa1a4d
7b7c6d2
31bf971
a0e460c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Licensed to the Apache Software Foundation (ASF) under one | ||
| # or more contributor license agreements. See the NOTICE file | ||
| # distributed with this work for additional information | ||
| # regarding copyright ownership. The ASF licenses this file | ||
| # to you under the Apache License, Version 2.0 (the | ||
| # "License"); you may not use this file except in compliance | ||
| # with the License. You may obtain a copy of the License at | ||
| # | ||
| # http://www.apache.org/licenses/LICENSE-2.0 | ||
| # | ||
| # Unless required by applicable law or agreed to in writing, | ||
| # software distributed under the License is distributed on an | ||
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
| # KIND, either express or implied. See the License for the | ||
| # specific language governing permissions and limitations | ||
| # under the License. | ||
|
|
||
|
|
||
| from datetime import date | ||
| from fastapi import status, APIRouter, HTTPException | ||
| from hugegraph_llm.utils.log import log | ||
|
|
||
| API_CALL_TRACKER = {} | ||
|
|
||
| # pylint: disable=too-many-statements | ||
| def vector_http_api( | ||
| router: APIRouter, | ||
| update_embedding_func, | ||
| ): | ||
| @router.post("/vector/embedding", status_code=status.HTTP_200_OK) | ||
| def update_embedding_api(daily_limit: int = 2): | ||
| today = date.today() | ||
| for call_date in list(API_CALL_TRACKER.keys()): | ||
| if call_date != today: | ||
| del API_CALL_TRACKER[call_date] | ||
| call_count = API_CALL_TRACKER.get(today, 0) | ||
| if call_count >= daily_limit: | ||
| log.error("Rate limit exceeded for update_vid_embedding. Maximum %d calls per day.", daily_limit) | ||
| raise HTTPException( | ||
| status_code=status.HTTP_429_TOO_MANY_REQUESTS, | ||
| detail=f"API call limit of {daily_limit} per day exceeded. Please try again tomorrow." | ||
| ) | ||
| API_CALL_TRACKER[today] = call_count + 1 | ||
| result = update_embedding_func() | ||
| return result |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,7 +53,7 @@ | |
| """ | ||
|
|
||
| PROPERTY_QUERY_NEIGHBOR_TPL = """\ | ||
| g.V().has('{prop}', within({keywords})) | ||
| g.V().has('{current_prop_name}', '{current_prop_value}') | ||
| .repeat( | ||
| bothE({edge_labels}).limit({edge_limit}).otherV().dedup() | ||
| ).times({max_deep}).emit() | ||
|
|
@@ -65,8 +65,8 @@ | |
| ) | ||
| .by(project('label', 'inV', 'outV', 'props') | ||
| .by(label()) | ||
| .by(inV().values('{prop}')) | ||
| .by(outV().values('{prop}')) | ||
| .by(inV().values('{current_prop_name}')) | ||
| .by(outV().values('{current_prop_name}')) | ||
| .by(valueMap().by(unfold())) | ||
| ) | ||
| .limit({max_items}) | ||
|
|
@@ -129,12 +129,13 @@ def run(self, context: Dict[str, Any]) -> Dict[str, Any]: | |
| def _gremlin_generate_query(self, context: Dict[str, Any]) -> Dict[str, Any]: | ||
| query = context["query"] | ||
| vertices = context.get("match_vids") | ||
| properties = context.get("match_props") | ||
| query_embedding = context.get("query_embedding") | ||
|
|
||
| self._gremlin_generator.clear() | ||
| self._gremlin_generator.example_index_query(num_examples=self._num_gremlin_generate_example) | ||
| gremlin_response = self._gremlin_generator.gremlin_generate_synthesize( | ||
| context["simple_schema"], vertices=vertices, gremlin_prompt=self._gremlin_prompt | ||
| context["simple_schema"], vertices=vertices, gremlin_prompt=self._gremlin_prompt, properties=properties | ||
| ).run(query=query, query_embedding=query_embedding) | ||
| if self._num_gremlin_generate_example > 0: | ||
| gremlin = gremlin_response["result"] | ||
|
|
@@ -160,12 +161,14 @@ def _gremlin_generate_query(self, context: Dict[str, Any]) -> Dict[str, Any]: | |
| def _subgraph_query(self, context: Dict[str, Any]) -> Dict[str, Any]: | ||
| # 1. Extract params from context | ||
| matched_vids = context.get("match_vids") | ||
| matched_props = context.get("match_props") | ||
| if isinstance(context.get("max_deep"), int): | ||
| self._max_deep = context["max_deep"] | ||
| if isinstance(context.get("max_items"), int): | ||
| self._max_items = context["max_items"] | ||
| if isinstance(context.get("prop_to_match"), str): | ||
| self._prop_to_match = context["prop_to_match"] | ||
| if isinstance(context.get("match_props"), list): | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Evidence: whenever |
||
| self._prop_to_match = matched_props[0][0] if matched_props else None | ||
| log.debug("Prop to match: %s", self._prop_to_match) | ||
|
|
||
| # 2. Extract edge_labels from graph schema | ||
| _, edge_labels = self._extract_labels_from_schema() | ||
|
|
@@ -207,31 +210,34 @@ def _subgraph_query(self, context: Dict[str, Any]) -> Dict[str, Any]: | |
| vertex_degree_list[0].update(vertex_knowledge) | ||
| else: | ||
| vertex_degree_list.append(vertex_knowledge) | ||
| else: | ||
| elif matched_props: | ||
| # WARN: When will the query enter here? | ||
| keywords = context.get("keywords") | ||
| assert keywords, "No related property(keywords) for graph query." | ||
| keywords_str = ",".join("'" + kw + "'" for kw in keywords) | ||
| gremlin_query = PROPERTY_QUERY_NEIGHBOR_TPL.format( | ||
| prop=self._prop_to_match, | ||
| keywords=keywords_str, | ||
| edge_labels=edge_labels_str, | ||
| edge_limit=edge_limit_amount, | ||
| max_deep=self._max_deep, | ||
| max_items=self._max_items, | ||
| ) | ||
| log.warning("Unable to find vid, downgraded to property query, please confirm if it meets expectation.") | ||
| graph_chain_knowledge = set() | ||
| for prop_name, prop_value in matched_props: | ||
| self._prop_to_match = prop_name | ||
| gremlin_query = PROPERTY_QUERY_NEIGHBOR_TPL.format( | ||
| current_prop_name=prop_name, | ||
| current_prop_value=prop_value, | ||
| edge_labels=edge_labels_str, | ||
| edge_limit=edge_limit_amount, | ||
| max_deep=self._max_deep, | ||
| max_items=self._max_items | ||
| ) | ||
| log.warning("Unable to find vid, downgraded to property query, please confirm if it meets expectation.") | ||
| log.debug("property gremlin: %s", gremlin_query) | ||
|
|
||
| paths: List[Any] = self._client.gremlin().exec(gremlin=gremlin_query)["data"] | ||
| graph_chain_knowledge, vertex_degree_list, knowledge_with_degree = self._format_graph_query_result( | ||
| query_paths=paths | ||
| ) | ||
| paths: List[Any] = self._client.gremlin().exec(gremlin=gremlin_query)["data"] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Evidence: this traversal starts from vertices with |
||
| log.debug("paths: %s", paths) | ||
| temp_graph_chain_knowledge, vertex_degree_list, knowledge_with_degree = self._format_graph_query_result( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Evidence: |
||
| query_paths=paths | ||
| ) | ||
| graph_chain_knowledge.update(temp_graph_chain_knowledge) | ||
|
|
||
| context["graph_result"] = list(graph_chain_knowledge) | ||
| if context["graph_result"]: | ||
| context["graph_result_flag"] = 0 | ||
| context["vertex_degree_list"] = [list(vertex_degree) for vertex_degree in vertex_degree_list] | ||
| context["knowledge_with_degree"] = knowledge_with_degree | ||
| context["knowledge_with_degree"] = knowledge_with_degree # pylint: disable=possibly-used-before-assignment | ||
| context["graph_context_head"] = ( | ||
| f"The following are graph knowledge in {self._max_deep} depth, e.g:\n" | ||
| "`vertexA--[links]-->vertexB<--[links]--vertexC ...`" | ||
|
|
@@ -340,7 +346,7 @@ def _process_vertex( | |
| node_str = matched_str | ||
| else: | ||
| v_cache.add(matched_str) | ||
| node_str = f"{item['id']}{{{props_str}}}" | ||
| node_str = f"{item['id']}{{{props_str}}}" if use_id_to_match else f"{item['props']}{{{props_str}}}" | ||
|
|
||
| flat_rel += node_str | ||
| nodes_with_degree.append(node_str) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.