ashe0042 commited on
Commit
e2f6f30
·
1 Parent(s): d53dec4

Config 5: grounded/citation-forced RAG pipeline complete

Browse files
Files changed (1) hide show
  1. src/configs/grounded.py +94 -0
src/configs/grounded.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Config 5: grounded / citation-forced RAG. Model must quote the provision
3
+ verbatim and cite the paragraph ID, and refuse if unsupported.
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from dotenv import load_dotenv
11
+ from openai import OpenAI
12
+
13
+ sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
14
+
15
+ from retrieval import embed_query, rrf_search
16
+
17
+ load_dotenv()
18
+
19
+ GENERATION_MODEL = "gpt-4o"
20
+ CONFIG_NAME = "grounded"
21
+ UNSUPPORTED_PREFIX = "UNSUPPORTED:"
22
+
23
+ OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
24
+
25
+ client = OpenAI(api_key=OPENAI_API_KEY)
26
+
27
+ SYSTEM_PROMPT = (
28
+ "You are a precise legal research assistant for Australian financial regulation.\n"
29
+ "You must answer using ONLY the provided context, following these strict rules:\n"
30
+ "1. For every obligation or legal requirement you state, you MUST include:\n"
31
+ " a) The exact verbatim quote from the provision (in quotation marks)\n"
32
+ " b) The citation in format [source | paragraph_id]\n"
33
+ "2. Do NOT paraphrase obligations — quote them exactly as written.\n"
34
+ "3. If the provided context does not contain sufficient information to answer "
35
+ "the question with verbatim quotes, respond with exactly:\n"
36
+ "'UNSUPPORTED: The retrieved context does not contain sufficient information "
37
+ "to answer this question with required citations.'\n"
38
+ "4. Never invent, infer, or extrapolate obligations not explicitly stated "
39
+ "in the provided context.\n"
40
+ "The source name must be the exact source identifier from the context "
41
+ "(e.g. corporations_act_2001, cps234), not the word 'source'."
42
+ )
43
+
44
+
45
+ def run(query: str, source_filter: list[str] | None = None) -> dict:
46
+ query_embedding = embed_query(query)
47
+ retrieved_chunks = rrf_search(query, query_embedding, source_filter=source_filter, top_k=5)
48
+
49
+ context = "\n".join(
50
+ f"[{chunk['source']} | {chunk['paragraph_id']}]\n{chunk['text']}\n"
51
+ for chunk in retrieved_chunks
52
+ )
53
+
54
+ user_prompt = context + "\n\nQuestion: " + query
55
+
56
+ response = client.chat.completions.create(
57
+ model=GENERATION_MODEL,
58
+ messages=[
59
+ {"role": "system", "content": SYSTEM_PROMPT},
60
+ {"role": "user", "content": user_prompt},
61
+ ],
62
+ )
63
+ answer = response.choices[0].message.content
64
+ unsupported = answer.strip().startswith(UNSUPPORTED_PREFIX)
65
+
66
+ return {
67
+ "query": query,
68
+ "answer": answer,
69
+ "retrieved_chunks": [
70
+ {
71
+ "source": chunk["source"],
72
+ "paragraph_id": chunk["paragraph_id"],
73
+ "text": chunk["text"][:200],
74
+ "rrf_score": chunk["rrf_score"],
75
+ "dense_rank": chunk["dense_rank"],
76
+ "bm25_rank": chunk["bm25_rank"],
77
+ }
78
+ for chunk in retrieved_chunks
79
+ ],
80
+ "model": GENERATION_MODEL,
81
+ "config_name": CONFIG_NAME,
82
+ "unsupported": unsupported,
83
+ }
84
+
85
+
86
+ if __name__ == "__main__":
87
+ smoke_query = "What are the general obligations of a financial services licensee?"
88
+
89
+ result = run(smoke_query)
90
+
91
+ print(result["answer"])
92
+ print("\nRetrieved paragraph IDs:")
93
+ for chunk in result["retrieved_chunks"]:
94
+ print(f"- [{chunk['source']}] {chunk['paragraph_id']}")