File size: 10,377 Bytes
a8a231d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f90fc86
a8a231d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
"""Main entry point for the Scientific Content Generation Agent."""

import argparse
import asyncio
import contextlib
import logging
import os
import uuid

from google.adk.plugins.logging_plugin import LoggingPlugin
from google.adk.runners import Runner
from google.adk.sessions import DatabaseSessionService
from google.genai import types

from src.agents import create_content_generation_pipeline
from src.config import GOOGLE_API_KEY, LOG_FILE, LOG_LEVEL
from src.profile import (
    DEFAULT_PROFILE,
    PROFILE_DIR,
    PROFILE_PATH,
    load_user_profile,
    save_profile_to_yaml,
)
from src.profile_editor import edit_profile_interactive, validate_after_edit
from src.session_manager import delete_session, format_session_list, list_sessions


async def run_content_generation(topic: str, preferences: dict = None, session_id: str = None):
    """Run the content generation pipeline for a given topic.

    Args:
        topic: The research topic to generate content about
        preferences: Optional dict with user preferences:
            - platforms: List of platforms (default: ["blog", "linkedin", "twitter"])
            - tone: Preferred tone (default: "professional")
            - target_audience: Target audience description
            - max_papers: Maximum papers to search (default: 5)
        session_id: Optional session ID to resume a conversation

    Returns:
        Final content for all platforms
    """
    if not GOOGLE_API_KEY:
        raise ValueError(
            "GOOGLE_API_KEY not found. Please set it in .env file.\n"
            "Get your key from: https://aistudio.google.com/app/api_keys"
        )

    # Set environment variable
    os.environ["GOOGLE_API_KEY"] = GOOGLE_API_KEY

    # Load user profile
    profile = load_user_profile()
    print(f"πŸ‘€ Generating content for: {profile.name} ({profile.target_role})")

    # Create the agent pipeline
    print("\nπŸ€– Initializing Scientific Content Generation Agent...\n")
    agent = create_content_generation_pipeline()

    # Configure logging
    logging.basicConfig(
        level=getattr(logging, LOG_LEVEL),
        format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
        handlers=[
            logging.FileHandler(LOG_FILE),
            # logging.StreamHandler()  # Uncomment to see logs in console
        ],
    )

    # Initialize persistent session service
    # Note: PROFILE_DIR is created at module import in src/profile.py
    db_path = PROFILE_DIR / "sessions.db"
    db_url = f"sqlite:///{db_path}"
    session_service = DatabaseSessionService(db_url=db_url)

    # Create runner
    app_name = "scientific-content-agent"
    runner = Runner(
        agent=agent, app_name=app_name, session_service=session_service, plugins=[LoggingPlugin()]
    )

    # Generate or use provided session ID
    if not session_id:
        session_id = str(uuid.uuid4())
        print(f"πŸ†• Starting new session: {session_id}")
    else:
        print(f"πŸ”„ Resuming session: {session_id}")

    # Build the user message
    preferences = preferences or {}
    platforms = preferences.get("platforms", ["blog", "linkedin", "twitter"])
    tone = preferences.get("tone", profile.content_tone)
    audience = preferences.get("target_audience", "researchers and professionals")

    # Inject profile summary into the prompt
    profile_summary = profile.get_profile_summary()

    user_message = f"""Generate scientific content on the following topic: {topic}

Preferences:
- Target platforms: {", ".join(platforms)}
- Tone: {tone}
- Target audience: {audience}

User Profile Context:
{profile_summary}

Please create engaging, credible content that:
1. Incorporates recent research and academic sources
2. Builds professional credibility on LinkedIn
3. Demonstrates expertise in the field
4. Is suitable for scientific research monitoring
5. Aligns with the user's profile and expertise

Generate content for all three platforms: blog article, LinkedIn post, and Twitter thread.
"""

    print(f"πŸ“ Topic: {topic}")
    print(f"🎯 Target platforms: {', '.join(platforms)}")
    print(f"πŸ‘₯ Target audience: {audience}\n")
    print("=" * 80)
    print("\nπŸ”„ Running content generation pipeline...\n")
    print("Step 1: ResearchAgent - Searching for papers and current trends...")

    final_content = ""
    try:
        # Ensure session exists
        with contextlib.suppress(Exception):
            await session_service.create_session(
                app_name=app_name, user_id=profile.name, session_id=session_id
            )

        # Run the agent
        query = types.Content(role="user", parts=[types.Part(text=user_message)])

        async for event in runner.run_async(
            user_id=profile.name, session_id=session_id, new_message=query
        ):
            # Check for final content in state delta
            if (
                event.actions
                and event.actions.state_delta
                and "final_content" in event.actions.state_delta
            ):
                final_content = event.actions.state_delta["final_content"]

            # Also check if the model returned a text response (fallback)
            if event.content and event.content.parts:
                for part in event.content.parts:
                    if part.text:
                        # This might be intermediate thought or final answer depending on agent structure
                        # For now we rely on state_delta as per original design, but keep this as backup
                        pass

        if not final_content:
            final_content = "No content generated. Please check the logs."

        print("\nβœ… Content generation complete!\n")
        print("=" * 80)
        print("\nπŸ“„ GENERATED CONTENT:\n")
        print(final_content)
        print("\n" + "=" * 80)

        return final_content

    except Exception as e:
        print(f"\n❌ Error during content generation: {str(e)}")
        raise


