Skip to content

feat: AB-938 - add citations to ask graph block#1272

Merged
neelasha-writer merged 2 commits intodevfrom
AB-938-feat-add-citations
Jan 26, 2026
Merged

feat: AB-938 - add citations to ask graph block#1272
neelasha-writer merged 2 commits intodevfrom
AB-938-feat-add-citations

Conversation

@neelasha-writer
Copy link
Collaborator

@neelasha-writer neelasha-writer commented Jan 26, 2026

Changes to response format when citations are not enabled to make sure existing agents don't break

Summary by CodeRabbit

  • Changes

    • Graph citations are now disabled by default when processing questions.
    • Result finalization is centralized for both streaming and non-streaming flows, yielding more consistent outputs.
  • Bug Fixes

    • Fixed state handling to prevent incomplete or duplicate results during streaming and non-streaming processing.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

📝 Walkthrough

Walkthrough

The WriterAskGraphQuestion block's graphCitations field default changed from "yes" to "no". Streaming and non-streaming flows were refactored: per-chunk state mutations and inline result emissions removed; state updates and final result assignment are centralized at end-of-processing and gated by graphCitations.

Changes

Cohort / File(s) Summary
WriterAskGraphQuestion Control Flow Refactor
src/writer/blocks/writeraskkg.py
Changed default for graphCitations from "yes" to "no". Consolidated streaming result handling into a new _parse_response helper; removed per-chunk state mutations and in-loop result emission. Non-streaming path now returns final answer or dict only when citations enabled; state updates occur at finalization.

Sequence Diagram(s)

(Skipped — changes are internal refactor + default change without multi-component sequential interaction requiring visualization.)

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • UladzislauK-Writer
  • madeindjs

Poem

🐰 I nibble on defaults, yes turned into no,
Streams hush their updates till the final show,
Citations wait still, answers dance bright,
State settles softly at the end of the night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main change: adding citations support to the ask graph block, which aligns with the primary objective of the PR.
Linked Issues check ✅ Passed The PR implements the core requirement from AB-938 to add citations support to the Ask graph question block, including new logic for handling citations in both streaming and non-streaming modes.
Out of Scope Changes check ✅ Passed All changes are focused on adding citations functionality to the WriterAskGraphQuestion block; no unrelated modifications outside the stated objective are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/writer/blocks/writeraskkg.py`:
- Around line 139-150: The code calls self._set_state(state_element, ...)
unconditionally which will fail when state_element is None; update the branches
around graph_citations to only call self._set_state if state_element is truthy
(e.g., if state_element: self._set_state(...)), otherwise skip the call but
still set self.result appropriately; ensure this check is applied in both the
graph_citations and non-graph_citations branches (references: _set_state,
state_element, result_dict, answer_so_far, graph_citations, self.result).

Comment on lines +139 to +150
answer_so_far = response.answer
result_dict["answer"] = answer_so_far


if graph_citations:
citations_so_far = response.sources or []
result_dict["citations"] = citations_so_far

self._set_state(state_element, result_dict)
self.result = answer_so_far
result_dict["answer"] = answer_so_far
result_dict["citations"] = response.sources or []

if graph_citations:
self._set_state(state_element, result_dict)
self.result = result_dict
else:
self._set_state(state_element, answer_so_far)
self.result = answer_so_far
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard _set_state when stateElement is absent.
Non-streaming allows stateElement to be omitted, but Line 145-150 now calls _set_state unconditionally, which will likely error when state_element is None. This breaks previously valid configurations.

✅ Suggested fix
-            if graph_citations:
-                self._set_state(state_element, result_dict)
-                self.result = result_dict
-            else: 
-                self._set_state(state_element, answer_so_far)
-                self.result = answer_so_far
+            if graph_citations:
+                if state_element:
+                    self._set_state(state_element, result_dict)
+                self.result = result_dict
+            else:
+                if state_element:
+                    self._set_state(state_element, answer_so_far)
+                self.result = answer_so_far
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
answer_so_far = response.answer
result_dict["answer"] = answer_so_far
if graph_citations:
citations_so_far = response.sources or []
result_dict["citations"] = citations_so_far
self._set_state(state_element, result_dict)
self.result = answer_so_far
result_dict["answer"] = answer_so_far
result_dict["citations"] = response.sources or []
if graph_citations:
self._set_state(state_element, result_dict)
self.result = result_dict
else:
self._set_state(state_element, answer_so_far)
self.result = answer_so_far
answer_so_far = response.answer
if graph_citations:
result_dict["answer"] = answer_so_far
result_dict["citations"] = response.sources or []
if graph_citations:
if state_element:
self._set_state(state_element, result_dict)
self.result = result_dict
else:
if state_element:
self._set_state(state_element, answer_so_far)
self.result = answer_so_far
🤖 Prompt for AI Agents
In `@src/writer/blocks/writeraskkg.py` around lines 139 - 150, The code calls
self._set_state(state_element, ...) unconditionally which will fail when
state_element is None; update the branches around graph_citations to only call
self._set_state if state_element is truthy (e.g., if state_element:
self._set_state(...)), otherwise skip the call but still set self.result
appropriately; ensure this check is applied in both the graph_citations and
non-graph_citations branches (references: _set_state, state_element,
result_dict, answer_so_far, graph_citations, self.result).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@UladzislauK-Writer @madeindjs I am a bit confused, I thought the definition of state element was not dependent on useStreaming, and was just used to update the agent state. I believe before this feature implementation, state element was used to set state regardless of whether streaming was enabled. Any insights into this suggestion would be awesome

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Collaborator

@UladzislauK-Writer UladzislauK-Writer Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, state_element was used regardless of use_streaming

This is talking about the fact that you can have use_streaming=False and state_element=None. In this case execution will reach these lines and calling self._set_state(None, ...) will (most likely) throw an error

@neelasha-writer

Comment on lines -134 to -135
self._set_state(state_element, result_dict)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this breaks the "Use streaming" functionality. The whole purpose of it is to update the state variable as we iterate over the response which is arriving over the network in multiple parts. With it the user can get more "AI-chatbot-like" experience when you are not waiting for the whole answer to appear at once but instead see it being generated

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@UladzislauK-Writer I see, so when streaming is enabled, I should include the set state inside the for loop?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/writer/blocks/writeraskkg.py`:
- Around line 150-151: The except block catching json.JSONDecodeError in
writeraskkg.py is incorrect because model_extra.get() doesn't parse JSON and
that exception will never be raised; change the handler to catch a broader
exception (e.g., Exception as e) around the section that accesses
model_extra.get(...) / graph.question and replace logging.error(...) with
logging.exception(...) so the traceback is captured and the error message
includes context (e.g., "Could not parse stream chunk from graph.question"). If
you determine the try/except is unnecessary (dead code), remove it instead.
Ensure you update the exception block associated with the
model_extra.get()/graph.question access in the same function where this diff
appears.
🧹 Nitpick comments (3)
src/writer/blocks/writeraskkg.py (3)

