Skip to content

Feature/con 118 campaign assistant assets - #69

Merged
grsmv merged 7 commits into
mainfrom
feature/con-118-campaign-assistant-assets
Jul 17, 2026
Merged

Feature/con 118 campaign assistant assets#69
grsmv merged 7 commits into
mainfrom
feature/con-118-campaign-assistant-assets

Conversation

@grsmv

@grsmv grsmv commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Lets the Campaign Assistant use a campaign's attached assets (PDFs, markdown, etc.) as a source for content generation, records which assets informed each post, and answers questions grounded in them (sub-issue of CON-112).

  1. Generate from attached assets — when a campaign has ready attached assets, "generate a content plan" / "add a few posts" now draws on them automatically. content_plan already injects each asset's excerpt into the generation prompt when UseAssets is on; the assistant now enables and persists UseAssets whenever the campaign has attached assets, so asset-sourced generation is triggerable straight from the chat (previously it only happened if UseAssets had been set elsewhere).
  2. Grounded asset→post bindingPost.UsedAssetIDs is set from the assets actually retrieved into context, not the model's self-reported (hallucination-prone) AssetRefs.
  3. ProvenancerunContentPlan / generatePosts report which assets informed the posts via a new assets_used SSE event and a line in the reply.
  4. Asset Q&A — new read tool askCampaignAssets answers questions grounded in the attached assets via pgvector chunk search.

What changed

campaign_assistant

  • ensureCampaignAssetUse: before generating, if the campaign has ready attached assets and UseAssets is off, turn it on and persist it (best-effort; falls back to asset-free generation on failure).
  • New assets_used SSE event; UsedAssets on tool results/outputs; prompt names used assets by title.
  • New askCampaignAssets tool (resolve ready assets → embed query → chunk search → titled/paged excerpts), degrading cleanly (never fails the turn).
  • FlowConfig gains Embedder; CampaignAssistantRepos gains Assets + Chunks; wired through server/.

content_plan

  • UsedAssetIDs grounded on the retrieved asset set (assetIDsOf); AssetRefs no longer persisted (kept as a prose-citation hint).
  • ContentPlanResponse.UsedAssets (id + title) surfaced from the retrieved pieces.

Decisions

  • Auto-use attached assets for generation, persisting UseAssets on first use — the assistant drives asset use from the chat (revises the earlier "respect campaign settings only" call).
  • Binding grounded against retrieved assets (reliability over per-post precision).
  • Report used assets only — silent when none.
  • Asset Q&A does not require UseAssets — an explicit request to consult the assets.

Testing

  • go build ./..., go vet, unit tests (assetIDsOf / assetRefsOf / pageRef) — green.
  • Integration (src/integration/campaign_assistant_tAPI_KEY): attach an asset with UseAssets off →generate → assert the assistant enabled + persisted UseAssets, every post's UsedAssetIDs is bound to the retrieved asset, and
    assets_used names it; plus an asset-question turn embedder.
  • Not exercised in-harness: the full askCampaignAssets RAG answer path (needs a Gemini embedder) — only its degradation is covered.

Commits

  • CON-118: ground content-plan asset→post binding on retrieved assets
  • `CON-118: report which assets informed generated p
  • CON-118: add askCampaignAssets Q&A tool over attached assets
  • `CON-118: integration tests for asset-grounded gen
  • CON-118: auto-generate content from attached campaign assets

Closes CON-118.

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features
    • Campaign Assistant can answer questions about attached campaign assets using relevant excerpts and page references.
    • Generated posts and content plans now report which campaign assets informed them.
  • Improvements
    • Real-time updates now include asset provenance via an assets_used streaming event.
    • Asset-grounded answers and generation handle missing/unavailable or unmatched assets gracefully, and cite “not found” when appropriate.
  • Tests
    • Added unit and end-to-end coverage for page citation formatting and asset usage behavior.

