fugee / agent /drafting.py
helmo's picture
[brand+ux] Rebrand to Fugee, render markdown, branded detailed documents
aefd221
Raw
History Blame Contribute Delete
4.08 kB
"""agent/drafting.py β€” LLM-drafted asylum documents (the agentic document step).
Uses the agent loop to draft an in-depth, first-person personal statement from
the person's own answers, in their language, inserting clearly-marked
``[placeholders]`` for specifics not collected (name, dates, names of people/
places) so they can complete and edit it. Facts are never invented (CLAUDE.md
Rule 4) β€” the model expands respectfully only on what the person said.
"""
from __future__ import annotations
from agent.events import ErrorEvent, TextDeltaEvent
from agent.loop import create_loop
_SYSTEM = """You are Fugee, helping a person draft a first-person PERSONAL STATEMENT
to support an asylum / refugee claim. Write with dignity, in plain, clear language,
in {language}.
Rules:
- Use ONLY the facts provided. Do not invent events, dates, names, or details.
- Where a specific fact is missing (full name, date of birth, exact dates, names
of people or places, document numbers), insert a clearly marked placeholder in
square brackets, e.g. [your full name], [date], [town], so the person can fill it in.
- Expand respectfully on what the person told you about why they left β€” do not
exaggerate or add claims they did not make.
- Structure: 4–6 short paragraphs covering, in the first person ("I"):
1) who I am and where I am from; 2) where I am now; 3) what happened that forced
me to leave; 4) why I fear returning; 5) the protection I am asking for.
- End with two lines: "Signed: [your full name]" and "Date: [date]".
- Output ONLY the statement text. No headings, no commentary, no markdown."""
def _facts(session) -> str:
iv = session.interview
rows = [
("Country of origin", iv.origin_country),
("Current country", iv.current_country),
("What happened (their words)", iv.free_text_history
or (", ".join(iv.persecution_types) if iv.persecution_types else None)),
("In immediate danger", None if iv.immediate_danger is None else ("yes" if iv.immediate_danger else "no")),
("Time since leaving", iv.displacement_duration),
("Travelling with", iv.family_situation),
("Languages", ", ".join(iv.languages_spoken) if iv.languages_spoken else None),
("Seeking protection in", session.selected_country),
]
return "\n".join(f"- {k}: {v}" for k, v in rows if v)
async def draft_personal_statement(session, loop=None) -> str:
"""Draft the statement via the LLM. Returns plain text with [placeholders]."""
loop = loop or create_loop()
language = getattr(session, "language", None) or "English"
system_prompt = _SYSTEM.format(language=language)
prompt = (
"Draft my personal statement now using these facts:\n\n" + _facts(session)
+ "\n\nWrite it in " + language + "."
)
acc = ""
async for ev in loop.run(prompt, session=None, system_prompt=system_prompt, thinking_level="off"):
if isinstance(ev, TextDeltaEvent):
acc += ev.delta
elif isinstance(ev, ErrorEvent):
acc += "" # fall back to template below if the model failed
return acc.strip()
def fallback_statement(session) -> str:
"""Deterministic statement if the model is unavailable (no fabrication)."""
iv = session.interview
origin = iv.origin_country or "[country of origin]"
current = iv.current_country or "[current country]"
reason = iv.free_text_history or "[describe what happened that made you leave]"
return (
f"My name is [your full name]. I am a national of {origin}, born in [town] on [date of birth].\n\n"
f"I am currently in {current}.\n\n"
f"I had to leave {origin} because: {reason}\n\n"
"I fear that if I am returned I will be at serious risk of harm for the reasons described above.\n\n"
"I am asking for protection and the right not to be returned to a place where my life or freedom "
"would be threatened.\n\n"
"Signed: [your full name]\nDate: [date]"
)
__all__ = ["draft_personal_statement", "fallback_statement"]