Галоўная / Артыкулы / Практычныя прытамулі: Майстэрства Neo4j і LangChain4j: GraphRAG, стойкая памяць AI

Практычныя прытамулі: Майстэрства Neo4j і LangChain4j: GraphRAG, стойкая памяць AI

Практычныя прыказкі: Как аптэмураваць Neo4j і LangChain4j: GraphRAG, стойкая память AI: контракты, перакрыцці і слоты для коду для команд, якія викорыстоўваюць гэты патэрн.

5031 слоў

Наступныя прытамлівкі паказваюць практычны шлях для вывучэння курсу «Mastering Neo4j & LangChain4j: GraphRAG, Persistent AI Memory and more». Акцэнт ставіцца на контракты, пераконтроўкі і месца для коду, які можна легка заменіць, а не на мотывацыйныя аспекты. Калі працуеце над стадзіяй агляду, спачатку запісайце контракт: неабходныя вхідныя даны, сігнал успеху і тое, што вядзецца праз частковую нявыполненасць. Такі список пераканае вас робіць чыстыя змены ў кодзе пазней. Валідзіце маленькія, тэставаныя елементы заместо велікіх скрыптав. Калі якаясь ступеня не выйшла, прычына нявыполненасці должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны процес.

<dependencies>
  <dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-community-neo4j-retriever</artifactId>
    <version>${langchain.version}</version>
  </dependency>
  <dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j</artifactId>
    <version>${langchain.version}</version>
  </dependency>
  <dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-community-neo4j</artifactId>
    <version>${langchain.version}</version>
  </dependency>
  <!-- other deps -->
</dependencies>

Дынамічная абстракцыя схемы: Neo4jGraph

Этап Neo4jGraph з дынамічным абстракцыяй схемы працюе найкраща, калі яго розглядаюць як меравальную плошчу. Запісаўце адна ідеальная транскрыпцыя, адзін прыклад неудачы і прыметку па анулюванні змян перад расшырэнням масштаба. Разглядзіце этап як кантракт межа вхіднымі даннымі і перакананымі выходнымі рэзультатамі. Даўце назвы артыфактам, задаце критэрыя успеху і не падпісваюцеся на тыхое часткова завершэння задання. Раздзеліце політыку часткавага оброблэння дадзеных ад політыкі ўтрымання іх. Змена адной з яных не должна прымусіваць перапісванне другой, калі зменяюцца паказнікі якосці.

Атрыманне схемы за аднам запитам

Этап адзыявання схемы за дапамою аднае запыткі працуе наяўней калі яго расследжваюць як мэтрычны показнік. Запісайце адна ідеальная транскрыпцыю, адзін прыклад неудачы і прыметку па абратанні змян перш чым расширваць сферу дзеяння. Запісвайце часы выканання і кост токеноў або запыткаў праза функцыйнае рэзультаты. Відразлівая візуабельнасць костаў з самага пачатку запобегае неспакойным рахункам, калі процес пераходзіць з дэмовай среды ў спакульную. Раздзеліце правілы часткавання дадзеных ад правілаў адзыявання. Змена аднаго з іх не должна вымагаць перапісвання другога, калі зменяюцца показнікі якасці.

import dev.langchain4j.store.graph.neo4j.Neo4jGraph;
// If I want to initialize the graph abstraction and load the schema:
Neo4jGraph graph = Neo4jGraph.builder()
    .driver(driver)
    .build();
// Under the hood, this executes a single, consolidated APOC query that yields
// labels, element types, and properties all at once, avoiding multiple DB calls.
graph.refreshSchema();
Neo4jGraph.StructuredSchema schema = graph.getStructuredSchema();
// We expect a well-formatted string logically divided into three sections,
// exactly as formatted by the new Neo4jGraphSchemaUtils class:
//
// `schema.nodesProperties()` is the following:
// :Person {name: STRING}, :Company {name: STRING}
//
// `schema.relationshipsProperties()` is the following:
// :WORKS_FOR {since: INTEGER}
//
// `schema.patterns()` is the following:
// (:Person)-[:WORKS_FOR]->(:Company)
System.out.println("Current Database Schema Context:\n" + schema);

Канфігурацыя выборкавання схемы і ўдосконаленняў

Процес налаштавання схемы выборкі і етапаў працюе найэфектывней, калі яго розглядаць як меруючую плошчу. Зберагучы адна ідеальная транскрыпція, адзін прыклад неудачы і запіс пра відкат, перш чым расширваць масштабы. Закладзіце налаштаванні за межамі коду прыкладнага програмы. Файлы сераўнавальной среды, хранільнікі секрэтных дадзеных і флагі функцый должны знаходзіцца ў аднам месцы, куды аператары можаць аудытуваць іх, не чытаючы весь ланцуг. Раздзеліце політыку часткавання дадзеных ад політыки ўтрымання іх. Змена адной з яных не должна вымагаць перапісвання другой, калі зменяюцыся показнікі якосці. Процес налаштавання схемы выборкі і етапаў працюе найэфектывней, калі яго розглядаць як меруючую плошчу. Зберагучы адна ідеальная транскрыпція, адзін прыклад неудачы і запіс пра відкат, перш чым расширваць масштабы. Валідзіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшаў, прычына неудачы должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцуг задач.

// If I want to scan a very large database efficiently by configuring the APOC sampling,
// and I want to enhance the LLM's understanding with sample property values:
Neo4jGraph optimizedGraph = Neo4jGraph.builder()
    .driver(driver)
    // We can configure the underlying apoc.meta.data parameters.
    // 'sample' limits the number of nodes inspected per label to speed up execution.
    // 'maxRels' limits the number of relationships inspected per node.
    // (Note: These are passed internally to the getSchemaFromMetadata utility)
    .build();

