diff --git a/fieldExtraction/src/investment/one_to_one_funcs.py b/fieldExtraction/src/investment/one_to_one_funcs.py index fc5c539..2e3027b 100644 --- a/fieldExtraction/src/investment/one_to_one_funcs.py +++ b/fieldExtraction/src/investment/one_to_one_funcs.py @@ -83,7 +83,8 @@ def run_smart_chunked_fields(one_to_one_fields: FieldSet, ) if len(fields) == 1: - field_group_answer_dict = {fields[0].field_name: field_group_answer_raw} + field_group_answer_parsed = string_utils.extract_text_from_delimiters(field_group_answer_raw, Delimiter.PIPE) + field_group_answer_dict = {fields[0].field_name: field_group_answer_parsed} elif len(fields) > 1: field_group_answer_dict = string_utils.json_parsing_search( field_group_answer_raw, [field.field_name for field in fields] diff --git a/fieldExtraction/src/investment/smart_chunking_funcs.py b/fieldExtraction/src/investment/smart_chunking_funcs.py index e2a43bb..b99f115 100644 --- a/fieldExtraction/src/investment/smart_chunking_funcs.py +++ b/fieldExtraction/src/investment/smart_chunking_funcs.py @@ -36,7 +36,7 @@ embeddings_model = BedrockEmbeddings( DATE_PATTERN = r"(?i)\bday of\b|\b(?:jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|jul(?:y)?|aug(?:ust)?|sep(?:tember)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\b|\b(?:\d{1,2}[\/\.-]\d{1,2}[\/\.-](?:\d{2}|\d{4})|\d{4}[\/\.-]\d{1,2}[\/\.-]\d{1,2}|\d{8}|\d{2}\d{2}|\d{4}[\/\.-]\d{1,2}[\/\.-]\d{1,2}|\d{4}[\.]\d{1,2}[\.]\d{1,2})\b" -def field_context(one_to_one_fields: FieldSet, contract_text: str, filename: str): +def field_context(one_to_one_fields: FieldSet, contract_text: str, filename: str): # TODO: change chroma output filename to be {filename}_chroma """this function creates chunks from whole contract text and returns relevant chunks for each field based on sementic search and keyword search @@ -47,6 +47,7 @@ def field_context(one_to_one_fields: FieldSet, contract_text: str, filename: str dict[Any, set[str]]: retrieved text for each field list[str]: list of all chunks """ + chroma_filename = f"{filename}_chroma" text_splitter = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=500) splits = text_splitter.split_text(contract_text) @@ -55,7 +56,7 @@ def field_context(one_to_one_fields: FieldSet, contract_text: str, filename: str splits = ["chunk " + str(idx+1).zfill(3) + ": " + str(split) for idx,split in enumerate(splits)] # create vector retriever for vector/sementic search and keyword retriever for keyword search - vectorstore = Chroma.from_texts(texts=splits, embedding=embeddings_model, persist_directory=filename) + vectorstore = Chroma.from_texts(texts=splits, embedding=embeddings_model, persist_directory=chroma_filename) vectorstore_retreiver = vectorstore.as_retriever(search_kwargs={"k": 4}) keyword_retriever = BM25Retriever.from_texts(splits, k=4, preprocess_func=word_tokenize) # keyword_retriever.k = 3 @@ -82,7 +83,7 @@ def field_context(one_to_one_fields: FieldSet, contract_text: str, filename: str vectorstore.delete_collection() try: - shutil.rmtree(filename) + shutil.rmtree(chroma_filename) except OSError as e: print("Error: %s - %s." % (e.filename, e.strerror)) diff --git a/fieldExtraction/src/prompts/investment_prompts.json b/fieldExtraction/src/prompts/investment_prompts.json index cc1f331..b9b9ec2 100644 --- a/fieldExtraction/src/prompts/investment_prompts.json +++ b/fieldExtraction/src/prompts/investment_prompts.json @@ -132,7 +132,7 @@ "field_name": "CONTRACT_EFFECTIVE_DT", "relationship": "one_to_one", "field_type": "smart_chunked", - "prompt": "What is the contract effective date mentioned in any of the following locations: the signatory section, the preamble of the agreement, or the start of the amendment? Look for phrases such as /'This amendment is effective/'. If there is no clear effective date, return the date from the signature page. Return the date converted to YYYY-MM-DD format, with no other commentary or explanation. If there is no date, simply return 'N/A'.", + "prompt": "Please analyze the following contract and extract ONLY the effective date of the CURRENT contract or CURRENT amendment. THE DATE MAY OR MAY NOT BE EXPLICITLY LABELED AS AN EFFECTIVE DATE. Follow these retrieval rules in order:\n[RETRIEVAL RULES]\nLOOK FOR dates that appear after these common phrases but not limited to (case-insensitive matching):\n'Effective Date:' FOLLOWED BY a date\n'is entered into as of' FOLLOWED BY a date\n'effective as of' FOLLOWED BY a date\n'shall begin on' FOLLOWED BY a date\n'Effective Date of Agreement:' FOLLOWED BY a date\n'Effective Date of Amendment' FOLLOWED BY a date\n'made this' FOLLOWED BY a date\n'made and entered into' FOLLOWED BY a date\n\nALSO LOOK FOR dates in phrases that semantically indicate an effective date (may or may not be explictly labelled as effective date), even if they do not exactly match the phrases above.\nExamples include but ARE NOT LIMITED TO:\nPhrases indicating when an contract or amendment begins or takes effect, Phrases specifying the start date of the contract or amendment, Phrases defining when the contract or amendment becomes operational, or Any phrase indicating contract or amendment creation or execution date\n\n[OUTPUT DATE HANDLING]\nCRITICAL - DATE FORMAT PRESERVATION:\n- You MUST preserve the EXACT format of the date AS IT APPEARS in the document\n- DO NOT modify, standardize, or reformat the date in ANY way\n- Preserve ALL original: Spacing (including multiple spaces), punctuation, separators (commas, hyphens, forward slashes, etc.), leading/trailing spaces, or partial or incomplete dates\n\nExamples of EXACT DATE preservation:\n✓ If document shows: '3-1 , , 03' → return |3-1 , , 03|\n✓ If document shows: 'June 1st,2010' → return |June 1st,2010|\n✓ If document shows: 'Feb 1st day of , 10' → return |Feb 1st day of , 10|\n✓ If document shows: '6 /1/ 2010' → return |6 /1/ 2010|\n✓ If document shows: '12-1-20' → return |12-1-20|\n✓ If document shows: 'made this day of Jan, 2009' → return |day of Jan, 2009|\n✓ If document shows: 'to be effective as of , 20 ( the 'Effective Date').' → return |, 20|\n\nIt is POSSIBLE that the intended effective date could be blank (left blank, not filled), empty, incomplete, or partial. This is still acceptable as the ANSWER. DO NOT ASSUME/INFER anything about the date or its components.\nReturn N/A only if NO valid effective date is found (excluding blank/empty/incomplete dates)\n\nIF MULTIPLE matches are retrieved:\n - VERIFY that you have PRECISELY followed the retrieval rules\n - If multiple matches are present, PRIORITIZE and SELECT the LATEST date as the answer\n - MAINTAIN the EXACT FORMAT of the selected date as it appears in the document\n\nBefore providing the final answer, SHOW your reasoning:\n - LIST ALL dates found that match patterns in the retrieval rules\n- For EACH date found:\n* EXPLAIN why it is considered for effective date or not\n* Include the EXACT text snippet where the date was found\n- JUSTIFY your final date selection or why N/A is being returned\n\nFinal Answer Format:\n - Your answer MUST be ENCLOSED in |pipes|\n - The text between the pipes MUST be an EXACT COPY of how the date appears in the document\n - DO NOT add or remove ANY characters, spaces, or formatting", "keywords": ["Effective Date", "entered into", "shall be effective", "will become effective"] }, { diff --git a/fieldExtraction/src/prompts/investment_prompts.py b/fieldExtraction/src/prompts/investment_prompts.py index b8f2c16..7b74055 100644 --- a/fieldExtraction/src/prompts/investment_prompts.py +++ b/fieldExtraction/src/prompts/investment_prompts.py @@ -79,7 +79,8 @@ class Field: except: resolved_prompt = resolved_prompt.replace(f"{{{placeholder}}}", str(resolved_value)) else: - raise ValueError(f"Unknown placeholder: {placeholder}") + pass + # raise ValueError(f"Unknown placeholder: {placeholder}") start_idx = resolved_prompt.find("{", end_idx) @@ -241,25 +242,38 @@ Here is the text to analyze: """ def ONE_TO_ONE_SINGLE_FIELD_TEMPLATE(context, question): - return f"""## START CONTRACT TEXT ## + return f"""The following is a contract (or excerpt thereof). Answer the following question: {question} + +## START CONTRACT TEXT ## {context} ## END CONTRACT TEXT ## -Answer the following question, using the above contract text given as context: {question}. - -Do NOT add any additional commentary explaining the answer. Return ONLY the exact text from the contract, or N/A. +Feel free to justify your answer, but enclose your final answer in |pipes|. If the question cannot be answered with the given context, return |N/A|. """ -def ONE_TO_ONE_MULTI_FIELD_TEMPLATE(context, questions): - return f"""## START CONTRACT TEXT ## +def ONE_TO_ONE_MULTI_FIELD_TEMPLATE(context, questions: dict[str, str]): + return f"""The following is a contract (or excerpt thereof). Answer the following questions: {questions} + +## START CONTRACT TEXT ## {context} ## END CONTRACT TEXT ## -Answer the following questions, using the above contract text given as context. -For each question, provide ONLY the answer, with no additional commentary or explanation. Answer in JSON dictionary format, with every key included and no missing answers. -If for any question, no correct answer is found, return 'N/A'. -{questions}. +For each question, explain your reasoning. Then provide your final answers in a JSON dictionary format. + +The JSON should: +- Include every key +- Use 'N/A' for any question that cannot be answered from the context +- Contain only the final answers, not explanations + +Example format: +I found the effective date in section 2.1 which states... +The term length appears in two places but I'm selecting... + +{{ + "effective_date": "January 1, 2024", + "term_length": "36 months" +}} """