Vineetiitg commited on
Commit
8b2afda
·
1 Parent(s): 8c57ab1

feat: add answer confidence scoring

Browse files
Files changed (2) hide show
  1. app/graph/workflow.py +23 -9
  2. app/main.py +3 -1
app/graph/workflow.py CHANGED
@@ -17,6 +17,8 @@ class GraphState(TypedDict):
17
  documents: List[Document]
18
  sources: Optional[list[dict]]
19
  run_count: int
 
 
20
 
21
  llm = ChatOllama(model=settings.OLLAMA_MODEL, temperature=0, base_url=settings.OLLAMA_BASE_URL)
22
  llm_json = ChatOllama(model=settings.OLLAMA_MODEL, temperature=0, format="json", base_url=settings.OLLAMA_BASE_URL)
@@ -87,31 +89,41 @@ def decide_to_generate(state: GraphState):
87
  logger.info("ROUTE: RELEVANT DOCS FOUND")
88
  return "generate"
89
 
90
- def check_hallucinations(state: GraphState):
 
91
  documents = state["documents"]
92
  generation = state["generation"]
93
- run_count = state["run_count"]
94
 
95
- if run_count >= 3:
96
- logger.info("ROUTE: MAX RETRIES REACHED")
97
- return "end"
98
-
99
  context = build_context(documents)
100
  prompt = PromptTemplate(
101
  template="""You are evaluating whether a generated answer is fully grounded in the retrieved facts.
102
  Facts: \n\n {context} \n\n
103
  Answer: {generation} \n
104
  If the answer is supported by the facts, return 'yes'. If it contains hallucinations, return 'no'.
105
- Provide a JSON with a single key 'score' and value 'yes' or 'no'.""",
106
  input_variables=["context", "generation"],
107
  )
108
  grader = prompt | llm_json
109
 
110
  result = grader.invoke({"context": context, "generation": generation})
111
  try:
112
- grade = json.loads(result.content).get("score", "yes")
 
 
113
  except:
114
  grade = "yes"
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
  if grade.lower() == "yes":
117
  logger.info("ROUTE: GROUNDED")
@@ -124,8 +136,10 @@ def compile_workflow():
124
  workflow.add_node("retrieve", retrieve)
125
  workflow.add_node("grade_documents", grade_documents)
126
  workflow.add_node("generate", generate)
 
127
  workflow.add_edge(START, "retrieve")
128
  workflow.add_edge("retrieve", "grade_documents")
129
  workflow.add_conditional_edges("grade_documents", decide_to_generate, {"generate": "generate", "end": END})
130
- workflow.add_conditional_edges("generate", check_hallucinations, {"end": END, "regenerate": "generate"})
 
131
  return workflow.compile()
 
17
  documents: List[Document]
18
  sources: Optional[list[dict]]
19
  run_count: int
20
+ confidence_score: float
21
+ grounded: str
22
 
23
  llm = ChatOllama(model=settings.OLLAMA_MODEL, temperature=0, base_url=settings.OLLAMA_BASE_URL)
24
  llm_json = ChatOllama(model=settings.OLLAMA_MODEL, temperature=0, format="json", base_url=settings.OLLAMA_BASE_URL)
 
89
  logger.info("ROUTE: RELEVANT DOCS FOUND")
90
  return "generate"
91
 
92
+ def evaluate_answer(state: GraphState):
93
+ logger.info("NODE: EVALUATE ANSWER")
94
  documents = state["documents"]
95
  generation = state["generation"]
 
96
 
 
 
 
 
97
  context = build_context(documents)
98
  prompt = PromptTemplate(
99
  template="""You are evaluating whether a generated answer is fully grounded in the retrieved facts.
100
  Facts: \n\n {context} \n\n
101
  Answer: {generation} \n
102
  If the answer is supported by the facts, return 'yes'. If it contains hallucinations, return 'no'.
103
+ Provide a JSON with keys 'score' (yes/no) and 'confidence' (float 0.0-1.0).""",
104
  input_variables=["context", "generation"],
105
  )
106
  grader = prompt | llm_json
107
 
108
  result = grader.invoke({"context": context, "generation": generation})
109
  try:
110
+ parsed = json.loads(result.content)
111
+ grade = parsed.get("score", "yes")
112
+ confidence = float(parsed.get("confidence", 0.8))
113
  except:
114
  grade = "yes"
115
+ confidence = 0.5
116
+
117
+ return {"grounded": grade, "confidence_score": confidence}
118
+
119
+ def check_hallucinations(state: GraphState):
120
+ run_count = state["run_count"]
121
+
122
+ if run_count >= 3:
123
+ logger.info("ROUTE: MAX RETRIES REACHED")
124
+ return "end"
125
+
126
+ grade = state.get("grounded", "yes")
127
 
128
  if grade.lower() == "yes":
129
  logger.info("ROUTE: GROUNDED")
 
136
  workflow.add_node("retrieve", retrieve)
137
  workflow.add_node("grade_documents", grade_documents)
138
  workflow.add_node("generate", generate)
139
+ workflow.add_node("evaluate_answer", evaluate_answer)
140
  workflow.add_edge(START, "retrieve")
141
  workflow.add_edge("retrieve", "grade_documents")
142
  workflow.add_conditional_edges("grade_documents", decide_to_generate, {"generate": "generate", "end": END})
143
+ workflow.add_edge("generate", "evaluate_answer")
144
+ workflow.add_conditional_edges("evaluate_answer", check_hallucinations, {"end": END, "regenerate": "generate"})
145
  return workflow.compile()
app/main.py CHANGED
@@ -62,6 +62,7 @@ class ChatResponse(BaseModel):
62
  query: str
63
  answer: str
64
  sources: list[SourceCitation] = []
 
65
 
66
  class IngestionRequest(BaseModel):
67
  data_dir: str = "data/docs"
@@ -145,11 +146,12 @@ async def chat_endpoint(request: ChatRequest, http_request: Request, user: UserC
145
  final_state = rag_agent.invoke(initial_state)
146
  answer = redact_sensitive_data(final_state.get("generation", "Unable to compile answer."))
147
  sources = final_state.get("sources", [])
 
148
  except Exception as e:
149
  raise CopilotError(str(e), status_code=500)
150
 
151
  log_request_metrics(metrics, route="/chat", sources=len(sources), model=settings.OLLAMA_MODEL)
152
- return ChatResponse(query=request.query, answer=answer, sources=sources)
153
 
154
  @app.post("/chat/stream")
155
  async def chat_stream_endpoint(request: ChatRequest, http_request: Request, user: UserContext = Depends(resolve_user)):
 
62
  query: str
63
  answer: str
64
  sources: list[SourceCitation] = []
65
+ confidence: float = 0.0
66
 
67
  class IngestionRequest(BaseModel):
68
  data_dir: str = "data/docs"
 
146
  final_state = rag_agent.invoke(initial_state)
147
  answer = redact_sensitive_data(final_state.get("generation", "Unable to compile answer."))
148
  sources = final_state.get("sources", [])
149
+ confidence = final_state.get("confidence_score", 0.0)
150
  except Exception as e:
151
  raise CopilotError(str(e), status_code=500)
152
 
153
  log_request_metrics(metrics, route="/chat", sources=len(sources), model=settings.OLLAMA_MODEL)
154
+ return ChatResponse(query=request.query, answer=answer, sources=sources, confidence=confidence)
155
 
156
  @app.post("/chat/stream")
157
  async def chat_stream_endpoint(request: ChatRequest, http_request: Request, user: UserContext = Depends(resolve_user)):