// When the schema is refreshed, the underlying query runs:
// CALL apoc.meta.data({maxRels: $maxRels, sample: $sample})
optimizedGraph.refreshSchema();
// The LLM now receives a fast, accurately sampled schema representation,
// protecting database performance during application startup or schema refreshes.
System.out.println("Optimized Schema loaded successfully.");

Автаматызаванне стварэння графа знаёмасцей і павязыванне да выканаў

У стадзіі автаматызаваннага стварэння графа знаёмасцей неабходна праканалізаваць вхідныя даны, абярнучага гэтага крока і критэрыя завершэння прычыні змены коду. Аперацыйныя працавнікі должны магчымае запускаць гэты крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Цюю стадзію трэба спрыяваць як кантракт межа вхіднымі данымі і пераканалізаванымі выканамі. Назваць рэзультаты працы, праканалізаваць критэрыя успеху і адмовіцца ад беззвучнага частковага завершэння. Указваць тыя фрагменты, якія насправдзе сталі падставай для адпаведнай адказы. Без ціх цитатаў аперацыйныя працавнікі не зможуць адразніць галюцинацыю ад працявання з непаштоўнымі даннымі.

Пачатковы набор даных і метод кількісных запускаў

Для пачкі з дадзенымі і пачатковага ўражэння неабходна перад змянай коду адзначыць вхідныя даны, адпаведальнага за крок і критэрыя завершэння. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не прабуючы спадарацца прыватны стан. Запісваюць час выконання і вартасць токеноў або запытак праза разам з функцыйнальнымі рэзултатамі. Відразы вартасцей з самага пачатку запобегае неспакоўным рахункам, калі працэс пераходзіць з дэмовай среды ў спакульную. Калі наступны крок — це код або вызов інструмента, лепш выкарыстоўваць структураваныя выходны даны з перакананнем схэмы, чым вольныя тэкстовыя форматы.

[
   {
      "tail": "Microsoft",
      "head": "Adam",
      "head_type": "Person",
      "text": "Adam is a software engineer in Microsoft since 2009...",
      "relation": "WORKS_FOR",
      "tail_type": "Company"
   },
   {
      "tail": "Microsoft Word",
      "head": "Microsoft",
      "head_type": "Company",
      "text": "Microsoft is a tech company that provides several products...",
      "relation": "PRODUCED_BY",
      "tail_type": "Product"
   }
]
import dev.langchain4j.community.data.document.transformer.graph.LLMGraphTransformer;
import dev.langchain4j.community.data.document.graph.GraphDocument;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DefaultDocument;
import dev.langchain4j.data.document.Metadata;
import java.util.List;

ChatModel chatModel = /* dev.langchain4j.model.chat instance */
Driver driver = /* org.neo4j.driver.Driver instance */

// If I want to guide the extraction by providing a structured set of examples:
LLMGraphTransformer transformer = LLMGraphTransformer.builder()
    .model(chatModel)
    .examples(EXAMPLES_PROMPT) // Injects the above JSON dataset into the system prompt
    .build();
Document docKeanu = new DefaultDocument(
    "Keanu Reeves acted in Matrix",
    Metadata.from("key33", "value3")
);
// The LLM will transform the text, structuring nodes and relationships based on the examples
List<GraphDocument> graphDocs = transformer.transformAll(List.of(docKeanu));

/*
The above `graphDocs` returns this result:

GraphDocument
├─ Nodes
│  ├─ GraphNode
│  │  ├─ id: Matrix
│  │  ├─ type: Movie
│  │  └─ properties: {}
│  │
│  └─ GraphNode
│     ├─ id: Keanu Reeves
│     ├─ type: Person
│     └─ properties: {}
│
├─ Relationships
│  └─ GraphEdge
│     ├─ type: ACTED_IN
│     ├─ sourceNode
│     │  ├─ id: Keanu Reeves
│     │  └─ type: Person
│     ├─ targetNode
│     │  ├─ id: Matrix
│     │  └─ type: Movie
│     └─ properties: {}
│
└─ Source
   ├─ text: "Keanu Reeves acted in Matrix"
   └─ metadata
      └─ key33: value3
*/

Ідэмпатныя методы зберагання графа

Для стадіі выкарыстоўвання ідэмпатентных графаў неабходна перад зменай коду адзначыць вхідныя даны, адпаведальнага за крок і крэтыяры завершэння. Аператары должны магчымае перадзванаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Конфігурацыю трэба знаходзіць за межамі коду прыемленае. Файлы сераўнавання, хранільнікі секрэтных дадзеных і флагі функцыйяў должны быць у аднам месца, якое аператары можаць пераглядаць, не чытаючы весь граф. Наводзіць трэба тыя часткі тексту, якія фактычна служылі падставай для адпаведнай адказу. Без ціх цытатаў аператары не можаць разлічыць галюцинацію ад працягу індэксавання. Для стадіі выкарыстоўвання ідэмпатентных графаў неабходна перад зменай коду адзначыць вхідныя даны, адпаведальнага за крок і крэтыяры завершэння. Аператары должны магчымае перадзванаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптаў. Калі крок не выйшоў, прычына неудачы должна вказываць на адну конкрэтную адпаведальнасць, а не на цэлы комплекс проблем.

спрагнутая трубапрацэс.

import dev.langchain4j.community.rag.content.retriever.neo4j.KnowledgeGraphWriter;

Neo4jGraph neo4jGraph = /* dev.langchain4j.store.graph.neo4j.Neo4jGraph instance */;
LLMGraphTransformer graphTransformer = /* dev.langchain4j.community.data.document.transformer.graph.LLMGraphTransformer instance */;

Document docKeanu = new DefaultDocument(
        "Keanu Reeves acted in Matrix",
        Metadata.from("key33", "value3")
);
List<GraphDocument> graphDocs = graphTransformer.transformAll(List.of(docKeanu));