grsmv added 5 commits July 17, 2026 12:54
CON-112: Campaign Assistant — per-campaign chat over content_plan + enrich_brief
Post.UsedAssetIDs was set from the model's self-reported AssetRefs, which can
hallucinate or omit ids. Bind each generated post to the assets actually
retrieved into the generation context instead (both the full-plan and targeted
paths), and surface that set as ContentPlanResponse.UsedAssets (id+title) so the
campaign assistant can report provenance.

AssetRefs stays in the model output schema (it still nudges the prose to cite
sources) but no longer drives persistence. Add assetIDsOf/assetRefsOf helpers
(dedupe, order-preserving, skip empty) with unit tests; the persisted-binding
assertion is covered by the integration suite.
runContentPlan and generatePosts now read ContentPlanResponse.UsedAssets, emit a
new assets_used SSE event when non-empty, carry the assets on their tool result
and request-state result, and the prompt instructs the planner to name the used
assets by title in its explanation (and stay silent when none were used).
New read tool that answers questions grounded in a campaign's attached assets:
resolve the campaign's ready assets (AssetIDs, or all ready, excluding
failed/partial; UseAssets is not required for an explicit Q&A), embed the query,
and search their chunks via pgvector, returning the top excerpts (title + page)
for the planner to answer from. Degrades cleanly (available:false / a note) when
the embedder is unavailable, no assets are ready, or nothing matches — it never
fails the turn.

Wire Embedder + Assets/Chunks repos through the flow config and server, register
the tool, and document it in the prompt. Unit-test the page-citation helper.
Add campaign_assistant integration specs (require ANTHROPIC_API_KEY):
- asset-grounded content plan: seed a ready asset, attach it to the campaign,
  generate a plan, then assert every persisted post's UsedAssetIDs is bound to
  the retrieved asset and an assets_used event names it — validating the
  grounded binding + provenance via content_plan's creation-order fallback (no
  embedder needed).
- asset-question degradation: with no embedder wired, an asset question still
  completes the turn cleanly (askCampaignAssets degrades to unavailable).

Wire the asset + chunk repos into the assistant harness and set MaxContextAssets
so the creation-order fallback includes the attached asset.
@linear-code

linear-code Bot commented Jul 17, 2026

Copy link
Copy Markdown

CON-118

@grsmv

grsmv commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Campaign Assistant now answers questions from attached campaign assets, reports assets used in generated content, persists grounded asset IDs, emits asset usage events, and wires asset repositories and embedding support through the server runtime.

Changes

Campaign asset grounding

Layer / File(s) Summary
Asset contracts and runtime wiring
src/genkit/flows/campaign_assistant/types.go, src/genkit/flows/content_plan/types.go, src/server/..., src/integration/campaign_assistant_test.go
Adds asset provenance types, repository and embedder configuration, SSE payloads, and runtime dependency wiring.
Grounded asset provenance
src/genkit/flows/content_plan/assets.go, src/genkit/flows/content_plan/flow.go, src/genkit/flows/content_plan/generate.go, src/genkit/flows/campaign_assistant/tools.go
Derives retrieved-asset references, persists grounded asset IDs, and exposes asset usage in tool results and SSE events.
Attached-asset question answering
src/genkit/flows/campaign_assistant/tools.go, src/genkit/flows/campaign_assistant/run.go, src/genkit/flows/campaign_assistant/prompts/campaign_assistant.tmpl
Registers askCampaignAssets, embeds queries, searches ready chunks, returns cited excerpts, and defines unavailable/no-match behavior.
Asset behavior validation
src/genkit/flows/campaign_assistant/tools_test.go, src/genkit/flows/content_plan/assets_test.go, src/integration/campaign_assistant_test.go
Tests citation formatting, provenance filtering, emitted asset events, persisted asset IDs, and unavailable embedding behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CampaignAssistant
  participant askCampaignAssets
  participant Embedder
  participant AssetChunksRepository
  User->>CampaignAssistant: ask question about attached assets
  CampaignAssistant->>askCampaignAssets: invoke read-only asset search
  askCampaignAssets->>Embedder: embed query
  askCampaignAssets->>AssetChunksRepository: search ready asset chunks
  AssetChunksRepository-->>askCampaignAssets: return matching excerpts
  askCampaignAssets-->>CampaignAssistant: return excerpts with page citations
  CampaignAssistant-->>User: provide grounded explanation
