mirror of
https://github.com/MervinPraison/PraisonAI.git
synced 2026-11-09 17:31:52 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
181d7e1ede | ||
|
|
4039878422 | ||
|
|
461eba18d5 | ||
|
|
331a1de610 | ||
|
|
91632eb5e4 | ||
|
|
ad46225483 | ||
|
|
9af6758eb6 | ||
|
|
aac67a6572 | ||
|
|
2ceff9bfae | ||
|
|
3b7af02bea | ||
|
|
1ee7cfa2ef | ||
|
|
b01bd41574 | ||
|
|
6570e445b8 | ||
|
|
b421bcf925 | ||
|
|
41d0998b76 | ||
|
|
393e0ee33d | ||
|
|
0dce4de4a4 | ||
|
|
7fe4b48c6e | ||
|
|
5286912f34 | ||
|
|
d6cb1c0165 | ||
|
|
08ed3f94dc | ||
|
|
d3bfbc4b92 | ||
|
|
af7081d294 | ||
|
|
3b2f26699e | ||
|
|
7aa61b9155 |
No files matched your search
+1
-1
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
flask \
|
||||
"praisonai>=2.2.51" \
|
||||
"praisonai>=2.2.52" \
|
||||
"praisonai[api]" \
|
||||
gunicorn \
|
||||
markdown
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.2.51" \
|
||||
"praisonai>=2.2.52" \
|
||||
"praisonai[chat]" \
|
||||
"embedchain[github,youtube]"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.2.51" \
|
||||
"praisonai>=2.2.52" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[chat]" \
|
||||
"praisonai[realtime]" \
|
||||
|
||||
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
|
||||
# Install Python packages (using latest versions)
|
||||
RUN pip install --no-cache-dir \
|
||||
praisonai_tools \
|
||||
"praisonai>=2.2.51" \
|
||||
"praisonai>=2.2.52" \
|
||||
"praisonai[ui]" \
|
||||
"praisonai[crewai]"
|
||||
|
||||
|
||||
+2
-2
@@ -121,7 +121,7 @@ healthcheck:
|
||||
## 📦 Package Versions
|
||||
|
||||
All Docker images use consistent, up-to-date versions:
|
||||
- PraisonAI: `>=2.2.51`
|
||||
- PraisonAI: `>=2.2.52`
|
||||
- PraisonAI Agents: `>=0.0.92`
|
||||
- Python: `3.11-slim`
|
||||
|
||||
@@ -218,7 +218,7 @@ docker-compose up -d
|
||||
### Version Pinning
|
||||
To use specific versions, update the Dockerfile:
|
||||
```dockerfile
|
||||
RUN pip install "praisonai==2.2.51" "praisonaiagents==0.0.92"
|
||||
RUN pip install "praisonai==2.2.52" "praisonaiagents==0.0.92"
|
||||
```
|
||||
|
||||
## 🌐 Production Deployment
|
||||
|
||||
@@ -72,7 +72,7 @@ workflow = PraisonAIAgents(
|
||||
)
|
||||
|
||||
# Run the workflow
|
||||
results = workflow.start()
|
||||
results = workflow.start(return_dict=True)
|
||||
|
||||
# Print results
|
||||
print("\nWorkflow Results:")
|
||||
|
||||
@@ -22,9 +22,9 @@ def multiply(a: int, b: int) -> int:
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant. You can use the tools provided to you to help the user.",
|
||||
llm="openai/gpt-4o-mini",
|
||||
llm="gemini/gemini-2.5-pro",
|
||||
tools=[get_stock_price, multiply]
|
||||
)
|
||||
|
||||
result = agent.start("multiply the Google stock price with 2")
|
||||
result = agent.start("what is the stock price of Google? multiply the Google stock price with 2")
|
||||
print(result)
|
||||
@@ -0,0 +1,30 @@
|
||||
from praisonaiagents import Agent
|
||||
|
||||
def get_stock_price(company_name: str) -> str:
|
||||
"""
|
||||
Get the stock price of a company
|
||||
|
||||
Args:
|
||||
company_name (str): The name of the company
|
||||
|
||||
Returns:
|
||||
str: The stock price of the company
|
||||
"""
|
||||
return f"The stock price of {company_name} is 100"
|
||||
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""
|
||||
Multiply two numbers
|
||||
"""
|
||||
return a * b
|
||||
|
||||
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant. You can use the tools provided to you to help the user.",
|
||||
llm="ollama/qwen3",
|
||||
tools=[get_stock_price, multiply]
|
||||
)
|
||||
|
||||
result = agent.start("what is the stock price of Google? multiply the Google stock price with 2")
|
||||
print(result)
|
||||
@@ -0,0 +1,30 @@
|
||||
from praisonaiagents import Agent
|
||||
|
||||
def get_stock_price(company_name: str) -> str:
|
||||
"""
|
||||
Get the stock price of a company
|
||||
|
||||
Args:
|
||||
company_name (str): The name of the company
|
||||
|
||||
Returns:
|
||||
str: The stock price of the company
|
||||
"""
|
||||
return f"The stock price of {company_name} is 100"
|
||||
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""
|
||||
Multiply two numbers
|
||||
"""
|
||||
return a * b
|
||||
|
||||
|
||||
|
||||
agent = Agent(
|
||||
instructions="You are a helpful assistant. You can use the tools provided to you to help the user.",
|
||||
llm="gpt-4o",
|
||||
tools=[get_stock_price, multiply]
|
||||
)
|
||||
|
||||
result = agent.start("what is the stock price of Google? multiply the Google stock price with 2")
|
||||
print(result)
|
||||
@@ -1235,7 +1235,8 @@ Your Goal: {self.goal}"""
|
||||
agent_role=self.role,
|
||||
agent_tools=[t.__name__ if hasattr(t, '__name__') else str(t) for t in (tools if tools is not None else self.tools)],
|
||||
execute_tool_fn=self.execute_tool, # Pass tool execution function
|
||||
reasoning_steps=reasoning_steps
|
||||
reasoning_steps=reasoning_steps,
|
||||
stream=stream # Pass the stream parameter from chat method
|
||||
)
|
||||
|
||||
self.chat_history.append({"role": "assistant", "content": response_text})
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# Ollama Handling in LLM.py
|
||||
|
||||
The `ollama_handled` flag is used in `llm.py` (not in `agent.py`) to track whether special Ollama model handling has been applied for tool results. Here's what I found:
|
||||
|
||||
## Where it's used in llm.py:
|
||||
- **Line 863**: `ollama_handled = False` - initialized at the beginning of the tool execution loop
|
||||
- **Line 864**: Special handling logic via `_handle_ollama_model()` method
|
||||
- **Line 907**: `ollama_handled = True` - set when Ollama special handling is applied
|
||||
- **Line 928 & 959**: Checks `if not ollama_handled` to avoid duplicate processing
|
||||
|
||||
## Why it's needed:
|
||||
The flag prevents duplicate processing when Ollama models require special handling. Some Ollama models return only the tool call JSON without processing the results, requiring a follow-up prompt to get the final answer. The `ollama_handled` flag ensures this special handling happens only once.
|
||||
|
||||
## Does this contradict the requirement?
|
||||
**No, it doesn't.** The issue states:
|
||||
|
||||
> "ollama/xxxx model is already handled via llm.py via litellm, so no special condition or handling required for ollama inside agent.py"
|
||||
|
||||
✅ **Current implementation is correct:**
|
||||
|
||||
- `agent.py` has NO ollama-specific conditions (only one documentation mention as an example)
|
||||
- All ollama handling is contained within `llm.py` as intended
|
||||
- The `ollama_handled` flag is an internal implementation detail within `llm.py`
|
||||
|
||||
The codebase already follows the desired pattern where ollama models are handled through the generic LLM interface via litellm without special conditions in agent.py.
|
||||
@@ -680,6 +680,7 @@ class LLM:
|
||||
max_iterations = 10 # Prevent infinite loops
|
||||
iteration_count = 0
|
||||
final_response_text = ""
|
||||
stored_reasoning_content = None # Store reasoning content from tool execution
|
||||
|
||||
while iteration_count < max_iterations:
|
||||
try:
|
||||
@@ -857,8 +858,6 @@ class LLM:
|
||||
iteration_count += 1
|
||||
continue
|
||||
|
||||
# If we reach here, no more tool calls needed - get final response
|
||||
# Make one more call to get the final summary response
|
||||
# Special handling for Ollama models that don't automatically process tool results
|
||||
ollama_handled = False
|
||||
ollama_params = self._handle_ollama_model(response_text, tool_results, messages, original_prompt)
|
||||
@@ -918,15 +917,23 @@ class LLM:
|
||||
console=console
|
||||
)
|
||||
|
||||
# Return the final response after processing Ollama's follow-up
|
||||
# Update messages and continue the loop instead of returning
|
||||
if final_response_text:
|
||||
return final_response_text
|
||||
# Update messages with the response to maintain conversation context
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": final_response_text
|
||||
})
|
||||
# Continue the loop to check if more tools are needed
|
||||
iteration_count += 1
|
||||
continue
|
||||
else:
|
||||
logging.warning("[OLLAMA_DEBUG] Ollama follow-up returned empty response")
|
||||
|
||||
# If reasoning_steps is True and we haven't handled Ollama already, do a single non-streaming call
|
||||
# Handle reasoning_steps after tool execution if not already handled by Ollama
|
||||
if reasoning_steps and not ollama_handled:
|
||||
resp = litellm.completion(
|
||||
# Make a non-streaming call to capture reasoning content
|
||||
reasoning_resp = litellm.completion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
@@ -934,89 +941,28 @@ class LLM:
|
||||
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
|
||||
)
|
||||
)
|
||||
reasoning_content = resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
|
||||
response_text = resp["choices"][0]["message"]["content"]
|
||||
reasoning_content = reasoning_resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
|
||||
response_text = reasoning_resp["choices"][0]["message"]["content"]
|
||||
|
||||
# Optionally display reasoning if present
|
||||
if verbose and reasoning_content:
|
||||
display_interaction(
|
||||
original_prompt,
|
||||
f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}",
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
else:
|
||||
display_interaction(
|
||||
original_prompt,
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
|
||||
# Otherwise do the existing streaming approach if not already handled
|
||||
elif not ollama_handled:
|
||||
# Get response after tool calls
|
||||
if stream:
|
||||
# Streaming approach
|
||||
if verbose:
|
||||
with Live(display_generating("", current_time), console=console, refresh_per_second=4) as live:
|
||||
final_response_text = ""
|
||||
for chunk in litellm.completion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
tools=formatted_tools,
|
||||
temperature=temperature,
|
||||
stream=True,
|
||||
**kwargs
|
||||
)
|
||||
):
|
||||
if chunk and chunk.choices and chunk.choices[0].delta.content:
|
||||
content = chunk.choices[0].delta.content
|
||||
final_response_text += content
|
||||
live.update(display_generating(final_response_text, current_time))
|
||||
else:
|
||||
final_response_text = ""
|
||||
for chunk in litellm.completion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
tools=formatted_tools,
|
||||
temperature=temperature,
|
||||
stream=True,
|
||||
**kwargs
|
||||
)
|
||||
):
|
||||
if chunk and chunk.choices and chunk.choices[0].delta.content:
|
||||
final_response_text += chunk.choices[0].delta.content
|
||||
else:
|
||||
# Non-streaming approach
|
||||
resp = litellm.completion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
tools=formatted_tools,
|
||||
temperature=temperature,
|
||||
stream=False,
|
||||
**kwargs
|
||||
)
|
||||
)
|
||||
final_response_text = resp.get("choices", [{}])[0].get("message", {}).get("content", "") or ""
|
||||
# Store reasoning content for later use
|
||||
if reasoning_content:
|
||||
stored_reasoning_content = reasoning_content
|
||||
|
||||
final_response_text = final_response_text.strip()
|
||||
# Update messages with the response
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": response_text
|
||||
})
|
||||
|
||||
# Display final response
|
||||
if verbose:
|
||||
display_interaction(
|
||||
original_prompt,
|
||||
final_response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
|
||||
return final_response_text
|
||||
# After tool execution, continue the loop to check if more tools are needed
|
||||
# instead of immediately trying to get a final response
|
||||
iteration_count += 1
|
||||
continue
|
||||
else:
|
||||
# No tool calls, we're done with this iteration
|
||||
# If we've executed tools in previous iterations, this response contains the final answer
|
||||
if iteration_count > 0:
|
||||
final_response_text = response_text.strip()
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
@@ -1029,16 +975,30 @@ class LLM:
|
||||
|
||||
# No tool calls were made in this iteration, return the response
|
||||
if verbose:
|
||||
display_interaction(
|
||||
original_prompt,
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
# If we have stored reasoning content from tool execution, display it
|
||||
if stored_reasoning_content:
|
||||
display_interaction(
|
||||
original_prompt,
|
||||
f"Reasoning:\n{stored_reasoning_content}\n\nAnswer:\n{response_text}",
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
else:
|
||||
display_interaction(
|
||||
original_prompt,
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
|
||||
response_text = response_text.strip()
|
||||
|
||||
# Return reasoning content if reasoning_steps is True and we have it
|
||||
if reasoning_steps and stored_reasoning_content:
|
||||
return stored_reasoning_content
|
||||
|
||||
# Handle output formatting
|
||||
if output_json or output_pydantic:
|
||||
self.chat_history.append({"role": "user", "content": original_prompt})
|
||||
@@ -1053,8 +1013,8 @@ class LLM:
|
||||
display_interaction(original_prompt, response_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console)
|
||||
# Return reasoning content if reasoning_steps is True
|
||||
if reasoning_steps and reasoning_content:
|
||||
return reasoning_content
|
||||
if reasoning_steps and stored_reasoning_content:
|
||||
return stored_reasoning_content
|
||||
return response_text
|
||||
|
||||
# Handle self-reflection loop
|
||||
@@ -1317,118 +1277,126 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
# Format tools for LiteLLM using the shared helper
|
||||
formatted_tools = self._format_tools_for_litellm(tools)
|
||||
|
||||
response_text = ""
|
||||
if reasoning_steps:
|
||||
# Non-streaming call to capture reasoning
|
||||
resp = await litellm.acompletion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
# Initialize variables for iteration loop
|
||||
max_iterations = 10 # Prevent infinite loops
|
||||
iteration_count = 0
|
||||
final_response_text = ""
|
||||
stored_reasoning_content = None # Store reasoning content from tool execution
|
||||
|
||||
while iteration_count < max_iterations:
|
||||
response_text = ""
|
||||
reasoning_content = None
|
||||
tool_calls = []
|
||||
|
||||
if reasoning_steps and iteration_count == 0:
|
||||
# Non-streaming call to capture reasoning
|
||||
resp = await litellm.acompletion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
stream=False, # force non-streaming
|
||||
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
|
||||
)
|
||||
)
|
||||
reasoning_content = resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
|
||||
response_text = resp["choices"][0]["message"]["content"]
|
||||
|
||||
if verbose and reasoning_content:
|
||||
display_interaction(
|
||||
"Initial reasoning:",
|
||||
f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}",
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
elif verbose:
|
||||
display_interaction(
|
||||
"Initial response:",
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
else:
|
||||
# Determine if we should use streaming based on tool support
|
||||
use_streaming = stream
|
||||
if formatted_tools and not self._supports_streaming_tools():
|
||||
# Provider doesn't support streaming with tools, use non-streaming
|
||||
use_streaming = False
|
||||
|
||||
if use_streaming:
|
||||
# Streaming approach (with or without tools)
|
||||
tool_calls = []
|
||||
reasoning_content = resp["choices"][0]["message"].get("provider_specific_fields", {}).get("reasoning_content")
|
||||
response_text = resp["choices"][0]["message"]["content"]
|
||||
|
||||
if verbose:
|
||||
async for chunk in await litellm.acompletion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
stream=True,
|
||||
tools=formatted_tools,
|
||||
**kwargs
|
||||
)
|
||||
):
|
||||
if chunk and chunk.choices and chunk.choices[0].delta:
|
||||
delta = chunk.choices[0].delta
|
||||
response_text, tool_calls = self._process_stream_delta(
|
||||
delta, response_text, tool_calls, formatted_tools
|
||||
)
|
||||
if delta.content:
|
||||
print("\033[K", end="\r")
|
||||
print(f"Generating... {time.time() - start_time:.1f}s", end="\r")
|
||||
|
||||
else:
|
||||
# Non-verbose streaming
|
||||
async for chunk in await litellm.acompletion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
stream=True,
|
||||
tools=formatted_tools,
|
||||
**kwargs
|
||||
)
|
||||
):
|
||||
if chunk and chunk.choices and chunk.choices[0].delta:
|
||||
delta = chunk.choices[0].delta
|
||||
if delta.content:
|
||||
response_text += delta.content
|
||||
|
||||
# Capture tool calls from streaming chunks if provider supports it
|
||||
if formatted_tools and self._supports_streaming_tools():
|
||||
tool_calls = self._process_tool_calls_from_stream(delta, tool_calls)
|
||||
|
||||
response_text = response_text.strip()
|
||||
|
||||
# We already have tool_calls from streaming if supported
|
||||
# No need for a second API call!
|
||||
else:
|
||||
# Non-streaming approach (when tools require it or streaming is disabled)
|
||||
tool_response = await litellm.acompletion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
stream=False,
|
||||
tools=formatted_tools,
|
||||
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
|
||||
)
|
||||
)
|
||||
response_text = tool_response.choices[0].message.get("content", "")
|
||||
tool_calls = tool_response.choices[0].message.get("tool_calls", [])
|
||||
|
||||
if verbose:
|
||||
# Display the complete response at once
|
||||
if verbose and reasoning_content:
|
||||
display_interaction(
|
||||
original_prompt,
|
||||
"Initial reasoning:",
|
||||
f"Reasoning:\n{reasoning_content}\n\nAnswer:\n{response_text}",
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
elif verbose:
|
||||
display_interaction(
|
||||
"Initial response:",
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
else:
|
||||
# Determine if we should use streaming based on tool support
|
||||
use_streaming = stream
|
||||
if formatted_tools and not self._supports_streaming_tools():
|
||||
# Provider doesn't support streaming with tools, use non-streaming
|
||||
use_streaming = False
|
||||
|
||||
if use_streaming:
|
||||
# Streaming approach (with or without tools)
|
||||
tool_calls = []
|
||||
|
||||
if verbose:
|
||||
async for chunk in await litellm.acompletion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
stream=True,
|
||||
tools=formatted_tools,
|
||||
**kwargs
|
||||
)
|
||||
):
|
||||
if chunk and chunk.choices and chunk.choices[0].delta:
|
||||
delta = chunk.choices[0].delta
|
||||
response_text, tool_calls = self._process_stream_delta(
|
||||
delta, response_text, tool_calls, formatted_tools
|
||||
)
|
||||
if delta.content:
|
||||
print("\033[K", end="\r")
|
||||
print(f"Generating... {time.time() - start_time:.1f}s", end="\r")
|
||||
|
||||
# Now handle tools if we have them (either from streaming or non-streaming)
|
||||
if tools and execute_tool_fn and tool_calls:
|
||||
|
||||
if tool_calls:
|
||||
else:
|
||||
# Non-verbose streaming
|
||||
async for chunk in await litellm.acompletion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
stream=True,
|
||||
tools=formatted_tools,
|
||||
**kwargs
|
||||
)
|
||||
):
|
||||
if chunk and chunk.choices and chunk.choices[0].delta:
|
||||
delta = chunk.choices[0].delta
|
||||
if delta.content:
|
||||
response_text += delta.content
|
||||
|
||||
# Capture tool calls from streaming chunks if provider supports it
|
||||
if formatted_tools and self._supports_streaming_tools():
|
||||
tool_calls = self._process_tool_calls_from_stream(delta, tool_calls)
|
||||
|
||||
response_text = response_text.strip()
|
||||
|
||||
# We already have tool_calls from streaming if supported
|
||||
# No need for a second API call!
|
||||
else:
|
||||
# Non-streaming approach (when tools require it or streaming is disabled)
|
||||
tool_response = await litellm.acompletion(
|
||||
**self._build_completion_params(
|
||||
messages=messages,
|
||||
temperature=temperature,
|
||||
stream=False,
|
||||
tools=formatted_tools,
|
||||
**{k:v for k,v in kwargs.items() if k != 'reasoning_steps'}
|
||||
)
|
||||
)
|
||||
response_text = tool_response.choices[0].message.get("content", "")
|
||||
tool_calls = tool_response.choices[0].message.get("tool_calls", [])
|
||||
|
||||
if verbose:
|
||||
# Display the complete response at once
|
||||
display_interaction(
|
||||
original_prompt,
|
||||
response_text,
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
|
||||
# Now handle tools if we have them (either from streaming or non-streaming)
|
||||
if tools and execute_tool_fn and tool_calls:
|
||||
# Convert tool_calls to a serializable format for all providers
|
||||
serializable_tool_calls = self._serialize_tool_calls(tool_calls)
|
||||
messages.append({
|
||||
@@ -1509,9 +1477,16 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
console=console
|
||||
)
|
||||
|
||||
# Return the final response after processing Ollama's follow-up
|
||||
# Store the response for potential final return
|
||||
if final_response_text:
|
||||
return final_response_text
|
||||
# Update messages with the response to maintain conversation context
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": final_response_text
|
||||
})
|
||||
# Continue the loop to check if more tools are needed
|
||||
iteration_count += 1
|
||||
continue
|
||||
else:
|
||||
logging.warning("[OLLAMA_DEBUG] Ollama follow-up returned empty response")
|
||||
|
||||
@@ -1577,6 +1552,27 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
response_text += chunk.choices[0].delta.content
|
||||
|
||||
response_text = response_text.strip()
|
||||
|
||||
# After tool execution, update messages and continue the loop
|
||||
if response_text:
|
||||
messages.append({
|
||||
"role": "assistant",
|
||||
"content": response_text
|
||||
})
|
||||
|
||||
# Store reasoning content if captured
|
||||
if reasoning_steps and reasoning_content:
|
||||
stored_reasoning_content = reasoning_content
|
||||
|
||||
# Continue the loop to check if more tools are needed
|
||||
iteration_count += 1
|
||||
continue
|
||||
else:
|
||||
# No tool calls, we're done with this iteration
|
||||
# If we've executed tools in previous iterations, this response contains the final answer
|
||||
if iteration_count > 0:
|
||||
final_response_text = response_text.strip()
|
||||
break
|
||||
|
||||
# Handle output formatting
|
||||
if output_json or output_pydantic:
|
||||
@@ -1588,13 +1584,27 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
|
||||
return response_text
|
||||
|
||||
if not self_reflect:
|
||||
# Use final_response_text if we went through tool iterations
|
||||
display_text = final_response_text if final_response_text else response_text
|
||||
|
||||
# Display with stored reasoning content if available
|
||||
if verbose:
|
||||
display_interaction(original_prompt, response_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console)
|
||||
# Return reasoning content if reasoning_steps is True
|
||||
if reasoning_steps and reasoning_content:
|
||||
return reasoning_content
|
||||
return response_text
|
||||
if stored_reasoning_content:
|
||||
display_interaction(
|
||||
original_prompt,
|
||||
f"Reasoning:\n{stored_reasoning_content}\n\nAnswer:\n{display_text}",
|
||||
markdown=markdown,
|
||||
generation_time=time.time() - start_time,
|
||||
console=console
|
||||
)
|
||||
else:
|
||||
display_interaction(original_prompt, display_text, markdown=markdown,
|
||||
generation_time=time.time() - start_time, console=console)
|
||||
|
||||
# Return reasoning content if reasoning_steps is True and we have it
|
||||
if reasoning_steps and stored_reasoning_content:
|
||||
return stored_reasoning_content
|
||||
return display_text
|
||||
|
||||
# Handle self-reflection
|
||||
reflection_prompt = f"""
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.125"
|
||||
version = "0.0.126"
|
||||
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
|
||||
requires-python = ">=3.10"
|
||||
authors = [
|
||||
|
||||
@@ -11,14 +11,30 @@ def analyze_code_changes():
|
||||
]
|
||||
return issues[int(time.time()) % 3]
|
||||
|
||||
def suggest_fixes(issue: Dict):
|
||||
def suggest_fixes(issue):
|
||||
"""Simulates fix suggestions"""
|
||||
fixes = {
|
||||
"style": "Apply PEP 8 formatting",
|
||||
"security": "Implement input validation",
|
||||
"security": "Implement input validation",
|
||||
"performance": "Use list comprehension"
|
||||
}
|
||||
return fixes.get(issue["type"], "Review manually")
|
||||
|
||||
# Handle both dict and string inputs
|
||||
if isinstance(issue, dict):
|
||||
issue_type = issue.get("type", "unknown")
|
||||
else:
|
||||
# If it's a string, try to extract the type
|
||||
issue_str = str(issue).lower()
|
||||
if "security" in issue_str:
|
||||
issue_type = "security"
|
||||
elif "style" in issue_str:
|
||||
issue_type = "style"
|
||||
elif "performance" in issue_str:
|
||||
issue_type = "performance"
|
||||
else:
|
||||
issue_type = "unknown"
|
||||
|
||||
return fixes.get(issue_type, "Review manually")
|
||||
|
||||
def apply_automated_fix(fix: str):
|
||||
"""Simulates applying automated fixes"""
|
||||
@@ -98,11 +114,8 @@ def main():
|
||||
# Print results
|
||||
print("\nCode Review Results:")
|
||||
print("=" * 50)
|
||||
for task_id, result in results["task_results"].items():
|
||||
if result:
|
||||
print(f"\nTask: {task_id}")
|
||||
print(f"Result: {result.raw}")
|
||||
print("-" * 50)
|
||||
print(f"Results: {results}")
|
||||
print("-" * 50)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -69,7 +69,7 @@ workflow = PraisonAIAgents(
|
||||
)
|
||||
|
||||
# Run the workflow
|
||||
results = workflow.start()
|
||||
results = workflow.start(return_dict=True)
|
||||
|
||||
# Print results
|
||||
print("\nWorkflow Results:")
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
"""
|
||||
Test script to verify the fix for sequential tool calling.
|
||||
The agent should:
|
||||
1. Call get_stock_price to get Google's stock price (100)
|
||||
2. Call multiply to multiply 100 by 2
|
||||
3. Return the final result (200)
|
||||
"""
|
||||
|
||||
from praisonaiagents import Agent
|
||||
|
||||
def get_stock_price(company_name: str) -> str:
|
||||
"""
|
||||
Get the stock price of a company
|
||||
|
||||
Args:
|
||||
company_name (str): The name of the company
|
||||
|
||||
Returns:
|
||||
str: The stock price of the company
|
||||
"""
|
||||
print(f"[Tool Called] get_stock_price({company_name})")
|
||||
return f"The stock price of {company_name} is 100"
|
||||
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""
|
||||
Multiply two numbers
|
||||
|
||||
Args:
|
||||
a (int): First number
|
||||
b (int): Second number
|
||||
|
||||
Returns:
|
||||
int: Product of a and b
|
||||
"""
|
||||
print(f"[Tool Called] multiply({a}, {b})")
|
||||
return a * b
|
||||
|
||||
# Test with Gemini
|
||||
print("=" * 60)
|
||||
print("Testing with Gemini model")
|
||||
print("=" * 60)
|
||||
|
||||
agent_gemini = Agent(
|
||||
instructions="You are a helpful assistant. You can use the tools provided to you to help the user. When asked to multiply a stock price, first get the stock price, then multiply it.",
|
||||
llm="gemini/gemini-2.5-pro",
|
||||
tools=[get_stock_price, multiply],
|
||||
verbose=True
|
||||
)
|
||||
|
||||
result_gemini = agent_gemini.start("what is the stock price of Google? multiply the Google stock price with 2")
|
||||
print(f"\nFinal Result (Gemini): {result_gemini}")
|
||||
|
||||
# Test with GPT-4
|
||||
print("\n" + "=" * 60)
|
||||
print("Testing with GPT-4 model")
|
||||
print("=" * 60)
|
||||
|
||||
agent_gpt4 = Agent(
|
||||
instructions="You are a helpful assistant. You can use the tools provided to you to help the user. When asked to multiply a stock price, first get the stock price, then multiply it.",
|
||||
llm="gpt-4o",
|
||||
tools=[get_stock_price, multiply],
|
||||
verbose=True
|
||||
)
|
||||
|
||||
result_gpt4 = agent_gpt4.start("what is the stock price of Google? multiply the Google stock price with 2")
|
||||
print(f"\nFinal Result (GPT-4): {result_gpt4}")
|
||||
|
||||
# Verify results
|
||||
print("\n" + "=" * 60)
|
||||
print("Test Results Summary")
|
||||
print("=" * 60)
|
||||
print(f"Gemini result contains '200': {'200' in str(result_gemini) if result_gemini else False}")
|
||||
print(f"GPT-4 result contains '200': {'200' in str(result_gpt4) if result_gpt4 else False}")
|
||||
print(f"Gemini returned empty: {not result_gemini or result_gemini == ''}")
|
||||
print(f"GPT-4 returned empty: {not result_gpt4 or result_gpt4 == ''}")
|
||||
Generated
+1
-1
@@ -2375,7 +2375,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.125"
|
||||
version = "0.0.126"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
|
||||
@@ -3,8 +3,8 @@ class Praisonai < Formula
|
||||
|
||||
desc "AI tools for various AI applications"
|
||||
homepage "https://github.com/MervinPraison/PraisonAI"
|
||||
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.51.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.51.tar.gz | shasum -a 256`.split.first
|
||||
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.52.tar.gz"
|
||||
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v2.2.52.tar.gz | shasum -a 256`.split.first
|
||||
license "MIT"
|
||||
|
||||
depends_on "[email protected]"
|
||||
|
||||
@@ -57,7 +57,7 @@ class CloudDeployer:
|
||||
file.write("FROM python:3.11-slim\n")
|
||||
file.write("WORKDIR /app\n")
|
||||
file.write("COPY . .\n")
|
||||
file.write("RUN pip install flask praisonai==2.2.51 gunicorn markdown\n")
|
||||
file.write("RUN pip install flask praisonai==2.2.52 gunicorn markdown\n")
|
||||
file.write("EXPOSE 8080\n")
|
||||
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "PraisonAI"
|
||||
version = "2.2.51"
|
||||
version = "2.2.52"
|
||||
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
|
||||
readme = "README.md"
|
||||
license = ""
|
||||
@@ -12,7 +12,7 @@ dependencies = [
|
||||
"rich>=13.7",
|
||||
"markdown>=3.5",
|
||||
"pyparsing>=3.0.0",
|
||||
"praisonaiagents>=0.0.125",
|
||||
"praisonaiagents>=0.0.126",
|
||||
"python-dotenv>=0.19.0",
|
||||
"instructor>=1.3.3",
|
||||
"PyYAML>=6.0",
|
||||
@@ -95,7 +95,7 @@ autogen = ["pyautogen>=0.2.19", "praisonai-tools>=0.0.15", "crewai"]
|
||||
|
||||
[tool.poetry]
|
||||
name = "PraisonAI"
|
||||
version = "2.2.51"
|
||||
version = "2.2.52"
|
||||
description = "PraisonAI is an AI Agents Framework with Self Reflection. PraisonAI application combines PraisonAI Agents, AutoGen, and CrewAI into a low-code solution for building and managing multi-agent LLM systems, focusing on simplicity, customisation, and efficient human-agent collaboration."
|
||||
authors = ["Mervin Praison"]
|
||||
license = ""
|
||||
@@ -113,7 +113,7 @@ python = ">=3.10,<3.13"
|
||||
rich = ">=13.7"
|
||||
markdown = ">=3.5"
|
||||
pyparsing = ">=3.0.0"
|
||||
praisonaiagents = ">=0.0.125"
|
||||
praisonaiagents = ">=0.0.126"
|
||||
python-dotenv = ">=0.19.0"
|
||||
instructor = ">=1.3.3"
|
||||
PyYAML = ">=6.0"
|
||||
|
||||
Generated
+5
-5
@@ -3931,7 +3931,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonai"
|
||||
version = "2.2.51"
|
||||
version = "2.2.52"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "instructor" },
|
||||
@@ -4073,7 +4073,7 @@ requires-dist = [
|
||||
{ name = "plotly", marker = "extra == 'realtime'", specifier = ">=5.24.0" },
|
||||
{ name = "praisonai-tools", marker = "extra == 'autogen'", specifier = ">=0.0.15" },
|
||||
{ name = "praisonai-tools", marker = "extra == 'crewai'", specifier = ">=0.0.15" },
|
||||
{ name = "praisonaiagents", specifier = ">=0.0.125" },
|
||||
{ name = "praisonaiagents", specifier = ">=0.0.126" },
|
||||
{ name = "pyautogen", marker = "extra == 'autogen'", specifier = ">=0.2.19" },
|
||||
{ name = "pydantic", marker = "extra == 'chat'", specifier = "<=2.10.1" },
|
||||
{ name = "pydantic", marker = "extra == 'code'", specifier = "<=2.10.1" },
|
||||
@@ -4130,7 +4130,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "praisonaiagents"
|
||||
version = "0.0.125"
|
||||
version = "0.0.126"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "aiohttp" },
|
||||
@@ -4140,9 +4140,9 @@ dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ed/e8/bed80966c49482a565e9e6a7eab93ea81ce5381d3f20b60f550c3624fbcb/praisonaiagents-0.0.125.tar.gz", hash = "sha256:a5ec4da7e3a948303038f0a56f1863add3183af8b415e587e007895ce752615a", size = 185807 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/05/68/7b8be1f9b69b02a36763009b8a847861964bb49aa67d68c02fd5fd77b5a0/praisonaiagents-0.0.126.tar.gz", hash = "sha256:229413f76dd6bb5d5f2d22ef04613f85b8f251bfe5fbd9bdff3440de429748f3", size = 186412 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1e/0c/6072b7e5e5eb489279381cd6d18f200916905fa731d0cc4318ac514f2152/praisonaiagents-0.0.125-py3-none-any.whl", hash = "sha256:fff3f92103d9801da517f4b253d86e76afbfee566e632e12792cd22d583223d9", size = 207023 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0b/7b/4a3672046fa8104e11948352b5fa8815b4248473337629bbb7420149c908/praisonaiagents-0.0.126-py3-none-any.whl", hash = "sha256:30a0cea45f89516052e36f1806ea043c2268cdf5f7e7347c8b1a9ddac22e955f", size = 206978 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in new issue
Block a user