drewgenai commited on
Commit
d799a95
·
1 Parent(s): 3462136

agent and tool change csv download

Browse files
Files changed (2) hide show
  1. .gitignore +1 -0
  2. app.py +191 -225
.gitignore CHANGED
@@ -4,6 +4,7 @@ __pycache__/
4
  .env
5
  /data
6
  /output/
 
7
  /upload/
8
  /uploads/
9
  *.jsonl
 
4
  .env
5
  /data
6
  /output/
7
+ /outputs/
8
  /upload/
9
  /uploads/
10
  *.jsonl
app.py CHANGED
@@ -1,4 +1,6 @@
1
  import os
 
 
2
  from typing import Annotated, Literal
3
  from typing_extensions import TypedDict
4
 
@@ -30,10 +32,10 @@ from langchain_core.output_parsers import StrOutputParser
30
  # Load environment variables
31
  load_dotenv()
32
  #####langsmith
33
- import uuid
34
- os.environ["LANGCHAIN_PROJECT"] = f"HEAL-SYNC - {uuid.uuid4().hex[0:8]}"
35
- os.environ["LANGCHAIN_TRACING_V2"] = "true"
36
- print(os.environ["LANGCHAIN_PROJECT"])
37
  ###########langsmith
38
 
39
 
@@ -366,120 +368,86 @@ def search_all_data(query: str, doc_type: str = None) -> str:
366
  return f"Error searching data: {str(e)}"
367
 
368
  @tool
369
- def search_core_reference(query: str, top_k: int = 3) -> str:
370
- """Search core reference data and protocol data for information related to the query."""
371
- global embedding_model, global_qdrant_client
372
 
373
- # Create a retriever for the core embeddings
374
- core_retriever = QdrantVectorStore(
375
- client=global_qdrant_client,
376
- collection_name=INITIAL_EMBEDDINGS_NAME,
377
- embedding=embedding_model
378
- ).as_retriever(search_kwargs={"k": top_k})
379
-
380
- # Create a retrieval chain for core documents
381
- core_retrieval_chain = (
382
- {"context": itemgetter("question") | core_retriever | format_docs,
383
- "question": itemgetter("question")}
384
- | rag_prompt
385
- | chat_model
386
- | StrOutputParser()
387
- )
388
 
389
- # Get results from core reference
390
- result = core_retrieval_chain.invoke({"question": query})
391
 
392
- # Get the session-specific Qdrant client
393
- session_qdrant_client = cl.user_session.get("session_qdrant_client")
394
- if not session_qdrant_client:
395
- return result # Return only core results if no session client
396
 
397
- # If we have a user collection, also search that
398
- try:
399
- # Check if user collection exists
400
- if USER_EMBEDDINGS_NAME not in [c.name for c in session_qdrant_client.get_collections().collections]:
401
- # If no user collection exists yet, just return core reference results
402
- return result
403
-
404
- # Create a retrieval chain for user documents
405
- user_retriever = QdrantVectorStore(
406
- client=session_qdrant_client,
407
- collection_name=USER_EMBEDDINGS_NAME,
408
- embedding=embedding_model
409
- ).as_retriever(search_kwargs={"k": top_k})
410
-
411
- user_retrieval_chain = (
412
- {"context": itemgetter("question") | user_retriever | format_docs,
413
- "question": itemgetter("question")}
414
- | rag_prompt
415
- | chat_model
416
- | StrOutputParser()
417
- )
418
-
419
- user_result = user_retrieval_chain.invoke({"question": query})
420
 
421
- # Combine results
422
- return f"From core reference files:\n{result}\n\nFrom your uploaded PDF:\n{user_result}"
423
- except Exception as e:
424
- print(f"Error searching user vector store: {str(e)}")
425
- # If error occurs, just return core reference results
426
- return result
427
-
428
- @tool
429
- def load_and_embed_protocol(file_path: str = None) -> str:
430
- """Load and embed a protocol PDF file into the vector store."""
431
- # Get the session-specific Qdrant client
432
- session_qdrant_client = cl.user_session.get("session_qdrant_client")
433
- if not session_qdrant_client:
434
- return "No session-specific Qdrant client found. Please restart the chat."
435
 
