跳到正文
DEV Community· Muhammad Talha Khan·· 2 小時前AI 評分56

Ask Germany:作者為德國官方資訊打造非官方問答入口

Germany Has No america.gov. So I Built an Unofficial One.

AI 導讀

作者 Muhammad Talha Khan 打造 Ask Germany,讓用戶以多種語言查詢德國官方行政資訊,並按所在地列出負責機關及來源連結。系統收集 46,641 個官方頁面,整理成約 161,000 個可搜尋段落;在 165 題、五種語言的測試中,正確官方來源有 91% 位於前 8 個結果。

正文

You move to Germany. You need to register your address. You open one tab, then twelve. Three PDFs. A forum thread from 2019 that contradicts all of them.

Here's the part nobody tells you: Germans have the same problem.

The answers exist. They sit on official websites, spread across the federal government, 16 states and 400 districts and cities. Each has its own portal, its own rules and its own responsible office. The US has america.gov as one front door. Germany doesn't.

So I built Ask Germany. You ask in your own language, it answers only from official sources, links every sentence to the page it came from, and tells you which office is responsible where you live.

This is how it works, and what broke along the way.

The numbers first

  • 46,641 official pages, split into about 161,000 searchable passages
  • The federal service directory for all 400 districts and cities, plus about 370 city and district websites
  • Answers in German, English, Turkish, Arabic and Ukrainian
  • About $0.003 in AI cost per answer
  • About $90 a month in infrastructure, all in the EU

Rule one: the AI never answers from memory

A general chatbot will happily tell you how to register your address. It will also mix up Bavaria and Berlin, cite a relocation blog and invent a deadline. For residence permits and benefits, a confident wrong answer is worse than no answer.

So the model is never asked what it knows. It only gets passages retrieved from official texts, and the pipeline enforces that:

  1. Every statement must carry a source number that points to a retrieved passage.
  2. Statements without a valid source are removed after generation.
  3. If no official text is close enough to the question, the visitor gets "not covered" and a link to the right official portal. No guess.

That last one matters most. Saying "I don't know" is a feature.

Where the official knowledge comes from

The backbone is the federal service directory (Servicesuche Bund, also called PVOG). It has a public search API that describes administrative services for every district in Germany, including the responsible office with address, phone and opening hours. That's what makes answers local.

On top of that, a crawler collects federal and state portals and about 370 city and district websites. It respects robots.txt and the text-and-data-mining opt-out in German copyright law, and it stays at one request per second per site. Some state portals are JavaScript apps or sit behind bot protection. I don't get around that. Those states come from the federal directory instead.

Every crawled text goes into an S3 archive first. The archive is the source of truth: when I switched embedding models, I rebuilt the whole index from it without crawling a single page again.

German breaks normal search

German glues words together. "Wohnungsgeberbestätigung" (the landlord's confirmation you need to register an address) is one word. A user might type "Bestätigung vom Vermieter". Keyword search sees nothing in common.

So compound words get split at both index time and query time, and the parts are searched too. On the questions in my eval set that depend on compounds, recall went from 0.68 to 0.88.

The search itself is hybrid: meaning (Titan Text Embeddings v2, stored as binary vectors in pgvector) plus German full-text search in Postgres, run together and filtered to the visitor's town, district, state and federal level. On a 165-question eval set in five languages, the right official source is in the top 8 results 91% of the time.

A question's journey

  1. The browser removes tax IDs, IBANs, ID numbers, phone numbers and email addresses before anything is sent, and shows the visitor what it removed.
  2. The browser solves a small proof-of-work puzzle instead of a CAPTCHA, and gets a short-lived anonymous session. No account.
  3. On the server, a second pass removes names, and a safety model (Qwen3 235B) screens the question.
  4. DeepSeek V3.2 detects the language, extracts the place and rewrites the question as a German search query in official terms.
  5. The place becomes an official regional key through the federal directory.
  6. Hybrid search runs, then the relevance gate decides: answer or "not covered".
  7. DeepSeek writes a short answer in the visitor's language from the passages only, with the official German term in brackets.
  8. Unsourced statements are removed, and the answer streams back with sources and the responsible office.

Nothing is saved. Not the question, not the answer.

Privacy was a firm rule, and it cost me

No accounts. No cookies. Questions and answers are never stored, logged or used for training.

That rule kills easy wins. A normal app would cache answers to save money. A cache stores questions, so there is no cache. Analytics would tell me which topics people ask about. That's stored data too, so the only counters are anonymous: model, tokens and cost per hour, never text.

I think it's the right trade for this audience. Many people asking about residence permits are in a vulnerable situation. "We store nothing" is a promise they can actually check.

The stack

  • Website: Next.js on Vercel, functions in Frankfurt
  • API: Python and FastAPI on AWS ECS Fargate, Frankfurt
  • Database: PostgreSQL 17 with pgvector on RDS, in a private network with no internet access
  • AI: DeepSeek V3.2, Qwen3 235B and Titan Embeddings v2 on Amazon Bedrock, Stockholm
  • Crawler and jobs: Python, httpx and trafilatura, as Fargate tasks
  • Infrastructure: one CloudFormation template

What broke along the way

The free tier ran out. I started on free Postgres tiers. The full index came to about 480 MB against a 500 MB limit, before adding anything else. I moved everything to AWS instead of cutting coverage.

Bedrock wasn't available where I wanted it. My plan was Frankfurt. On my account, Bedrock only worked in Stockholm. Still EU, so the privacy promise held, but the plan changed.

I underestimated a re-index badly. I first estimated a self-hosted embedding re-index at about a dollar and under an hour. Measured on real CPUs, it was closer to 180 CPU-hours. Lesson: measure before you quote.

Crawlers saw a different page than users. The start page said "70 official websites" in the server-rendered HTML, a hard-coded fallback. Browsers replaced it with the live number, 495. Search engines and link previews never ran that script, so they saw 70. If you care about how AI search engines describe your site, check what your HTML says before JavaScript runs.

What's next

Guide pages for the most common questions, so search engines and AI assistants have something concrete to cite. A proper German and English URL split. And a lot of listening to people who find wrong answers.

If you live in Germany, or are about to move here, try it with a question you actually had: askgermany.live. It's independent, not a government website, and not legal advice.

And if you've built retrieval over messy government data in another country, I'd love to hear how you handled it. What would you have done differently?

來源:DEV Community · dev.to