praisonaiagents 0.0.54__py3-none-any.whl → 0.0.57__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -97,36 +97,79 @@ def process_stream_chunks(chunks):
97
97
 
98
98
  content_list = []
99
99
  reasoning_list = []
100
+ tool_calls = []
101
+ current_tool_call = None
100
102
 
103
+ # First pass: Get initial tool call data
101
104
  for chunk in chunks:
102
105
  if not hasattr(chunk, "choices") or not chunk.choices:
103
106
  continue
104
107
 
105
- # Track usage from each chunk
106
- if hasattr(chunk, "usage"):
107
- completion_tokens += getattr(chunk.usage, "completion_tokens", 0)
108
- prompt_tokens += getattr(chunk.usage, "prompt_tokens", 0)
109
-
110
108
  delta = getattr(chunk.choices[0], "delta", None)
111
109
  if not delta:
112
110
  continue
113
-
111
+
112
+ # Handle content and reasoning
114
113
  if hasattr(delta, "content") and delta.content:
115
114
  content_list.append(delta.content)
116
115
  if hasattr(delta, "reasoning_content") and delta.reasoning_content:
117
116
  reasoning_list.append(delta.reasoning_content)
117
+
118
+ # Handle tool calls
119
+ if hasattr(delta, "tool_calls") and delta.tool_calls:
120
+ for tool_call_delta in delta.tool_calls:
121
+ if tool_call_delta.index is not None and tool_call_delta.id:
122
+ # Found the initial tool call
123
+ current_tool_call = {
124
+ "id": tool_call_delta.id,
125
+ "type": "function",
126
+ "function": {
127
+ "name": tool_call_delta.function.name,
128
+ "arguments": ""
129
+ }
130
+ }
131
+ while len(tool_calls) <= tool_call_delta.index:
132
+ tool_calls.append(None)
133
+ tool_calls[tool_call_delta.index] = current_tool_call
134
+ current_tool_call = tool_calls[tool_call_delta.index]
135
+ elif current_tool_call is not None and hasattr(tool_call_delta.function, "arguments"):
136
+ if tool_call_delta.function.arguments:
137
+ current_tool_call["function"]["arguments"] += tool_call_delta.function.arguments
138
+
139
+ # Remove any None values and empty tool calls
140
+ tool_calls = [tc for tc in tool_calls if tc and tc["id"] and tc["function"]["name"]]
118
141
 
119
142
  combined_content = "".join(content_list) if content_list else ""
120
143
  combined_reasoning = "".join(reasoning_list) if reasoning_list else None
121
144
  finish_reason = getattr(last_chunk.choices[0], "finish_reason", None) if hasattr(last_chunk, "choices") and last_chunk.choices else None
122
145
 
146
+ # Create ToolCall objects
147
+ processed_tool_calls = []
148
+ if tool_calls:
149
+ try:
150
+ from openai.types.chat import ChatCompletionMessageToolCall
151
+ for tc in tool_calls:
152
+ tool_call = ChatCompletionMessageToolCall(
153
+ id=tc["id"],
154
+ type=tc["type"],
155
+ function={
156
+ "name": tc["function"]["name"],
157
+ "arguments": tc["function"]["arguments"]
158
+ }
159
+ )
160
+ processed_tool_calls.append(tool_call)
161
+ except Exception as e:
162
+ print(f"Error processing tool call: {e}")
163
+
123
164
  message = ChatCompletionMessage(
124
165
  content=combined_content,
125
- reasoning_content=combined_reasoning
166
+ role="assistant",
167
+ reasoning_content=combined_reasoning,
168
+ tool_calls=processed_tool_calls if processed_tool_calls else None
126
169
  )
127
170
 
128
171
  choice = Choice(
129
- finish_reason=finish_reason,
172
+ finish_reason=finish_reason or "tool_calls" if processed_tool_calls else None,
130
173
  index=0,
131
174
  message=message
132
175
  )
@@ -528,6 +571,53 @@ Your Goal: {self.goal}
528
571
  def __str__(self):
529
572
  return f"Agent(name='{self.name}', role='{self.role}', goal='{self.goal}')"
530
573
 
574
+ def _process_stream_response(self, messages, temperature, start_time, formatted_tools=None, reasoning_steps=False):
575
+ """Process streaming response and return final response"""
576
+ try:
577
+ # Create the response stream
578
+ response_stream = client.chat.completions.create(
579
+ model=self.llm,
580
+ messages=messages,
581
+ temperature=temperature,
582
+ tools=formatted_tools if formatted_tools else None,
583
+ stream=True
584
+ )
585
+
586
+ full_response_text = ""
587
+ reasoning_content = ""
588
+ chunks = []
589
+
590
+ # Create Live display with proper configuration
591
+ with Live(
592
+ display_generating("", start_time),
593
+ console=self.console,
594
+ refresh_per_second=4,
595
+ transient=True,
596
+ vertical_overflow="ellipsis",
597
+ auto_refresh=True
598
+ ) as live:
599
+ for chunk in response_stream:
600
+ chunks.append(chunk)
601
+ if chunk.choices[0].delta.content:
602
+ full_response_text += chunk.choices[0].delta.content
603
+ live.update(display_generating(full_response_text, start_time))
604
+
605
+ # Update live display with reasoning content if enabled
606
+ if reasoning_steps and hasattr(chunk.choices[0].delta, "reasoning_content"):
607
+ rc = chunk.choices[0].delta.reasoning_content
608
+ if rc:
609
+ reasoning_content += rc
610
+ live.update(display_generating(f"{full_response_text}\n[Reasoning: {reasoning_content}]", start_time))
611
+
612
+ # Clear the last generating display with a blank line
613
+ self.console.print()
614
+ final_response = process_stream_chunks(chunks)
615
+ return final_response
616
+
617
+ except Exception as e:
618
+ display_error(f"Error in stream processing: {e}")
619
+ return None
620
+
531
621
  def _chat_completion(self, messages, temperature=0.2, tools=None, stream=True, reasoning_steps=False):
532
622
  start_time = time.time()
533
623
  logging.debug(f"{self.name} sending messages to LLM: {messages}")
@@ -554,20 +644,31 @@ Your Goal: {self.goal}
554
644
  logging.warning(f"Tool {tool} not recognized")
555
645
 
556
646
  try:
557
- initial_response = client.chat.completions.create(
558
- model=self.llm,
559
- messages=messages,
560
- temperature=temperature,
561
- tools=formatted_tools if formatted_tools else None,
562
- stream=False
563
- )
647
+ if stream:
648
+ # Process as streaming response with formatted tools
649
+ final_response = self._process_stream_response(
650
+ messages,
651
+ temperature,
652
+ start_time,
653
+ formatted_tools=formatted_tools if formatted_tools else None,
654
+ reasoning_steps=reasoning_steps
655
+ )
656
+ else:
657
+ # Process as regular non-streaming response
658
+ final_response = client.chat.completions.create(
659
+ model=self.llm,
660
+ messages=messages,
661
+ temperature=temperature,
662
+ tools=formatted_tools if formatted_tools else None,
663
+ stream=False
664
+ )
564
665
 
565
- tool_calls = getattr(initial_response.choices[0].message, 'tool_calls', None)
666
+ tool_calls = getattr(final_response.choices[0].message, 'tool_calls', None)
566
667
 
567
668
  if tool_calls:
568
669
  messages.append({
569
- "role": "assistant",
570
- "content": initial_response.choices[0].message.content,
670
+ "role": "assistant",
671
+ "content": final_response.choices[0].message.content,
571
672
  "tool_calls": tool_calls
572
673
  })
573
674
 
@@ -590,55 +691,24 @@ Your Goal: {self.goal}
590
691
  "content": results_str
591
692
  })
592
693
 
593
- if stream:
594
- response_stream = client.chat.completions.create(
595
- model=self.llm,
596
- messages=messages,
597
- temperature=temperature,
598
- stream=True
599
- )
600
- full_response_text = ""
601
- reasoning_content = ""
602
- chunks = []
603
-
604
- # Create Live display with proper configuration
605
- with Live(
606
- display_generating("", start_time),
607
- console=self.console,
608
- refresh_per_second=4,
609
- transient=True,
610
- vertical_overflow="ellipsis",
611
- auto_refresh=True
612
- ) as live:
613
- for chunk in response_stream:
614
- chunks.append(chunk)
615
- if chunk.choices[0].delta.content:
616
- full_response_text += chunk.choices[0].delta.content
617
- live.update(display_generating(full_response_text, start_time))
618
-
619
- # Update live display with reasoning content if enabled
620
- if reasoning_steps and hasattr(chunk.choices[0].delta, "reasoning_content"):
621
- rc = chunk.choices[0].delta.reasoning_content
622
- if rc:
623
- reasoning_content += rc
624
- live.update(display_generating(f"{full_response_text}\n[Reasoning: {reasoning_content}]", start_time))
625
-
626
- # Clear the last generating display with a blank line
627
- self.console.print()
628
-
629
- final_response = process_stream_chunks(chunks)
630
- return final_response
631
- else:
632
- if tool_calls:
694
+ # Get final response after tool calls
695
+ if stream:
696
+ final_response = self._process_stream_response(
697
+ messages,
698
+ temperature,
699
+ start_time,
700
+ formatted_tools=formatted_tools if formatted_tools else None,
701
+ reasoning_steps=reasoning_steps
702
+ )
703
+ else:
633
704
  final_response = client.chat.completions.create(
634
705
  model=self.llm,
635
706
  messages=messages,
636
707
  temperature=temperature,
637
708
  stream=False
638
709
  )
639
- return final_response
640
- else:
641
- return initial_response
710
+
711
+ return final_response
642
712
 
643
713
  except Exception as e:
644
714
  display_error(f"Error in chat completion: {e}")
@@ -758,8 +828,7 @@ Your Goal: {self.goal}
758
828
 
759
829
  tool_calls = getattr(response.choices[0].message, 'tool_calls', None)
760
830
  response_text = response.choices[0].message.content.strip()
761
-
762
- if tool_calls:
831
+ if tool_calls: ## TODO: Most likely this tool call is already called in _chat_completion, so maybe we can remove this.
763
832
  messages.append({
764
833
  "role": "assistant",
765
834
  "content": response_text,
@@ -17,6 +17,8 @@ from ..main import (
17
17
  from rich.console import Console
18
18
  from rich.live import Live
19
19
 
20
+ # TODO: Include in-build tool calling in LLM class
21
+ # TODO: Restructure so that duplicate calls are not made (Sync with agent.py)
20
22
  class LLMContextLengthExceededException(Exception):
21
23
  """Raised when LLM context length is exceeded"""
22
24
  def __init__(self, message: str):
@@ -111,11 +113,28 @@ class LLM:
111
113
  litellm.success_callback = []
112
114
  litellm._async_success_callback = []
113
115
  litellm.callbacks = []
114
- # Additional logging suppression
115
- litellm.suppress_debug_messages = True
116
- litellm._logging._disable_debugging()
117
- logging.getLogger("litellm.utils").setLevel(logging.WARNING)
118
- logging.getLogger("litellm.main").setLevel(logging.WARNING)
116
+
117
+ verbose = extra_settings.get('verbose', True)
118
+
119
+ # Only suppress logs if not in debug mode
120
+ if not isinstance(verbose, bool) and verbose >= 10:
121
+ # Enable detailed debug logging
122
+ logging.getLogger("asyncio").setLevel(logging.DEBUG)
123
+ logging.getLogger("selector_events").setLevel(logging.DEBUG)
124
+ logging.getLogger("litellm.utils").setLevel(logging.DEBUG)
125
+ logging.getLogger("litellm.main").setLevel(logging.DEBUG)
126
+ litellm.suppress_debug_messages = False
127
+ litellm.set_verbose = True
128
+ else:
129
+ # Suppress debug logging for normal operation
130
+ logging.getLogger("asyncio").setLevel(logging.WARNING)
131
+ logging.getLogger("selector_events").setLevel(logging.WARNING)
132
+ logging.getLogger("litellm.utils").setLevel(logging.WARNING)
133
+ logging.getLogger("litellm.main").setLevel(logging.WARNING)
134
+ litellm.suppress_debug_messages = True
135
+ litellm._logging._disable_debugging()
136
+ warnings.filterwarnings("ignore", category=RuntimeWarning)
137
+
119
138
  except ImportError:
120
139
  raise ImportError(
121
140
  "LiteLLM is required but not installed. "
@@ -143,7 +162,7 @@ class LLM:
143
162
  self.extra_settings = extra_settings
144
163
  self.console = Console()
145
164
  self.chat_history = []
146
- self.verbose = extra_settings.get('verbose', True)
165
+ self.verbose = verbose
147
166
  self.markdown = extra_settings.get('markdown', True)
148
167
  self.self_reflect = extra_settings.get('self_reflect', False)
149
168
  self.max_reflect = extra_settings.get('max_reflect', 3)
@@ -44,6 +44,8 @@ class Process:
44
44
  current_task = start_task
45
45
  visited_tasks = set()
46
46
  loop_data = {} # Store loop-specific data
47
+
48
+ # TODO: start task with loop feature is not available in aworkflow method
47
49
 
48
50
  while current_task:
49
51
  current_iter += 1
@@ -350,9 +352,10 @@ Provide a JSON with the structure:
350
352
  if row: # Skip empty rows
351
353
  task_desc = row[0] # Take first column
352
354
  row_task = Task(
353
- description=task_desc, # Keep full row as description
355
+ description=f"{start_task.description}\n{task_desc}" if start_task.description else task_desc,
354
356
  agent=start_task.agent,
355
- name=task_desc, # Use first column as name
357
+ name=f"{start_task.name}_{i+1}" if start_task.name else task_desc,
358
+ expected_output=getattr(start_task, 'expected_output', None),
356
359
  is_start=(i == 0),
357
360
  task_type="task",
358
361
  condition={
@@ -374,9 +377,10 @@ Provide a JSON with the structure:
374
377
  previous_task = None
375
378
  for i, line in enumerate(lines):
376
379
  row_task = Task(
377
- description=line.strip(),
380
+ description=f"{start_task.description}\n{line.strip()}" if start_task.description else line.strip(),
378
381
  agent=start_task.agent,
379
- name=line.strip(),
382
+ name=f"{start_task.name}_{i+1}" if start_task.name else line.strip(),
383
+ expected_output=getattr(start_task, 'expected_output', None),
380
384
  is_start=(i == 0),
381
385
  task_type="task",
382
386
  condition={
@@ -135,6 +135,27 @@ TOOL_MAPPINGS = {
135
135
  'group_by': ('.pandas_tools', 'PandasTools'),
136
136
  'pivot_table': ('.pandas_tools', 'PandasTools'),
137
137
  'pandas_tools': ('.pandas_tools', 'PandasTools'),
138
+
139
+ # Chain of Thought Training Tools
140
+ 'cot_run': ('.train.data.generatecot', 'GenerateCOT'), # Orchestrates text solution
141
+ 'cot_run_dict': ('.train.data.generatecot', 'GenerateCOT'), # Orchestrates dict-based solution
142
+ 'cot_generate': ('.train.data.generatecot', 'GenerateCOT'), # Generate text solution
143
+ 'cot_generate_dict': ('.train.data.generatecot', 'GenerateCOT'), # Generate structured solution
144
+ 'cot_improve': ('.train.data.generatecot', 'GenerateCOT'), # Improve text solution
145
+ 'cot_improve_dict': ('.train.data.generatecot', 'GenerateCOT'), # Improve dict-based solution
146
+ 'cot_check': ('.train.data.generatecot', 'GenerateCOT'), # Check correctness
147
+ 'cot_find_error': ('.train.data.generatecot', 'GenerateCOT'), # Locate error in solution
148
+ 'cot_load_answers': ('.train.data.generatecot', 'GenerateCOT'), # Load QA pairs
149
+
150
+ # COT Save/Export with QA Pairs
151
+ 'cot_save_solutions_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Save with QA pairs
152
+ 'cot_append_solutions_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Append with QA pairs
153
+ 'cot_export_json_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Export JSON with QA pairs
154
+ 'cot_export_csv_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Export CSV with QA pairs
155
+ 'cot_append_csv_with_qa_pairs': ('.train.data.generatecot', 'GenerateCOT'), # Append CSV with QA pairs
156
+ 'cot_save': ('.train.data.generatecot', 'GenerateCOT'), # Save single QA to file
157
+ 'cot_upload_to_huggingface': ('.train.data.generatecot', 'GenerateCOT'), # Upload dataset to HuggingFace
158
+ 'cot_tools': ('.train.data.generatecot', 'GenerateCOT'), # Full toolkit access
138
159
  }
139
160
 
140
161
  _instances = {} # Cache for class instances
@@ -161,7 +182,7 @@ def __getattr__(name: str) -> Any:
161
182
  ]:
162
183
  return getattr(module, name)
163
184
  if name in ['file_tools', 'pandas_tools', 'wikipedia_tools',
164
- 'newspaper_tools', 'arxiv_tools', 'spider_tools', 'duckdb_tools', 'csv_tools', 'json_tools', 'excel_tools', 'xml_tools', 'yaml_tools', 'calculator_tools', 'python_tools', 'shell_tools']:
185
+ 'newspaper_tools', 'arxiv_tools', 'spider_tools', 'duckdb_tools', 'csv_tools', 'json_tools', 'excel_tools', 'xml_tools', 'yaml_tools', 'calculator_tools', 'python_tools', 'shell_tools', 'cot_tools']:
165
186
  return module # Returns the callable module
166
187
  return getattr(module, name)
167
188
  else:
@@ -0,0 +1,493 @@
1
+ from typing import Dict, Optional, Union, Any
2
+ import json
3
+ from datetime import datetime
4
+ from openai import OpenAI
5
+ from pydantic import BaseModel
6
+
7
+ # Lazy loader for LLM
8
+ def get_llm():
9
+ try:
10
+ from praisonaiagents.llm.llm import LLM
11
+ return LLM
12
+ except ImportError:
13
+ raise ImportError(
14
+ "LLM is required for this toolbut not installed. "
15
+ "Please install with: pip install 'praisonaiagents[llm]' datasets huggingface-hub pandas"
16
+ )
17
+
18
+ class GenerateCOT:
19
+ def __init__(
20
+ self,
21
+ qa_pairs: Optional[Dict[str, str]] = None,
22
+ model: str = "gpt-4o-mini",
23
+ api_key: Optional[str] = None,
24
+ max_attempts: int = 100
25
+ ):
26
+ self.qa_pairs = qa_pairs or {}
27
+ self.max_attempts = max_attempts
28
+ self.solutions = {}
29
+ self.llm = get_llm()(model=model) # Get LLM class and instantiate
30
+ self.model = model
31
+
32
+ def _ask_ai(self, prompt: str) -> str:
33
+ return self.llm.get_response(prompt, temperature=0.7)
34
+
35
+ def _build_solution_prompt(self, question: str, context: str) -> str:
36
+ return f"""
37
+ Solve this problem step by step: {question}
38
+ Context: {context}
39
+ Steps needed:
40
+ 1. Break down the problem
41
+ 2. Show your work
42
+ 3. Explain each step
43
+ 4. Give final answer
44
+ """
45
+
46
+ def cot_generate(self, question: str, context: str = "") -> str:
47
+ prompt = self._build_solution_prompt(question, context)
48
+ return self._ask_ai(prompt)
49
+
50
+ def cot_check(self, question: str, answer: str) -> bool:
51
+ if question not in self.qa_pairs:
52
+ raise ValueError(f"No correct answer found for: {question}")
53
+
54
+ prompt = f"""
55
+ Question: {question}
56
+ Given Answer: {answer}
57
+ Correct Answer: {self.qa_pairs[question]}
58
+ Is the given answer correct? Reply only with 'true' or 'false'.
59
+ """
60
+ return self._ask_ai(prompt).lower().strip() == "true"
61
+
62
+ def cot_find_error(self, question: str, solution: str) -> int:
63
+ if self.cot_check(question, solution):
64
+ return -1
65
+
66
+ sentences = [s.strip() for s in solution.replace('。', '.').split('.') if s.strip()]
67
+ left, right = 0, len(sentences)
68
+
69
+ while left < right:
70
+ mid = (left + right) // 2
71
+ partial = '. '.join(sentences[:mid]) + '.'
72
+ if self.cot_check(question, partial):
73
+ left = mid + 1
74
+ else:
75
+ right = mid
76
+
77
+ return left
78
+
79
+ def cot_improve(self, question: str, current: str) -> str:
80
+ best_solution = current
81
+ best_score = self._rate_solution(question, current)
82
+
83
+ for _ in range(self.max_attempts):
84
+ new_solution = self.cot_generate(question, current)
85
+ new_score = self._rate_solution(question, new_solution)
86
+
87
+ if new_score > best_score:
88
+ best_solution = new_solution
89
+ best_score = new_score
90
+
91
+ if best_score > 0.9:
92
+ break
93
+
94
+ return best_solution
95
+
96
+ def _rate_solution(self, question: str, solution: str) -> float:
97
+ prompt = f"""
98
+ Rate this solution from 0 to 1:
99
+ Question: {question}
100
+ Solution: {solution}
101
+ Correct Answer: {self.qa_pairs.get(question, '')}
102
+ Return only a number between 0 and 1.
103
+ """
104
+ try:
105
+ score = float(self._ask_ai(prompt))
106
+ return min(max(score, 0), 1)
107
+ except:
108
+ return 0.0
109
+
110
+ def cot_run(self, question: str) -> str:
111
+ """Run COT generation for a single question."""
112
+ solution = self.cot_generate(question)
113
+ if self.cot_check(question, solution):
114
+ return solution
115
+
116
+ solution = self.cot_improve(question, solution)
117
+
118
+ error_pos = self.cot_find_error(question, solution)
119
+ if error_pos != -1:
120
+ correct_part = '. '.join(solution.split('. ')[:error_pos]) + '.'
121
+ solution = self.cot_generate(question, correct_part)
122
+
123
+ self.solutions[question] = {
124
+ "solution": solution,
125
+ "error_position": error_pos,
126
+ }
127
+ return solution
128
+
129
+ def cot_load_answers(self, filepath: str) -> bool:
130
+ try:
131
+ with open(filepath, 'r', encoding='utf-8') as f:
132
+ data = json.load(f)
133
+ self.qa_pairs.update(data)
134
+ return True
135
+ except Exception as e:
136
+ print(f"Error loading answers: {e}")
137
+ return False
138
+
139
+ def _is_qa_pairs(self, qa_pairs: Any) -> bool:
140
+ """Validate if input is a proper QA pairs dictionary."""
141
+ if not qa_pairs:
142
+ return True # None or empty is valid
143
+ if not isinstance(qa_pairs, dict):
144
+ raise ValueError("qa_pairs must be a dictionary with questions as keys and answers as values")
145
+ return True
146
+
147
+ def cot_append_solutions_with_qa_pairs(
148
+ self,
149
+ filepath: str = 'solutions.json',
150
+ qa_pairs: Optional[Dict[str, str]] = None
151
+ ) -> None:
152
+ """Appends current solutions to existing file or creates a new one."""
153
+ try:
154
+ self._is_qa_pairs(qa_pairs) # Validate format
155
+ if qa_pairs:
156
+ self.qa_pairs.update(qa_pairs)
157
+
158
+ data = {
159
+ "solutions": self.solutions,
160
+ "qa_pairs": self.qa_pairs,
161
+ "saved_at": datetime.now().isoformat()
162
+ }
163
+
164
+ existing_data = {}
165
+ try:
166
+ with open(filepath, 'r', encoding='utf-8') as f:
167
+ existing_data = json.load(f)
168
+ except (FileNotFoundError, json.JSONDecodeError):
169
+ pass
170
+
171
+ if existing_data:
172
+ existing_data["solutions"].update(data["solutions"])
173
+ existing_data["qa_pairs"].update(data["qa_pairs"])
174
+ existing_data["saved_at"] = data["saved_at"]
175
+ data = existing_data
176
+
177
+ with open(filepath, 'w', encoding='utf-8') as f:
178
+ json.dump(data, f, ensure_ascii=False, indent=2)
179
+ except Exception as e:
180
+ print(f"Error appending solutions: {e}")
181
+
182
+ def cot_save_solutions_with_qa_pairs(
183
+ self,
184
+ filepath: str = 'solutions.json',
185
+ append: bool = False,
186
+ qa_pairs: Optional[Dict[str, str]] = None
187
+ ) -> None:
188
+ try:
189
+ self._is_qa_pairs(qa_pairs) # Validate format
190
+ if qa_pairs:
191
+ self.qa_pairs.update(qa_pairs)
192
+
193
+ if append:
194
+ self.cot_append_solutions_with_qa_pairs(filepath)
195
+ return
196
+
197
+ data = {
198
+ "solutions": self.solutions,
199
+ "qa_pairs": self.qa_pairs,
200
+ "saved_at": datetime.now().isoformat()
201
+ }
202
+ with open(filepath, 'w', encoding='utf-8') as f:
203
+ json.dump(data, f, ensure_ascii=False, indent=2)
204
+ except Exception as e:
205
+ print(f"Error saving solutions: {e}")
206
+
207
+ def cot_generate_dict(self, question: str, context: str = "") -> dict:
208
+ prompt = self._build_solution_prompt(question, context)
209
+ thought_process = self._ask_ai(prompt)
210
+
211
+ final_answer_prompt = f"""
212
+ Based on this solution, what is the final answer only:
213
+ {thought_process}
214
+ Give only the final answer, no explanation.
215
+ """
216
+ final_answer = self._ask_ai(final_answer_prompt)
217
+ return {
218
+ "thought_process": thought_process,
219
+ "final_answer": final_answer
220
+ }
221
+
222
+ def cot_improve_dict(self, question: str, current_solution: str) -> dict:
223
+ """
224
+ Improves the existing solution (text form), returning the best dictionary-based version.
225
+ """
226
+ best_solution = {
227
+ "thought_process": current_solution,
228
+ "final_answer": current_solution
229
+ }
230
+ best_score = self._rate_solution(question, current_solution)
231
+
232
+ for _ in range(self.max_attempts):
233
+ new_solution = self.cot_generate_dict(question, current_solution)
234
+ new_score = self._rate_solution(question, new_solution["thought_process"])
235
+ if new_score > best_score:
236
+ best_solution = new_solution
237
+ best_score = new_score
238
+ if best_score > 0.9:
239
+ break
240
+ return best_solution
241
+
242
+ def cot_run_dict(self, question: str) -> dict:
243
+ """Uses the dictionary-based solution approach, storing the final solution in self.solutions."""
244
+ solution = self.cot_generate_dict(question)
245
+ if self.cot_check(question, solution["final_answer"]):
246
+ self.solutions[question] = solution
247
+ return solution
248
+
249
+ improved = self.cot_improve_dict(question, solution["thought_process"])
250
+ if self.cot_check(question, improved["final_answer"]):
251
+ self.solutions[question] = improved
252
+ return improved
253
+
254
+ error_pos = self.cot_find_error(question, improved["thought_process"])
255
+ if error_pos != -1:
256
+ partial_solution = '. '.join(improved["thought_process"].split('. ')[:error_pos]) + '.'
257
+ final = self.cot_generate_dict(question, partial_solution)
258
+ self.solutions[question] = final
259
+ return final
260
+
261
+ self.solutions[question] = improved
262
+ return improved
263
+
264
+ def cot_export_json_with_qa_pairs(
265
+ self,
266
+ filepath: str = 'dataset.json',
267
+ save_to_file: bool = True,
268
+ qa_pairs: Optional[Dict[str, str]] = None
269
+ ) -> Union[str, list]:
270
+ """
271
+ Export solutions in Alpaca training format with their full thought process.
272
+ """
273
+ try:
274
+ self._is_qa_pairs(qa_pairs) # Validate format
275
+ if qa_pairs:
276
+ self.qa_pairs.update(qa_pairs)
277
+ # Generate solutions if empty
278
+ if not self.solutions:
279
+ for question in qa_pairs:
280
+ self.cot_run_dict(question)
281
+
282
+ alpaca_data = []
283
+ for question, sol in self.solutions.items():
284
+ alpaca_data.append({
285
+ "instruction": question,
286
+ "input": "",
287
+ "output": sol.get("thought_process", "")
288
+ })
289
+
290
+ if not save_to_file:
291
+ return alpaca_data
292
+
293
+ with open(filepath, 'w', encoding='utf-8') as f:
294
+ json.dump(alpaca_data, f, ensure_ascii=False, indent=2)
295
+ return filepath
296
+ except Exception as e:
297
+ print(f"Error exporting to Alpaca format: {e}")
298
+ return None
299
+
300
+ def cot_export_csv_with_qa_pairs(
301
+ self,
302
+ filepath: str = 'dataset.csv',
303
+ qa_pairs: Optional[Dict[str, str]] = None
304
+ ) -> Optional[str]:
305
+ """Export solutions in CSV format."""
306
+ try:
307
+ self._is_qa_pairs(qa_pairs) # Validate format
308
+ if qa_pairs:
309
+ self.qa_pairs.update(qa_pairs)
310
+ # Generate solutions if empty
311
+ if not self.solutions:
312
+ for question in qa_pairs:
313
+ self.cot_run_dict(question)
314
+
315
+ with open(filepath, 'w', newline='', encoding='utf-8') as f:
316
+ writer = csv.writer(f)
317
+ writer.writerow(['instruction', 'input', 'output'])
318
+ for question, sol in self.solutions.items():
319
+ writer.writerow([question, '', sol.get("thought_process", "")])
320
+ return filepath
321
+ except Exception as e:
322
+ print(f"Error exporting to CSV format: {e}")
323
+ return None
324
+
325
+ def cot_save(
326
+ self,
327
+ question: str,
328
+ answer: str,
329
+ filepath: str = 'dataset.csv'
330
+ ) -> Optional[str]:
331
+ """
332
+ Save a single question-answer pair with chain of thought to CSV file.
333
+ Creates file with headers if it doesn't exist.
334
+ """
335
+ try:
336
+ # Remove timestamp-based filename generation since we have default
337
+ solution = self.cot_run_dict(question)
338
+
339
+ import csv
340
+ import os
341
+ file_exists = os.path.exists(filepath)
342
+
343
+ with open(filepath, 'a', newline='', encoding='utf-8') as f:
344
+ writer = csv.writer(f)
345
+ if not file_exists:
346
+ writer.writerow(['instruction', 'input', 'output'])
347
+ writer.writerow([question, '', solution.get("thought_process", "")])
348
+ return filepath
349
+ except Exception as e:
350
+ print(f"Error appending to CSV: {e}")
351
+ return None
352
+
353
+ # Rename existing function to indicate it handles qa_pairs dictionary
354
+ def cot_append_csv_with_qa_pairs(
355
+ self,
356
+ filepath: str = 'dataset.csv',
357
+ qa_pairs: Optional[Dict[str, str]] = None
358
+ ) -> Optional[str]:
359
+ """Append solutions to CSV file using qa_pairs dictionary."""
360
+ try:
361
+ self._is_qa_pairs(qa_pairs) # Validate format
362
+ if qa_pairs:
363
+ self.qa_pairs.update(qa_pairs)
364
+
365
+ import csv
366
+ import os
367
+ file_exists = os.path.exists(filepath)
368
+
369
+ with open(filepath, 'a', newline='', encoding='utf-8') as f:
370
+ writer = csv.writer(f)
371
+ if not file_exists:
372
+ writer.writerow(['instruction', 'input', 'output'])
373
+
374
+ for question, sol in self.solutions.items():
375
+ writer.writerow([question, '', sol.get("thought_process", "")])
376
+ return filepath
377
+ except Exception as e:
378
+ print(f"Error appending to CSV: {e}")
379
+ return None
380
+
381
+ def cot_upload_to_huggingface(
382
+ self,
383
+ huggingface_username: str,
384
+ dataset_name: str,
385
+ filepath: str,
386
+ private: bool = False
387
+ ) -> str:
388
+ """Upload generated solutions to HuggingFace datasets."""
389
+ try:
390
+ from datasets import Dataset
391
+ from huggingface_hub import HfApi, login
392
+ import pandas as pd
393
+
394
+ # Determine file type and load data
395
+ if filepath.endswith('.csv'):
396
+ data = pd.read_csv(filepath)
397
+ elif filepath.endswith('.json'):
398
+ data = pd.read_json(filepath)
399
+ else:
400
+ raise ValueError("Only CSV and JSON files are supported")
401
+
402
+ # Convert to HuggingFace dataset
403
+ dataset = Dataset.from_pandas(data)
404
+
405
+ # Upload to HuggingFace
406
+ repo_id = f"{huggingface_username}/{dataset_name}"
407
+ dataset.push_to_hub(
408
+ repo_id,
409
+ private=private
410
+ )
411
+
412
+ return f"Dataset uploaded successfully to {repo_id}"
413
+
414
+ except Exception as e:
415
+ print(f"Error uploading to HuggingFace: {e}")
416
+ return None
417
+
418
+ # Usage example:
419
+ if __name__ == "__main__":
420
+ # Direct QA Pairs Export Example
421
+ print("\n=== Direct QA Pairs Export Example ===")
422
+ direct_qa_data = {
423
+ "Number of r's in the word strawberry": "3"
424
+ }
425
+
426
+ direct_generator = GenerateCOT()
427
+
428
+ # Export with qa_pairs passed directly to functions
429
+ direct_generator.cot_export_csv_with_qa_pairs(
430
+ filepath='direct_solutions.csv',
431
+ qa_pairs=direct_qa_data
432
+ )
433
+
434
+ # Example of using cot_save for a single QA pair
435
+ direct_generator.cot_save(
436
+ question="What is the capital of France?",
437
+ answer="Paris",
438
+ filepath="single_qa.csv"
439
+ )
440
+
441
+
442
+
443
+ # Upload to HuggingFace
444
+ direct_generator.cot_upload_to_huggingface(
445
+ huggingface_username="mervinpraison",
446
+ dataset_name="cot-test",
447
+ filepath="single_qa.csv"
448
+ )
449
+
450
+ # direct_generator.cot_export_json_with_qa_pairs(
451
+ # filepath='direct_solutions.json',
452
+ # qa_pairs=direct_qa_data
453
+ # )
454
+
455
+ # # Rest of the original examples...
456
+ # qa_data = {
457
+ # "What is 2+2?": "4",
458
+ # "How many letters in 'hello'?": "5"
459
+ # }
460
+
461
+ # generator = GenerateCOT(qa_pairs=qa_data)
462
+ # for question in qa_data:
463
+ # solution = generator.cot_run(question)
464
+ # print(f"Question: {question}")
465
+ # print(f"Solution: {solution}\n")
466
+ # answer = generator.cot_run("What is 2+2?")
467
+ # print(answer)
468
+
469
+ # # Additional QA data processing example
470
+ # print("\n=== Processing Additional QA Data ===")
471
+ # extra_qa_data = {
472
+
473
+ # "What is 5 * 3?": "15"
474
+ # }
475
+
476
+ # # Create separate generator for additional data
477
+ # extra_generator = GenerateCOT(qa_pairs=extra_qa_data)
478
+
479
+ # # Process and save solutions
480
+ # for question in extra_qa_data:
481
+ # solution = extra_generator.cot_run_dict(question)
482
+ # print(f"Processing extra question: {question}")
483
+
484
+ # # Save solutions separately
485
+ # extra_generator.cot_save_solutions_with_qa_pairs('extra_qa_solutions.json')
486
+
487
+ # # Export in Alpaca format
488
+ # extra_generator.cot_export_json_with_qa_pairs(filepath='extra_qa_alpaca.json', save_to_file=True)
489
+
490
+ # # Demonstrate loading saved data
491
+ # loaded_generator = GenerateCOT(qa_pairs={})
492
+ # loaded_generator.cot_load_answers('extra_qa_solutions.json')
493
+ # print("\nLoaded extra QA pairs:", loaded_generator.qa_pairs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: praisonaiagents
3
- Version: 0.0.54
3
+ Version: 0.0.57
4
4
  Summary: Praison AI agents for completing complex tasks with Self Reflection Agents
5
5
  Author: Mervin Praison
6
6
  Requires-Dist: pydantic
@@ -1,7 +1,7 @@
1
1
  praisonaiagents/__init__.py,sha256=JtPibbmeFv3meIb3vkKjckB0p7m-Vqt2RYPwOH8P41k,1228
2
2
  praisonaiagents/main.py,sha256=0kB9gn9meXtr4EIrdgA2lAioKIHCRJ61audsGDwuTm4,14428
3
3
  praisonaiagents/agent/__init__.py,sha256=sKO8wGEXvtCrvV1e834r1Okv0XAqAxqZCqz6hKLiTvA,79
4
- praisonaiagents/agent/agent.py,sha256=Rd2ZCToraAoe57UDT1JfrB03ffRKtZ-Tct9avFcZyT4,54257
4
+ praisonaiagents/agent/agent.py,sha256=VZPci7g_LAvTz7_rWvWAZ1JgCGKQc1vSfI6x0fqi2os,57598
5
5
  praisonaiagents/agents/__init__.py,sha256=_1d6Pqyk9EoBSo7E68sKyd1jDRlN1vxvVIRpoMc0Jcw,168
6
6
  praisonaiagents/agents/agents.py,sha256=94YPQl-hl-EPY6-Xk2Rj9wlIs9YtiLQbsutSOXWX8QI,36156
7
7
  praisonaiagents/agents/autoagents.py,sha256=bjC2O5oZmoJItJXIMPTWc2lsp_AJC9tMiTQOal2hwPA,13532
@@ -9,13 +9,13 @@ praisonaiagents/knowledge/__init__.py,sha256=xL1Eh-a3xsHyIcU4foOWF-JdWYIYBALJH9b
9
9
  praisonaiagents/knowledge/chunking.py,sha256=FzoNY0q8MkvG4gADqk4JcRhmH3lcEHbRdonDgitQa30,6624
10
10
  praisonaiagents/knowledge/knowledge.py,sha256=fQNREDiwdoisfIxJBLVkteXgq_8Gbypfc3UaZbxf5QY,13210
11
11
  praisonaiagents/llm/__init__.py,sha256=ttPQQJQq6Tah-0updoEXDZFKWtJAM93rBWRoIgxRWO8,689
12
- praisonaiagents/llm/llm.py,sha256=M6xh9cuO0KZjzpAkHZrnktxw4eCmXLymoZqMoXeq-0U,50352
12
+ praisonaiagents/llm/llm.py,sha256=G2wKMwitWBJRS6nOq9W77zXtsxvJwsVwXFOKYcllY0E,51386
13
13
  praisonaiagents/memory/memory.py,sha256=I8dOTkrl1i-GgQbDcrFOsSruzJ7MiI6Ys37DK27wrUs,35537
14
14
  praisonaiagents/process/__init__.py,sha256=lkYbL7Hn5a0ldvJtkdH23vfIIZLIcanK-65C0MwaorY,52
15
- praisonaiagents/process/process.py,sha256=_1Nk37kOYakPaUWAJff86rP0ENyykXqMnhTp8E0efuE,30802
15
+ praisonaiagents/process/process.py,sha256=gP3QQxxFO4oUw_HYLf8MoyWyaj_104LIL_AbwLiBxaU,31261
16
16
  praisonaiagents/task/__init__.py,sha256=VL5hXVmyGjINb34AalxpBMl-YW9m5EDcRkMTKkSSl7c,80
17
17
  praisonaiagents/task/task.py,sha256=ikFjzNm4WPYONSLtWA3uDGNIUx_TvXTeU5SukWoC66E,14271
18
- praisonaiagents/tools/__init__.py,sha256=-0lV5n5cG54vYW6REjXIfuJnCLKnfQIDlXsySCaPB9s,7347
18
+ praisonaiagents/tools/__init__.py,sha256=CWOYV9SudYY82r45LnNgaVRV3cmsAFdasNRkPrLsgmI,9198
19
19
  praisonaiagents/tools/arxiv_tools.py,sha256=1stb31zTjLTon4jCnpZG5de9rKc9QWgC0leLegvPXWo,10528
20
20
  praisonaiagents/tools/calculator_tools.py,sha256=S1xPT74Geurvjm52QMMIG29zDXVEWJmM6nmyY7yF298,9571
21
21
  praisonaiagents/tools/csv_tools.py,sha256=gX2nYz4ktmpKvXB36jx5-GqddntEQD4G2fVQWTIKrwU,8435
@@ -35,7 +35,8 @@ praisonaiagents/tools/wikipedia_tools.py,sha256=pGko-f33wqXgxJTv8db7TbizY5XnzBQR
35
35
  praisonaiagents/tools/xml_tools.py,sha256=iYTMBEk5l3L3ryQ1fkUnNVYK-Nnua2Kx2S0dxNMMs1A,17122
36
36
  praisonaiagents/tools/yaml_tools.py,sha256=uogAZrhXV9O7xvspAtcTfpKSQYL2nlOTvCQXN94-G9A,14215
37
37
  praisonaiagents/tools/yfinance_tools.py,sha256=s2PBj_1v7oQnOobo2fDbQBACEHl61ftG4beG6Z979ZE,8529
38
- praisonaiagents-0.0.54.dist-info/METADATA,sha256=Zfzek1Y53OzW35U-lLAX5mTpdS0xxV57mCDdHhcSfYo,830
39
- praisonaiagents-0.0.54.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
40
- praisonaiagents-0.0.54.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
41
- praisonaiagents-0.0.54.dist-info/RECORD,,
38
+ praisonaiagents/tools/train/data/generatecot.py,sha256=HA8HwbhGIavfALxMbKTdGwABP5S6qzuiPtmUiV-FTZI,17491
39
+ praisonaiagents-0.0.57.dist-info/METADATA,sha256=ad3iyUlLBQqjpuTcbka6Z6MAX57RaJGRbkifyYEhz-w,830
40
+ praisonaiagents-0.0.57.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
41
+ praisonaiagents-0.0.57.dist-info/top_level.txt,sha256=_HsRddrJ23iDx5TTqVUVvXG2HeHBL5voshncAMDGjtA,16
42
+ praisonaiagents-0.0.57.dist-info/RECORD,,