436
- # If no specific file path is provided, use all PDFs in the upload directory
437
- if not file_path:
438
- uploaded_files = [f for f in os.listdir(UPLOAD_PATH) if f.endswith('.pdf')]
439
- if not uploaded_files:
440
- return "No protocol documents found in the upload directory."
441
-
442
- # Create file objects for processing
443
- files = []
444
- for filename in uploaded_files:
445
- file_path = os.path.join(UPLOAD_PATH, filename)
446
- # Create a simple object with the necessary attributes
447
- class FileObj:
448
- def __init__(self, path, name, size):
449
- self.path = path
450
- self.name = name
451
- self.size = size
452
-
453
- file_size = os.path.getsize(file_path)
454
- files.append(FileObj(file_path, filename, file_size))
455
- else:
456
- # Create a file object for the specific file
457
- if not os.path.exists(file_path):
458
- return f"File not found: {file_path}"
459
-
460
- filename = os.path.basename(file_path)
461
- file_size = os.path.getsize(file_path)
462
 
463
- class FileObj:
464
- def __init__(self, path, name, size):
465
- self.path = path
466
- self.name = name
467
- self.size = size
468
 
469
- files = [FileObj(file_path, filename, file_size)]
470
-
471
- # Process the files asynchronously
472
- import asyncio
473
- documents_with_metadata = asyncio.run(load_and_chunk_protocol_files(files))
474
- user_vectorstore = asyncio.run(embed_protocol_in_qdrant(documents_with_metadata, session_qdrant_client, EMBEDDING_MODEL_ID))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
475
 
476
- if user_vectorstore:
477
- return f"Successfully embedded {len(documents_with_metadata)} chunks from {len(files)} protocol document(s)."
478
- else:
479
- return "Failed to embed protocol document(s)."
480
 
481
- @tool
482
- def search_protocol_for_instruments(domain: str) -> dict:
483
  """Search the protocol for instruments related to a specific NIH HEAL CDE core domain."""
484
  global embedding_model
485
 
@@ -545,108 +513,115 @@ def search_protocol_for_instruments(domain: str) -> dict:
545
  print(f"Error identifying instrument for {domain}: {str(e)}")
546
  return {"domain": domain, "instrument": "Error during identification", "context": str(e)}
547
 
548
- @tool
549
- def analyze_protocol_domains() -> list:
550
- """Analyze all NIH HEAL CDE core domains and identify instruments used in the protocol."""
551
- # Check if protocol document exists
552
- uploaded_files = [f for f in os.listdir(UPLOAD_PATH) if f.endswith('.pdf')]
553
- if not uploaded_files:
554
- return "No protocol document has been uploaded yet."
555
-
556
- # For each domain, search for relevant instruments
557
- domain_analysis_results = []
558
 