// If I want to persist the extracted entities safely:
KnowledgeGraphWriter writer = KnowledgeGraphWriter.builder()
    .graph(neo4jGraph)
    .build();
// The first write populates the database
writer.addGraphDocuments(graphDocs, false);
// Executing the exact same command again is safe.
// The internal UNWIND and MERGE logic generated by the writer guarantees
// that no duplicate entities or relationships are created.
writer.addGraphDocuments(graphDocs, false);
// Expected resulting topology in the database:
// (:__Entity__ {id: 'keanu'})-[:ACTED]->(:__Entity__ {id: 'matrix'})
System.out.println("Entities persisted successfully. No duplicates created.");

Спыяванне на даследні дакумент (includeSource)

Кал працуеце з этапам спыявання на даследні дакумент includeSource, спачатку запісайце умовы кантракту: неабяжлівыя вхідныя даны, сігнал успеху і тое, што выходзіць у разе частковага нявыпання. Такі список пераконвае ў тым, што пазнейшыя змены коду будуць адкрытымі. Спрыятлівайце гэтам этапу як кантракту межаў вхідных дадзеных і перакананых выходных. Дайце назвы артыфактам, задаце перакананні успеху і не падзволяйце частковаму завершэнню без паведамлення. Змяркуйце ступень вярнага аднаходжэння на фіксаванай сэтцы запитаў прычым падлашоўванню запрасаў. Частыя змены запрасаў рэдка калі выправляюць слабую систему аднаходжэння дадзеных.

// If I want to maintain data provenance and link entities back to their source:
writer.addGraphDocuments(graphDocs, true); // true = includeSource

// Behind the scenes, the writer executes three crucial operations:
// 1. It creates the Document node, copying the original metadata (e.g., key33: value3) and the text.
// 2. If the original document lacks an ID, the writer automatically generates
//    an MD5 hash of the text to use as a unique identifier.
// 3. It links the document to the extracted entities using a relationship (default: HAS_ENTITY).
// Expected resulting topology in the database:
// (:Document {id: '<MD5_hash>', text: 'Keanu Reeves...', key33: 'value3'})-[:HAS_ENTITY]->(:__Entity__ {id: 'keanu'})
System.out.println("Source document successfully linked to the extracted entities.");

Глыбокая налаштовка схемы і безпека

Калі працюеце над практыкай «Глыбока налаштованасць схемы», спачатку запісайце умовы: неабяжлівыя даны, сигнал успеху і тое, што выканаецца у разы частковага нявыполнення. Такі список контроля дапамагае заліцвачыць змяны ў кодзе.

// If I want to adapt the ingestion to a pre-existing enterprise schema,
// and customize the relationship that links the document to the entities:
KnowledgeGraphWriter customWriter = KnowledgeGraphWriter.builder()
    .graph(neo4jGraph)
    .label("ActorOrMovie")             // Replaces "__Entity__"
    .idProperty("customId")            // Replaces "id"
    .textProperty("customText")        // Replaces "text" for the Document node
    .relType("MENTIONED_IN_SOURCE")    // Replaces "HAS_ENTITY"
    .constraintName("unique_custom")   // Sets a specific name for the Neo4j CONSTRAINT
    .build();

// Inserting the data with includeSource set to true will now use the new nomenclature:
customWriter.addGraphDocuments(graphDocs, true);

// Example of the resulting Cypher pattern generated by the writer:
// (:Document {customText: '...'})-[:MENTIONED_IN_SOURCE]->(:ActorOrMovie {customId: 'keanu'})

Безпека за дапамогою інтэграціі Cypher DSL

Калі працуеце над стадзіяй Security via Cypher DSL, спачатку запісайце контракт: неабяжлівыя даннэ, сигнал успеху і тое, што выходзіць пад частыя неудачы. Такі список контроля дапамагае заліцьварыць пазнейшыя змены коду. Зберагаеце настройкі праз чынны код аплікацыі. Файлы сераўіснага сэрвісу, храненні секретных данных і флагі функцыйяў должны знаходзіцца ў аднам месцы, куды аператары можаюць адбавіць аудыт без неабяжлівага чытання всей структуры. Перад налаштаваннем запитоў пераканайцеся ў рэкале на фіксованым наборы запитанняў. Частыя змены запитоў рэдка калі выправляюць слабыя аспекты адзысквання данных. Калі працуеце над стадзіяй Security via Cypher DSL, спачатку запісайце контракт: неабяжлівыя даннэ, сигнал успеху і тое, што выходзіць пад частыя неудачы. Такі список контроля дапамагае заліцьварыць пазнейшыя змены коду. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшае, неудача должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаную структуру працы.

import org.neo4j.cypherdsl.core.Cypher;
import org.neo4j.cypherdsl.core.Statement;
import org.neo4j.cypherdsl.core.Node;

Driver driver = /* org.neo4j.driver.Driver instance */

// Demonstrating how internal queries are constructed safely via the DSL
// We define a Node representation first
Node documentNode = Cypher.node("Document").named("d");
// We build the query programmatically using the fluent API.
// Notice how literal values are wrapped safely, preventing injection.
Statement statement = Cypher.match(documentNode)
    .where(Cypher.property("d", "id").isEqualTo(Cypher.literalOf("doc-123")))
    .returning(Cypher.property("d", "text"))
    .build();
// The DSL engine traverses the AST and compiles it into a syntactically safe string.
String safeCypherQuery = statement.getCypher();
// Expected output: MATCH (d:`Document`) WHERE d.id = 'doc-123' RETURN d.text
System.out.println("Generated safe Cypher via DSL: " + safeCypherQuery);

Прафільтрацыя ў рамках індэкса з синтаксам 2026.01

