-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathcontentStrategistAgent.ts
More file actions
161 lines (147 loc) · 5 KB
/
contentStrategistAgent.ts
File metadata and controls
161 lines (147 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import { Agent } from '@mastra/core/agent'
import { pgMemory } from '../config/pg-storage'
import {
scrapingSchedulerTool,
} from '../tools/web-scraper-tool'
import { google, type GoogleGenerativeAIProviderOptions } from '@ai-sdk/google'
import { InternalSpans } from '@mastra/core/observability'
import { TokenLimiterProcessor } from '@mastra/core/processors'
import {
getUserTierFromContext,
USER_ID_CONTEXT_KEY,
type AgentRequestContext,
} from './request-context'
import {
createCompletenessScorer,
createTextualDifferenceScorer,
createToneScorer,
} from '../evals/scorers/prebuilt'
import { chartSupervisorTool } from '../tools/financial-chart-tools'
import { fetchTool } from '../tools/fetch.tool'
const STAGGERED_OUTPUT_CONTEXT_KEY = 'staggeredOutput' as const
const SECTION_COUNT_CONTEXT_KEY = 'sectionCount' as const
const STRATEGY_CONTEXT_KEY = 'strategy' as const
const BACKUP_DATA_TOOLS_CONTEXT_KEY = 'backupDataTools' as const
export type ContentAgentContext = AgentRequestContext<{
[STAGGERED_OUTPUT_CONTEXT_KEY]?: boolean
[SECTION_COUNT_CONTEXT_KEY]?: number
[STRATEGY_CONTEXT_KEY]?:
| 'iceberg'
| 'blue-ocean'
| 'structured'
| 'hybrid'
| 'custom'
[BACKUP_DATA_TOOLS_CONTEXT_KEY]?: string[]
}>
function getBooleanFromContext(
requestContext: { get: (key: string) => unknown },
key: string,
fallback: boolean
): boolean {
const value = requestContext.get(key)
return typeof value === 'boolean' ? value : fallback
}
function getNumberFromContext(
requestContext: { get: (key: string) => unknown },
key: string,
fallback: number
): number {
const value = requestContext.get(key)
return typeof value === 'number' ? value : fallback
}
function getStrategyFromContext(requestContext: {
get: (key: string) => unknown
}): NonNullable<ContentAgentContext[typeof STRATEGY_CONTEXT_KEY]> {
const strategy = requestContext.get(STRATEGY_CONTEXT_KEY)
return strategy === 'blue-ocean' ||
strategy === 'structured' ||
strategy === 'hybrid' ||
strategy === 'custom'
? strategy
: 'iceberg'
}
function getBackupDataToolsFromContext(requestContext: {
get: (key: string) => unknown
}): string[] {
const backupDataTools = requestContext.get(BACKUP_DATA_TOOLS_CONTEXT_KEY)
return Array.isArray(backupDataTools) && backupDataTools.every((tool) => typeof tool === 'string')
? backupDataTools
: ['chartSupervisorTool']
}
const contentStrategistTools = {
fetchTool,
chartSupervisorTool,
scrapingSchedulerTool,
google_search: google.tools.googleSearch({}),
url_context: google.tools.urlContext({}),
}
export const contentStrategistAgent = new Agent({
id: 'contentStrategistAgent',
name: 'Content Strategist',
description:
'Elite content strategist specializing in high-impact, data-driven content planning.',
instructions: ({ requestContext }) => {
const rawUserId = requestContext.get(USER_ID_CONTEXT_KEY)
const userId = typeof rawUserId === 'string' ? rawUserId : 'anonymous'
const userTier = getUserTierFromContext(requestContext)
const staggeredOutput = getBooleanFromContext(
requestContext,
STAGGERED_OUTPUT_CONTEXT_KEY,
false
)
const sectionCount = getNumberFromContext(
requestContext,
SECTION_COUNT_CONTEXT_KEY,
5
)
const strategy = getStrategyFromContext(requestContext)
const backupDataTools = getBackupDataToolsFromContext(requestContext)
return {
role: 'system',
content: `
# Content Strategist
User: ${userId} | Tier: ${userTier} | Style: ${strategy}
## Approach
1. **Research**: Use 'webScraperTool' for trends, audience, and competitors.
2. **Analyze**: Identify gaps and opportunities.
3. **Plan**: Objectives, audience, KPIs, and calendar (Staggered: ${staggeredOutput}, Sections: ${sectionCount}).
4. **Execute**: Use backup tools: ${backupDataTools.join(', ')}.
## Methodology
- **Iceberg**: Keywords → Gaps → Psych triggers (FOMO/Curiosity).
- **Blue Ocean**: Contrarian angles + hyper-specificity.
- **Structure**: Hook → Value stack → Open loops.
## Rules
- **Tool Efficiency**: Do NOT use the same tool repetitively or back-to-back for the same query.
- **Titles**: FOMO/Urgency triggers, 60 char max.
- **Output**: JSON only; always cite sources.
`,
providerOptions: {
google: {
thinkingConfig: {
includeThoughts: true,
thinkingBudget: -1,
},
mediaResolution: 'MEDIA_RESOLUTION_MEDIUM',
responseModalities: ['TEXT'],
} satisfies GoogleGenerativeAIProviderOptions,
},
}
},
model: 'google/gemini-3.1-flash-preview',
memory: pgMemory,
tools: contentStrategistTools,
options: {
tracingPolicy: {
internal: InternalSpans.ALL,
},
},
scorers: {
toneConsistency: { scorer: createToneScorer() },
textualDifference: { scorer: createTextualDifferenceScorer() },
completeness: { scorer: createCompletenessScorer() },
},
outputProcessors: [new TokenLimiterProcessor(1048576)],
//defaultOptions: {
// autoResumeSuspendedTools: true,
// },
})