559
- for domain in NIH_HEAL_CORE_DOMAINS:
560
- # Use the search_protocol_for_instruments tool to get results for each domain
561
- result = search_protocol_for_instruments(domain)
562
- print(f"Identified instrument for {domain}: {result['instrument']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
 
564
- # Add the result to our list of analysis results
565
- domain_analysis_results.append({
566
- "domain": domain,
567
- "instrument": result["instrument"]
568
- })
569
-
570
- # Return the raw analysis results instead of formatting them
571
- return domain_analysis_results
572
-
573
- @tool
574
- def format_domain_analysis(analysis_results: list, title: str = "NIH HEAL CDE Core Domains Analysis") -> str:
575
- """Format domain analysis results into a markdown table.
576
- Args:
577
- analysis_results: List of dictionaries with domain and instrument information
578
- title: Title for the markdown output
579
 
580
- Returns:
581
- Markdown formatted table of domains and identified instruments
582
- """
583
- # Get the name of the uploaded protocol file
584
- uploaded_files = [f for f in os.listdir(UPLOAD_PATH) if f.endswith('.pdf')]
585
- protocol_name = uploaded_files[0] if uploaded_files else "Unknown Protocol"
586
-
587
- # Format the results as a markdown table
588
- result = f"# {title}\n\n"
589
- result += f"| Domain | Protocol Instrument - {protocol_name} |\n"
590
- result += "|--------|" + "-" * (len(protocol_name) + 23) + "|\n"
591
-
592
- for item in analysis_results:
593
- domain = item.get("domain", "Unknown")
594
- instrument = item.get("instrument", "Not identified")
595
- result += f"| {domain} | {instrument} |\n"
 
 
 
 
 
 
 
 
 
 
596
 
597
- return result
 
 
 
 
 
 
 
598
 
599
- # Collect all tools
600
  tools = [
601
  search_all_data,
602
- search_core_reference,
603
- load_and_embed_protocol,
604
- search_protocol_for_instruments,
605
- analyze_protocol_domains,
606
- format_domain_analysis
607
  ]
608
 
609
  # ==================== LANGGRAPH SETUP ====================
610
  # LangGraph components
611
  model = ChatOpenAI(model_name=INSTRUMENT_ANALYSIS_LLM, temperature=0)
612
- final_model = ChatOpenAI(model_name=INSTRUMENT_ANALYSIS_LLM, temperature=0)
613
 
614
  # System message
615
  system_message = """You are a helpful assistant specializing in NIH HEAL CDE protocols.
616
 
617
  You have access to:
618
- 1. Core reference data through the search_core_reference tool
619
- 2. A tool to load and embed protocol PDFs (load_and_embed_protocol)
620
- 3. A tool to search for instruments in protocols for specific domains (search_protocol_for_instruments)
621
- 4. A tool to analyze all NIH HEAL domains at once (analyze_protocol_domains)
622
- 5. A tool to format analysis results into a markdown table (format_domain_analysis)
623
- 6. A tool to search all available data (search_all_data)
624
 
625
  WHEN TO USE TOOLS:
626
- - When users upload a protocol PDF, use the load_and_embed_protocol tool.
627
- - When users ask general questions about the protocol, use the search_all_data tool.
628
- - When users ask about a specific instrument for a domain, use the search_protocol_for_instruments tool.
629
  - When users want a complete analysis of all domains, use the analyze_protocol_domains tool.
630
- - When users ask about data or information in the core reference files, use the search_core_reference tool.
631
- - When you have multiple analysis results to present, use format_domain_analysis to create a nice table.
632
 
633
  Be specific in your tool queries to get the most relevant information.
634
  Always use the appropriate tool before responding to questions about the protocol or core reference data.
 
 
635
  """
636
 
637
  # Bind tools and configure models
638
  model = model.bind_tools(tools)
639
- final_model = final_model.with_config(tags=["final_node"])
640
  tool_node = ToolNode(tools=tools)
641
 
642
- def should_continue(state: MessagesState) -> Literal["tools", "final"]:
643
  messages = state["messages"]
644
  last_message = messages[-1]
645
  # If the LLM makes a tool call, then we route to the "tools" node
646
  if last_message.tool_calls:
647
  return "tools"
648
- # Otherwise, we stop (reply to the user)
649
- return "final"
650
 
651
  def call_model(state: MessagesState):
652
  messages = state["messages"]
@@ -657,35 +632,19 @@ def call_model(state: MessagesState):
657
  # We return a list, because this will get added to the existing list
658
  return {"messages": [response]}
659
 
660
- def call_final_model(state: MessagesState):
661
- messages = state["messages"]
662
- last_ai_message = messages[-1]
663
- response = final_model.invoke(
664
- [
665
- #SystemMessage("Rewrite this in the voice of a helpful and kind assistant"),
666
- SystemMessage("do not alter just present the information"),
667
- HumanMessage(last_ai_message.content),
668
- ]
669
- )
670
- # overwrite the last AI message from the agent
671
- response.id = last_ai_message.id
672
- return {"messages": [response]}
673
-
674
  # Build the graph
675
  builder = StateGraph(MessagesState)
676
 
677
- builder.add_node("agent", call_model)
678
  builder.add_node("tools", tool_node)
679
- builder.add_node("final", call_final_model)
680
 
681
- builder.add_edge(START, "agent")
682
  builder.add_conditional_edges(
683
- "agent",
684
  should_continue,
685
  )
686
 
687
- builder.add_edge("tools", "agent")
688
- builder.add_edge("final", END)
689
 
690
  graph = builder.compile()
691
 
@@ -716,39 +675,26 @@ async def on_chat_start():
716
  user_vectorstore = await embed_protocol_in_qdrant(documents_with_metadata, session_qdrant_client)
717
 
718
  if user_vectorstore:
719
- analysis_msg = cl.Message(content="Analyzing your protocol to identify instruments (CRF questionaires) for NIH HEAL CDE core domains...")
720
- await analysis_msg.send()
721
-
722
- # Use the analyze_protocol_domains tool to analyze the protocol
723
- config = {"configurable": {"thread_id": cl.context.session.id}}
724
-
725
- # Create a message to trigger the analysis
726
- analysis_request = HumanMessage(content="Please analyze the uploaded protocol and identify instruments for each NIH HEAL CDE core domain.")
727
-
728
- final_answer = cl.Message(content="")
729
-
730
- for msg, metadata in graph.stream(
731
- {"messages": [analysis_request]},
732
- stream_mode="messages",
733
- config=config
734
- ):
735
- if (
736
- msg.content
737
- and not isinstance(msg, HumanMessage)
738
- and metadata["langgraph_node"] == "final"
739
- ):
740
- await final_answer.stream_token(msg.content)
741
-
742
- await final_answer.send()
743
 
744
- await cl.Message(content="You can now ask questions about the protocol.").send()
745
  else:
746
  await cl.Message(content="There was an issue processing your PDF. Please try uploading again.").send()
747
 
748
  @cl.on_message
749
  async def on_message(msg: cl.Message):
750
  config = {"configurable": {"thread_id": cl.context.session.id}}
751
-
752
 
753
  # For all messages, use the graph to handle the logic
754
  final_answer = cl.Message(content="")
@@ -762,8 +708,28 @@ async def on_message(msg: cl.Message):
762
  if (
763
  msg_response.content
764
  and not isinstance(msg_response, HumanMessage)
765
- and metadata["langgraph_node"] == "final"
766
  ):
767
  await final_answer.stream_token(msg_response.content)
768
-
769
- await final_answer.send()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import csv
3
+
4
  from typing import Annotated, Literal
5
  from typing_extensions import TypedDict
6
 
 
32
  # Load environment variables
33
  load_dotenv()
34
  #####langsmith
35
+ # import uuid
36
+ # os.environ["LANGCHAIN_PROJECT"] = f"HEAL-SYNC - {uuid.uuid4().hex[0:8]}"
37
+ # os.environ["LANGCHAIN_TRACING_V2"] = "true"
38
+ # print(os.environ["LANGCHAIN_PROJECT"])
39
  ###########langsmith
40
 
41
 
 
368
  return f"Error searching data: {str(e)}"
369
 
370
  @tool
371
+ def analyze_protocol_domains(export_csv: bool = True) -> str:
372
+ """Analyze all NIH HEAL CDE core domains and identify instruments used in the protocol.
 
373
 
374
+ Args:
375
+ export_csv: Whether to also export results to a CSV file
376
+
377
+ Returns:
378
+ Markdown formatted table of domains and identified instruments
379
+ """
380
+ # Check if protocol document exists
381
+ uploaded_files = [f for f in os.listdir(UPLOAD_PATH) if f.endswith('.pdf')]
382
+ if not uploaded_files:
383
+ return "No protocol document has been uploaded yet."
 
 
 
 
 
384
 
385
+ # Get the name of the uploaded protocol file
386
+ protocol_name = uploaded_files[0] if uploaded_files else "Unknown Protocol"
387
 
388
+ # For each domain, search for relevant instruments
389
+ domain_analysis_results = []
 
 
390
 
391
+ for domain in NIH_HEAL_CORE_DOMAINS:
392
+ # Search for instruments related to this domain in the protocol
393
+ result = _search_protocol_for_instruments(domain)
394
+ print(f"Identified instrument for {domain}: {result['instrument']}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
 
396
+ # Add the result to our list of analysis results
397
+ domain_analysis_results.append({
398
+ "domain": domain,
399
+ "instrument": result["instrument"]
400
+ })
 
 
 
 
 
 
 
 
 
401
 
402
+ # Format the results as a markdown table
403
+ title = "NIH HEAL CDE Core Domains Analysis"
404
+ result = f"# {title}\n\n"
405
+ result += f"| Domain | Protocol Instrument - {protocol_name} |\n"
406
+ result += "|--------|" + "-" * (len(protocol_name) + 23) + "|\n"
407
+
408
+ for item in domain_analysis_results:
409
+ domain = item.get("domain", "Unknown")
410
+ instrument = item.get("instrument", "Not identified")
411
+ result += f"| {domain} | {instrument} |\n"
412
+
413
+ # Export to CSV if requested
414
+ csv_path = None
415
+ if export_csv:
416
+ # Create output directory if it doesn't exist
417
+ output_dir = "./outputs"
418
+ os.makedirs(output_dir, exist_ok=True)
 
 
 
 
 
 
 
 
 
419
 
420
+ # Full path for the CSV file
421
+ filename = "domain_analysis.csv"
422
+ csv_path = os.path.join(output_dir, filename)
 
 
423
 
424
+ # Write the data to CSV
425
+ try:
426
+ with open(csv_path, 'w', newline='') as csvfile:
427
+ fieldnames = ['Domain', f'Protocol Instrument - {protocol_name}']
428
+ writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
429
+
430
+ writer.writeheader()
431
+ for item in domain_analysis_results:
432
+ domain = item.get("domain", "Unknown")
433
+ instrument = item.get("instrument", "Not identified")
434
+ writer.writerow({
435
+ 'Domain': domain,
436
+ f'Protocol Instrument - {protocol_name}': instrument
437
+ })
438
+
439
+ # Store the CSV path in the user session
440
+ cl.user_session.set("csv_path", csv_path)
441
+
442
+ # No longer adding any note about the CSV file in the markdown output
443
+
444
+ except Exception as e:
445
+ result += f"\n\nError creating CSV file: {str(e)}"
446
 
447
+ return result
 
 
 
448
 
449
+ # Helper functions (not exposed as tools)
450
+ def _search_protocol_for_instruments(domain: str) -> dict:
451
  """Search the protocol for instruments related to a specific NIH HEAL CDE core domain."""
452
  global embedding_model
453
 
 
513
  print(f"Error identifying instrument for {domain}: {str(e)}")
514
  return {"domain": domain, "instrument": "Error during identification", "context": str(e)}
515
 
516
+ def create_rag_chain(doc_type=None):
517
+ """Create a RAG chain based on the document type."""
518
+ # Get the session-specific Qdrant client
519
+ session_qdrant_client = cl.user_session.get("session_qdrant_client")
 
 
 
 
 
 
520
 
521
+ # Create retrievers based on document type
522
+ if doc_type == "protocol" and session_qdrant_client:
523
+ # Check if user collection exists
524
+ try:
525
+ if USER_EMBEDDINGS_NAME in [c.name for c in session_qdrant_client.get_collections().collections]:
526
+ protocol_vectorstore = QdrantVectorStore(
527
+ client=session_qdrant_client,
528
+ collection_name=USER_EMBEDDINGS_NAME,
529
+ embedding=embedding_model
530
+ )
531
+ retriever = protocol_vectorstore.as_retriever(search_kwargs={"k": 5})
532
+ else:
533
+ raise ValueError("No protocol document embedded")
534
+ except Exception as e:
535
+ raise ValueError(f"Error accessing protocol: {str(e)}")
536
+ elif doc_type == "core_reference":
537
+ if core_vectorstore:
538
+ retriever = core_vectorstore.as_retriever(search_kwargs={"k": 5})
539
+ else:
540
+ raise ValueError("Core reference data not available")
541
+ else:
542
+ # Default: search both if available
543
+ retrievers = []
544
 
545
+ # Add core reference retriever if available
546
+ if core_vectorstore:
547
+ core_retriever = core_vectorstore.as_retriever(search_kwargs={"k": 3})
548
+ retrievers.append(core_retriever)
 
 
 
 
 
 
 
 
 
 
 
549
 
550
+ # Add protocol retriever if available
551
+ if session_qdrant_client:
552
+ try:
553
+ if USER_EMBEDDINGS_NAME in [c.name for c in session_qdrant_client.get_collections().collections]:
554
+ protocol_vectorstore = QdrantVectorStore(
555
+ client=session_qdrant_client,
556
+ collection_name=USER_EMBEDDINGS_NAME,
557
+ embedding=embedding_model
558
+ )
559
+ protocol_retriever = protocol_vectorstore.as_retriever(search_kwargs={"k": 3})
560
+ retrievers.append(protocol_retriever)
561
+ except Exception as e:
562
+ print(f"Error accessing protocol: {str(e)}")
563
+
564
+ if not retrievers:
565
+ raise ValueError("No data sources available")
566
+
567
+ # If we have multiple retrievers, use them in sequence
568
+ if len(retrievers) > 1:
569
+ from langchain.retrievers import EnsembleRetriever
570
+ retriever = EnsembleRetriever(
571
+ retrievers=retrievers,
572
+ weights=[1.0/len(retrievers)] * len(retrievers)
573
+ )
574
+ else:
575
+ retriever = retrievers[0]
576
 
577
+ # Create and return the RAG chain
578
+ return (
579
+ {"context": itemgetter("question") | retriever | format_docs,
580
+ "question": itemgetter("question")}
581
+ | rag_prompt
582
+ | chat_model
583
+ | StrOutputParser()
584
+ )
585
 
586
+ # Collect all tools - now just the two core tools
587
  tools = [
588
  search_all_data,
589
+ analyze_protocol_domains
 
 
 
 
590
  ]
591
 
592
  # ==================== LANGGRAPH SETUP ====================
593
  # LangGraph components
594
  model = ChatOpenAI(model_name=INSTRUMENT_ANALYSIS_LLM, temperature=0)
 
595
 
596
  # System message
597
  system_message = """You are a helpful assistant specializing in NIH HEAL CDE protocols.
598
 
599
  You have access to:
600
+ 1. A tool to search all available data (search_all_data) - Use this to answer questions about the protocol or core reference data
601
+ 2. A tool to analyze all NIH HEAL domains at once (analyze_protocol_domains) - This will identify instruments for each NIH HEAL CDE core domain, return the result in markdown, and also create a CSV file
 
 
 
 
602
 
603
  WHEN TO USE TOOLS:
604
+ - When users ask general questions about the protocol or core reference data, use the search_all_data tool.
 
 
605
  - When users want a complete analysis of all domains, use the analyze_protocol_domains tool.
 
 
606
 
607
  Be specific in your tool queries to get the most relevant information.
608
  Always use the appropriate tool before responding to questions about the protocol or core reference data.
609
+
610
+ IMPORTANT: When returning tool outputs, especially markdown tables or formatted content, preserve the exact formatting without adding any commentary, introduction, or conclusion.
611
  """
612
 
613
  # Bind tools and configure models
614
  model = model.bind_tools(tools)
 
615
  tool_node = ToolNode(tools=tools)
616
 
617
+ def should_continue(state: MessagesState) -> Literal["tools", END]:
618
  messages = state["messages"]
619
  last_message = messages[-1]
620
  # If the LLM makes a tool call, then we route to the "tools" node
621
  if last_message.tool_calls:
622
  return "tools"
623
+ # Otherwise, we end the graph (reply to the user)
624
+ return END
625
 
626
  def call_model(state: MessagesState):
627
  messages = state["messages"]
 
632
  # We return a list, because this will get added to the existing list
633
  return {"messages": [response]}
634
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
635
  # Build the graph
636
  builder = StateGraph(MessagesState)
637
 
638
+ builder.add_node("supervisor", call_model)
639
  builder.add_node("tools", tool_node)
 
640
 
641
+ builder.add_edge(START, "supervisor")
642
  builder.add_conditional_edges(
643
+ "supervisor",
644
  should_continue,
645
  )
646
 
647
+ builder.add_edge("tools", "supervisor")
 
648
 
649
  graph = builder.compile()
650
 
 
675
  user_vectorstore = await embed_protocol_in_qdrant(documents_with_metadata, session_qdrant_client)
676
 
677
  if user_vectorstore:
678
+ # Present options to the user instead of automatically running analysis
679
+ options_message = """
680
+ Your protocol has been successfully processed! What would you like to do next?
681
+
682
+ 1. Ask questions about the uploaded protocol
683
+ (This will use RAG to answer questions about your protocol document)
684
+
685
+ 2. Run a complete analysis of what core domain instruments are used in the uploaded protocol
686
+ (This will identify instruments for each NIH HEAL CDE core domain, return the result in markdown, and also create a CSV file)
687
+
688
+ Please let me know which option you'd like to proceed with, or feel free to ask any other questions.
689
+ """
 
 
 
 
 
 
 
 
 
 
 
 
690
 
691
+ await cl.Message(content=options_message).send()
692
  else:
693
  await cl.Message(content="There was an issue processing your PDF. Please try uploading again.").send()
694
 
695
  @cl.on_message
696
  async def on_message(msg: cl.Message):
697
  config = {"configurable": {"thread_id": cl.context.session.id}}
 
698
 
699
  # For all messages, use the graph to handle the logic
700
  final_answer = cl.Message(content="")
 
708
  if (
709
  msg_response.content
710
  and not isinstance(msg_response, HumanMessage)
711
+ and metadata["langgraph_node"] == "supervisor"
712
  ):
713
  await final_answer.stream_token(msg_response.content)
714
+
715
+ await final_answer.send()
716
+
717
+ # Check if we need to attach a CSV file
718
+ csv_path = cl.user_session.get("csv_path")
719
+ if csv_path:
720
+ try:
721
+ # Create a message with the CSV file
722
+ file_message = cl.Message(content="Here's the CSV file with the analysis results:")
723
+ await file_message.send()
724
+
725
+ # Now attach the file to this message
726
+ await cl.File(
727
+ name="domain_analysis.csv",
728
+ path=csv_path,
729
+ display="inline"
730
+ ).send(for_id=file_message.id)
731
+
732
+ # Clear the path to avoid sending it multiple times
733
+ cl.user_session.set("csv_path", None)
734
+ except Exception as e:
735
+ print(f"Error attaching CSV file: {str(e)}")