Этап прафільтрацыі ў рамках індэкса з синтаксам 2026 даўа найлепыя рэзультаты, калі яго спрыяваць як меркаваную велічыну. Зберагучы адна ідеальная транскрыпцыя, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану, перш чым расширваць масштабы. Спрыяйце цэму этапу як кантракту межаў вхідных дадзеных і перакананыя выходныя рэзультаты. Даўце назвы артыфактам, задаць критэрыя успеху і адмовіцца ад мовчанкавага частковага завершэння задачы. Раздзеліце правілы часткавага абрабатвання дадзеных і правілы ўзяць іх з індэкса. Змена адных не павінна прымусваць перапісванне іншых, калі змянююцыся паметры якосці.

import dev.langchain4j.store.embedding.neo4j.Neo4jEmbeddingStore;
import dev.langchain4j.store.embedding.filter.Filter;
import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;

Driver driver = /* org.neo4j.driver.Driver instance */
Embedding embedding = /* dev.langchain4j.data.embedding.Embedding instance */

MatchSearchClauseStrategy matchSearchClauseStrategy = new MatchSearchClauseStrategy();

// Configure the store with the new syntax enabled
Neo4jEmbeddingStore store = Neo4jEmbeddingStore.builder()
    .driver(driver)
    .dimension(1536)
    .searchStrategy(matchSearchClauseStrategy) // Crucial flag: Enables the 2026.01 optimized native vector search syntax
    .filterMetadata(Arrays.asList("year", "department")) // Enable filtering for 'year' and 'department', which translates to the `WITH [indexName.year, indexName.department]` clause during index creation
    .build();

// Build a metadata filter combining multiple boolean conditions
Filter filter = metadataKey("year").isEqualTo(2024)
    .and(metadataKey("department").isEqualTo("Engineering"));
// Execute the search request
EmbeddingSearchRequest request = EmbeddingSearchRequest.builder()
    .queryEmbedding(embedding)
    .maxResults(5)
    .filter(filter) // Filter is executed natively inside the Neo4j Vector Index block
    .build();
SearchResult<TextSegment> results = store.search(request);
// The results will natively exclude any documents not matching the criteria specified in the `filter` instance
// returning the final Top-K, ensuring you always get 5 highly relevant segments if they exist.
System.out.println("Search executed with in-index filtering.");
// Execute results.matches() to verify in-index filtering

Канцэпцыі абрабатвання дадзеных GraphRAG: розраўневаныя структурныя абрабатчыкі і прыемнікі

Развітая стадія Conceptы адзьёбвання дакументаў GraphRAG працуе наякрацэ калі яе спрыяваць як мерыемую паверхню. Запісайце адны ідеальны прыклад, адну справу з бягам і прыметку па поверненню да пачатковага стану пры розшырэнні масштаба. Запісвайце часы выканання і кост токенав або запита праза функцыональнымі рэзультатамі. Відразлівасць костаў з самага пачатку запобегае неспакойным рахункам, калі процес пераходзіць з дэмавайнага режыма ў спяльныя сераўры. Раздзеліце правілы часткавання дакументаў ад правілаў адзьёбвання. Змена аднаго з іх не должна прымусіваць перапісванне другога, калі змянююцца паказнікі якосці.

Шаблон «Абака-дзеця»

Этап «Адміністратар-дзець» працюе найэфективней, калі яго розглядаць як параметрызаваную сферу. Зберагчыце адны ідеальны прыклад, адну справу з бягамі та запіс пра выканэнне атрыбута rollback перад расшырэнням масштаба. Зберагчыце настройкі пазначаныя за межамі коду прыемленае. Файлы сераўнавання, хранільнікі секрэтных дадзеных та флагі функцый крануцца на аднай пазначцы, якую аператары можаць пераглядаць без неабяжнага чытання всіх элементаў. Раздзеляйце правілы часткавання дадзеных ад правілаў ўзяць іх. Змена аднаго з іх не должна вымагаць перапісвы другога, калі зменяюцца паказнікі якосці. Этап «Адміністратар-дзець» працюе найэфективней, калі яго розглядаць як параметрызаваную сферу. Зберагчыце адны ідеальны прыклад, адну справу з бягамі та запіс пра выканэнне атрыбута rollback перад расшырэнням масштаба. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшаў, прычына бягу должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцужок задач.

import dev.langchain4j.store.graph.neo4j.Neo4jParentChildIngestor;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.splitter.DocumentSplitters;

// embeddingModel, embeddingStore, childSplitter instances...

// If I want to automatically ingest a document into a Parent-Child graph topology:
Neo4jEmbeddingStoreIngestor ingestor = ParentChildGraphIngestor.builder()
          .driver(driver)
          .embeddingModel(embeddingModel)
          // We define how the document should be chunked before ingestion
          .documentSplitter(DocumentSplitters.recursive(200, 20))
          .documentChildSplitter(childSplitter)
          .build();

Document document = Document.from( """Artificial Intelligence (AI) is a field of computer science. It focuses on creating intelligent agents capable of performing tasks that require human intelligence.
                        Machine Learning (ML) is a subset of AI. It uses data to learn patterns and make predictions. Deep Learning is a specialized form of ML based on neural networks.
                        """);

ingestor.ingest(List.of(document));
// The graph now contains one 'Document' node connected via 'HAS_CHILD'
// to multiple embedded 'DocumentChunk' nodes.
System.out.println("Parent and child nodes successfully ingested and linked.");
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;


// If I want to search against chunks but retrieve the rich parent document:
final EmbeddingStoreContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
        .embeddingStore(embeddingStore)
        .maxResults(1)
        .minScore(0.4)
        .build();

List<Content> contents = retriever.retrieve(Query.from("specific configuration detail"));
// The retriever hits the small 'DocumentChunk' index, traverses the 'HAS_CHILD'
// relationship, and returns the entire 'Document' node.
System.out.println("Retrieved full parent document context.");
/*
The result of `contents` is :

DefaultContent {
  textSegment = TextSegment {
    text = """
      Machine Learning (ML) is a subset of AI. It uses data to learn patterns and make predictions.
      Deep Learning is a specialized form of ML based on neural networks.

      Machine Learning (ML) is a subset of AI. It uses data to learn patterns and make predictions.
      Deep Learning is a specialized form of ML based on neural networks.

      Artificial Intelligence (AI) is a field of computer science. It focuses on creating intelligent agents
      capable of performing tasks that require human intelligence.
    """,
    metadata = {
      index = 1,
      source = Wikipedia link,
      title = AI Basics,
      url = https://example.com/ai,
      parentId = parent_1af3e080-5029-40ab-b3d7-0829064d800d
    }
  },
  metadata = {
    EMBEDDING_ID = null,
    SCORE = 0.8560410737991333
  }
}
*/

Шаблон падсумку

Для стадіі «Шаблон параграфування» неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтыры завершэння пры перадзеі коду. Аперацыйныя працавнікі павінны магчымае перадзеі крок з вядомай точкі контролю, не падозрываючы прыхованы стан. Спрыяць гэтай стадіі як даговору межа вхіднымі данымі і перакананымі выходнымі рэзультатамі. Даць назвы артыфактам, узначыць перакананні успеху і адмовіцца ад тыхоўскага частковага завершэння. Цітаваць тыя часткі, якія фактычна сталі падставай для адпаведнай адказы. Без цітатаў аперацыйныя працавнікі не зможуць адразніць галюцинацыю ад прасоў у індэксаванні.

import dev.langchain4j.community.store.embedding.neo4j.Neo4jEmbeddingStoreIngestor;
import dev.langchain4j.community.store.embedding.neo4j.SummaryGraphIngestor;

// If I want the LLM to summarize my document during ingestion and store the summary:
/* Neo4jEmbeddingStore, ChatModel and DocumentSplitter instances... */

final Neo4jEmbeddingStoreIngestor ingestor = SummaryGraphIngestor.builder()
        .driver(driver)
        .embeddingModel(embeddingModel)
        .questionModel(chatModel)
        .documentSplitter(parentSplitter)
        .build();

ingestor.ingest(List.of(document));
// The graph now has a 'Summary' node (containing the LLM-generated summary)
// linked to the specific 'DocumentChunk' nodes.
System.out.println("Document ingested and summarized successfully.");
import dev.langchain4j.community.store.embedding.neo4j.Neo4jEmbeddingStoreIngestor;
import dev.langchain4j.community.store.embedding.neo4j.SummaryGraphIngestor;

/* required instances */

Document document = Document.from("""
        Artificial Intelligence (AI) is a field of computer science. It focuses on creating intelligent agents capable of performing tasks that require human intelligence.
        Machine Learning (ML) is a subset of AI. It uses data to learn patterns and make predictions. Deep Learning is a specialized form of ML based on neural networks.
        """);

ingestor.ingest(document);

final EmbeddingStoreContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
        .embeddingModel(embeddingModel)
        .maxResults(5)
        .minScore(0.6)
        .embeddingStore(ingestor.getEmbeddingStore())
        .build();

/*
The result is something like this:

DefaultContent {
  textSegment = TextSegment {
    text = "Machine Learning (ML) is a subset of AI",
    metadata = {
      index  = 0,
      source = Wikipedia link,
      title  = Quantum Mechanics,
      url    = https://example.com/ai
    }
  },
  metadata = {
    EMBEDDING_ID = null,
    SCORE        = 0.8425111770629883
  }
}
*/

Шаблон гіпотэтычнага запытання

Для стадіі «Шаблон гіпотэтычнага запытання» неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтыры завершэння пры перадзеіснавленні коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не падозрываючы прыхованы стан. Запісваць час выконання і кост токена або запытку празаўседле з функцыйнальнымі рэзультатамі. Відразлівасць костаў з самага пачатку запобегае неспакою, калі траекторыя пераходзіць з дэмавай версіі ў спяльныя сераўы. Указваць часткі тексту, якія фактычна сталі падставай для адпаведнага адказу. Без ціх цытатаў аператары не можуць разлічыць галюцинацыю ад прасоў у індэксаванні.

import dev.langchain4j.community.store.embedding.neo4j.Neo4jEmbeddingStoreIngestor;
import dev.langchain4j.community.store.embedding.neo4j.HypotheticalQuestionGraphIngestor;

/* ... Neo4jEmbeddingStore, ChatModel and DocumentSplitter instances.. */

Neo4jEmbeddingStoreIngestor ingestor = HypotheticalQuestionGraphIngestor.builder()
        .embeddingModel(embeddingModel)
        .driver(driver)
        .documentSplitter(splitter)
        .questionModel(chatModel)
        .embeddingStore(embeddingStore)
        .build();

Document document = Document.from("""
                        Quantum mechanics studies how particles behave. It is a fundamental theory in physics.
                        Gradient descent and backpropagation algorithms.
                        Spaghetti carbonara and Italian dishes.
                        John Doe is a Super Saiyan.
                        """);
ingestor.ingest(document);

EmbeddingStoreContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
                .embeddingModel(embeddingModel)
                .maxResults(2)
                .minScore(0.5)
                .embeddingStore(ingestor.getEmbeddingStore())
                .build();

List<Content> results = retriever.retrieve(Query.from("Who is John Doe?"));

System.out.println("Retrieved Hypothetical context: " + results);

/*
The result is something like this:

DefaultContent {
  textSegment = TextSegment {
    text = "John Doe is a Super Saiyan.",
    metadata = {
      index  = 2,
      source = Wikipedia link,
      title  = Quantum Mechanics,
      url    = https://example.com/ai
    }
  },
  metadata = {
    SCORE        = 0.8234479427337646,
    EMBEDDING_ID = 2002cabe-2a3e-4c6e-96ec-a0292e26e817
  }
}

*/

Універсальны шаблон

Для стадіі «Універсальны патэрн» неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтырыя завершэння пры перадзеіснавленні коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы прыхованы стан. Конфігурацыю трэба знаходзіць за межамі коду прыкладнення. Файлы сераўнавання, хранільнікі секрэтных дадзеных і флагі функцый належыць у аднам месца, якое аператары можаць пераглядаць, не чытаяўшы весь ланцуг задач. Прыкладзіце часткі тексту, якія фактычна лежалі в основе адпаведнай адказы. Без цых цітатаў аператары не можаць разлічыць галюцинацію ад працягу індэксавання. Для стадіі «Універсальны патэрн» неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і крэтырыя завершэння пры перадзеіснавленні коду. Аператары должны магчымаць перзапуск крока з вядомай точкі контролю, не падозрываючы прыхованы стан. Валідзіце маленькія, тэставаныя елементы працэсу замест большых скрыптов. Калі крок не выканаецца, прычына неудачы должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцуг задач.

final Neo4jEmbeddingStore neo4jEmbeddingStore = /* Neo4jEmbeddingStore instance */

final EmbeddingStoreContentRetriever retriever = EmbeddingStoreContentRetriever.builder()
        .embeddingModel(embeddingModel)
        .maxResults(5)
        .minScore(0.4)
        .embeddingStore(neo4jEmbeddingStore)
        .build();

// other required instances ...

Document doc = Document.from("""
                        Quantum mechanics studies how particles behave. It is a fundamental theory in physics.
                        Gradient descent and backpropagation algorithms.
                        Spaghetti carbonara and Italian dishes.
                        John Doe is a Super Saiyan.
                        """);

// Ingest the document into Neo4j as parent-child nodes
final Neo4jEmbeddingStoreIngestor ingestor = Neo4jEmbeddingStoreIngestor.builder()
        .documentSplitter(parentSplitter)
        .documentChildSplitter(childSplitter)
        .driver(driver)
        .query("CREATE (:MainDoc $metadata)") // a Cypher query template used for storing the processed segment data in Neo4j
        .embeddingStore(neo4jEmbeddingStore)
        .embeddingModel(embeddingModel)
        .build();

ingestor.ingest(doc);

final String retrieveQuery = "Machine Learning";
List<Content> results = retriever.retrieve(Query.from(retrieveQuery));

System.out.println("Retrieved Generic context: " + results);

Апарат для выкарыстоўвання родзіча-дзеця, незалежны ад базы дадзеных

Кал працуеце над стадіяй выкарыстоўвання родзіча-дзеця, незалежна ад базы дадзеных, спачатку запісайце умовы: неабходныя данні, сігнал успеху і тое, што выканаецца у разе частковага няўспэху. Такі список контролю дапамагае заліцьваты змяны ў кодзе. Спрэчывайце гэтую стадію як угоду межа даннімі, якія вносзяцца, і перакананымі рэзультатамі. Дайце назвы элементам, задаць правіла пераканання успеху і не падзеўляйцеся частковым завершэнням без паведамлення. Перад налаштовваннем запитоў пераканайцеся ў рэгрэсіі на фіксаваным наборе запитанняў. Частае зменшэнне запитоў рэдка калі вярна выправляе слабыя аспекты выкарыстоўвання дадзеных.

import dev.langchain4j.community.store.embedding.ParentChildEmbeddingStoreIngestor;

/* Required instances */

ParentChildEmbeddingStoreIngestor ingestor = ParentChildEmbeddingStoreIngestor.builder()
                .documentTransformer(documentTransformer)
                .documentSplitter(documentSplitter)
                .textSegmentTransformer(textSegmentTransformer)
                .embeddingModel(embeddingModel)
                .embeddingStore(embeddingStore)
                .documentChildSplitter(documentChildSplitter)
                .childTextSegmentTransformer(childTextSegmentTransformer)
                .build();

AI з станам: Пастаянная памяць размовы ў графе

Калі працюеце над стадзіяй «Stateful AI Persistent Conversation», спачатку запісайце контракт: неабяжлівыя даны, сігнал успеху і тое, што выходзіць у разе частковага нявыпання. Такі список контроля дапамагае заліцьварыць пазнейшыя змены ў кодзе. Запісвайце час выканання і кост токеноў або запытаў праза функцыйнае рэзультат. Відразлівасць костаў з самага пачатку запобегае неспакойным рахункам, калі працэс пераходзіць з дэмаверыянту ў спакульнаныя сераўысы. Замерьце рэгрэт на фіксаваным наборы пытанняў прычыну налаштавання прамптаў. Частае змена прамптаў рэдка калі-небудзь выправляе слабыя аспекты адналічэння інформацыі.

Канфігурацыі

Калі працуеце на стадыі Канфігурацыйяў, спачатку запісайце умовы вядзення: неабходныя данні, сігнал успеху і тое, што выканаецца у разе частковага нявыполнення. Такі список контроля дапамагае залічыць пазнейшыя змены ў кодзе чыстымі. Зберагаюце канфігурацыі паза кодам прыемлі. Файлы сераўіса, хранільнікі секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, куды аператары можаюць адбавіць аудыт без неабяжнага чытання всіх элементаў. Перад налаштаваннем запытаў пераканайцеся ў роботы системы на фіксаваным наборе запытаў. Частае змена запытаў рэдка калі вярнайце сілу системы адзысквання дадзеных. Калі працуеце на стадыі Канфігурацыйяў, спачатку запісайце умовы вядзення: неабходныя данні, сігнал успеху і тое, што выканаецца у разе частковага нявыполнення. Такі список контроля дапамагае залічыць пазнейшыя змены ў кодзе чыстымі. Валіце маленькія, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшае, прычына нявыполнення должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцужок задач.

Кантролл чат-історыі для кальколяў і мультімодальных даных

Этап кантроллу чат-історыі для кальколяў працюе наяўней, калі яго розглядаць як мерыябельную плошчу. Запісаце адны ідеальны прыклад, адзін прыклад неудачы і прыметку па адвярненню перад расшырэнням масштаба. Разглядзіце этап як кантракт межа данымі, якія прыходзяць, і пасвярджанымі рэзультатамі. Даце назвы артыфактам, задаце критэрыя успеху і адмовіцеся ад тыхняе частковага завершэння без паведамлення. Раздзеліце правілы часткавага абрабатвання данных і правілы ўзяць іх. Змена адных не павінна вымагаць перапісвання іншых, калі змянююцца паказнікі якосці.

import dev.langchain4j.store.memory.chat.neo4j.Neo4jChatMemoryStore;
import dev.langchain4j.data.message.UserMessage;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.message.ImageContent;
import java.util.List;

// 1. Initialize the store using an existing driver
Neo4jChatMemoryStore memoryStore = Neo4jChatMemoryStore.builder()
    .driver(driver)
    .build();
// 2. Identify the specific user sessions
String sessionId1 = "user-alice-123";
String sessionId2 = "user-bob-456";
// 3. Append standard text messages for Alice
List<ChatMessage> aliceMessages = List.of(
    new UserMessage("Hi, I'm Alice."),
    new AiMessage("Hello Alice!")
);
memoryStore.updateMessages(sessionId1, aliceMessages);
// 4. Append multimodal messages (text + images) for Bob
List<ChatMessage> bobMessages = List.of(
    new UserMessage("What do you see in this image?", List.of(new ImageContent("https://...")))
);
memoryStore.updateMessages(sessionId2, bobMessages);
// When we retrieve or delete messages using sessionId1,
// the graph guarantees that Bob's linked list of messages remains completely untouched.
System.out.println("Isolated memory chains created for both Alice and Bob.");


// 5. Optionally delete messages
// memoryStore.deleteMessages(sessionId1);
// memoryStore.deleteMessages(sessionId2);

Індывідуальная налаштовка схемы графа

Этап налагоджэння схемы графа працюе найкраща, калі яго розглядаць як параметр, які можна вымерыць. Запісаўце адны ідеальны прыклад роботы, адну справу з бягам і прыметкі па поверненню да пачатковага стану прычым расшырэнню масштаба. Запісвайце часы выканання і кост токена або запиту разам з функцыональнымі рэзультатамі. Відразувая візуабельнае прадставлення костаў запобегае неспакойным рахункам, калі процес пераходзіць з дэмовай среды ў спяльнаныя сераўеры. Раздзеляйце правілы часткавага обробкі дадзеных і правілы ўтрымання іх. Змена аднаго з яных не павінна вымагаць перапісвы другога, калі зменяюцца паказнікі якосці.

// If I want to align the memory storage with my specific domain ontology:
Neo4jChatMemoryStore customMemoryStore = Neo4jChatMemoryStore.builder()
    .driver(driver)
    .memoryLabel("UserSession")         // Overrides the default "Memory" label
    .messageLabel("ChatTurn")           // Overrides the default "Message" label
    .lastMessageRelType("LATEST_CHAT")  // Overrides the default "LAST_MESSAGE" rel
    .nextMessageRelType("FOLLOWED_BY")  // Overrides the default "NEXT" rel
    .idProperty("sessionKey")           // Overrides the default "id" property
    .messageProperty("textContent")     // Overrides the default "message" property
    .build();

// Now, when the system persists a chat, it will execute domain-specific Cypher queries like:
// MERGE (m:UserSession {sessionKey: 'user-alice-123'})
// CREATE (msg:ChatTurn {textContent: 'Hi...'})
// MERGE (m)-[:LATEST_CHAT]->(msg)
System.out.println("Custom memory store initialized with domain-specific schema.");

Кантроль лімітаў токенаў (Размах контэкстнага вікна)

Этап каральбавання лімітаў токенама працюе найэфектывней, калі яго спрыяваць як мерыемую плошчу. Зберагачыце адні ідеальны прыклад роботы, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расширваць сферу дзеяння. Храніце настройкі параду ад коду прыемліка. Файлы серавэра, хранільнікі секретных данных і флагі функцый крануцца ў аднам месцы, якое аператары можаць пераглядаць без неабяжнага чытання всіх элементаў. Устанавіце ліміты токенама на кожны раунд і кожную сесію. Інструменты з агентным режымам агрэсіўна расширваюць контекст; жорсткія ліміты запобегаюць таму, каб дэманстрацыі ператварыліся на неспакоючыя рахункі. Этап каральбавання лімітаў токенама працюе найэфектывней, калі яго спрыяваць як мерыемую плошчу. Зберагачыце адні ідеальны прыклад роботы, адзін прыклад неудачы і запіс пра вярнэнне да пачатковага стану перш чым расширваць сферу дзеяння. Валіце малыя, тэставаныя елементы замест большых скрыптов. Калі якісь крок не выйшае, неудача должна вказваць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцуг задач.

// If I want to heavily restrict the context window to only the most recent interactions:
Neo4jChatMemoryStore slidingWindowStore = Neo4jChatMemoryStore.builder()
    .driver(driver)
    .size(3) // The default is 10. We limit it to the 3 most recent messages.
    .build();

// Assuming the user has sent 20 messages in this session over the past month.
List<ChatMessage> recentHistory = slidingWindowStore.getMessages("user-alice-123");
// The system efficiently traverses the graph starting from the LATEST_CHAT relationship
// and walks backwards via the FOLLOWED_BY relationships, stopping after it collects the
// limited batch of recent messages. The older historical messages remain safely in the
// database, but are not loaded into memory, saving precious tokens.
System.out.println("Loaded only the " + recentHistory.size() + " most recent messages.");
// If I want to extract the complete history for analytics or summarization:
Neo4jChatMemoryStore completeHistoryStore = Neo4jChatMemoryStore.builder()
    .driver(driver)
    .size(0) // 0 disables the sliding window limit
    .build();

List<ChatMessage> fullHistory = completeHistoryStore.getMessages("user-alice-123");
System.out.println("Extracted the complete session history containing " + fullHistory.size() + " messages.");

Апроставанне працы з падключэнням

У стадіўцы апроставання працы з падключэнням неабходна перад зменой коду задаць вхідныя даны, адпаведальнага за выкананне крока і крэтарыя для завершэння. Аператары должны магчымае перайсці на выкананне крока з вядомага пункта контролю, не спрабоўваючы здогадвацца пра схованы стан. Спрыяйце цій стадіўцы як даговору межа вхідных даных і перакананых выходных рэзультатаў. Дайце назвы артыфактам, задаць крэтарыя успеху і не прабоўваце прыймаць часткова завершаныя рэзультаты без падтверджэння. Указуйце тыя часткі тексту, якія фактычна лежаць у падставе адпаведнай адказу. Без ціх цытатаў аператары не зможаць розразліць галюцинацыю ад працягу індэксавання.

// If I want to instantiate the store directly without managing an external Driver instance:
Neo4jChatMemoryStore standaloneStore = Neo4jChatMemoryStore.builder()
    .withBasicAuth("bolt://localhost:7687", "neo4j", "password")
    .build();
System.out.println("Memory store connected directly via Basic Auth.");

Вывык

У стадії заканчэння неабяжна ўзначыць вхідныя даны, адпаведальнага за крок і критэрыя завершэння пры перадзеіснавленні коду. Аператары должны магчымаць перзапуск кроку з вядомай точкі контролю, не падозрываючы прыхованы стан. Запісваць час выконання і вартасць токеноў або запытак палягліва разам з функцыйнальнымі рэзультатамі. Відразлівае паказанне вартасцей запобегае неспадзяваным рахункам, калі процес пераходзіць з дэмовай среды ў спяльнаваныя сераўеры. Указваць тые часткі тексту, якія фактычна ляглі в основу адпаведнай адказы. Без цых цітатаў аператары не можуць разлічыць галюцинацію ад прасоўкі ў індэксаванні.

Рычыменты

Для стадіі Рэсурсаў неабходна перад змянай коду адзначыць вхідныя даны, адпаведальнага за крок і крэтыяры завершэння. Аператары должны магчымае перайсці на выкананне кроку з вядомага пункта контролю, не спрабоўваючы здагадвацца пра схованы стан. Канфігурацыю трэба залічыць параду ад коду прыкладнення. Файлы сераўнавання, хранільнікі секрэтных дадзеных і флагі функцыйяў должны знаходзіцца ў аднам месцы, якое аператары можаць пераглядаць, не чытаючы весь ланцуг задач. Наводзіць трэба тыя часткі тексту, якія фактычна лежаць у падставе адпаведнай адказы. Без ціх цитатаў аператары не можаць разлічыць галюцинацію ад працягу ў індэксаванні. Для стадіі Рэсурсаў неабходна перад змянай коду адзначыць вхідныя даны, адпаведальнага за крок і крэтыяры завершэння. Аператары должны магчымае перайсці на выкананне кроку з вядомага пункта контролю, не спрабоўваючы здагадвацца пра схованы стан. Лепш выбіраць маленькія, тэставаныя елементы замест большых скрыптаў. Калі крок не выкананы, прычына неудачы должна вказываць на адну конкрэтную адпаведальнасць, а не на заплутаны ланцуг задач.

Чэкліст аператыўнай роботы

На стадзіі чэкліста аператыўнай роботы неабходна праказаць вхідныя даны, адпаведальнага за кожны крок і крэтыяры завершэння пры змены коду. Аператары должны магчыма было перзапускаць крок з вядомай точкі контролю, не падозрываючы прыхованы стан.

Неабходна задокументаваць як шлях успеху, так і шлях вярнення да нормальнага стану. Перапрыбуткі, людзкія контрольныя пункты і обработка некоректных паведамленняў є частью продукту, а не чымсь, што дадзецца дагэўна пазніяй.

Прыцісніце цитаты з тых частак, якія фактычна падтрымлівалі адпаведны адказ. Без цитатаў аператары не зможуць разлічыць галюцинацію ад прасоўкі ў індэксаванні.

Напішыце кароткі посібнік: як роцыяваць канты, як спрачыслаць чергу, як анулюваць пасляпэўныя дзеянні.

Валідзіце маленькія, тэставаныя елементы замест большых скрыптаў. Калі крок не выйшоў, прычына нехарактернага рэзультата должна вказваць на аднойчынную адпаведальнасць, а не на заплутаны процес.

Указаць тыя часткі тексту, які фактычна сталі падставай для адпаведнай адказы. Без ціх цитатаў аператары не можу разлічыць галюцинацію ад працяжкай індэксацыі.

Перш чым пераводзіць систему на вышэйшую версію, заморозіць яе версіі, зафіксаваць «золаты» транскрыпт для критычнага маршруту і паказаць способы абвяртання. У спільных средах неабходны ліміты на частоту выкарыстоўвання, пераказы ўсіх праваў на выкарыстоўвання ресурсаў і чысткі власнік для змены секрэтных даных. Лепш выбіраць простую надзею на надзейнасць, чым хітрыя експерыментальныя рашэння.

Прымітка для 9f23f8fe623e: не клаці ключы прадаўца ў репазітарыю, задаць максымальную кантитатыву токенаў на сесію і зберагаць транскрыпты рядом з фіксатрамі для ацэнкі, каб пазнейшыя замены моделей заставаліся порównанымі.