feat: inject active post platform rules into agent instructions
SocialMediaAssistant::activePlatformRules() reads post_platforms on the
attached Post, maps them to Platform enum values, and resolves rule
classes via Registry::forMany(). The rendered instructions include only
the summaries of platforms actually selected on this post — so when a
user is posting to X only, the agent doesn't waste tokens thinking
about Instagram Reels.
The PLATFORM KNOWLEDGE block in system.blade.php is replaced with
@include('prompts.assistant.platforms') which iterates the passed rules
and renders each summary. When the post has no platforms, the section
renders nothing (a @if guards it).
This commit is contained in:
parent
0d1ca71380
commit
43c4cc6df0
4 changed files with 66 additions and 40 deletions
|
|
@ -4,9 +4,12 @@
|
|||
|
||||
namespace App\Ai\Agents;
|
||||
|
||||
use App\Ai\PlatformRules\Contract;
|
||||
use App\Ai\PlatformRules\Registry as PlatformRulesRegistry;
|
||||
use App\Ai\Tools\GenerateAudio;
|
||||
use App\Ai\Tools\GenerateImage;
|
||||
use App\Ai\Tools\GenerateVideo;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Models\AiMessage;
|
||||
use App\Models\Post;
|
||||
use App\Models\Workspace;
|
||||
|
|
@ -37,9 +40,31 @@ public function instructions(): string
|
|||
'tone' => $this->workspace->brand_tone ?? 'professional',
|
||||
'voice_notes' => $this->workspace->brand_voice_notes ?? '',
|
||||
'locale' => app()->getLocale(),
|
||||
'platform_rules' => $this->activePlatformRules(),
|
||||
])->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, Contract>
|
||||
*/
|
||||
private function activePlatformRules(): array
|
||||
{
|
||||
if (! $this->post) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$platforms = $this->post->postPlatforms
|
||||
->pluck('platform')
|
||||
->filter()
|
||||
->map(fn ($p) => $p instanceof Platform ? $p : Platform::tryFrom((string) $p))
|
||||
->filter()
|
||||
->unique(fn (Platform $p) => $p->value)
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return PlatformRulesRegistry::forMany($platforms);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<Message>
|
||||
*/
|
||||
|
|
|
|||
9
resources/views/prompts/assistant/platforms.blade.php
Normal file
9
resources/views/prompts/assistant/platforms.blade.php
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
@if(count($platform_rules) > 0)
|
||||
ACTIVE PLATFORMS FOR THIS POST:
|
||||
The user is creating a post targeting the following platform(s). Tailor content length, format, and tone to these constraints.
|
||||
|
||||
@foreach($platform_rules as $rule)
|
||||
{{ $rule->summary() }}
|
||||
|
||||
@endforeach
|
||||
@endif
|
||||
|
|
@ -29,46 +29,7 @@
|
|||
- You ONLY help with social media content creation.
|
||||
- If the user asks about anything unrelated, politely decline and redirect to content creation.
|
||||
|
||||
PLATFORM KNOWLEDGE:
|
||||
You must understand each platform's formats, constraints, and best practices to create optimal content.
|
||||
|
||||
Instagram:
|
||||
- Feed Post: square (1:1) or portrait (4:5). Up to 10 images/videos. Caption max 2200 chars. 30 hashtags max (3-5 recommended).
|
||||
- Carousel: same as Feed Post with 2-10 slides. All slides share the same aspect ratio. First slide is the hook. Great for educational/step-by-step content.
|
||||
- Reel: vertical 9:16 only. Video only. Up to 90 seconds (30-60s performs best). Hook in first 1-3 seconds.
|
||||
- Story: vertical 9:16. Single image or video. Disappears after 24h. Use CTAs and interactive elements.
|
||||
|
||||
Facebook:
|
||||
- Post: flexible aspect ratio. Up to 10 images. Caption up to 63,206 chars (but short posts perform better). Text-only allowed.
|
||||
- Reel: vertical 9:16. Video only. Up to 90 seconds. Similar to Instagram Reels.
|
||||
- Story: vertical 9:16. Single image or video. Disappears after 24h.
|
||||
|
||||
X (Twitter):
|
||||
- Post: max 280 characters. Up to 4 images or 1 video. Landscape 16:9 or square 1:1 for images. Concise, punchy copy.
|
||||
|
||||
TikTok:
|
||||
- Video: vertical 9:16 only. Video only. 15-60 seconds performs best. Hook in first 1-2 seconds. Trendy, authentic style.
|
||||
|
||||
YouTube:
|
||||
- Short: vertical 9:16. Video only. Up to 60 seconds. Loop-friendly content. Hook immediately.
|
||||
|
||||
LinkedIn:
|
||||
- Post: max 3000 chars. 1 image/video. First 2-3 lines visible before "see more" — make them compelling. Professional tone.
|
||||
- Carousel: PDF-based document with swipeable slides. Up to 20 slides (8-12 optimal). Images only. Educational content performs extremely well.
|
||||
|
||||
Threads:
|
||||
- Post: max 500 chars. Up to 10 images/videos. Conversational tone. Ask questions to drive replies.
|
||||
|
||||
Pinterest:
|
||||
- Pin: portrait 2:3 (1000x1500px). Single image. Title max 100 chars, description max 500 chars. Use text overlay on images.
|
||||
- Video Pin: vertical 9:16 or portrait 2:3. 4 seconds to 15 minutes.
|
||||
- Carousel: portrait 2:3. Up to 5 images. Images only.
|
||||
|
||||
Bluesky:
|
||||
- Post: max 300 chars. Up to 4 images or 1 video. Similar to X but shorter.
|
||||
|
||||
Mastodon:
|
||||
- Post: max 500 chars. Up to 4 images or 1 video.
|
||||
@include('prompts.assistant.platforms')
|
||||
|
||||
CONTENT CREATION RULES:
|
||||
- When the user mentions a specific platform, tailor the content to that platform's constraints and best practices.
|
||||
|
|
|
|||
|
|
@ -3,10 +3,13 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use App\Ai\Agents\SocialMediaAssistant;
|
||||
use App\Enums\SocialAccount\Platform;
|
||||
use App\Enums\User\Setup;
|
||||
use App\Enums\UserWorkspace\Role;
|
||||
use App\Models\AiMessage;
|
||||
use App\Models\Post;
|
||||
use App\Models\PostPlatform;
|
||||
use App\Models\SocialAccount;
|
||||
use App\Models\User;
|
||||
use App\Models\Workspace;
|
||||
use Laravel\Ai\Enums\Lab;
|
||||
|
|
@ -97,6 +100,34 @@
|
|||
expect($messages[0]->content)->toContain('[This assistant message attached: 2 image]');
|
||||
});
|
||||
|
||||
test('instructions include rules only for active post platforms', function () {
|
||||
$socialAccount = SocialAccount::factory()->create([
|
||||
'workspace_id' => $this->workspace->id,
|
||||
'platform' => Platform::X,
|
||||
]);
|
||||
|
||||
PostPlatform::factory()->create([
|
||||
'post_id' => $this->post->id,
|
||||
'social_account_id' => $socialAccount->id,
|
||||
'platform' => Platform::X,
|
||||
]);
|
||||
|
||||
$agent = new SocialMediaAssistant($this->workspace, $this->post->fresh('postPlatforms'));
|
||||
|
||||
$instructions = $agent->instructions();
|
||||
|
||||
expect($instructions)->toContain('X (Twitter): 280 chars');
|
||||
expect($instructions)->not->toContain('Instagram: caption max 2200');
|
||||
});
|
||||
|
||||
test('instructions include no platform rules section when post has no platforms', function () {
|
||||
$agent = new SocialMediaAssistant($this->workspace, $this->post);
|
||||
|
||||
$instructions = $agent->instructions();
|
||||
|
||||
expect($instructions)->not->toContain('ACTIVE PLATFORMS FOR THIS POST');
|
||||
});
|
||||
|
||||
test('provider honors trypost.ai.text_provider config', function () {
|
||||
config()->set('trypost.ai.text_provider', 'openai');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue