Syzygianinfern0 commited on
Commit
291adda
·
1 Parent(s): dc87cf7

Move PULS code

Browse files
neus_v/puls/llm.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import datetime
2
+ import json
3
+ import os
4
+
5
+
6
+ class LLM:
7
+ def __init__(self, client, save_dir="outputs"):
8
+ self.client = client
9
+ self.history = []
10
+ self.save_dir = save_dir
11
+ if save_dir:
12
+ os.makedirs(save_dir, exist_ok=True)
13
+
14
+ def prompt(self, p):
15
+ # Add user message to history
16
+ user_message = {"role": "user", "content": [{"type": "text", "text": p}]}
17
+ self.history.append(user_message)
18
+
19
+ # Create messages list with history
20
+ messages = self.history.copy()
21
+
22
+ response = self.client.chat.completions.create(
23
+ model="o1-mini-2024-09-12",
24
+ messages=self.history,
25
+ store=False,
26
+ )
27
+
28
+ # Extract assistant response
29
+ assistant_response = response.choices[0].message.content
30
+
31
+ # Add assistant response to history
32
+ assistant_message = {"role": "assistant", "content": [{"type": "text", "text": assistant_response}]}
33
+ self.history.append(assistant_message)
34
+
35
+ return assistant_response
36
+
37
+ def save_history(self, filename="conversation_history.json"):
38
+ """Save conversation history to a JSON file"""
39
+ if not self.save_dir:
40
+ return
41
+
42
+ # Add timestamp to filename
43
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
44
+ base_name, extension = os.path.splitext(filename)
45
+ timestamped_filename = f"{base_name}_{timestamp}{extension}"
46
+
47
+ save_path = os.path.join(self.save_dir, timestamped_filename)
48
+ try:
49
+ with open(save_path, "w", encoding="utf-8") as f:
50
+ json.dump(self.history, f, indent=4, ensure_ascii=False)
51
+ print(f"Conversation history saved to: {save_path}")
52
+ except Exception as e:
53
+ print(f"Failed to save conversation history: {e}")
54
+
55
+ def __del__(self):
56
+ """Destructor: Saves conversation history when the object is destroyed"""
57
+ if self.save_dir:
58
+ self.save_history()
neus_v/puls/main.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from puls import *
2
+
3
+ import argparse
4
+ import json
5
+ import time
6
+
7
+ def main():
8
+ prompt = "a dog sits on a mat while a ball rolls past the dog towards a couch, then the dog picks up the ball and places it beside the couch"
9
+ modes = [Mode.OBJECT_ACTION_ALIGNMENT, Mode.OVERALL_CONSISTENCY, Mode.OBJECT_EXISTENCE, Mode.SPATIAL_RELATIONSHIP]
10
+
11
+ parser = argparse.ArgumentParser(description='Set OpenAI API Key.')
12
+ parser.add_argument('--openai_key', type=str, help='Your OpenAI API key')
13
+ args = parser.parse_args()
14
+
15
+ start_time = time.time()
16
+ if args.openai_key:
17
+ data = PULS(prompt, modes, args.openai_key)
18
+ else:
19
+ data = PULS(prompt, modes)
20
+ end_time = time.time()
21
+
22
+ print(prompt)
23
+ print(json.dumps(data, indent=2))
24
+ print(f"Elapsed Time: {end_time - start_time}")
25
+
26
+ if __name__ == "__main__":
27
+ main()
28
+
neus_v/puls/prompts.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from enum import Enum
2
+
3
+ class Mode(Enum):
4
+ OBJECT_ACTION_ALIGNMENT = 1
5
+ OBJECT_EXISTENCE = 2
6
+ OVERALL_CONSISTENCY = 3
7
+ SPATIAL_RELATIONSHIP = 4
8
+
9
+ mode_prompts = {
10
+ Mode.OBJECT_ACTION_ALIGNMENT: (
11
+ "\"object_action_alignment\":\n"
12
+ "Extract actions and their participating objects. Each proposition must describe an action and its related objects.\n"
13
+ "Example:\n"
14
+ "\"object_action_alignment\": [\"person holds hotdog\", \"person walks\"]"
15
+ ),
16
+ Mode.OBJECT_EXISTENCE: (
17
+ "\"object_existence\":\n"
18
+ "Extract only the tangible objects mentioned in the prompt.\n"
19
+ "Example:\n"
20
+ "\"object_existence\": [\"person\", \"hotdog\", \"car\", \"truck\"]"
21
+ ),
22
+ Mode.OVERALL_CONSISTENCY: (
23
+ "\"overall_consistency\":\n"
24
+ "Extract all meaningful event propositions that describe the combined semantics of objects, actions, and spatial relationships — "
25
+ "but avoid TL keywords such as 'and', 'or', 'not', 'until', 'eventually'.\n"
26
+ "Example:\n"
27
+ "\"overall_consistency\": [\"person holds hotdog\", \"person walks\", \"car next to truck\"]"
28
+ ),
29
+ Mode.SPATIAL_RELATIONSHIP: (
30
+ "\"spatial_relationships\":\n"
31
+ "Extract only spatial relationships between tangible objects (e.g., \"object A next to object B\"). Do not infer or hallucinate spatial relationships.\n"
32
+ "Example:\n"
33
+ "\"spatial_relationships\": [\"car next to truck\"]"
34
+ )
35
+ }
36
+
37
+ mode_outputs = {
38
+ Mode.OBJECT_ACTION_ALIGNMENT: (
39
+ " \"object_action_alignment\": [...],\n"
40
+ " \"object_action_alignment_spec\": \"...\","
41
+ ),
42
+ Mode.OBJECT_EXISTENCE: (
43
+ " \"object_existence\": [...],\n"
44
+ " \"object_existence_spec\": \"...\","
45
+ ),
46
+ Mode.OVERALL_CONSISTENCY: (
47
+ " \"overall_consistency\": [...],\n"
48
+ " \"overall_consistency_spec\": \"...\","
49
+ ),
50
+ Mode.SPATIAL_RELATIONSHIP: (
51
+ " \"spatial_relationships\": [...],\n"
52
+ " \"spatial_relationships_spec\": \"...\""
53
+ )
54
+ }
55
+
56
+ header = (
57
+ "You are an intelligent agent designed to extract structured representations from video description prompts. "
58
+ "You will operate in two stages: (1) proposition extraction and (2) TL specification generation.\n\n"
59
+ )
60
+
61
+ stage1_intro = (
62
+ "Stage 1: Proposition Extraction\n\n"
63
+ "Given an input prompt summarizing a video, extract atomic propositions in the following four modes. "
64
+ "Return all outputs in JSON format.\n\n"
65
+ )
66
+
67
+ stage2_intro = "Stage 2: TL Specification Generation\n\n"
68
+
69
+ spec_gen_intro = (
70
+ "For each of the {n} list(s) of propositions extracted in Stage 1, generate a separate Temporal Logic (TL) specification "
71
+ "describing the structure or sequence of events in that list.\n\n"
72
+ )
73
+
74
+ tl_instructions = (
75
+ "Rules for TL specification:\n"
76
+ "- The input is a single list of propositions from one of the extraction modes.\n"
77
+ "- The output is a single TL formula using **only** the propositions from that list and the allowed TL symbols: "
78
+ "['AND', 'OR', 'NOT', 'UNTIL', 'ALWAYS', 'EVENTUALLY']\n"
79
+ "- Do not introduce any new propositions.\n"
80
+ "- Each formula should reflect the temporal or logical relationships between the propositions in a way that makes semantic sense.\n\n"
81
+ )
82
+
83
+ input_template = "Input:\n{{\n \"prompt\": \"{}\"\n}}\n\n"
84
+ expected_output_header = "Expected Output:\n{\n"
85
+ expected_output_footer = "\n}"
neus_v/puls/puls.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from llm import *
2
+ from openai import OpenAI
3
+ from prompts import *
4
+ import json
5
+ import os
6
+ import re
7
+
8
+ def clean_and_parse_json(raw_str):
9
+ start = raw_str.find('{')
10
+ end = raw_str.rfind('}') + 1
11
+ json_str = raw_str[start:end]
12
+ return json.loads(json_str)
13
+
14
+ def process_specification(specification, propositions):
15
+ new_propositions = []
16
+ for prop in propositions:
17
+ prop_cleaned = re.sub(r"^[^a-zA-Z]+|[^a-zA-Z]+$", "", prop)
18
+ prop_cleaned = re.sub(r"\s+", "_", prop_cleaned)
19
+ new_propositions.append(prop_cleaned)
20
+
21
+ for original, new in zip(propositions, new_propositions):
22
+ specification = specification.replace(original, f'"{new}"')
23
+
24
+ replacements = {
25
+ "AND": "&",
26
+ "OR": "|",
27
+ "UNTIL": "U",
28
+ "ALWAYS": "G",
29
+ "EVENTUALLY": "F",
30
+ "NOT": "!"
31
+ }
32
+ for word, symbol in replacements.items():
33
+ specification = specification.replace(word, symbol)
34
+
35
+ return new_propositions, specification
36
+
37
+ def create_prompt(prompt, modes):
38
+ full_prompt = header + stage1_intro
39
+
40
+ for i, m in enumerate(modes, start=1):
41
+ full_prompt += f"{i}. {mode_prompts[m]}\n\n"
42
+
43
+ full_prompt += stage2_intro
44
+ full_prompt += spec_gen_intro.format(n=len(modes))
45
+ full_prompt += tl_instructions
46
+ full_prompt += input_template.format(prompt)
47
+
48
+ full_prompt += expected_output_header
49
+ for i, m in enumerate(modes):
50
+ full_prompt += mode_outputs[m]
51
+ if i != len(modes) - 1:
52
+ full_prompt += "\n\n"
53
+ full_prompt += expected_output_footer
54
+
55
+ return full_prompt
56
+
57
+ def PULS(prompt, modes, openai_key=None):
58
+ if openai_key:
59
+ os.environ["OPENAI_API_KEY"] = openai_key
60
+
61
+ client = OpenAI()
62
+ llm = LLM(client)
63
+
64
+ full_prompt = create_prompt(prompt, modes)
65
+ llm_output = llm.prompt(full_prompt)
66
+ parsed = clean_and_parse_json(llm_output)
67
+
68
+ final_output = {}
69
+
70
+ for key, value in parsed.items():
71
+ if key.endswith("_spec"):
72
+ base_key = key.replace("_spec", "")
73
+ propositions = parsed.get(base_key, [])
74
+ cleaned_props, processed_spec = process_specification(value, propositions)
75
+ final_output[base_key] = cleaned_props
76
+ final_output[key] = processed_spec
77
+ else:
78
+ final_output[key] = value
79
+
80
+ return final_output