Loading

Possibly related PRs

  • ogen-app/ogen#51: Adds embedding and vector-type support used by asset chunk similarity search.
  • ogen-app/ogen#61: Adds optional embedder availability handling used when asset search cannot run.
  • ogen-app/ogen#68: Establishes the Campaign Assistant flow and tool wiring extended by this change.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% 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 is related to the main change: campaign assistant asset support.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/con-118-campaign-assistant-assets

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/integration/campaign_assistant_test.go (1)

444-454: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that askCampaignAssets actually runs.

This passes if the model answers directly or hallucinates. Capture SSEEventToolCall and require the tool name to be askCampaignAssets.

Proposed test
 It("handles an asset question without failing the turn", func() {
+	var called bool
+	onEvent := campaign_assistant.OnEventFunc(func(name campaign_assistant.SSEEventKind, data any) {
+		if name == campaign_assistant.SSEEventToolCall {
+			if p, ok := data.(campaign_assistant.ToolCallEventPayload); ok {
+				called = called || p.Name == "askCampaignAssets"
+			}
+		}
+	})
 	resp, err := callback(ctx, campaign_assistant.CampaignAssistantRequest{
 		CampaignID:  campaignID,
 		Instruction: "What do the attached assets say about concurrency benchmarks?",
-	}, nil)
+	}, onEvent)
 	Expect(err).NotTo(HaveOccurred())
 	Expect(resp).NotTo(BeNil())
 	Expect(resp.Explanation).NotTo(BeEmpty())
+	Expect(called).To(BeTrue())
 })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/integration/campaign_assistant_test.go` around lines 444 - 454,
Strengthen the asset-question test around callback by capturing the emitted
SSEEventToolCall event and asserting that its tool name is askCampaignAssets.
Keep the existing successful-response assertions, but ensure the test fails when
the model answers directly without invoking the asset tool.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/genkit/flows/campaign_assistant/tools.go`:
- Around line 584-615: Update readyCampaignAssetIDs to resolve only assets
attached to the campaign and whose status is explicitly ready. When AssetIDs is
empty, do not call assets.List or include tenant-wide assets; use the campaign’s
attached asset relationship and filter by models.AssetStatusReady, while
preserving exclusion of failed, partial, pending, and unknown statuses.

---

Nitpick comments:
In `@src/integration/campaign_assistant_test.go`:
- Around line 444-454: Strengthen the asset-question test around callback by
capturing the emitted SSEEventToolCall event and asserting that its tool name is
askCampaignAssets. Keep the existing successful-response assertions, but ensure
the test fails when the model answers directly without invoking the asset tool.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d86ff836-ce4e-43e1-8766-9e807189a136

📥 Commits

Reviewing files that changed from the base of the PR and between c504016 and 15c834b.

📒 Files selected for processing (14)
  • src/genkit/flows/campaign_assistant/prompts/campaign_assistant.tmpl
  • src/genkit/flows/campaign_assistant/run.go
  • src/genkit/flows/campaign_assistant/tools.go
  • src/genkit/flows/campaign_assistant/tools_test.go
  • src/genkit/flows/campaign_assistant/types.go
  • src/genkit/flows/content_plan/assets.go
  • src/genkit/flows/content_plan/assets_test.go
  • src/genkit/flows/content_plan/flow.go
  • src/genkit/flows/content_plan/generate.go
  • src/genkit/flows/content_plan/types.go
  • src/integration/campaign_assistant_test.go
  • src/server/campaign_assistant.go
  • src/server/genkit_runtime.go
  • src/server/server.go

