Spaces:
Sleeping
Sleeping
Vineetiitg commited on
Commit ·
8b2afda
1
Parent(s): 8c57ab1
feat: add answer confidence scoring
Browse files- app/graph/workflow.py +23 -9
- 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
|
|
|
|
| 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
|
| 106 |
input_variables=["context", "generation"],
|
| 107 |
)
|
| 108 |
grader = prompt | llm_json
|
| 109 |
|
| 110 |
result = grader.invoke({"context": context, "generation": generation})
|
| 111 |
try:
|
| 112 |
-
|
|
|
|
|
|
|
| 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.
|
|
|
|
| 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)):
|