RAG-demo: Automatiseret opdatering af Dify-bot
Dette projekt dokumenterer, hvordan jeg har bygget et automatiseret workflow i Java, der sørger for at min RAG-bot på Dify altid afspejler det nyeste indhold på min portfolio.
Problemet#
Når man bygger en RAG-bot med en videnbase, opstår der hurtigt et vedligeholdelsesproblem: indholdet bliver forældet. Hver gang jeg tilføjer en ny side til min portfolio, skal jeg manuelt opdatere Dify’s knowledge base – det er upraktisk og fejlbehæftet.
Løsningen er at automatisere processen, så botten selv holder sig opdateret.
Arkitektur#
Workflowet fungerer i tre trin:
- Sitemap-parsing – Java-programmet henter og parser portfolioens
sitemap.xmlfor at finde alle aktive URL’er. - Sammenligning – De fundne URL’er sammenlignes med den liste, der sidst blev indekseret i Dify.
- Opdatering via API – Nye URL’er sendes til Dify’s knowledge base API, så indholdet crawles og indekseres.
Java-implementering#
Projektet er skrevet i IntelliJ IDEA og bruger en dedikeret Sitemap-klasse til at håndtere al XML-parsing.
Sitemap-klassen#
import org.w3c.dom.*;
import javax.xml.parsers.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class Sitemap {
private final String sitemapUrl;
public Sitemap(String sitemapUrl) {
this.sitemapUrl = sitemapUrl;
}
public List<String> fetchUrls() throws Exception {
List<String> urls = new ArrayList<>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new URL(sitemapUrl).openStream());
NodeList locNodes = doc.getElementsByTagName("loc");
for (int i = 0; i < locNodes.getLength(); i++) {
urls.add(locNodes.item(i).getTextContent().trim());
}
return urls;
}
}Sitemap-klassen åbner en forbindelse direkte til sitemappets URL og parser XML-dokumentet med Java’s indbyggede DocumentBuilder. Alle <loc>-tags udtrækkes og returneres som en liste af strings.
Hoved-workflowet#
import java.util.List;
public class RagUpdater {
public static void main(String[] args) throws Exception {
String sitemapUrl = "https://sofusk7cdk.github.io/Portfolio/sitemap.xml";
String difyApiKey = System.getenv("DIFY_API_KEY");
String datasetId = System.getenv("DIFY_DATASET_ID");
Sitemap sitemap = new Sitemap(sitemapUrl);
List<String> currentUrls = sitemap.fetchUrls();
KnownUrlStore store = new KnownUrlStore("known_urls.txt");
List<String> knownUrls = store.load();
List<String> newUrls = currentUrls.stream()
.filter(url -> !knownUrls.contains(url))
.toList();
if (newUrls.isEmpty()) {
System.out.println("Ingen nye sider fundet – Dify er allerede opdateret.");
return;
}
DifyClient dify = new DifyClient(difyApiKey, datasetId);
for (String url : newUrls) {
dify.addWebDocument(url);
System.out.println("Tilføjet til Dify: " + url);
}
store.save(currentUrls);
System.out.println("Færdig – " + newUrls.size() + " ny(e) side(r) indekseret.");
}
}Programmet holder styr på kendte URL’er i en lokal tekstfil (known_urls.txt). Kun URL’er der er nye siden sidst, sendes til Dify – det minimerer unødvendige API-kald.
Dify API-kaldet#
import java.net.URI;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
public class DifyClient {
private final String apiKey;
private final String datasetId;
private static final String BASE_URL = "https://api.dify.ai/v1";
public DifyClient(String apiKey, String datasetId) {
this.apiKey = apiKey;
this.datasetId = datasetId;
}
public void addWebDocument(String url) throws Exception {
String body = """
{
"indexing_technique": "high_quality",
"data_source": {
"type": "web_page",
"info_list": {
"data_source_type": "web_page",
"web_page_list": ["%s"]
}
},
"process_rule": { "mode": "automatic" }
}
""".formatted(url);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/datasets/" + datasetId + "/document/create-by-url"))
.header("Authorization", "Bearer " + apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
throw new RuntimeException("Dify API fejl: " + response.statusCode() + " – " + response.body());
}
}
}API-nøgler og dataset-ID hentes fra miljøvariable (DIFY_API_KEY, DIFY_DATASET_ID) for at undgå at hardcode følsomme oplysninger i koden.
Automatisering#
Programmet er sat op til at køre som et GitHub Actions workflow, der udløses automatisk, hver gang der pushes til main-branchen på portfoliorepoet. Det betyder, at Dify-botten opdateres inden for minutter efter at en ny side er gået live.
name: Sync RAG bot
on:
push:
branches: [main]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- name: Kør RAG-opdatering
env:
DIFY_API_KEY: ${{ secrets.DIFY_API_KEY }}
DIFY_DATASET_ID: ${{ secrets.DIFY_DATASET_ID }}
run: mvn -q compile exec:java -Dexec.mainClass="RagUpdater"Resultat#
Botten har nu altid et opdateret billede af portfolioens indhold. Når en besøgende stiller spørgsmål om mine projekter, kan Dify hente præcis og aktuel information frem – uden at jeg behøver at løfte en finger efter et nyt deploy.
Min Chat Bot#
Eksempel på et svar:
There are no articles to list here yet.