Comment thread src/genkit/flows/campaign_assistant/tools.go
The core capability: the assistant now generates content from the campaign's
attached assets even when UseAssets is off. Before runContentPlan /
generatePosts, if the campaign has ready attached assets, enable UseAssets and
persist it. content_plan already injects attached assets into the generation
prompt whenever UseAssets is true, so flipping (and persisting) the flag is all
that's needed — no content_plan change. Best-effort: a persist failure falls
back to asset-free generation.

This reverses the earlier "respect campaign settings only" scoping call, which
left asset-sourced generation un-triggerable from the chat.

Integration spec updated: attach an asset with UseAssets off, generate, and
assert the assistant enables + persists UseAssets and grounds the posts'
UsedAssetIDs on the retrieved asset.
Each post created via campaign_assistant -> content_plan recorded an
identical UsedAssetIDs containing every attached asset, since persistOne
stamped all posts with the run-wide grounded set. Bind each post instead
to the model's self-reported AssetRefs, filtered to the ids actually
retrieved into context so a hallucinated id never persists. A post that
cited nothing now records an empty list.

The plan-level UsedAssets (assets_used SSE event / tool output) stays the
retrieved superset — "assets offered to the model for this plan".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/genkit/flows/content_plan/assets.go (1)

148-151: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Fix unintended asset truncation.

If cfg.MaxContextAssets is uninitialized (0), this truncates candidateIDs to empty, excluding all fallback assets. Bypass the check if 0 means unlimited, or set a default.

🐛 Proposed fix
-	if len(candidateIDs) > cfg.MaxContextAssets {
+	if cfg.MaxContextAssets > 0 && len(candidateIDs) > cfg.MaxContextAssets {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/genkit/flows/content_plan/assets.go` around lines 148 - 151, Update the
asset truncation logic around candidateIDs and cfg.MaxContextAssets so a zero
MaxContextAssets value means unlimited and does not slice candidateIDs to empty.
Only append the exclusion warning and truncate when the configured limit is
positive and candidateIDs exceeds it.
🧹 Nitpick comments (1)
src/genkit/flows/content_plan/assets.go (1)

46-54: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove premature abstraction.

As per path instructions, avoid premature abstractions; three similar lines is better than a speculative helper. Inline this map construction at the call site.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/genkit/flows/content_plan/assets.go` around lines 46 - 54, Remove the
idSet helper and inline its map construction at the call site that builds the
retrieved-context grounding set. Preserve the existing capacity and
ID-to-empty-struct entries, and update the caller to use the inline map
directly.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/genkit/flows/content_plan/assets.go`:
- Around line 148-151: Update the asset truncation logic around candidateIDs and
cfg.MaxContextAssets so a zero MaxContextAssets value means unlimited and does
not slice candidateIDs to empty. Only append the exclusion warning and truncate
when the configured limit is positive and candidateIDs exceeds it.

---

Nitpick comments:
In `@src/genkit/flows/content_plan/assets.go`:
- Around line 46-54: Remove the idSet helper and inline its map construction at
the call site that builds the retrieved-context grounding set. Preserve the
existing capacity and ID-to-empty-struct entries, and update the caller to use
the inline map directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 380ac12c-f7de-479e-9b0e-aad95bfede65

📥 Commits

Reviewing files that changed from the base of the PR and between 15c834b and eb0edc9.

📒 Files selected for processing (6)
  • src/genkit/flows/campaign_assistant/tools.go
  • src/genkit/flows/content_plan/assets.go
  • src/genkit/flows/content_plan/assets_test.go
  • src/genkit/flows/content_plan/generate.go
  • src/genkit/flows/content_plan/types.go
  • src/integration/campaign_assistant_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/genkit/flows/content_plan/generate.go
  • src/genkit/flows/content_plan/types.go
  • src/integration/campaign_assistant_test.go
  • src/genkit/flows/campaign_assistant/tools.go

@grsmv
grsmv merged commit 1babfb9 into main Jul 17, 2026
6 checks passed
@grsmv
grsmv deleted the feature/con-118-campaign-assistant-assets branch July 31, 2026 12:44
@coderabbitai coderabbitai Bot mentioned this pull request Aug 6, 2026
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.

1 participant