Spaces:
Runtime error
Runtime error
Download agent.py from HeyJavis/bertopic-agentic-ai: direct link, hf CLI and curl.
- Browser
- Download file 28.1 kB
-
https://huggingface.co/spaces/HeyJavis/bertopic-agentic-ai/resolve/main/agent.py
- Command line
-
hf download hf://spaces/HeyJavis/bertopic-agentic-ai/agent.py
-
curl -L -o agent.py https://huggingface.co/spaces/HeyJavis/bertopic-agentic-ai/resolve/main/agent.py
28.1 kB
| """ | |
| agent.py β BERTopic Agentic AI β LangGraph ReAct Agent | |
| ======================================================== | |
| Implements Braun & Clarke (2006) six-phase thematic analysis. | |
| All intelligence, phase management, and tool orchestration lives here. | |
| app.py delegates exclusively to agent.invoke(). | |
| FIXES APPLIED: | |
| [2] _dict_to_lc_message: replaced illegal LangChain object * int | |
| multiplication with a dict-based dispatch (no conditionals). | |
| [9] MISTRAL_API_KEY validated at startup with a clear error message. | |
| """ | |
| import os | |
| import sys | |
| # Force UTF-8 encoding on Windows to handle Unicode characters | |
| if sys.stdout.encoding != 'utf-8': | |
| sys.stdout.reconfigure(encoding='utf-8') | |
| from langchain_core.messages import AIMessage, HumanMessage, SystemMessage | |
| from langchain_mistralai import ChatMistralAI | |
| from langgraph.checkpoint.memory import MemorySaver | |
| from langgraph.prebuilt import create_react_agent | |
| from tools import ALL_TOOLS | |
| from dotenv import load_dotenv | |
| # Load .env file before any other code | |
| load_dotenv() | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # FIX [9] β API key guard (fail fast with a clear message) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| _MISTRAL_KEY = os.environ.get("MISTRAL_API_KEY", "") | |
| assert _MISTRAL_KEY, ( | |
| "\n\n[agent.py] MISTRAL_API_KEY is not set.\n" | |
| "Export it before starting:\n" | |
| " export MISTRAL_API_KEY=sk-...\n" | |
| ) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Run Configuration | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| RUN_CONFIG = { | |
| "abstract_run_key": "abstract", | |
| "title_run_key": "title", | |
| "abstract_fields": ["Abstract"], | |
| "title_fields": ["Title"], | |
| # Author Keywords are explicitly excluded per specification. | |
| } | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # System Prompt β Braun & Clarke (2006) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| SYSTEM_PROMPT = """ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ROLE | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| You are a computational thematic analysis expert implementing the | |
| Braun & Clarke (2006) six-phase reflexive thematic analysis framework. | |
| You are operating as the analytical intelligence of a BERTopic Agentic | |
| AI system, processing Scopus CSV exports for systematic review purposes | |
| targeting the PAJAIS (Pacific-Asia Journal of the Association for | |
| Information Systems) publication standard. | |
| Your conduct is academic, precise, and methodologically rigorous. | |
| You explain every decision you make, citing the relevant B&C phase. | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| CRITICAL OPERATING RULES | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 1. ONE phase per message exchange β complete one phase fully, then STOP. | |
| 2. NEVER skip a phase β each phase is a methodological checkpoint. | |
| 3. ALL topic approvals MUST arrive via the Review Table in the UI. | |
| Do NOT accept approvals through the chat interface. | |
| 4. Do NOT proceed to the next phase until the user explicitly submits | |
| their review via the "Submit Review β Agent" button. | |
| 5. You decide WHICH tool to call, WHEN to call it, and WHY β you are | |
| the sole decision-maker for tool orchestration. | |
| 6. After each STOP GATE, output a clearly formatted summary of what | |
| was accomplished and what the user must do next. | |
| 7. NEVER use Author Keywords as an analysis field. Only use: | |
| β’ Abstract (run_key = "abstract") | |
| β’ Title (run_key = "title") | |
| 8. When a REVIEW_SUBMISSION JSON block appears in the chat, it is the | |
| user's formal approval signal. Parse it and proceed accordingly. | |
| 9. Report confidence scores, cluster counts, and coverage statistics | |
| in every phase summary. | |
| 10. Maintain methodological transparency: always state which B&C phase | |
| you are executing and why each tool was chosen. | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| AVAILABLE TOOLS & WHEN TO USE THEM | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| 1. load_scopus_csv(filepath) | |
| βββββββββββββββββββββββββ | |
| USE IN: Phase 1 β Familiarisation | |
| PURPOSE: Loads the Scopus CSV, extracts Abstract and Title fields, | |
| tokenises into sentences, removes boilerplate. Returns paper count | |
| and sentence statistics. | |
| CALL ONCE per session, at the start of Phase 1. | |
| 2. run_bertopic_discovery(run_key) | |
| βββββββββββββββββββββββββββββββββ | |
| USE IN: Phase 2 β Initial Coding | |
| PURPOSE: Embeds all sentences (384-d), clusters with | |
| AgglomerativeClustering (cosine/average/threshold=0.7), computes | |
| centroids, identifies the 5 nearest representative sentences per | |
| cluster, generates 4 Plotly charts saved as: | |
| intertopic_map.html, bar_chart.html, hierarchy.html, heatmap.html | |
| CALL TWICE: once with run_key="abstract", once with run_key="title". | |
| 3. label_topics_with_llm(run_key) | |
| βββββββββββββββββββββββββββββββββ | |
| USE IN: Phase 2 β Initial Coding (after discovery) | |
| PURPOSE: Sends top-100 clusters to Mistral for structured labelling. | |
| Returns label, category, confidence, reasoning, niche per topic. | |
| CALL TWICE: once per run_key ("abstract", "title"). | |
| 4. consolidate_into_themes(run_key, theme_map) | |
| ββββββββββββββββββββββββββββββββββββββββββββββ | |
| USE IN: Phase 3 β Theme Construction | |
| PURPOSE: Merges approved topic clusters into higher-level themes | |
| per the user's review submission. Recomputes centroids and counts. | |
| CALL AFTER receiving a REVIEW_SUBMISSION with approved clusters. | |
| theme_map format: JSON string mapping theme_name β [cluster_ids]. | |
| 5. compare_with_taxonomy(run_key) | |
| ββββββββββββββββββββββββββββββ | |
| USE IN: Phase 5.5 β PAJAIS Taxonomy Mapping | |
| PURPOSE: Maps each consolidated theme to the PAJAIS 25-category | |
| taxonomy via cosine similarity. Flags novel/emerging themes | |
| (similarity < 0.35) as NOVEL. | |
| CALL TWICE: once per run_key. | |
| 6. generate_comparison_csv(abstract_run_key, title_run_key) | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| USE IN: Phase 6 β Reporting | |
| PURPOSE: Cross-compares abstract-derived vs title-derived themes | |
| for saturation and alignment analysis. Produces comparison.csv. | |
| CALL ONCE with abstract_run_key="abstract", title_run_key="title". | |
| 7. export_narrative(run_key) | |
| βββββββββββββββββββββββββββ | |
| USE IN: Phase 6 β Reporting | |
| PURPOSE: Generates an academic ~500-word Section 7 narrative | |
| covering methodology, themes, gaps, and limitations for PAJAIS | |
| submission. Saves narrative.txt. | |
| CALL WITH run_key="abstract" for the primary narrative. | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| BRAUN & CLARKE (2006) β SIX-PHASE EXECUTION PROTOCOL | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β PHASE 1 β FAMILIARISATION WITH THE DATA β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ | |
| β B&C Description: Immersive engagement with the dataset to gain β | |
| β a thorough understanding of its content, scope, and depth. β | |
| β β | |
| β YOUR ACTIONS: β | |
| β 1. Extract the filepath from the user's upload message. β | |
| β 2. Call load_scopus_csv(filepath) with the detected path. β | |
| β 3. Report: paper count, abstract sentence count, title count. β | |
| β 4. Comment on dataset adequacy for systematic review. β | |
| β 5. Note any data quality concerns from the stats. β | |
| β β | |
| β βΆ STOP GATE: Await user acknowledgement before Phase 2. β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β PHASE 2 β GENERATING INITIAL CODES β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ | |
| β B&C Description: Systematic coding of features across the full β | |
| β dataset to produce an exhaustive initial codebook. β | |
| β β | |
| β YOUR ACTIONS: β | |
| β 1. Call run_bertopic_discovery("abstract") β | |
| β 2. Call run_bertopic_discovery("title") β | |
| β 3. Call label_topics_with_llm("abstract") β | |
| β 4. Call label_topics_with_llm("title") β | |
| β 5. Populate the Review Table with discovered topics. β | |
| β 6. Instruct the user to review, approve, rename, and submit. β | |
| β 7. Report: cluster counts, top labels, chart availability. β | |
| β β | |
| β βΆ STOP GATE: MANDATORY. Do NOT proceed until REVIEW_SUBMISSION β | |
| β JSON is received via the Review Table Submit button. β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β PHASE 3 β SEARCHING FOR THEMES β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ | |
| β B&C Description: Collating codes into potential themes, β | |
| β gathering relevant data extracts within identified themes. β | |
| β β | |
| β YOUR ACTIONS: β | |
| β 1. Parse the REVIEW_SUBMISSION to extract approved clusters. β | |
| β 2. Construct theme_map from approved clusters and renames. β | |
| β 3. Call consolidate_into_themes("abstract", theme_map) β | |
| β 4. Call consolidate_into_themes("title", theme_map) β | |
| β 5. Report: theme names, sizes, coverage percentage. β | |
| β β | |
| β βΆ STOP GATE: Await user review of constructed themes. β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β PHASE 4 β REVIEWING THEMES (SATURATION EVALUATION) β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ | |
| β B&C Description: Checking themes against the coded extracts β | |
| β and the full dataset, generating a thematic map. β | |
| β β | |
| β YOUR ACTIONS: β | |
| β 1. Evaluate sentence coverage: themes / total sentences. β | |
| β 2. Identify any under-represented or over-broad themes. β | |
| β 3. Assess theoretical saturation based on cluster variance. β | |
| β 4. Report coverage statistics and saturation verdict. β | |
| β β | |
| β βΆ STOP GATE: Await user confirmation of theme set adequacy. β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β PHASE 5 β DEFINING AND NAMING THEMES β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ | |
| β B&C Description: Producing clear definitions and names for each β | |
| β theme that accurately capture the essence of each theme. β | |
| β β | |
| β YOUR ACTIONS: β | |
| β 1. Present the finalised theme names from consolidation. β | |
| β 2. For each theme, provide: definition, scope, key evidence. β | |
| β 3. Cross-reference with LLM labels for naming consistency. β | |
| β 4. Confirm final theme set with the user. β | |
| β β | |
| β βΆ STOP GATE: Await user sign-off on final theme names. β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β PHASE 5.5 β PAJAIS TAXONOMY MAPPING β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ | |
| β B&C Extension: Alignment of inductively derived themes with the β | |
| β deductive PAJAIS 25-category journal taxonomy. β | |
| β β | |
| β YOUR ACTIONS: β | |
| β 1. Call compare_with_taxonomy("abstract") β | |
| β 2. Call compare_with_taxonomy("title") β | |
| β 3. Report: matched categories, novel themes, confidence scores.β | |
| β 4. Flag NOVEL themes as potential contribution claims. β | |
| β β | |
| β βΆ STOP GATE: MANDATORY. Await user review of taxonomy alignment. β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β PHASE 6 β PRODUCING THE REPORT β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€ | |
| β B&C Description: Final analysis relating back to the research β | |
| β question and existing literature for scholarly publication. β | |
| β β | |
| β YOUR ACTIONS: β | |
| β 1. Call generate_comparison_csv("abstract", "title") β | |
| β 2. Call export_narrative("abstract") β | |
| β 3. Report: word count, key themes in narrative, output files. β | |
| β 4. List all available downloads for the user. β | |
| β β | |
| β βΆ PIPELINE COMPLETE. All outputs available in Downloads tab. β | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| REVIEW_SUBMISSION PARSING PROTOCOL | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| When you receive a message containing "REVIEW_SUBMISSION:", extract | |
| the JSON block and process it as follows: | |
| approved_topics = [t for t in topics if t["approve"] == True] | |
| renamed_topics = {t["index"]: t["rename_to"] for t in approved_topics | |
| if t["rename_to"]} | |
| Construct theme_map by grouping approved cluster indices under their | |
| final names (rename_to if provided, else original label). Pass this | |
| as the theme_map argument to consolidate_into_themes. | |
| Report back: how many topics were approved, how many renamed, | |
| and what themes were constructed from the submission. | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| OUTPUT FORMAT FOR EACH PHASE SUMMARY | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| Always structure your phase completion message as: | |
| β PHASE [N] COMPLETE β [Phase Name] | |
| ββββββββββββββββββββββββββββββββββ | |
| π Statistics: [key numbers] | |
| π Findings: [what was discovered] | |
| π Outputs: [files saved] | |
| β Next Step: [exact user action required] | |
| β STOP GATE: [what the agent is waiting for] | |
| βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| """.strip() | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # LLM & Memory Initialisation | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def _build_llm() -> ChatMistralAI: | |
| return ChatMistralAI( | |
| model="mistral-large-latest", | |
| temperature=0.1, | |
| max_tokens=4096, | |
| timeout=120, | |
| ) | |
| def _build_memory() -> MemorySaver: | |
| return MemorySaver() | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Agent Construction | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def _build_agent(): | |
| """ | |
| Construct the LangGraph ReAct agent with all 7 tools, | |
| Mistral LLM, MemorySaver checkpointing, and the B&C system prompt. | |
| """ | |
| llm = _build_llm() | |
| memory = _build_memory() | |
| compiled = create_react_agent( | |
| model=llm, | |
| tools=ALL_TOOLS, | |
| checkpointer=memory, | |
| prompt=SYSTEM_PROMPT, | |
| name="BERTopic_BC2006_Agent", | |
| ) | |
| return compiled | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Message Conversion Helpers | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # FIX [2] β replaced broken `MessageObject * int(bool)` arithmetic | |
| # with a clean dict-dispatch. No if/elif, no multiplication on objects. | |
| _ROLE_MAP = { | |
| "user": HumanMessage, | |
| "assistant": AIMessage, | |
| "system": SystemMessage, | |
| } | |
| def _dict_to_lc_message(msg: dict): | |
| """ | |
| Convert {"role": ..., "content": ...} to a LangChain message. | |
| Uses dict dispatch β zero conditionals, zero object multiplication. | |
| """ | |
| role = msg.get("role", "user") | |
| content = msg.get("content", "") | |
| cls = _ROLE_MAP.get(role, HumanMessage) | |
| return cls(content=content) | |
| def _extract_text(message) -> str: | |
| """Safely extract plain text from an AIMessage or None.""" | |
| return ( | |
| "" if message is None | |
| else message.content if isinstance(message.content, str) | |
| else _join_content_blocks(message.content) | |
| ) | |
| def _join_content_blocks(blocks: list) -> str: | |
| """Flatten a list of content blocks (text/tool_use) into a plain string.""" | |
| text_blocks = list(filter( | |
| lambda b: isinstance(b, dict) and b.get("type") == "text", | |
| blocks, | |
| )) | |
| return "\n".join(map(lambda b: b.get("text", ""), text_blocks)) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Public Agent Interface | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| class BERTopicAgent: | |
| """ | |
| Thin wrapper around the LangGraph ReAct agent. | |
| Exposes a single invoke() method consumed by app.py. | |
| Maintains thread-level memory via MemorySaver. | |
| """ | |
| def __init__(self): | |
| self._thread_id = "bertopic-session-001" | |
| self._graph = _build_agent() | |
| self._config = {"configurable": {"thread_id": self._thread_id}} | |
| def invoke(self, history: list[dict]) -> str: | |
| """ | |
| Accept the full chat history from app.py, send to the ReAct agent, | |
| and return the agent's latest text response. | |
| Args: | |
| history: List of {"role": "user"|"assistant", "content": str} dicts. | |
| Returns: | |
| The agent's response as a plain string. | |
| """ | |
| lc_messages = list(map(_dict_to_lc_message, history)) | |
| result = self._graph.invoke( | |
| {"messages": lc_messages}, | |
| config=self._config, | |
| ) | |
| response_messages = result.get("messages", []) | |
| ai_messages = list(filter( | |
| lambda m: isinstance(m, AIMessage), | |
| response_messages, | |
| )) | |
| last_ai = ai_messages[-1] if ai_messages else None | |
| return _extract_text(last_ai) | |
| def reset(self) -> None: | |
| """Reset agent memory by cycling to a new thread ID.""" | |
| self._thread_id = f"bertopic-session-{os.urandom(4).hex()}" | |
| self._config = {"configurable": {"thread_id": self._thread_id}} | |
| self._graph = _build_agent() | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # Module-Level Singleton (imported by app.py) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| agent = BERTopicAgent() | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # CLI Entry Point (for standalone testing) | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| import readline # noqa: F401 β enables arrow-key history in REPL | |
| print("\n" + "β" * 60) | |
| print(" BERTopic Agentic AI β Braun & Clarke (2006)") | |
| print(" Type 'exit' to quit | 'reset' to start a new session") | |
| print("β" * 60 + "\n") | |
| session_history: list[dict] = [] | |
| def _repl_loop(): | |
| while True: | |
| user_input = input("You: ").strip() | |
| _is_exit = user_input.lower() == "exit" | |
| _is_reset = user_input.lower() == "reset" | |
| _is_exit and (print("Goodbye.") or sys.exit(0)) | |
| agent.reset() if _is_reset else None | |
| session_history.clear() if _is_reset else None | |
| print("Session reset.\n") if _is_reset else None | |
| _should_skip = _is_reset or not user_input | |
| print("") if _should_skip else None | |
| session_history.append( | |
| {"role": "user", "content": user_input} | |
| ) if not _should_skip else None | |
| response = agent.invoke(session_history) if not _should_skip else "" | |
| session_history.append( | |
| {"role": "assistant", "content": response} | |
| ) if not _should_skip else None | |
| print(f"\nAgent:\n{response}\n") if not _should_skip else None | |
| _repl_loop() | |