118-121: Minor redundancy in streaming mode.

For streaming, _set_state is called on every chunk inside _parse_response (lines 146, 148), then again here after the method returns. The final call is redundant for streaming since the last chunk already set the final state.

Consider gating this to avoid the duplicate call:

 self.result = self._parse_response(response, state_element, use_streaming, graph_citations)
-if state_element:
+if state_element and not use_streaming:
     self._set_state(state_element, self.result)

123-128: Use bare raise to preserve full traceback.

Using raise e can lose traceback context in some Python versions. A bare raise re-raises the current exception with its original traceback intact.

Suggested fix
         except BaseException as e:
             self.outcome = "error"
-            raise e
-            
-
+            raise

143-146: Use list as default for sources to ensure type safety.

If sources is missing from model_extra, the default "" (empty string) won't cause issues with .extend(), but using [] is more semantically correct and defensive against unexpected types.

-                    delta_sources = chunk.model_extra.get("sources", "")
+                    delta_sources = chunk.model_extra.get("sources", [])

Comment on lines +150 to +151
except json.JSONDecodeError:
logging.error("Could not parse stream chunk from graph.question")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Exception handler appears mismatched and should use logging.exception.

Two issues here:

  1. Wrong exception type: The try block doesn't perform any JSON parsing—model_extra.get() returns dict values without parsing. json.JSONDecodeError will never be raised. Consider catching a broader exception type or removing this handler if it's dead code.

  2. Use logging.exception: When logging from an exception handler, logging.exception automatically includes the traceback.

Suggested fix
-            except json.JSONDecodeError:
-                logging.error("Could not parse stream chunk from graph.question")
+            except Exception:
+                logging.exception("Could not process stream chunk from graph.question")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
except json.JSONDecodeError:
logging.error("Could not parse stream chunk from graph.question")
except Exception:
logging.exception("Could not process stream chunk from graph.question")
🧰 Tools
🪛 Ruff (0.14.14)

151-151: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

🤖 Prompt for AI Agents
In `@src/writer/blocks/writeraskkg.py` around lines 150 - 151, The except block
catching json.JSONDecodeError in writeraskkg.py is incorrect because
model_extra.get() doesn't parse JSON and that exception will never be raised;
change the handler to catch a broader exception (e.g., Exception as e) around
the section that accesses model_extra.get(...) / graph.question and replace
logging.error(...) with logging.exception(...) so the traceback is captured and
the error message includes context (e.g., "Could not parse stream chunk from
graph.question"). If you determine the try/except is unnecessary (dead code),
remove it instead. Ensure you update the exception block associated with the
model_extra.get()/graph.question access in the same function where this diff
appears.

@neelasha-writer neelasha-writer merged commit d1a4b4e into dev Jan 26, 2026
16 checks passed
neelasha-writer added a commit that referenced this pull request Jan 26, 2026
@neelasha-writer neelasha-writer deleted the AB-938-feat-add-citations branch January 26, 2026 20:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants