XQ commited on
Commit
3a792db
·
1 Parent(s): 3c8edba

Remove thinking in executor and Planner

Browse files
Files changed (2) hide show
  1. src/agent/plan_and_execute.py +1 -1
  2. src/ui/app.py +154 -150
src/agent/plan_and_execute.py CHANGED
@@ -41,7 +41,6 @@ _MAX_STEPS = 6
41
  # ------------------------------------------------------------------
42
 
43
  _PLANNER_PROMPT = (
44
- "/no_think\n"
45
  "You are a planning assistant for the University of Copenhagen (KU) document system.\n\n"
46
  "Given a user question, produce a JSON list of 1–4 steps needed to answer it.\n"
47
  "Each step is an object with:\n"
@@ -71,6 +70,7 @@ _PLANNER_PROMPT = (
71
  )
72
 
73
  _EXECUTOR_SYSTEM = (
 
74
  "You are executing ONE step of a plan to answer a user's question about "
75
  "University of Copenhagen (KU) documents.\n\n"
76
  "You have retrieval tools available. Execute the step described below, "
 
41
  # ------------------------------------------------------------------
42
 
43
  _PLANNER_PROMPT = (
 
44
  "You are a planning assistant for the University of Copenhagen (KU) document system.\n\n"
45
  "Given a user question, produce a JSON list of 1–4 steps needed to answer it.\n"
46
  "Each step is an object with:\n"
 
70
  )
71
 
72
  _EXECUTOR_SYSTEM = (
73
+ "/no_think\n"
74
  "You are executing ONE step of a plan to answer a user's question about "
75
  "University of Copenhagen (KU) documents.\n\n"
76
  "You have retrieval tools available. Execute the step described below, "
src/ui/app.py CHANGED
@@ -617,6 +617,145 @@ st.markdown(
617
  # Subtitle placeholder — filled after we know whether search was clicked
618
  _subtitle_slot = st.empty()
619
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
  # ---------------------------------------------------------------------------
621
  # Search form
622
  # ---------------------------------------------------------------------------
@@ -838,161 +977,26 @@ if search_clicked and question.strip():
838
  st.error(f'{t["err_api"]}: {_sse_error.get("message", "")}')
839
  st.stop()
840
 
841
- # -- Metadata bar --
842
- confidence = data.get("confidence", 0.0)
843
- intent = data.get("intent", t["unknown"])
844
- confidence_pct = f"{confidence * 100:.0f}%"
845
-
846
- st.markdown(
847
- f'<div class="result-meta">'
848
- f'{t["confidence_label"]}: <strong>{confidence_pct}</strong> &nbsp;&middot;&nbsp; '
849
- f'{t["intent_label"]}: <strong>{intent}</strong> &nbsp;&middot;&nbsp; '
850
- f'{t["strategy_label"]}: <strong>{strategy}</strong> &nbsp;&middot;&nbsp; '
851
- f"top_k: <strong>{top_k}</strong>"
852
- f"</div>",
853
- unsafe_allow_html=True,
854
- )
855
-
856
- # -- Answer --
857
- answer = data.get("answer", t["no_answer"])
858
- st.markdown(answer)
859
-
860
- # -- Sources --
861
- sources = data.get("sources", [])
862
- if sources:
863
- with st.expander(f'{t["sources_label"]} ({len(sources)})', expanded=False):
864
- for src in sources:
865
- doc_name = src.get("document_id", src.get("chunk_id", t["unknown"]))
866
- text = src.get("text", "")
867
- score = src.get("score", 0.0)
868
- retrieval_source = src.get("source", "")
869
- metadata = src.get("metadata", {})
870
- page = metadata.get("page_number", "") if isinstance(metadata, dict) else ""
871
-
872
- page_info = f' &middot; {t["page_label"]} {page}' if page else ""
873
- score_display = f"{score:.3f}"
874
-
875
- st.markdown(
876
- f'<div class="source-card">'
877
- f'<div class="source-card-title">{html.escape(doc_name)}{page_info}</div>'
878
- f'<div class="source-card-text">{html.escape(text[:500])}</div>'
879
- f'<div class="source-card-meta">'
880
- f"Score: {score_display} &nbsp;&middot;&nbsp; {html.escape(retrieval_source)}"
881
- f"</div>"
882
- f"</div>",
883
- unsafe_allow_html=True,
884
- )
885
- else:
886
- st.info(t["no_sources"])
887
-
888
- # -- Pipeline Details --
889
- pd = data.get("pipeline_details", {})
890
- if pd:
891
- with st.expander(t["pipeline_heading"], expanded=False):
892
- # 0) Plan steps and tool calls (Plan-and-Execute mode)
893
- plan_steps = pd.get("plan_steps", [])
894
- if plan_steps:
895
- st.markdown(f'**{t["pipeline_plan_steps"]}**')
896
- for i, step in enumerate(plan_steps, 1):
897
- st.markdown(f"{i}. {step}")
898
- st.markdown("---")
899
-
900
- tool_calls = pd.get("tool_calls", [])
901
- if tool_calls:
902
- st.markdown(f'**{t["pipeline_tool_calls"]}**')
903
- for tc in tool_calls:
904
- st.markdown(f"- `{tc}`")
905
- st.markdown("---")
906
-
907
- # 1) Query translation (only show if translation actually happened)
908
- if pd.get("translated"):
909
- st.markdown(f'**{t["pipeline_translation"]}**')
910
- st.markdown(
911
- f'- {t["pipeline_lang"]}: **{pd.get("detected_language", "")}**\n'
912
- f'- {t["pipeline_original"]}: {pd.get("original_query", "")}\n'
913
- f'- {t["pipeline_translated"]}: {pd.get("retrieval_query", "")}'
914
- )
915
- st.markdown("---")
916
-
917
- def _truncate_doc(name: str, max_len: int = 30) -> str:
918
- """Truncate long document names for table display."""
919
- return name if len(name) <= max_len else name[:max_len - 1] + "…"
920
-
921
- def _render_result_table(results: list[dict], label: str) -> None:
922
- """Render a ranked results table."""
923
- st.markdown(f"**{label}**")
924
- if not results:
925
- st.caption(t["pipeline_no_results"])
926
- return
927
- header = f'| {t["pipeline_rank"]} | {t["pipeline_doc"]} | {t["pipeline_score"]} |\n|---|---|---|'
928
- rows = "\n".join(
929
- f'| {i + 1} | {_truncate_doc(r.get("document_id", ""))} | {r.get("score", 0):.4f} |'
930
- for i, r in enumerate(results)
931
- )
932
- st.markdown(f"{header}\n{rows}")
933
-
934
- _has_retrieval = bool(
935
- pd.get("dense_results") or pd.get("sparse_results") or pd.get("fused_results")
936
- )
937
-
938
- if _has_retrieval:
939
- # 2) BM25 results
940
- _render_result_table(pd.get("sparse_results", []), t["pipeline_bm25"])
941
-
942
- st.markdown("---")
943
-
944
- # 3) Vector search results
945
- _render_result_table(pd.get("dense_results", []), t["pipeline_dense"])
946
-
947
- st.markdown("---")
948
-
949
- # 4) RRF fused ranking
950
- _render_result_table(pd.get("fused_results", []), t["pipeline_fused"])
951
 
952
- st.markdown("---")
953
-
954
- # 5) Reranked / fetched results
955
- reranked = pd.get("reranked_results", [])
956
- st.markdown(f'**{t["pipeline_reranked"]}**')
957
- if reranked:
958
- if _has_retrieval:
959
- # Show score change from RRF → reranking
960
- fused_scores: dict[str, float] = {
961
- r.get("chunk_id", ""): r.get("score", 0.0)
962
- for r in pd.get("fused_results", [])
963
- }
964
- header = (
965
- f'| {t["pipeline_rank"]} | {t["pipeline_doc"]} | '
966
- f'{t["pipeline_score"]} | {t["pipeline_score_change"]} |\n'
967
- f"|---|---|---|---|"
968
- )
969
- rows_list = []
970
- for i, r in enumerate(reranked):
971
- cid = r.get("chunk_id", "")
972
- new_score = r.get("score", 0.0)
973
- old_score = fused_scores.get(cid)
974
- if old_score is not None:
975
- change = f"RRF {old_score:.4f} -> {new_score:.4f}"
976
- else:
977
- change = "-"
978
- rows_list.append(
979
- f'| {i + 1} | {_truncate_doc(r.get("document_id", ""))} | {new_score:.4f} | {change} |'
980
- )
981
- st.markdown(f"{header}\n" + "\n".join(rows_list))
982
- else:
983
- # No hybrid search was used (e.g. fetch_document only) — simple table
984
- header = f'| {t["pipeline_rank"]} | {t["pipeline_doc"]} | {t["pipeline_score"]} |\n|---|---|---|'
985
- rows = "\n".join(
986
- f'| {i + 1} | {_truncate_doc(r.get("document_id", ""))} | {r.get("score", 0):.4f} |'
987
- for i, r in enumerate(reranked)
988
- )
989
- st.markdown(f"{header}\n{rows}")
990
- else:
991
- st.caption(t["pipeline_no_results"])
992
 
993
  elif search_clicked:
994
  st.warning(t["empty_warning"])
995
 
 
 
 
 
 
 
 
 
 
996
  # ---------------------------------------------------------------------------
997
  # Footer
998
  # ---------------------------------------------------------------------------
 
617
  # Subtitle placeholder — filled after we know whether search was clicked
618
  _subtitle_slot = st.empty()
619
 
620
+ # ---------------------------------------------------------------------------
621
+ # Result rendering (extracted so it can be reused for cached results)
622
+ # ---------------------------------------------------------------------------
623
+ def _render_results(data: dict, t: dict, strategy: str, top_k: int) -> None:
624
+ """Render query results: metadata bar, answer, sources, pipeline details."""
625
+ confidence = data.get("confidence", 0.0)
626
+ intent = data.get("intent", t["unknown"])
627
+ confidence_pct = f"{confidence * 100:.0f}%"
628
+
629
+ st.markdown(
630
+ f'<div class="result-meta">'
631
+ f'{t["confidence_label"]}: <strong>{confidence_pct}</strong> &nbsp;&middot;&nbsp; '
632
+ f'{t["intent_label"]}: <strong>{intent}</strong> &nbsp;&middot;&nbsp; '
633
+ f'{t["strategy_label"]}: <strong>{strategy}</strong> &nbsp;&middot;&nbsp; '
634
+ f"top_k: <strong>{top_k}</strong>"
635
+ f"</div>",
636
+ unsafe_allow_html=True,
637
+ )
638
+
639
+ answer = data.get("answer", t["no_answer"])
640
+ st.markdown(answer)
641
+
642
+ sources = data.get("sources", [])
643
+ if sources:
644
+ with st.expander(f'{t["sources_label"]} ({len(sources)})', expanded=False):
645
+ for src in sources:
646
+ doc_name = src.get("document_id", src.get("chunk_id", t["unknown"]))
647
+ text = src.get("text", "")
648
+ score = src.get("score", 0.0)
649
+ retrieval_source = src.get("source", "")
650
+ metadata = src.get("metadata", {})
651
+ page = metadata.get("page_number", "") if isinstance(metadata, dict) else ""
652
+
653
+ page_info = f' &middot; {t["page_label"]} {page}' if page else ""
654
+ score_display = f"{score:.3f}"
655
+
656
+ st.markdown(
657
+ f'<div class="source-card">'
658
+ f'<div class="source-card-title">{html.escape(doc_name)}{page_info}</div>'
659
+ f'<div class="source-card-text">{html.escape(text[:500])}</div>'
660
+ f'<div class="source-card-meta">'
661
+ f"Score: {score_display} &nbsp;&middot;&nbsp; {html.escape(retrieval_source)}"
662
+ f"</div>"
663
+ f"</div>",
664
+ unsafe_allow_html=True,
665
+ )
666
+ else:
667
+ st.info(t["no_sources"])
668
+
669
+ pd_details = data.get("pipeline_details", {})
670
+ if pd_details:
671
+ with st.expander(t["pipeline_heading"], expanded=False):
672
+ plan_steps = pd_details.get("plan_steps", [])
673
+ if plan_steps:
674
+ st.markdown(f'**{t["pipeline_plan_steps"]}**')
675
+ for i, step_item in enumerate(plan_steps, 1):
676
+ st.markdown(f"{i}. {step_item}")
677
+ st.markdown("---")
678
+
679
+ tool_calls = pd_details.get("tool_calls", [])
680
+ if tool_calls:
681
+ st.markdown(f'**{t["pipeline_tool_calls"]}**')
682
+ for tc in tool_calls:
683
+ st.markdown(f"- `{tc}`")
684
+ st.markdown("---")
685
+
686
+ if pd_details.get("translated"):
687
+ st.markdown(f'**{t["pipeline_translation"]}**')
688
+ st.markdown(
689
+ f'- {t["pipeline_lang"]}: **{pd_details.get("detected_language", "")}**\n'
690
+ f'- {t["pipeline_original"]}: {pd_details.get("original_query", "")}\n'
691
+ f'- {t["pipeline_translated"]}: {pd_details.get("retrieval_query", "")}'
692
+ )
693
+ st.markdown("---")
694
+
695
+ def _truncate_doc(name: str, max_len: int = 30) -> str:
696
+ return name if len(name) <= max_len else name[:max_len - 1] + "\u2026"
697
+
698
+ def _render_result_table(results: list[dict], label: str) -> None:
699
+ st.markdown(f"**{label}**")
700
+ if not results:
701
+ st.caption(t["pipeline_no_results"])
702
+ return
703
+ header = f'| {t["pipeline_rank"]} | {t["pipeline_doc"]} | {t["pipeline_score"]} |\n|---|---|---|'
704
+ rows = "\n".join(
705
+ f'| {i + 1} | {_truncate_doc(r.get("document_id", ""))} | {r.get("score", 0):.4f} |'
706
+ for i, r in enumerate(results)
707
+ )
708
+ st.markdown(f"{header}\n{rows}")
709
+
710
+ _has_retrieval = bool(
711
+ pd_details.get("dense_results") or pd_details.get("sparse_results") or pd_details.get("fused_results")
712
+ )
713
+
714
+ if _has_retrieval:
715
+ _render_result_table(pd_details.get("sparse_results", []), t["pipeline_bm25"])
716
+ st.markdown("---")
717
+ _render_result_table(pd_details.get("dense_results", []), t["pipeline_dense"])
718
+ st.markdown("---")
719
+ _render_result_table(pd_details.get("fused_results", []), t["pipeline_fused"])
720
+ st.markdown("---")
721
+
722
+ reranked = pd_details.get("reranked_results", [])
723
+ st.markdown(f'**{t["pipeline_reranked"]}**')
724
+ if reranked:
725
+ if _has_retrieval:
726
+ fused_scores: dict[str, float] = {
727
+ r.get("chunk_id", ""): r.get("score", 0.0)
728
+ for r in pd_details.get("fused_results", [])
729
+ }
730
+ header = (
731
+ f'| {t["pipeline_rank"]} | {t["pipeline_doc"]} | '
732
+ f'{t["pipeline_score"]} | {t["pipeline_score_change"]} |\n'
733
+ f"|---|---|---|---|"
734
+ )
735
+ rows_list = []
736
+ for i, r in enumerate(reranked):
737
+ cid = r.get("chunk_id", "")
738
+ new_score = r.get("score", 0.0)
739
+ old_score = fused_scores.get(cid)
740
+ if old_score is not None:
741
+ change = f"RRF {old_score:.4f} -> {new_score:.4f}"
742
+ else:
743
+ change = "-"
744
+ rows_list.append(
745
+ f'| {i + 1} | {_truncate_doc(r.get("document_id", ""))} | {new_score:.4f} | {change} |'
746
+ )
747
+ st.markdown(f"{header}\n" + "\n".join(rows_list))
748
+ else:
749
+ header = f'| {t["pipeline_rank"]} | {t["pipeline_doc"]} | {t["pipeline_score"]} |\n|---|---|---|'
750
+ rows = "\n".join(
751
+ f'| {i + 1} | {_truncate_doc(r.get("document_id", ""))} | {r.get("score", 0):.4f} |'
752
+ for i, r in enumerate(reranked)
753
+ )
754
+ st.markdown(f"{header}\n{rows}")
755
+ else:
756
+ st.caption(t["pipeline_no_results"])
757
+
758
+
759
  # ---------------------------------------------------------------------------
760
  # Search form
761
  # ---------------------------------------------------------------------------
 
977
  st.error(f'{t["err_api"]}: {_sse_error.get("message", "")}')
978
  st.stop()
979
 
980
+ # Cache result in session_state so it survives iOS tombstone / reconnect
981
+ st.session_state["last_result"] = data
982
+ st.session_state["last_question"] = question.strip()
983
+ st.session_state["last_strategy"] = strategy
984
+ st.session_state["last_top_k"] = top_k
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
985
 
986
+ _render_results(data, t, strategy, top_k)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
987
 
988
  elif search_clicked:
989
  st.warning(t["empty_warning"])
990
 
991
+ elif "last_result" in st.session_state:
992
+ # Restore cached results after iOS tombstone / reconnect
993
+ _render_results(
994
+ st.session_state["last_result"],
995
+ t,
996
+ st.session_state.get("last_strategy", strategy),
997
+ st.session_state.get("last_top_k", top_k),
998
+ )
999
+
1000
  # ---------------------------------------------------------------------------
1001
  # Footer
1002
  # ---------------------------------------------------------------------------