async def main():
    """Main function to demonstrate the agent."""
    parser = argparse.ArgumentParser(description="Scientific Content Generation Agent")
    parser.add_argument(
        "--init-profile",
        action="store_true",
        help="Initialize a default user profile in ~/.agentic-content-generation/profile.yaml",
    )
    parser.add_argument(
        "--validate-profile",
        action="store_true",
        help="Validate the current profile and show warnings/errors",
    )
    parser.add_argument(
        "--edit-profile",
        action="store_true",
        help="Open profile in your default editor",
    )
    parser.add_argument(
        "--list-sessions",
        action="store_true",
        help="List all saved sessions",
    )
    parser.add_argument(
        "--delete-session",
        type=str,
        metavar="SESSION_ID",
        help="Delete a specific session by ID",
    )
    parser.add_argument(
        "--topic",
        type=str,
        default="Large Language Models and AI Agents",
        help="Topic to generate content about",
    )
    parser.add_argument(
        "--session-id",
        type=str,
        help="Session ID to resume a conversation",
    )
    args = parser.parse_args()

    print("\n" + "=" * 80)
    print("πŸ”¬ SCIENTIFIC CONTENT GENERATION AGENT")
    print("=" * 80)

    if args.init_profile:
        if PROFILE_PATH.exists():
            print(f"⚠️  Profile already exists at {PROFILE_PATH}")
            print("Edit this file to customize your profile.")
        else:
            save_profile_to_yaml(DEFAULT_PROFILE, PROFILE_PATH)
            print(f"βœ… Created default profile at {PROFILE_PATH}")
            print(
                "πŸ‘‰ Please edit this file with your personal information before running the agent."
            )
        return

    if args.validate_profile:
        print("\nπŸ” Validating profile...\n")
        try:
            profile = load_user_profile(validate=True)
            print("βœ… Profile validation complete!")
            if profile.name != "Your Name":
                print(f"πŸ‘€ Profile: {profile.name} ({profile.target_role})")
        except ValueError as e:
            print(f"\n❌ Validation failed: {e}")
            return
        return

    if args.edit_profile:
        print("\nπŸ“ Opening profile editor...\n")
        if not PROFILE_PATH.exists():
            print("⚠️  No profile found. Creating one first...")
            save_profile_to_yaml(DEFAULT_PROFILE, PROFILE_PATH)
            print(f"βœ… Created default profile at {PROFILE_PATH}\n")

        changed = edit_profile_interactive()
        if changed:
            # Validate after editing
            validate_after_edit()
        return

    if args.list_sessions:
        print("\nπŸ“‹ Listing all sessions...\n")
        sessions = list_sessions()
        if sessions:
            print(format_session_list(sessions))
            print(f"Total: {len(sessions)} session(s)")
            print("\nπŸ’‘ To resume a session: python main.py --session-id <SESSION_ID>")
            print("πŸ’‘ To delete a session: python main.py --delete-session <SESSION_ID>")
        else:
            print("No sessions found. Start a new conversation to create one!")
        return

    if args.delete_session:
        session_id_to_delete = args.delete_session
        print(f"\nπŸ—‘οΈ  Deleting session: {session_id_to_delete}...")
        result = delete_session(session_id_to_delete)
        if result["status"] == "success":
            print(f"βœ… {result['message']}")
        else:
            print(f"❌ {result['message']}")
        return

    # Example usage
    topic = args.topic
    session_id = args.session_id

    preferences = {
        "platforms": ["blog", "linkedin", "twitter"],
        # Tone is now loaded from profile by default
        "target_audience": "AI researchers and industry professionals",
    }

    result = await run_content_generation(topic, preferences, session_id)

    # Save output to file
    output_dir = "output"
    os.makedirs(output_dir, exist_ok=True)

    output_file = f"{output_dir}/content_{topic.replace(' ', '_').lower()}.txt"
    with open(output_file, "w", encoding="utf-8") as f:
        f.write(result)

    print(f"\nπŸ’Ύ Content saved to: {output_file}")
    print("\n✨ Done!")


if __name__ == "__main__":
    asyncio.run(main())