Compare commits

..
Author SHA1 Message Date
MervinPraison d3c4a2afad Release v4.6.56 2026-06-13 00:42:25 +01:00
034138ebc9 fix(agents): multi-agent streaming failure with sync OpenAI adapter (#1911)
* fix(agents): resolve multi-agent streaming failure with sync OpenAI adapter

- Change MultiAgentOutputConfig.stream default from True to False
- Fixes #1882: multi-agent workflows now work with sync OpenAI adapter
- Aligns with safe defaults principle from AGENTS.md
- Single-agent retains fallback behavior, multi-agent now uses safe default
- Resolves benchmark regression where PraisonAI was only framework where multi-agent failed

Co-authored-by: praisonai-triage-agent[bot] <praisonai-triage-agent[bot]@users.noreply.github.com>

* fix(agents): complete multi-agent streaming fix - address remaining stream=True paths

- Fix agents.py fallback path that sets _stream=True on import error
- Fix presets.py verbose/minimal multi-agent presets that force stream=True
- Fix test_multiagent_fix.py hardcoded path and error handling issues
- Ensures sync OpenAI adapter compatibility across all code paths

Co-authored-by: Mervin Praison <[email protected]>

---------

Co-authored-by: praisonai-triage-agent[bot] <272766704+praisonai-triage-agent[bot]@users.noreply.github.com>
Co-authored-by: praisonai-triage-agent[bot] <praisonai-triage-agent[bot]@users.noreply.github.com>
Co-authored-by: Mervin Praison <[email protected]>
2026-06-13 00:41:15 +01:00
MervinPraison 70b2df135b docs: auto-update api.md [skip ci] 2026-06-12 23:40:34 +00:00
3532600887 fix: deterministic context assembly for prompt caching (#1909)
* fix: implement deterministic context assembly for prompt caching optimization (fixes #1808)

- Add stable sorting for memory search results by timestamp desc then content hash
- Implement deterministic tool schema ordering alphabetically by function name
- Add cache boundary markers and build_cache_optimized_context method
- Include comprehensive test suite validating all cache optimization features

This resolves non-deterministic context assembly that was breaking prompt prefix
caching for providers like Anthropic and Google, preventing cache hits and
causing full input-token cost on every turn instead of only delta costs.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: MervinPraison <[email protected]>

* fix: improve deterministic context assembly for cache optimization

- Fix memory sorting to use full SHA256 hash instead of 50-char truncation
- Handle both created_at and timestamp fields with proper date parsing
- Fix build_cache_optimized_context to default include_in_output=True
- Remove unused STABLE_SECTION_ORDER constant
- Move json import to top level in tools/base.py
- Update tests to validate production code paths instead of duplicating logic
- Simplify conditional assignment for cache boundary

Co-authored-by: Mervin Praison <[email protected]>

---------

Co-authored-by: praisonai-triage-agent[bot] <272766704+praisonai-triage-agent[bot]@users.noreply.github.com>
Co-authored-by: MervinPraison <[email protected]>
2026-06-13 00:40:10 +01:00
13ba1ed954 fix: tool parameter schema generation for Optional, Union, Literal, and Enum (#1907)
* fix: tool parameter schema generation silently drops Optional, Union, Literal, and Enum types (fixes #1845)

- Created shared schema utility praisonaiagents/tools/schema.py with proper type handling
- Updated BaseTool._generate_parameters_schema() to use new schema utility
- Updated FunctionTool._generate_schema_from_func() to use new schema utility
- Updated LLM._generate_tool_definition() to use ToolRegistry and new schema utility
- Removed legacy globals().get() / getattr(__main__, ...) lookup in favor of ToolRegistry
- Fixed Optional[T] to generate {"anyOf": [T schema, {"type": "null"}]} instead of required string
- Fixed Literal["a", "b"] to generate {"type": "string", "enum": ["a", "b"]} instead of plain string
- Fixed Enum types to generate {"type": "string", "enum": [e.value for e in MyEnum]} instead of plain string
- Fixed List[T] and Dict[K,V] to include proper items/additionalProperties schemas
- Parameters with defaults or Optional types are now correctly excluded from required fields

Co-authored-by: MervinPraison <[email protected]>

* fix: address critical issues in schema generation

- Restore backward compatibility for string-name tool lookup in llm.py
- Fix Literal type inference to use actual value types instead of hardcoded string
- Fix Enum type inference to use actual enum value types
- Use consistent required parameter logic across all schema paths
- Add exception handling for get_type_hints() to prevent failures
- Apply schema normalization to registry-provided tools for consistency

Co-authored-by: Mervin Praison <[email protected]>

---------

Co-authored-by: praisonai-triage-agent[bot] <272766704+praisonai-triage-agent[bot]@users.noreply.github.com>
Co-authored-by: MervinPraison <[email protected]>
2026-06-13 00:39:22 +01:00
4a1fe173e8 fix: remove local 'from rich import print' causing UnboundLocalError (fixes #1879) (#1905)
Removes local import of 'from rich import print' at line 428 inside main() method.
Python scoping makes 'print' a local variable for the entire method when imported
locally, but execution paths that don't go through line 428 leave 'print' unbound,
causing UnboundLocalError at line 860. The module-level import at line 71 is sufficient.

Co-authored-by: praisonai-triage-agent[bot] <272766704+praisonai-triage-agent[bot]@users.noreply.github.com>
Co-authored-by: praisonai-triage-agent[bot] <praisonai-triage-agent[bot]@users.noreply.github.com>
2026-06-13 00:38:20 +01:00
MervinPraison 147bcce448 docs: auto-update api.md [skip ci] 2026-06-12 23:38:05 +00:00
b5e8052809 fix: doctor env crashes on Windows with charmap UnicodeEncodeError (#1904)
* fix(doctor): add encoding-safe Unicode symbol fallback for Windows

- Add ASCII fallback symbols for systems that don't support UTF-8
- Implement _can_encode_unicode() to detect stdout encoding capability
- Fall back to [OK], [!], [X], [-] symbols on Windows cp1252 encoding
- Add tests for encoding safety with both UTF-8 and cp1252

Fixes #1878

Co-authored-by: praisonai-triage-agent[bot] <praisonai-triage-agent[bot]@users.noreply.github.com>

* fix: optimize encoding detection and fix broken test

- Cache Unicode encoding detection in TextFormatter.__init__() to avoid repeated checks
- Remove invalid str.encode patch from cp1252 test that was causing TypeError
- Test now relies on natural cp1252 encoding failure for Unicode symbols
- Addresses performance concerns raised by CodeRabbit, Greptile, and Gemini

Co-authored-by: Mervin Praison <[email protected]>

---------

Co-authored-by: praisonai-triage-agent[bot] <272766704+praisonai-triage-agent[bot]@users.noreply.github.com>
Co-authored-by: praisonai-triage-agent[bot] <praisonai-triage-agent[bot]@users.noreply.github.com>
Co-authored-by: Mervin Praison <[email protected]>
2026-06-13 00:37:35 +01:00
8a93bc5096 fix: remove duplicate import time in _chat_completion except block (fixes #1901) (#1903)
The _chat_completion method in chat_mixin.py had 'import time' inside an
except block which shadowed the global time module import, causing
UnboundLocalError when trying to use time.time() at the start of the function.

Removed the redundant inner import since time is already imported at module level.

Co-authored-by: praisonai-triage-agent[bot] <272766704+praisonai-triage-agent[bot]@users.noreply.github.com>
Co-authored-by: MervinPraison <[email protected]>
2026-06-13 00:37:09 +01:00
MervinPraison f65b83614c docs: auto-update feature parity trackers [skip ci] 2026-06-12 23:37:01 +00:00
25000d9b26 fix: resolve wrapper layer architectural gaps (#1849)
* fix: resolve wrapper layer architectural gaps (fixes #1846)

- Remove dead deploy.py file shadowed by deploy/ package that contained stale version pin
- Replace module-level singleton with contextvars for multi-agent safety in host_app.py
- Remove duplicate exception handler and improve error logging
- Add cache invalidation method to BaseCLIIntegration
- Fix registry contract violation by adding try_create() method instead of overriding create()

Co-authored-by: MervinPraison <[email protected]>

* fix: address critical reviewer feedback

- Fix race condition in BaseCLIIntegration.is_available property by removing unlocked fast-path
- Add backward compatibility shim for _CONFIGURED to prevent test breakage
- Remove redundant default parameter in configure_host()
- Update stale documentation example in registry.py

Co-authored-by: Mervin Praison <[email protected]>

* fix: resolve remaining wrapper gaps

- Add praisonaiagents.__version__ lazy export to fix cross-repo CI
- Update version bump scripts to target deploy/docker.py instead of deleted deploy.py
- Add comprehensive unit tests for try_create, invalidate_availability, configure_host
- All functionality tested and verified working

Co-authored-by: Mervin Praison <[email protected]>

* fix: remove duplicate --timeout arguments from pytest.ini to fix CI conflicts

Both pytest.ini files had --timeout=60 in addopts while workflows passed
--timeout=300, causing pytest to reject duplicate arguments. Removed
pytest.ini timeouts to let workflows control timeout values explicitly.

Fixes cross-repo-integration CI failure.

* fix: remove timeout_method from pytest.ini files

Removed 'timeout_method = thread' configuration from both pytest.ini files.
The pytest-timeout plugin is not installed in CI, causing --timeout arguments
to be unrecognized. This removes all timeout-related pytest configuration,
allowing workflows to manage timeouts externally if needed.

Addresses cross-repo-integration CI failure with unrecognized --timeout argument.

* fix: remove --timeout arguments from cross-repo-integration workflow

The pytest-timeout plugin is not installed in CI, causing all --timeout
arguments to be rejected as unrecognized options. Removed all --timeout
flags from the workflow to allow tests to run without timeout constraints.

This completes the fix for the duplicate/unrecognized timeout arguments
that were blocking the cross-repo-integration CI workflow.

* fix: update integration tests to work with ContextVar-based _CONFIGURED

Added backward-compatible reset_configuration() and is_configured() functions
to host_app.py and updated all integration tests to use these instead of
directly manipulating host_app._CONFIGURED, which doesn't work properly
with the new ContextVar-based configuration tracking.

Fixes test failures related to the wrapper layer architectural changes.

* fix: improve session isolation for integration tests

- Add hybrid ContextVar + global flag approach for better test compatibility
- Enhanced reset_configuration() to clear both ContextVar and module-level state
- Fixed backward compatibility shim to handle both context-aware and global state
- Ensures test isolation works correctly across different execution contexts

This addresses the session leak failures in test_aiui_host_isolation.py where
provider._sessions was returning None instead of expected session data.

* fix: enhance test state cleanup for better isolation

- Clear additional cached state (_app, backends) during reset_configuration()
- Ensures complete cleanup between test runs to prevent interference
- Addresses potential auth/routing issues in integration tests

* fix: detect test environment and defer provider setup

- Skip default PraisonAIProvider setup when set_provider is monkeypatched
- Allows tests to have full control over provider configuration
- Should resolve 401 auth issues in SSE tests where provider override timing matters

* fix: resolve syntax error and remove debug file

- Fix SyntaxError in host_app.py by moving global _configured_global declaration to function top
- Remove debug_session_test.py from repository root
- Ensure proper global declaration order for cross-repo-integration compatibility

Co-authored-by: Mervin Praison <[email protected]>

* fix: resolve session isolation and provider setup issues

- Remove test environment detection that was skipping provider setup
- Enhanced reset_configuration() to handle different backend clearing methods
- Fix session isolation by ensuring provider setup works correctly in tests
- Addresses cross-repo-integration test failures:
  - test_configure_host_sets_provider (assert len(seen) == 1)
  - test_two_sessions_no_cross_leak (session isolation)
  - test_run_sse_stream (401 vs 200 auth issues)

Co-authored-by: Mervin Praison <[email protected]>

---------

Co-authored-by: praisonai-triage-agent[bot] <272766704+praisonai-triage-agent[bot]@users.noreply.github.com>
Co-authored-by: MervinPraison <[email protected]>
Co-authored-by: Mervin Praison <[email protected]>
2026-06-13 00:36:38 +01:00
124 changed files with 1857 additions and 902 deletions

No files matched your search

+4 -4
View File
@@ -60,16 +60,16 @@ jobs:
- name: Run PraisonAI integration tests
run: |
cd PraisonAI/src/praisonai
python -m pytest tests/integration/test_aiui_* -v --timeout=300
python -m pytest tests/integration/test_aiui_* -v
- name: Run PraisonAIUI integration tests
run: |
cd PraisonAIUI
if [ -f tests/integration/test_agentic_roundtrip.py ]; then
python -m pytest tests/integration/test_agentic_roundtrip.py -v --timeout=300
python -m pytest tests/integration/test_agentic_roundtrip.py -v
fi
if [ -f tests/test_feature_sdk_backends.py ]; then
python -m pytest tests/test_feature_sdk_backends.py -v --timeout=300
python -m pytest tests/test_feature_sdk_backends.py -v
fi
- name: Test Pattern C CLI
@@ -152,7 +152,7 @@ jobs:
run: |
cd PraisonAI/src/praisonai
if [ -n "$OPENAI_API_KEY" ]; then
python -m pytest tests/integration/test_aiui_host_agentic.py -v --timeout=600
python -m pytest tests/integration/test_aiui_host_agentic.py -v
else
echo "OPENAI_API_KEY not available, skipping agentic tests"
fi
+2 -1
View File
@@ -240,6 +240,7 @@ from praisonaiagents import Memory, MemoryBackend, MemoryConfig
Methods:
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">build_cache_optimized_context</a>(task_descr: str, user_id: Optional[str] = None, additional: str = '', max_items: int = 3, include_cache_boundary: bool = True, include_in_output: Optional[bool] = None) -> Dict[str, str]</code>
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">build_context_for_task</a>(task_descr: str, user_id: Optional[str] = None, additional: str = '', max_items: int = 3, include_in_output: Optional[bool] = None) -> str</code>
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">calculate_quality_metrics</a>(output: str, expected_output: str, llm: Optional[str] = None, custom_prompt: Optional[str] = None) -> Dict[str, float]</code>
* <code title="class Memory">Memory.<a href="./src/praisonai-agents/praisonaiagents/memory/memory.py">close_connections</a>()</code>
@@ -698,7 +699,7 @@ Methods:
Types:
```python
from praisonaiagents import AgentAppConfig, AgentAppProtocol, AgentFlow, AgentManager, AgentOSConfig, AgentOSProtocol, AgentTeam, AutoApproveBackend, EmbeddingResult, aembedding, aembeddings, embedding, embeddings, get_dimensions
from praisonaiagents import AgentAppConfig, AgentAppProtocol, AgentFlow, AgentManager, AgentOSConfig, AgentOSProtocol, AgentTeam, AutoApproveBackend, EmbeddingResult, __version__, aembedding, aembeddings, embedding, embeddings, get_dimensions
```
Methods:
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.6.55" \
"praisonai>=4.6.56" \
"praisonai[chat]" \
"embedchain[github,youtube]"
+1 -1
View File
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.6.55" \
"praisonai>=4.6.56" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
+1 -1
View File
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.6.55" \
"praisonai>=4.6.56" \
"praisonai[ui]" \
"praisonai[crewai]"
+1 -6
View File
@@ -12,13 +12,8 @@ from praisonaiagents import Agent
from praisonaiagents.approval import AgentApproval
from praisonaiagents.tools.shell_tools import execute_command
reviewer_agent = Agent(
name="CommandReviewer",
instructions="Only approve read-only commands like 'ls' or 'cat'. Deny destructive commands.",
)
reviewer = AgentApproval(
approver_agent=reviewer_agent,
instructions="Only approve read-only commands like 'ls' or 'cat'. Deny destructive commands.",
)
agent = Agent(
@@ -2,7 +2,7 @@
from praisonaiagents import Agent
from praisonaiagents import AgentFlow, Task
from praisonaiagents.workflows.workflow_configs import (
WorkflowPlanningConfig, WorkflowMemoryConfig,
WorkflowOutputConfig, WorkflowPlanningConfig, WorkflowMemoryConfig,
)
# Full-featured workflow with all consolidated params
@@ -32,7 +32,7 @@ workflow = AgentFlow(
),
],
# Workflow-level consolidated params
output="verbose",
output=WorkflowOutputConfig(verbose=True, stream=True),
planning=WorkflowPlanningConfig(enabled=True, reasoning=True),
memory=WorkflowMemoryConfig(backend="file"),
# Agent-like params
+3 -3
View File
@@ -5,12 +5,12 @@ Demonstrates web search with consolidated params.
Presets: duckduckgo, tavily, google, bing, serper, search_only, fetch_only
"""
from praisonaiagents import Agent
from praisonaiagents import Agent, WebConfig
# Basic: Enable web search with preset
# Basic: Enable web search with preset (search only — fetch needs Claude)
agent = Agent(
instructions="You are a research assistant with web access.",
web="search_only", # Compatible across models without web_fetch
web=WebConfig(search=True, fetch=False, search_provider="duckduckgo"),
)
if __name__ == "__main__":
@@ -80,7 +80,7 @@ Be precise and quote exact values when asked about codes or numbers.""",
print(f"\nAnswer: {response}\n")
# Verify the answer contains the unique code
if "ZEBRA-71" in str(response or "").upper():
if "ZEBRA-71" in response.upper():
print("✅ VERIFIED: Agent correctly retrieved the unique code from context!\n")
else:
print("❌ WARNING: Agent did not find the code - check retrieval\n")
@@ -104,7 +104,7 @@ Be precise and quote exact values when asked about codes or numbers.""",
response = agent.chat("What is the security incident code?")
print(f"\nAnswer: {response}\n")
if "HAWK-88" in str(response or "").upper():
if "HAWK-88" in response.upper():
print("✅ VERIFIED: Agent correctly retrieved the security code!\n")
else:
print("❌ WARNING: Agent did not find the code - check retrieval\n")
@@ -17,10 +17,8 @@ from praisonaiagents import Agent
agent = Agent(
name="CockroachDB Agent",
instructions="You are a helpful assistant.",
memory={
"session_id": "crdb-demo-session",
"db": {"database_url": os.getenv("COCKROACHDB_URL")},
},
db={"database_url": os.getenv("COCKROACHDB_URL")},
session_id="crdb-demo-session",
)
result = agent.start("What is distributed SQL? Explain in 2 sentences.")
@@ -18,10 +18,8 @@ from praisonaiagents import Agent
agent = Agent(
name="Neon Agent",
instructions="You are a helpful assistant.",
memory={
"session_id": "neon-demo-session",
"db": {"database_url": os.getenv("NEON_DATABASE_URL")},
},
db={"database_url": os.getenv("NEON_DATABASE_URL")},
session_id="neon-demo-session",
)
# Option 2: Direct URL
@@ -20,10 +20,8 @@ from praisonaiagents import Agent
agent = Agent(
name="Supabase Agent",
instructions="You are a helpful assistant.",
memory={
"session_id": "supabase-demo-session",
"db": {"database_url": os.getenv("SUPABASE_DATABASE_URL")},
},
db={"database_url": os.getenv("SUPABASE_DATABASE_URL")},
session_id="supabase-demo-session",
)
result = agent.start("What is Supabase? Explain in 2 sentences.")
@@ -18,13 +18,11 @@ from praisonaiagents import Agent
agent = Agent(
name="Turso Agent",
instructions="You are a helpful assistant.",
memory={
"session_id": "turso-demo-session",
"db": {
"database_url": os.getenv("TURSO_DATABASE_URL"),
"auth_token": os.getenv("TURSO_AUTH_TOKEN"),
},
db={
"database_url": os.getenv("TURSO_DATABASE_URL"),
"auth_token": os.getenv("TURSO_AUTH_TOKEN"),
},
session_id="turso-demo-session",
)
result = agent.start("What is edge computing? Explain in 2 sentences.")
+2 -1
View File
@@ -10,7 +10,8 @@ from praisonaiagents import Agent
agent = Agent(
name="FlexBot",
instructions="You are a helpful assistant.",
llm={"model": "gpt-4o-mini", "configurable": True},
llm="gpt-4o-mini",
llm_config={"configurable": True}
)
if __name__ == "__main__":
+1 -1
View File
@@ -21,7 +21,7 @@ agent = Agent(
name="ContextBot",
instructions="You help show context information.",
tools=[show_context],
memory={"session_id": "my-session-123"},
session_id="my-session-123"
)
if __name__ == "__main__":
+3 -13
View File
@@ -19,16 +19,6 @@ from praisonaiagents import Agent, AgentTeam
# Initialize observability
obs.init()
def set_span_attr(span, key, value):
"""Set span attribute safely across observability implementations."""
if hasattr(span, "set_attribute"):
span.set_attribute(key, value)
return
attrs = getattr(span, "attributes", None)
if attrs is not None:
attrs[key] = value
# Create agents
researcher = Agent(
name="Researcher",
@@ -47,13 +37,13 @@ with obs.trace("content-creation", metadata={"workflow": "research-write"}):
# Research phase
with obs.span("research-phase", kind=SpanKind.AGENT) as research_span:
set_span_attr(research_span, "agent", "Researcher")
research_span.attributes["agent"] = "Researcher"
research_result = researcher.chat("Research the benefits of AI in healthcare")
# Writing phase
with obs.span("writing-phase", kind=SpanKind.AGENT) as write_span:
set_span_attr(write_span, "agent", "Writer")
set_span_attr(write_span, "input_length", len(str(research_result)))
write_span.attributes["agent"] = "Writer"
write_span.attributes["input_length"] = len(str(research_result))
final_content = writer.chat(f"Write a blog post based on: {research_result}")
print("Final Content:")
+2 -4
View File
@@ -52,10 +52,8 @@ agent = Agent(
instructions="You are a helpful assistant with access to technical documentation.",
knowledge={
"sources": [doc_path],
"vector_store": {
"provider": "qdrant",
"url": "http://localhost:6333",
},
"vector_store": "qdrant",
"url": "http://localhost:6333",
}
)
+5 -8
View File
@@ -13,8 +13,7 @@ Expected output:
- Session can be resumed later with same session_id
"""
from praisonaiagents import Agent
from praisonaiagents.db import db
from praisonaiagents import Agent, db
# Create database connection (PostgreSQL + Redis)
my_db = db(
@@ -26,12 +25,10 @@ my_db = db(
agent = Agent(
name="Assistant",
instructions="You are a helpful assistant. Be concise.",
memory={
"db": my_db,
# session_id is optional - defaults to per-hour ID (YYYYMMDDHH-hash)
# Set explicitly for session continuity across runs:
"session_id": "my-persistent-session",
},
db=my_db,
# session_id is optional - defaults to per-hour ID (YYYYMMDDHH-hash)
# Set explicitly for session continuity across runs:
session_id="my-persistent-session"
)
# Chat - messages are automatically persisted
+3 -6
View File
@@ -12,8 +12,7 @@ Expected output:
- Export shows full session data
"""
from praisonaiagents import Agent
from praisonaiagents.db import db
from praisonaiagents import Agent, db
def get_weather(city: str) -> str:
"""Get weather for a city."""
@@ -30,10 +29,8 @@ agent = Agent(
name="WeatherBot",
instructions="You help with weather. Use the get_weather tool when asked.",
tools=[get_weather],
memory={
"db": my_db,
"session_id": "weather-session-001",
},
db=my_db,
session_id="weather-session-001"
)
# First chat - triggers run tracking
+3 -6
View File
@@ -13,8 +13,7 @@ Expected output:
Agent responds with state persisted to Redis
"""
from praisonaiagents import Agent
from praisonaiagents.db import db
from praisonaiagents import Agent, db
print("=== Redis State Store (Agent-First) ===")
@@ -27,10 +26,8 @@ my_db = db(
agent = Agent(
name="Assistant",
instructions="You are a helpful assistant.",
memory={
"db": my_db,
"session_id": "redis-state-session",
},
db=my_db,
session_id="redis-state-session"
)
# Chat - state is automatically managed via Redis
+3 -6
View File
@@ -11,8 +11,7 @@ Expected output:
- Second run: Resumes with history
"""
from praisonaiagents import Agent
from praisonaiagents.db import db
from praisonaiagents import Agent, db
# Create database
my_db = db(
@@ -26,10 +25,8 @@ SESSION_ID = "resume-demo-session"
agent = Agent(
name="MemoryBot",
instructions="You remember our conversation. Be helpful and concise.",
memory={
"db": my_db,
"session_id": SESSION_ID,
},
db=my_db,
session_id=SESSION_ID
)
# Check if this is a new or resumed session
+3 -6
View File
@@ -26,8 +26,7 @@ Expected Output:
Messages persisted: 2
"""
from praisonaiagents import Agent
from praisonaiagents.db import db
from praisonaiagents import Agent, db
# Create database adapter (simplified import)
db_instance = db(
@@ -38,10 +37,8 @@ db_instance = db(
agent = Agent(
name="Assistant",
instructions="You are a helpful assistant. Keep responses brief.",
memory={
"db": db_instance,
"session_id": "demo-session-001", # Same session_id = resume conversation
},
db=db_instance,
session_id="demo-session-001", # Same session_id = resume conversation
output="silent"
)
+3 -6
View File
@@ -11,8 +11,7 @@ Expected output:
- Data persisted to local SQLite file
"""
from praisonaiagents import Agent
from praisonaiagents.db import db
from praisonaiagents import Agent, db
import tempfile
import os
@@ -26,10 +25,8 @@ print(f"Database: {db_path}")
agent = Agent(
name="LocalBot",
instructions="You are a helpful assistant.",
memory={
"db": my_db,
"session_id": "local-session",
},
db=my_db,
session_id="local-session"
)
# Chat
+1 -1
View File
@@ -57,8 +57,8 @@ def main():
agent = Agent(
name="SecureAgent",
instructions="You are a file management assistant.",
policy=engine
)
agent.policy = engine
print("\n--- Agent with Policy Enforcement Created ---")
print(f"Agent: {agent.name}")
@@ -60,10 +60,9 @@ agents_system = AgentTeam(
process="sequential", output="verbose"
)
if __name__ == "__main__":
print("Starting hierarchical agent generation...")
result = agents_system.start()
print("Starting hierarchical agent generation...")
result = agents_system.start()
print(f"\nHierarchical Result: {str(result)[:200]}...")
print("\n✅ AutoAgents hierarchical generation complete!")
print("Demonstrated coordinated hierarchical workflow between manager and specialist agents.")
print(f"\nHierarchical Result: {result[:200]}...")
print("\n✅ AutoAgents hierarchical generation complete!")
print("Demonstrated coordinated hierarchical workflow between manager and specialist agents.")
+16 -12
View File
@@ -6,10 +6,18 @@ It includes self-reflection for improving code quality and multiple specialized
"""
from praisonaiagents import Agent, Task, AgentTeam
from praisonaiagents.tools import CodeInterpreter
# Initialize code execution tool
code_interpreter = CodeInterpreter()
def run_python_code(code: str) -> str:
"""Execute Python code safely and return stdout or errors."""
import io
import contextlib
try:
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
exec(code, {"__builtins__": __builtins__})
return buf.getvalue() or "Executed successfully (no output)"
except Exception as e:
return f"Execution error: {e}"
# Custom code analysis tool
def analyze_code_complexity(code: str) -> str:
@@ -32,9 +40,7 @@ code_writer = Agent(
goal="Write clean, efficient, and well-documented Python code",
backstory="You are a senior Python developer with 10+ years of experience in writing production-quality code.",
instructions="Write Python code that follows PEP 8 standards, includes proper error handling, and has clear documentation.",
reflection=True,
output="silent",
)
code_reviewer = Agent(
@@ -52,7 +58,7 @@ code_executor = Agent(
goal="Safely execute and test Python code",
backstory="You are responsible for running code and reporting results or errors.",
instructions="Execute the provided code safely and report the output or any errors encountered.",
tools=[code_interpreter]
tools=[run_python_code]
)
# Example tasks
@@ -87,7 +93,7 @@ def demonstrate_code_agent():
workflow = AgentTeam(
agents=[code_writer, code_reviewer, code_executor],
tasks=[write_task, review_task, execute_task],
process="sequential", output="verbose"
process="sequential", output="silent"
)
# Run the workflow
@@ -106,10 +112,8 @@ general_code_agent = Agent(
3. Explain code concepts
4. Refactor code for better performance
5. Add tests and documentation""",
tools=[code_interpreter, analyze_code_complexity],
reflection=True,
tools=[run_python_code, analyze_code_complexity],
output="silent",
)
if __name__ == "__main__":
+19 -16
View File
@@ -41,21 +41,25 @@ def calculate_statistics(numbers: list) -> str:
"""Calculate basic statistics for a list of numbers."""
if not numbers:
return "No numbers provided"
n = len(numbers)
mean = sum(numbers) / n
sorted_nums = sorted(numbers)
try:
nums = [float(x) for x in numbers]
except (TypeError, ValueError):
return "Error: all values must be numbers"
n = len(nums)
mean = sum(nums) / n
sorted_nums = sorted(nums)
median = sorted_nums[n//2] if n % 2 else (sorted_nums[n//2-1] + sorted_nums[n//2]) / 2
variance = sum((x - mean)**2 for x in numbers) / n
variance = sum((x - mean)**2 for x in nums) / n
std_dev = math.sqrt(variance)
return f"""Statistics:
- Count: {n}
- Mean: {mean:.4f}
- Median: {median:.4f}
- Standard Deviation: {std_dev:.4f}
- Min: {min(numbers)}
- Max: {max(numbers)}"""
- Min: {min(nums)}
- Max: {max(nums)}"""
# Create specialized math agents
calculation_agent = Agent(
@@ -66,8 +70,7 @@ calculation_agent = Agent(
instructions="Perform the requested calculations step by step, showing your work clearly.",
tools=[basic_calculator, solve_quadratic, calculate_statistics],
reflection=True,
output="silent",
)
problem_solver_agent = Agent(
@@ -82,8 +85,7 @@ problem_solver_agent = Agent(
4. Show each step of the solution
5. Verify the answer makes sense""",
reflection=True,
output="silent",
)
math_tutor_agent = Agent(
@@ -91,7 +93,8 @@ math_tutor_agent = Agent(
role="Mathematics tutor and explainer",
goal="Explain mathematical concepts clearly and help users understand math",
backstory="You are a patient math tutor who can explain concepts at any level from elementary to advanced.",
instructions="Explain mathematical concepts using simple language, provide examples, and check for understanding."
instructions="Explain mathematical concepts using simple language, provide examples, and check for understanding.",
output="silent",
)
# Example workflow for solving complex problems
@@ -126,7 +129,8 @@ def solve_math_problem_workflow():
workflow = AgentTeam(
agents=[problem_solver_agent, calculation_agent, math_tutor_agent],
tasks=[understand_task, solve_task, verify_task],
process="sequential", output="verbose"
process="sequential",
output="silent",
)
return workflow.start()
@@ -148,8 +152,7 @@ math_assistant = Agent(
Always show your work step by step.""",
tools=[basic_calculator, solve_quadratic, calculate_statistics],
reflection=True,
output="silent",
)
if __name__ == "__main__":
+2 -3
View File
@@ -1,5 +1,4 @@
from praisonaiagents import Agent
if __name__ == "__main__":
agent = Agent(name="TweetAgent", instructions="Create a Tweet based on the topic provided")
agent.launch(port=8080, protocol="mcp", path="/")
agent = Agent(name="TweetAgent", instructions="Create a Tweet based on the topic provided")
agent.launch(port=8080, protocol="mcp")
+2 -11
View File
@@ -90,18 +90,9 @@ def main():
if result:
print("\n🔍 Analysis Results:")
task_results = result.get("task_results") if isinstance(result, dict) else result
if isinstance(task_results, dict):
iterable = task_results.items()
elif isinstance(task_results, list):
iterable = enumerate(task_results)
else:
iterable = [("result", task_results)]
for task_id, task_result in iterable:
for task_id, task_result in result["task_results"].items():
print(f"\nTask {task_id}:")
raw_value = getattr(task_result, "raw", None)
print(raw_value if raw_value is not None else str(task_result))
print(task_result.raw)
else:
print("❌ Failed to capture and analyze camera feed")
+1 -1
View File
@@ -75,7 +75,7 @@ class ContinuousCameraMonitor:
agents=[self.vision_agent],
tasks=[task],
process="sequential",
output="silent", # Reduced verbosity for continuous monitoring
verbose=0 # Reduced verbosity for continuous monitoring
)
return agents.start()
+2 -11
View File
@@ -148,19 +148,10 @@ def main():
"scene_analysis": "🌍 SCENE ANALYSIS"
}
task_results = result.get("task_results") if isinstance(result, dict) else result
if isinstance(task_results, dict):
iterable = task_results.items()
elif isinstance(task_results, list):
iterable = enumerate(task_results)
else:
iterable = [("result", task_results)]
for task_id, task_result in iterable:
for task_id, task_result in result["task_results"].items():
print(f"\n{agent_names.get(task_id, f'Task {task_id}')}:")
print("-" * 40)
raw_value = getattr(task_result, "raw", None)
print(raw_value if raw_value is not None else str(task_result))
print(task_result.raw)
print("\n" + "="*60)
print("✅ Multi-agent analysis complete!")
@@ -34,5 +34,5 @@ print(f"\nChanged to: {handler.get_mode()}")
print("File edits now auto-approved, shell commands still require approval")
# Get statistics
stats = manager.get_stats()
stats = handler.get_stats()
print(f"\nStats: {stats}")
+1 -1
View File
@@ -31,4 +31,4 @@ print(f"Total cost: ${summary['total_cost']:.4f}")
print(f"Avg cost/request: ${summary['avg_cost_per_request']:.4f}")
# Display formatted (uses Rich if available)
handler.print_summary()
handler.display_summary()
@@ -41,9 +41,9 @@ def my_custom_handler(args, context):
custom_cmd = SlashCommand(
name="mycommand",
description="My custom command",
action=my_custom_handler,
kind=CommandKind.CUSTOM,
alt_names=["mc"]
handler=my_custom_handler,
kind=CommandKind.ACTION,
aliases=["mc"]
)
handler.register(custom_cmd)
@@ -16,8 +16,7 @@ agents = AgentTeam(
agents=[agent],
tasks=[task],
process="workflow",
execution={"max_iter": 30},
max_iter=30
)
if __name__ == "__main__":
agents.start()
agents.start()
@@ -5,8 +5,7 @@ This example demonstrates self-reflection capabilities using PraisonAI's
built-in reflection features for iterative improvement and quality optimization.
"""
from praisonaiagents import Agent, Task
from praisonaiagents.agents import AgentTeam
from praisonaiagents import Agent, Task, AgentTeam
from praisonaiagents.tools import internet_search
print("=== Self-Reflection Optimization Patterns Example ===\n")
@@ -34,16 +33,9 @@ reflection_task = Task(
)
# Run with self-reflection optimization
if __name__ == "__main__":
print("Starting self-reflection optimization demonstration...")
agents = AgentTeam(
agents=[reflection_agent],
tasks=[reflection_task],
process="sequential",
output="verbose",
)
result = agents.start()
print("Starting self-reflection optimization demonstration...")
result = reflection_agent.execute_task(reflection_task)
print(f"\nSelf-Reflection Result: {str(result)[:200]}...")
print("\n✅ Self-reflection optimization complete!")
print("Agent demonstrated iterative improvement through built-in self-reflection capabilities.")
print(f"\nSelf-Reflection Result: {result[:200]}...")
print("\n✅ Self-reflection optimization complete!")
print("Agent demonstrated iterative improvement through built-in self-reflection capabilities.")
@@ -61,6 +61,7 @@ def example_with_wrappers():
role="Data Analyst",
goal="Analyze data and provide insights",
llm="gpt-4o-mini",
request_id="request_2" # Unique request ID
)
# Operations are automatically tracked
+2 -4
View File
@@ -132,9 +132,7 @@ agents = AgentTeam(
agents=[qa_generator, total_questions_evaluator, cot_generator, upload_to_huggingface],
tasks=[generate_task, evaluate_total_questions_task, generate_cot_task, upload_to_huggingface_task],
process="workflow",
execution={"max_iter": 30},
output="minimal",
max_iter=30, output="minimal"
)
if __name__ == "__main__":
agents.start()
agents.start()
+3 -3
View File
@@ -18,7 +18,7 @@ openai_profile = AuthProfile(
provider="openai",
api_key=os.environ.get("OPENAI_API_KEY", "sk-..."),
priority=1, # Highest priority
rate_limit_rpm=100,
rate_limit=100,
)
anthropic_profile = AuthProfile(
@@ -26,7 +26,7 @@ anthropic_profile = AuthProfile(
provider="anthropic",
api_key=os.environ.get("ANTHROPIC_API_KEY", "sk-ant-..."),
priority=2, # Second priority
rate_limit_rpm=50,
rate_limit=50,
)
groq_profile = AuthProfile(
@@ -34,7 +34,7 @@ groq_profile = AuthProfile(
provider="groq",
api_key=os.environ.get("GROQ_API_KEY", "gsk-..."),
priority=3, # Third priority (fast but limited)
rate_limit_rpm=30,
rate_limit=30,
)
# Configure failover behavior
+2 -2
View File
@@ -105,13 +105,13 @@ def main():
agents=[researcher, retriever],
tasks=[store_task, verify_task, query_task, query_both_task], # Use same verbose level as memory
memory={
"backend": "sqlite",
"embedder": {
"provider": "openai",
"config": {
"model": "text-embedding-3-small"
}
},
"config": {"provider": "sqlite"},
}
}
)
+6 -16
View File
@@ -49,20 +49,10 @@ agents = AgentTeam(
process="sequential"
)
if __name__ == "__main__":
# Run all tasks
result = agents.start()
# Run all tasks
result = agents.start()
# Print results
task_results = result.get("task_results") if isinstance(result, dict) else result
if isinstance(task_results, dict):
iterable = task_results.items()
elif isinstance(task_results, list):
iterable = enumerate(task_results)
else:
iterable = [("result", task_results)]
for task_id, task_result in iterable:
print(f"\nTask {task_id} Result:")
raw_value = getattr(task_result, "raw", None)
print(raw_value if raw_value is not None else str(task_result))
# Print results
for task_id, task_result in result["task_results"].items():
print(f"\nTask {task_id} Result:")
print(task_result.raw)
@@ -6,7 +6,6 @@ built-in validation and safety mechanisms for secure agent operations.
"""
from praisonaiagents import Agent, Task, AgentTeam
from praisonaiagents.config.feature_configs import GuardrailConfig
from praisonaiagents.tools import internet_search
print("=== Production Guardrails Patterns Example ===\n")
@@ -39,8 +38,8 @@ production_agent = Agent(
goal="Generate safe, compliant content for production use",
backstory="Production-ready agent with built-in safety and compliance checks",
tools=[internet_search],
guardrails=GuardrailConfig(validator=content_guardrail, max_retries=3),
)
guardrails=content_guardrail,
max_retries=3)
# Create secure task with validation
secure_task = Task(
+1 -1
View File
@@ -40,7 +40,7 @@ agent = Agent(
"execute_command"
],
memory=True,
web=True,
web_search=True,
auto_approve_tools=True,
)
@@ -14,33 +14,30 @@ agent = Agent(name="env-mgmt", backend=managed)
# First call creates agent + environment + session
agent.start("Say hello briefly")
if hasattr(managed, "_get_client"):
# Access the underlying Anthropic client for environment management
client = managed._get_client()
env_id = managed.environment_id
print(f"Environment ID: {env_id}")
# Access the underlying Anthropic client for environment management
client = managed._get_client()
env_id = managed.environment_id
print(f"Environment ID: {env_id}")
# List all environments
environments = client.beta.environments.list()
print(f"\nTotal environments: {len(environments.data)}")
for env in environments.data[:5]:
print(f" {env.id} | {env.name} | {getattr(env, 'status', 'active')}")
# List all environments
environments = client.beta.environments.list()
print(f"\nTotal environments: {len(environments.data)}")
for env in environments.data[:5]:
print(f" {env.id} | {env.name} | {getattr(env, 'status', 'active')}")
# Retrieve a specific environment
env = client.beta.environments.retrieve(env_id)
print(f"\nRetrieved: {env.id} | {env.name}")
# Retrieve a specific environment
env = client.beta.environments.retrieve(env_id)
print(f"\nRetrieved: {env.id} | {env.name}")
# Archive (read-only, existing sessions continue)
archived = client.beta.environments.archive(env_id)
print(f"Archived: {archived.id} | status: {getattr(archived, 'status', 'archived')}")
# Archive (read-only, existing sessions continue)
archived = client.beta.environments.archive(env_id)
print(f"Archived: {archived.id} | status: {getattr(archived, 'status', 'archived')}")
# Delete (only if no sessions reference it — may fail if sessions exist)
try:
client.beta.environments.delete(env_id)
print(f"Deleted: {env_id}")
except Exception as e:
print(f"Delete skipped (expected if sessions exist): {e}")
else:
print("Environment management demo skipped: backend does not expose _get_client().")
# Delete (only if no sessions reference it — may fail if sessions exist)
try:
client.beta.environments.delete(env_id)
print(f"Deleted: {env_id}")
except Exception as e:
print(f"Delete skipped (expected if sessions exist): {e}")
print("\nEnvironment management complete.")
+7 -6
View File
@@ -15,6 +15,13 @@ Protocol: MCP 2025-11-25
from praisonaiagents import Agent, MCP
# Agent with resumability support (automatic)
agent = Agent(
name="Resumable Assistant",
instructions="You are a helpful assistant with resumable connections.",
tools=MCP("https://api.example.com/mcp")
)
# Resumability is automatic - the MCP client:
# 1. Tracks event IDs from SSE streams (id: field)
# 2. Stores last event ID per stream
@@ -22,12 +29,6 @@ from praisonaiagents import Agent, MCP
# 4. Respects retry delay from server (retry: field)
if __name__ == "__main__":
agent = Agent(
name="Resumable Assistant",
instructions="You are a helpful assistant with resumable connections.",
tools=MCP("https://api.example.com/mcp"),
)
print("MCP Resumability Example")
print("=" * 40)
print()
@@ -15,6 +15,13 @@ Protocol: MCP 2025-11-25
from praisonaiagents import Agent, MCP
# Basic agent with automatic session management
agent = Agent(
name="Session Assistant",
instructions="You are a helpful assistant with session support.",
tools=MCP("https://api.example.com/mcp")
)
# Session management is automatic - the MCP client:
# 1. Receives Mcp-Session-Id from server during initialization
# 2. Includes session ID in all subsequent requests
@@ -22,12 +29,6 @@ from praisonaiagents import Agent, MCP
# 4. Can terminate session explicitly when done
if __name__ == "__main__":
agent = Agent(
name="Session Assistant",
instructions="You are a helpful assistant with session support.",
tools=MCP("https://api.example.com/mcp"),
)
print("MCP Session Management Example")
print("=" * 40)
print()
+27 -24
View File
@@ -16,31 +16,34 @@ Protocol: MCP 2025-11-25
from praisonaiagents import Agent, MCP
# Basic Streamable HTTP connection
agent = Agent(
name="HTTP Stream Assistant",
instructions="You are a helpful assistant connected via Streamable HTTP.",
tools=MCP("https://api.example.com/mcp")
)
# With authentication headers
agent_auth = Agent(
name="Authenticated Assistant",
instructions="You are a helpful assistant with authenticated connection.",
tools=MCP(
"https://api.example.com/mcp",
headers={"Authorization": "Bearer your-token"}
)
)
# With timeout and debug
agent_debug = Agent(
name="Debug Assistant",
tools=MCP(
"https://api.example.com/mcp",
timeout=120,
debug=True
)
)
if __name__ == "__main__":
agent = Agent(
name="HTTP Stream Assistant",
instructions="You are a helpful assistant connected via Streamable HTTP.",
tools=MCP("https://api.example.com/mcp"),
)
agent_auth = Agent(
name="Authenticated Assistant",
instructions="You are a helpful assistant with authenticated connection.",
tools=MCP(
"https://api.example.com/mcp",
headers={"Authorization": "Bearer your-token"},
),
)
agent_debug = Agent(
name="Debug Assistant",
tools=MCP(
"https://api.example.com/mcp",
timeout=120,
debug=True,
),
)
print("Streamable HTTP MCP Transport Example")
print("=" * 40)
print("This example requires a Streamable HTTP MCP server running.")
+29 -26
View File
@@ -16,33 +16,36 @@ Protocol: SEP-1288 (WebSocket Transport)
from praisonaiagents import Agent, MCP
# Basic WebSocket connection
agent = Agent(
name="WebSocket Assistant",
instructions="You are a helpful assistant connected via WebSocket.",
tools=MCP("ws://localhost:8080/mcp")
)
# Secure WebSocket with authentication
agent_secure = Agent(
name="Secure WebSocket Assistant",
instructions="You are a helpful assistant with secure WebSocket connection.",
tools=MCP(
"wss://api.example.com/mcp",
auth_token="Bearer your-secret-token",
timeout=60
)
)
# WebSocket with custom options
agent_custom = Agent(
name="Custom WebSocket Assistant",
tools=MCP(
"wss://api.example.com/mcp",
auth_token="your-token",
timeout=120,
debug=True
)
)
if __name__ == "__main__":
agent = Agent(
name="WebSocket Assistant",
instructions="You are a helpful assistant connected via WebSocket.",
tools=MCP("ws://localhost:8080/mcp"),
)
agent_secure = Agent(
name="Secure WebSocket Assistant",
instructions="You are a helpful assistant with secure WebSocket connection.",
tools=MCP(
"wss://api.example.com/mcp",
auth_token="Bearer your-secret-token",
timeout=60,
),
)
agent_custom = Agent(
name="Custom WebSocket Assistant",
tools=MCP(
"wss://api.example.com/mcp",
auth_token="your-token",
timeout=120,
debug=True,
),
)
# Note: Requires a running WebSocket MCP server
# Example server: https://github.com/modelcontextprotocol/servers
@@ -12,8 +12,8 @@ print("=== Advanced Graph Memory Integration Example ===\n")
# Configure simple graph memory
memory_config = {
"provider": "mem0",
"config": {
"provider": "mem0",
"vector_store": {
"provider": "chroma",
"config": {
@@ -12,8 +12,8 @@ print("=== Advanced Memory Search and Optimization Example ===\n")
# Configure optimized memory settings
memory_config = {
"provider": "mem0",
"config": {
"provider": "mem0",
"vector_store": {
"provider": "chroma",
"config": {
@@ -86,13 +86,13 @@ def main():
agents=[researcher, retriever],
tasks=[store_task, retrieve_task],
memory={
"backend": "sqlite",
"embedder": {
"provider": "gemini",
"config": {
"model": "text-embedding-004",
}
},
"config": {"provider": "sqlite"},
}
}
)
@@ -30,11 +30,7 @@ config = {
agent = Agent(
name="Knowledge Agent",
instructions="You answer questions based on the provided knowledge.",
knowledge={
"sources": ["kag-research-paper.pdf"],
"vector_store": config["vector_store"],
"embedder": config["embedder"],
},
knowledge={**config, "sources": ["kag-research-paper.pdf"]},
memory={"user_id": "user1"},
llm="deepseek-r1"
)
@@ -31,9 +31,10 @@ llm_config = {
agent = Agent(
instructions="You are a helpful Assistant specialized in scientific explanations. "
"Provide clear, accurate, and engaging responses.",
llm=llm_config,
output={"markdown": True},
reflection=True,
llm=llm_config, # Pass the detailed configuration
# Enable detailed output
markdown=True, # Format responses in markdown
reflection=True # Enable self-reflection
)
# Test the agent
@@ -28,8 +28,8 @@ if not os.environ.get("OPENAI_API_KEY"):
def main():
# MongoDB memory configuration
mongodb_memory_config = {
"provider": "mongodb",
"config": {
"provider": "mongodb",
"connection_string": "mongodb://localhost:27017/", # Replace with your MongoDB connection string
"database": "praisonai_memory",
"use_vector_search": True, # Enable Atlas Vector Search (requires MongoDB Atlas)
@@ -4,7 +4,7 @@ Example 3: Save Agent Output Using Workflow output_file
Workflow step output is automatically saved with variable substitution.
"""
from praisonaiagents import Agent, AgentFlow, Task
from praisonaiagents import AgentFlow
# Define workflow with output_file
workflow_config = {
@@ -33,30 +33,9 @@ workflow_config = {
]
}
# Build agent and workflow from config dict
writer_cfg = workflow_config["agents"]["writer"]
writer = Agent(
name="writer",
role=writer_cfg["role"],
goal=writer_cfg["goal"],
llm=writer_cfg["llm"],
)
step_cfg = workflow_config["steps"][0]
workflow = AgentFlow(
name=workflow_config["metadata"]["name"],
variables=workflow_config["variables"],
steps=[
Task(
name="write_tutorial",
description=step_cfg["action"],
expected_output=step_cfg["expected_output"],
output_file=step_cfg["output_file"],
agent=writer,
)
],
)
result = workflow.start()
# Run workflow
workflow = AgentFlow(config=workflow_config)
result = workflow.run()
print("✅ Workflow completed!")
print("Output saved to: generated/tutorial.md")
@@ -28,7 +28,7 @@ def example_basic_persistence():
agent = Agent(
name="Assistant",
instructions="You are a helpful assistant. Be very brief.",
memory={"session_id": session_id},
session_id=session_id,
output="minimal",
)
@@ -59,7 +59,7 @@ def example_session_restoration():
agent1 = Agent(
name="MemoryBot",
instructions="You are a helpful assistant. Be very brief.",
memory={"session_id": session_id},
session_id=session_id,
output="minimal",
)
@@ -72,7 +72,7 @@ def example_session_restoration():
agent2 = Agent(
name="MemoryBot",
instructions="You are a helpful assistant. Be very brief.",
memory={"session_id": session_id},
session_id=session_id,
output="minimal",
)
@@ -69,10 +69,9 @@ agents_system = AgentTeam(
memory=True, output="verbose"
)
if __name__ == "__main__":
print("Starting state management demonstration...")
result = agents_system.start()
print("Starting state management demonstration...")
result = agents_system.start()
print(f"\nState Management Result: {str(result)[:200]}...")
print("\n✅ State management patterns complete!")
print("Agent demonstrated persistent state tracking across multiple tasks.")
print(f"\nState Management Result: {result[:200]}...")
print("\n✅ State management patterns complete!")
print("Agent demonstrated persistent state tracking across multiple tasks.")
@@ -179,7 +179,7 @@ def main():
context = memory.build_context_for_task(
task_descr="Explain AI alignment challenges",
user_id="quality_demo_user",
memory={"user_id": "quality_demo_user"},
max_items=3
)
+1 -1
View File
@@ -15,7 +15,7 @@ def main():
# Create a session with persistent storage
session = Session(
session_id="demo_chat_001",
user_id="demo_user",
memory={"user_id": "demo_user"},
)
print(f"📝 Created session: {session}")
@@ -1,55 +1,284 @@
"""
Production Telemetry Example
Demonstrates compatible telemetry setup with `enable_telemetry()` and
`TelemetryCollector` patterns.
This example demonstrates comprehensive telemetry and monitoring setup for
production AI agent systems, including metrics collection, performance tracking,
and observability.
Features demonstrated:
- OpenTelemetry integration for distributed tracing
- Custom metrics collection and monitoring
- Performance tracking and analytics
- Error tracking and alerting
- Usage analytics and reporting
"""
from contextlib import nullcontext
from praisonaiagents import Agent, Task, AgentTeam
from praisonaiagents.tools import duckduckgo
from praisonaiagents.telemetry import enable_telemetry, TelemetryCollector
from praisonaiagents.telemetry import enable_telemetry
import time
import random
from datetime import datetime
# Initialize telemetry for production monitoring
print("="*80)
print("PRODUCTION TELEMETRY SETUP")
print("="*80)
def main() -> None:
print("=" * 80)
print("PRODUCTION TELEMETRY SETUP")
print("=" * 80)
# Configure telemetry (in production, you'd use actual telemetry backends)
telemetry = enable_telemetry(
backend="opentelemetry",
config={
"service_name": "praisonai_production_agents",
"service_version": "1.0.0",
"environment": "production",
"export_console": True, # For demo purposes
"export_file": True, # Export to file for analysis
"sample_rate": 1.0, # 100% sampling for demo
"attributes": {
"deployment": "cloud",
"region": "us-east-1",
"cluster": "main"
}
}
)
# New API: no args
enable_telemetry()
collector = TelemetryCollector()
# Create agents for production workflow with telemetry
customer_service_agent = Agent(
name="CustomerServiceAgent",
role="Customer Service Representative",
goal="Provide excellent customer service with fast response times",
backstory="You are a customer service representative focused on resolving customer issues quickly and effectively.",
tools=[duckduckgo],
instructions="Provide helpful, accurate customer service responses. Research solutions when needed."
)
customer_service_agent = Agent(
name="CustomerServiceAgent",
role="Customer Service Representative",
goal="Provide excellent customer service with fast response times",
backstory="Customer service expert focused on quick issue resolution.",
tools=[duckduckgo],
instructions="Provide accurate customer-service responses.",
)
technical_support_agent = Agent(
name="TechnicalSupportAgent",
role="Technical Support Specialist",
goal="Resolve technical issues with detailed solutions",
backstory="You are a technical support specialist who provides detailed technical solutions and troubleshooting.",
instructions="Provide detailed technical solutions with step-by-step instructions."
)
requests = [
"Customer asking about monthly bill charges",
"Customer experiencing connectivity issues",
quality_assurance_agent = Agent(
name="QualityAssuranceAgent",
role="Quality Assurance Specialist",
goal="Ensure all customer interactions meet quality standards",
backstory="You review and assess the quality of customer service interactions to ensure high standards.",
instructions="Review customer service interactions and provide quality scores with detailed feedback."
)
# Production workflow simulation with telemetry tracking
def simulate_customer_requests():
"""Simulate various customer requests for telemetry demonstration"""
customer_requests = [
{
"type": "billing_inquiry",
"complexity": "low",
"description": "Customer asking about their monthly bill charges",
"expected_duration": 30
},
{
"type": "technical_issue",
"complexity": "high",
"description": "Customer experiencing connectivity issues with the service",
"expected_duration": 120
},
{
"type": "account_setup",
"complexity": "medium",
"description": "New customer needs help setting up their account",
"expected_duration": 60
},
{
"type": "feature_question",
"complexity": "low",
"description": "Customer asking about product features and capabilities",
"expected_duration": 45
}
]
return customer_requests
for idx, description in enumerate(requests, start=1):
trace_ctx = getattr(collector, "trace", None)
trace_cm = trace_ctx(f"customer_request_{idx}") if callable(trace_ctx) else nullcontext()
with trace_cm:
task = Task(
name=f"customer_request_{idx}",
description=description,
expected_output="Professional customer service response with solution",
agent=customer_service_agent,
)
agents = AgentTeam(agents=[customer_service_agent], tasks=[task], output="minimal")
result = agents.start()
print(f"✅ Request {idx} completed, response length={len(str(result))}")
# Execute production workflow with comprehensive telemetry
print("Starting production workflow with telemetry tracking...")
print("\nTelemetry demonstration completed.")
customer_requests = simulate_customer_requests()
for i, request in enumerate(customer_requests):
print(f"\n{'='*60}")
print(f"PROCESSING CUSTOMER REQUEST {i+1}: {request['type'].upper()}")
print(f"Complexity: {request['complexity']} | Expected Duration: {request['expected_duration']}s")
print(f"{'='*60}")
# Start telemetry trace for this customer interaction
with telemetry.trace_operation(f"customer_request_{request['type']}") as trace:
# Add custom attributes to the trace
trace.set_attributes({
"request.type": request['type'],
"request.complexity": request['complexity'],
"request.expected_duration": request['expected_duration'],
"customer.id": f"customer_{random.randint(1000, 9999)}",
"agent.assigned": "CustomerServiceAgent"
})
start_time = time.time()
# Create customer service task with telemetry
customer_task = Task(
name=f"customer_request_{i+1}",
description=request['description'],
expected_output="Professional customer service response with solution",
agent=customer_service_agent
)
# Execute with telemetry tracking
try:
# Customer service response
with telemetry.trace_agent_execution("CustomerServiceAgent") as agent_trace:
agents = AgentTeam(
agents=[customer_service_agent],
tasks=[customer_task],
output="minimal" # Reduce verbosity for telemetry demo
)
cs_result = agents.start()
agent_trace.set_attributes({
"agent.response_length": len(str(cs_result)),
"agent.tools_used": len(customer_service_agent.tools) if customer_service_agent.tools else 0
})
# Technical support (if needed for high complexity)
if request['complexity'] == 'high':
with telemetry.trace_agent_execution("TechnicalSupportAgent") as tech_trace:
tech_task = Task(
name=f"technical_support_{i+1}",
description=f"Provide detailed technical support for: {request['description']}",
expected_output="Detailed technical solution with troubleshooting steps",
agent=technical_support_agent,
context=[customer_task]
)
tech_agents = AgentTeam(
agents=[technical_support_agent],
tasks=[tech_task], output="minimal"
)
tech_result = tech_agents.start()
tech_trace.set_attributes({
"agent.response_length": len(str(tech_result)),
"agent.escalation": True
})
# Quality assurance check
with telemetry.trace_agent_execution("QualityAssuranceAgent") as qa_trace:
qa_task = Task(
name=f"quality_check_{i+1}",
description=f"Review the quality of customer service provided for: {request['description']}",
expected_output="Quality assessment score and feedback",
agent=quality_assurance_agent
)
qa_agents = AgentTeam(
agents=[quality_assurance_agent],
tasks=[qa_task], output="minimal"
)
qa_result = qa_agents.start()
# Extract quality score (simulated)
quality_score = random.uniform(0.8, 1.0) # Simulated score
qa_trace.set_attributes({
"qa.score": quality_score,
"qa.passed": quality_score >= 0.85
})
# Calculate performance metrics
end_time = time.time()
duration = end_time - start_time
# Record custom metrics
telemetry.record_metric("request_duration", duration, {
"request_type": request['type'],
"complexity": request['complexity']
})
telemetry.record_metric("request_success", 1, {
"request_type": request['type']
})
# Set final trace attributes
trace.set_attributes({
"request.duration_seconds": duration,
"request.status": "completed",
"request.quality_score": quality_score,
"request.escalated": request['complexity'] == 'high'
})
print(f"✅ Request completed successfully in {duration:.2f}s (Quality: {quality_score:.2f})")
except Exception as e:
# Record error metrics
telemetry.record_metric("request_error", 1, {
"request_type": request['type'],
"error_type": type(e).__name__
})
trace.set_attributes({
"request.status": "error",
"request.error": str(e)
})
print(f"❌ Request failed: {str(e)}")
# Add some delay between requests
time.sleep(2)
if __name__ == "__main__":
main()
# Generate telemetry summary
print(f"\n{'='*80}")
print("TELEMETRY SUMMARY AND ANALYTICS")
print(f"{'='*80}")
# Simulated telemetry analytics (in production, this would come from your telemetry backend)
print("📊 Performance Metrics:")
print("- Total Requests Processed: 4")
print("- Average Response Time: 3.2s")
print("- Success Rate: 100%")
print("- Quality Score Average: 0.91")
print("- High Complexity Requests: 25%")
print("\n📈 Usage Analytics:")
print("- Most Common Request Type: technical_issue")
print("- Peak Performance Window: All requests within SLA")
print("- Agent Utilization: CustomerService (100%), TechnicalSupport (25%), QA (100%)")
print("\n🔍 Observability Features Demonstrated:")
print("- Distributed tracing across agent interactions")
print("- Custom metrics for business KPIs")
print("- Performance monitoring and SLA tracking")
print("- Error tracking and alerting")
print("- Quality assurance metrics")
print("- User journey tracking")
print("- Resource utilization monitoring")
print("\n⚙️ Production Monitoring Setup:")
print("- OpenTelemetry integration for standardized observability")
print("- Custom metrics for business-specific KPIs")
print("- Trace correlation across multi-agent workflows")
print("- Performance benchmarking against expected durations")
print("- Quality scoring and automated validation")
print("- Error categorization and alerting")
print(f"\n{'='*80}")
print("TELEMETRY DEMONSTRATION COMPLETED")
print(f"{'='*80}")
print("This example demonstrated comprehensive production telemetry including:")
print("- Request tracing and performance monitoring")
print("- Custom business metrics collection")
print("- Quality assurance tracking")
print("- Error handling and alerting")
print("- Multi-agent workflow observability")
print("- Production-ready monitoring patterns")
@@ -14,7 +14,7 @@ def main():
goal="Find information about a topic",
backstory="You are an expert researcher who finds comprehensive information.",
llm="gpt-4o-mini", # Use a cost-effective model for examples
output={"metrics": True} # Token metrics via consolidated output config
metrics=True # 🎯 SIMPLIFIED: Just add this parameter!
)
# Create a simple task
@@ -14,7 +14,7 @@ def main():
goal="Analyze data and provide insights",
backstory="You are an experienced data analyst with expertise in market research.",
llm="gpt-4o-mini",
output={"metrics": True}
metrics=True # 🎯 Enable metrics tracking
)
writer_agent = Agent(
@@ -23,7 +23,7 @@ def main():
goal="Create well-structured content",
backstory="You are a skilled technical writer who creates clear, engaging content.",
llm="gpt-4o-mini",
output={"metrics": True}
metrics=True # 🎯 Enable metrics tracking
)
# Create multiple tasks
@@ -14,7 +14,7 @@ def main():
goal="Conduct comprehensive market research",
backstory="You are a seasoned market researcher with 10+ years of experience.",
llm="gpt-4o-mini",
output={"metrics": True}
metrics=True # 🎯 Enable automatic metrics tracking
)
strategy_agent = Agent(
@@ -23,7 +23,7 @@ def main():
goal="Develop actionable business strategies",
backstory="You are a strategic consultant who translates research into business plans.",
llm="gpt-4o-mini",
output={"metrics": True}
metrics=True # 🎯 Enable automatic metrics tracking
)
# Create tasks that will generate substantial token usage
@@ -22,8 +22,7 @@ rag_agent = Agent(
You can use the internet_search function to search the web when needed.""",
llm="gpt-4o",
output={"markdown": True},
)
markdown=True)
if __name__ == "__main__":
print("🤖 Thai Recipe RAG Agent is ready!")
@@ -5,17 +5,11 @@ from dotenv import load_dotenv
load_dotenv()
serper_api_key = os.getenv("SERPER_API_KEY")
if serper_api_key is not None:
os.environ["SERPER_API_KEY"] = serper_api_key
os.environ['SERPER_API_KEY'] = os.getenv('SERPER_API_KEY')
search = GoogleSerperAPIWrapper()
if __name__ == "__main__":
if not serper_api_key:
print("SERPER_API_KEY is not set. Skipping Google Serper example.")
else:
search = GoogleSerperAPIWrapper()
data_agent = Agent(instructions="Suggest me top 5 most visited websites for Dosa Recipe", tools=[search])
editor_agent = Agent(instructions="List out the websites with their url and a short description")
agents = AgentTeam(agents=[data_agent, editor_agent])
agents.start()
data_agent = Agent(instructions="Suggest me top 5 most visited websites for Dosa Recipe", tools=[search])
editor_agent = Agent(instructions="List out the websites with their url and a short description")
agents = AgentTeam(agents=[data_agent, editor_agent])
agents.start()
@@ -2,7 +2,6 @@ from praisonaiagents import Agent, Task, AgentTeam
from pydantic import BaseModel
from typing import List, Dict
from gitingest import ingest
import sys
class CodeMetrics(BaseModel):
category: str
@@ -61,10 +60,7 @@ def analyze_code(code_source: str) -> CodeAnalysisReport:
Analyze code from directory path or GitHub URL
"""
# Ingest code content
try:
summary, tree, content = ingest(code_source)
except Exception as exc:
raise RuntimeError(f"Failed to ingest code source: {exc}") from exc
summary, tree, content = ingest(code_source)
# Concatenate context into structured format
context_text = f"""
@@ -90,22 +86,10 @@ def analyze_code(code_source: str) -> CodeAnalysisReport:
tasks=[code_analysis_task]
)
result = agents.start(context_text)
if result is None:
raise RuntimeError("No analysis result returned by AgentTeam.start()")
return result
def main() -> None:
# Example usage
code_source = "https://github.com/openai/openai-python/tree/main/src/openai/cli/_api/chat" # GitHub URL or local directory
try:
result = analyze_code(code_source)
except RuntimeError as exc:
print(f"Code analysis failed: {exc}")
sys.exit(1)
print(result)
return agents.start(context_text)
if __name__ == "__main__":
main()
# Example usage
code_source = "https://github.com/openai/openai-python/tree/main/src/openai/cli/_api/chat" # GitHub URL or local directory
result = analyze_code(code_source)
print(result)
@@ -77,44 +77,25 @@ agents = AgentTeam(
process="sequential", output="verbose"
)
def _extract_pydantic_result(result_obj):
"""Extract pydantic payload from AgentTeam.start() output shapes."""
if hasattr(result_obj, "pydantic"):
return result_obj.pydantic
if isinstance(result_obj, dict):
task_results = result_obj.get("task_results")
if isinstance(task_results, dict):
values = list(task_results.values())
if values and hasattr(values[-1], "pydantic"):
return values[-1].pydantic
if isinstance(task_results, list) and task_results and hasattr(task_results[-1], "pydantic"):
return task_results[-1].pydantic
return None
# Start the analysis
result = agents.start()
pydantic_result = _extract_pydantic_result(result)
if pydantic_result is None:
raise ValueError("Could not extract structured CVAnalysisReport from workflow result")
# Access the structured results
print("\nCV Analysis Results:")
print(f"\nOverall Score: {pydantic_result.overall_score}/100")
print(f"\nOverall Score: {result.pydantic.overall_score}/100")
print("\nKey Strengths:")
for strength in pydantic_result.key_strengths:
for strength in result.pydantic.key_strengths:
print(f"- {strength}")
print("\nSkill Analysis:")
print("\nTechnical Skills:")
for skill in pydantic_result.skill_analysis.technical_skills:
for skill in result.pydantic.skill_analysis.technical_skills:
print(f"- {skill}")
print("\nExperience Highlights:")
for achievement in pydantic_result.experience_analysis.key_achievements:
for achievement in result.pydantic.experience_analysis.key_achievements:
print(f"- {achievement}")
print("\nRecommendations:")
for recommendation in pydantic_result.recommendations:
for recommendation in result.pydantic.recommendations:
print(f"- {recommendation}")
@@ -100,8 +100,6 @@ def evaluate_project(video_path: str) -> ProjectEvaluation:
evaluation_data = response
print(f"Debug - Parsed evaluation_data: {evaluation_data}")
if not evaluation_data or not isinstance(evaluation_data, dict):
raise ValueError("No structured evaluation data returned by agent")
# Create and return ProjectEvaluation instance
return ProjectEvaluation(
+3 -9
View File
@@ -1,11 +1,5 @@
from praisonaiagents import VideoAgent
import os
if __name__ == "__main__":
if not os.getenv("AZURE_API_BASE"):
print("AZURE_API_BASE is not configured. Skipping Azure video example.")
else:
agent = VideoAgent(llm="azure/sora-2")
video = agent.generate("Mountain landscape")
print(video.id)
agent = VideoAgent(llm="azure/sora-2")
video = agent.generate("Mountain landscape")
print(video.id)
+2 -5
View File
@@ -6,7 +6,6 @@ with Agents for workflow monitoring and logging.
"""
from praisonaiagents import Agent, Task, AgentTeam
from praisonaiagents.config.feature_configs import MultiAgentHooksConfig
# Create an agent
researcher = Agent(
@@ -48,10 +47,8 @@ if __name__ == "__main__":
agents=[researcher],
tasks=[task1, task2],
process="workflow",
hooks=MultiAgentHooksConfig(
on_task_start=on_start,
on_task_complete=on_complete,
),
on_task_start=on_start,
on_task_complete=on_complete,
variables={"global_var": "shared_value"} # Global variables
)
@@ -6,7 +6,6 @@ to different next steps based on output content.
"""
from praisonaiagents import AgentFlow, Task
from praisonaiagents import AgentFlowManager
# Create a workflow with branching
workflow = AgentFlow(
@@ -36,16 +35,9 @@ workflow = AgentFlow(
)
if __name__ == "__main__":
# Create manager and register workflow
manager = WorkflowManager()
manager.workflows["Decision Workflow"] = workflow
# Execute - will branch to success_handler since 42 is positive
result = manager.execute(
"Decision Workflow",
default_llm="gpt-4o-mini"
)
result = workflow.run("", llm="gpt-4o-mini", verbose=True)
print("Workflow completed!")
for step_result in result["results"]:
print(f" {step_result['step']}: {step_result['status']}")
for step in result.get("steps", []):
name = step.get("step", step.get("name", "unknown"))
status = step.get("status", "completed")
print(f" {name}: {status}")
@@ -54,13 +54,13 @@ workflow = AgentFlow(
analyzer, # Always runs
Task(
name="tech_review",
description="Review technical aspects when technical keywords are detected.",
description="Review technical aspects of the content",
agent=tech_reviewer,
should_run=is_technical # Only runs for technical content
),
Task(
name="creative_enhance",
description="Enhance narrative quality when creative keywords are detected.",
description="Enhance the creative aspects of the content",
agent=creative_enhancer,
should_run=needs_creative # Only runs for creative content
),
@@ -6,9 +6,8 @@ functions that return StepResult with stop_workflow=True.
"""
from praisonaiagents import (
Workflow, Task, WorkflowContext, StepResult
AgentFlow, Task, WorkflowContext, StepResult
)
from praisonaiagents import AgentFlowManager
# Custom validator that can stop the workflow
def validate_data(context: WorkflowContext) -> StepResult:
@@ -50,20 +49,13 @@ workflow = AgentFlow(
)
if __name__ == "__main__":
manager = WorkflowManager()
manager.workflows["Data Processing"] = workflow
# Test with invalid data (will stop early)
print("=== Testing with invalid data ===")
result = manager.execute("Data Processing", default_llm="gpt-4o-mini")
for step_result in result["results"]:
print(f" {step_result['step']}: {step_result['output']}")
# Test with valid data (will complete)
result = workflow.run("", llm="gpt-4o-mini", verbose=True)
for step in result.get("steps", []):
print(f" {step.get('step', 'step')}: {step.get('output', step.get('status'))}")
print("\n=== Testing with valid data ===")
workflow.variables["data"] = {"value": 42}
result = manager.execute("Data Processing", default_llm="gpt-4o-mini")
for step_result in result["results"]:
print(f" {step_result['step']}: {step_result['status']}")
result = workflow.run("", llm="gpt-4o-mini", verbose=True)
for step in result.get("steps", []):
print(f" {step.get('step', 'step')}: {step.get('status', 'completed')}")
+8 -15
View File
@@ -6,7 +6,6 @@ Each item is processed individually with the loop variable.
"""
from praisonaiagents import AgentFlow, Task
from praisonaiagents import AgentFlowManager
# Create a workflow with loop
workflow = AgentFlow(
@@ -30,18 +29,12 @@ workflow = AgentFlow(
)
if __name__ == "__main__":
# Create manager and register workflow
manager = WorkflowManager()
manager.workflows["Process Items"] = workflow
# Execute - will process each fruit in the list
result = manager.execute(
"Process Items",
default_llm="gpt-4o-mini"
)
result = workflow.run("", llm="gpt-4o-mini", verbose=True)
print("Workflow completed!")
for step_result in result["results"]:
print(f" {step_result['step']}: {step_result['status']}")
if isinstance(step_result.get("output"), list):
print(f" Loop processed {len(step_result['output'])} items")
for step in result.get("steps", []):
name = step.get("step", step.get("name", "unknown"))
status = step.get("status", "completed")
print(f" {name}: {status}")
out = step.get("output")
if isinstance(out, list):
print(f" Loop processed {len(out)} items")
@@ -53,7 +53,7 @@ workflow = AgentFlow(
# Step 4: Function - formats output
format_output
],
llm="gpt-4o-mini", # Used for action-based steps
llm="gpt-4o-mini" # Used for action-based steps
)
if __name__ == "__main__":
@@ -5,7 +5,7 @@ Demonstrates running multiple agents concurrently and
combining their results with an aggregator agent.
"""
from praisonaiagents import Agent, Workflow
from praisonaiagents import Agent, AgentFlow
from praisonaiagents.workflows import parallel
# Create parallel research agents
@@ -42,7 +42,6 @@ def main():
# Create tasks with robustness features
research_task = Task(
name="research",
description="Research the latest trends in AI agents for 2025",
expected_output="A summary of key AI agent trends",
agent=researcher,
@@ -52,7 +51,6 @@ def main():
# Optional enrichment step - workflow continues even if this fails
enrich_task = Task(
name="enrich",
description="Enrich the research with real-world examples",
expected_output="Research with added examples",
agent=enricher,
@@ -62,7 +60,6 @@ def main():
)
write_task = Task(
name="write",
description="Write a brief summary based on the research",
expected_output="A well-written summary paragraph",
agent=writer,
@@ -5,7 +5,7 @@ Demonstrates decision-based routing where an agent classifier
routes to specialized handler agents based on the request type.
"""
from praisonaiagents import Agent, Workflow
from praisonaiagents import Agent, AgentFlow
from praisonaiagents.workflows import route
# Create classifier agent
+7 -9
View File
@@ -51,15 +51,13 @@ print("=" * 60)
agent = Agent(
name="Knowledge Agent",
instructions="You are a helpful assistant that answers questions based on the provided knowledge. Be concise.",
knowledge={
"sources": [".praison/test_docs/overview.txt"],
"config": {
"policy": "always",
"top_k": 3,
"citations": True,
"max_context_tokens": 2000,
},
},
knowledge=[".praison/test_docs/overview.txt"],
retrieval_config={
"policy": "always", # Always retrieve for this demo
"top_k": 3, # Get top 3 chunks
"citations": True, # Include citations
"max_context_tokens": 2000,
}
)
# Test 1: Basic chat with retrieval
+1 -1
View File
@@ -60,7 +60,7 @@ print("=" * 50)
agent_with_discovery = Agent(
name="Multi-Skill Agent",
instructions="You are a versatile assistant with multiple skills.",
skills=["./"], # Scan current directory for skill subdirectories
skills_dirs=["./"], # Scan current directory for skill subdirectories
)
if agent_with_discovery.skill_manager:
+1 -1
View File
@@ -190,7 +190,7 @@ def example_run_history_with_backend():
# Use with RunHistory
history = RunHistory(backend=backend)
runs = history.list_runs()
runs = history.list()
print(f"RunHistory created with SQLite backend, {len(runs)} runs")
print("Recipe runs will be stored in database instead of files")
+1 -1
View File
@@ -257,7 +257,7 @@ if __name__ == "__main__":
# Check dependencies
try:
import praisonaiagents
print(f"✅ PraisonAI version: {getattr(praisonaiagents, '__version__', None)}")
print(f"✅ PraisonAI version: {praisonaiagents.__version__}")
except ImportError:
print("❌ PraisonAI not installed: pip install praisonaiagents")
sys.exit(1)
+1 -1
View File
@@ -12,7 +12,7 @@ assert os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY must be set"
# Enable web search and fetch
agent = Agent(
instructions="You are a research assistant with web access.",
web="search_only",
web=True,
)
# Disable web (default)
+11 -3
View File
@@ -4,7 +4,7 @@ WebConfig Example
Demonstrates using WebConfig for fine-grained control.
"""
import os
from praisonaiagents import Agent
from praisonaiagents import Agent, WebConfig
# Ensure API key is set from environment
assert os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY must be set"
@@ -12,13 +12,21 @@ assert os.getenv("OPENAI_API_KEY"), "OPENAI_API_KEY must be set"
# Custom web configuration
agent = Agent(
instructions="You are a research assistant.",
web="search_only",
web=WebConfig(
search=True,
fetch=True,
search_provider="duckduckgo",
max_results=5,
),
)
# Search only (no fetch)
agent_search_only = Agent(
instructions="You are a research assistant.",
web="search_only",
web=WebConfig(
search=True,
fetch=False,
),
)
if __name__ == "__main__":
+1 -1
View File
@@ -93,7 +93,7 @@ def example_3_execute_yaml_directly():
result = manager.execute_yaml(
yaml_file,
input_data="The future of AI",
verbose=True,
output="verbose"
)
print(f"Status: {result['status']}")
@@ -114,6 +114,9 @@ def _get_lazy_cache():
# ============================================================================
_LAZY_IMPORTS = {
# Version information (lazy loaded to avoid file I/O on import)
'__version__': ('praisonaiagents._version', '__version__'),
# Tools (moved from eager imports for lazy loading)
'Tools': ('praisonaiagents.tools.tools', 'Tools'),
'BaseTool': ('praisonaiagents.tools.base', 'BaseTool'),
@@ -775,6 +778,9 @@ def warmup(include_litellm: bool = False, include_openai: bool = True) -> dict:
# ============================================================================
__all__ = [
# Version information
'__version__',
# Core classes - the essentials
'Agent',
'AgentTeam', # Primary class for multi-agent coordination (v1.0+)
@@ -0,0 +1,51 @@
"""
Version utilities for praisonaiagents package.
This module provides version information by reading from pyproject.toml
to avoid duplication and ensure the single source of truth.
"""
import re
from pathlib import Path
def get_version() -> str:
"""
Get the version string from pyproject.toml.
Returns:
str: Version string (e.g., "1.6.52")
Raises:
RuntimeError: If version cannot be found or parsed
"""
try:
# Get the package root (praisonaiagents directory)
package_root = Path(__file__).parent
# Go up to src/praisonai-agents directory
pyproject_path = package_root.parent / "pyproject.toml"
if not pyproject_path.exists():
raise RuntimeError(f"pyproject.toml not found at {pyproject_path}")
content = pyproject_path.read_text()
# Look for version = "X.Y.Z" pattern
match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
if not match:
raise RuntimeError("Version not found in pyproject.toml")
return match.group(1)
except Exception as e:
# Fallback to "unknown" to avoid breaking imports
import warnings
warnings.warn(
f"Failed to read version from pyproject.toml: {e}. Using 'unknown'.",
RuntimeWarning
)
return "unknown"
# Cache the version to avoid reading the file multiple times
__version__ = get_version()
@@ -353,9 +353,9 @@ class RouterAgent(Agent):
return report
def _build_system_prompt(self, tools=None, **kwargs) -> str:
def _build_system_prompt(self) -> str:
"""Build system prompt (inherited from parent but can be customized)."""
base_prompt = super()._build_system_prompt(tools=tools, **kwargs)
base_prompt = super()._build_system_prompt()
# Add multi-model context if needed
if self.routing_strategy == "auto":
@@ -652,7 +652,7 @@ class AgentTeam(SpawnAnnounceProtocol):
_stream = _output_config.stream
else:
_verbose = 0
_stream = True
_stream = False
# ─────────────────────────────────────────────────────────────────────
# Resolve EXECUTION param using canonical resolver
@@ -1089,8 +1089,8 @@ class MultiAgentOutputConfig:
# Verbosity level (0=silent, 1=minimal, 2+=verbose)
verbose: int = 0
# Streaming output
stream: bool = True
# Streaming output - False by default for sync multi-agent compatibility
stream: bool = False
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary."""
@@ -322,8 +322,8 @@ CACHING_PRESETS: Dict[str, Dict[str, Any]] = {
# =============================================================================
MULTI_AGENT_OUTPUT_PRESETS: Dict[str, Dict[str, Any]] = {
"verbose": {"verbose": 2, "stream": True},
"minimal": {"verbose": 1, "stream": True},
"verbose": {"verbose": 2, "stream": False},
"minimal": {"verbose": 1, "stream": False},
"silent": {"verbose": 0, "stream": False},
}
+84 -43
View File
@@ -24,6 +24,7 @@ import xml.etree.ElementTree as ET
from ..errors import AgentErrorKind, FailoverDecision, IdleTimeoutBreaker
# Gap 2: Tool call execution imports
from ..tools.call_executor import ToolCall, create_tool_call_executor
from ..tools.schema import annotation_to_json_schema, get_parameter_requirements
# Display functions - lazy loaded to avoid importing rich at startup
# These are only needed when output=verbose
_display_module = None
@@ -5886,34 +5887,74 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
function_name = function_or_name
logging.debug(f"Attempting to generate tool definition for: {function_name}")
# First try to get the tool definition if it exists
tool_def_name = f"{function_name}_definition"
tool_def = globals().get(tool_def_name)
logging.debug(f"Looking for {tool_def_name} in globals: {tool_def is not None}")
if not tool_def:
import __main__
tool_def = getattr(__main__, tool_def_name, None)
logging.debug(f"Looking for {tool_def_name} in __main__: {tool_def is not None}")
if tool_def:
logging.debug(f"Found tool definition: {tool_def}")
return tool_def
# Try to find the function
func = globals().get(function_name)
logging.debug(f"Looking for {function_name} in globals: {func is not None}")
if not func:
import __main__
func = getattr(__main__, function_name, None)
logging.debug(f"Looking for {function_name} in __main__: {func is not None}")
if not func or not callable(func):
logging.debug(f"Function {function_name} not found or not callable")
return None
# First try to get from the tool registry (preferred method)
try:
from ..tools.registry import get_registry
registry = get_registry()
tool = registry.get(function_name)
if tool:
logging.debug(f"Found tool in registry: {function_name}")
# If it's a BaseTool, get its schema
if hasattr(tool, 'get_schema'):
tool_def = tool.get_schema()
# Apply same normalization as the callable path
if (
isinstance(tool_def, dict)
and isinstance(tool_def.get("function"), dict)
and isinstance(tool_def["function"].get("parameters"), dict)
):
tool_def = tool_def.copy()
tool_def["function"] = tool_def["function"].copy()
tool_def["function"]["parameters"] = self._fix_array_schemas(
tool_def["function"]["parameters"]
)
return tool_def
# If it's a callable, use it as func
if callable(tool):
func = tool
else:
logging.debug(f"Tool {function_name} in registry is not callable")
return None
else:
logging.debug(f"Tool {function_name} not found in registry, falling back to globals/__main__")
# Fall back to globals and __main__ for backward compatibility
tool_def_name = f"{function_name}_definition"
tool_def = globals().get(tool_def_name)
if not tool_def:
import __main__
tool_def = getattr(__main__, tool_def_name, None)
if tool_def:
return tool_def
func = globals().get(function_name)
if not func:
import __main__
func = getattr(__main__, function_name, None)
if not func or not callable(func):
logging.debug(f"Function {function_name} not found or not callable")
return None
except ImportError:
logging.debug("Tool registry not available, falling back to globals/__main__")
# Fall back to globals and __main__ when registry unavailable
tool_def_name = f"{function_name}_definition"
tool_def = globals().get(tool_def_name)
if not tool_def:
import __main__
tool_def = getattr(__main__, tool_def_name, None)
if tool_def:
return tool_def
func = globals().get(function_name)
if not func:
import __main__
func = getattr(__main__, function_name, None)
if not func or not callable(func):
logging.debug(f"Function {function_name} not found or not callable")
return None
import inspect
from typing import get_type_hints
# Handle Langchain and CrewAI tools
if inspect.isclass(func) and hasattr(func, 'run') and not hasattr(func, '_run'):
original_func = func
@@ -5961,26 +6002,26 @@ Output MUST be JSON with 'reflection' and 'satisfactory'.
logging.debug(f"Parameter descriptions: {param_descriptions}")
# Get type hints for proper schema generation
try:
hints = get_type_hints(func) if getattr(func, "__annotations__", None) else {}
except (NameError, TypeError, AttributeError):
hints = getattr(func, "__annotations__", {}) or {}
for name, param in parameters_list:
param_type = "string" # Default type
if param.annotation != inspect.Parameter.empty:
if param.annotation == int:
param_type = "integer"
elif param.annotation == float:
param_type = "number"
elif param.annotation == bool:
param_type = "boolean"
elif param.annotation == list:
param_type = "array"
elif param.annotation == dict:
param_type = "object"
# Get type annotation
param_type = hints.get(name, param.annotation if param.annotation != inspect.Parameter.empty else str)
parameters["properties"][name] = {
"type": param_type,
"description": param_descriptions.get(name, "Parameter description not available")
}
# Use new schema utility for proper type handling
prop_schema = annotation_to_json_schema(param_type)
if param.default == inspect.Parameter.empty:
# Add description from docstring
prop_schema["description"] = param_descriptions.get(name, "Parameter description not available")
parameters["properties"][name] = prop_schema
# Check if required using improved logic
if get_parameter_requirements(sig, name):
parameters["required"].append(name)
logging.debug(f"Generated parameters: {parameters}")
@@ -29,6 +29,9 @@ logger = get_logger(__name__, extra_data={"subsystem": "memory"})
TRACE_LEVEL = 5
logging.addLevelName(TRACE_LEVEL, 'TRACE')
# Cache boundary constants for prompt prefix caching optimization
CACHE_BOUNDARY = "\n\n<!-- CACHE_BOUNDARY -->\n\n"
class Memory(StorageMixin, SearchMixin, MemoryCoreMixin):
@@ -1704,6 +1707,41 @@ class Memory(StorageMixin, SearchMixin, MemoryCoreMixin):
entities = self.search_entity(q, limit=max_items)
user_mem = self.search_user_memory(user_id, q, limit=max_items) if user_id else []
# Apply stable sorting to ensure deterministic order for prompt caching
# Sort by timestamp descending, then by content hash for stable ordering
def _sort_memory_results(results: List[Any]) -> List[Any]:
"""Stable sort so identical query sets produce identical context strings."""
import hashlib
if not results:
return results
def sort_key(r):
if not isinstance(r, dict):
return (0.0, hashlib.sha256(str(r).encode()).hexdigest())
# Prefer created_at over timestamp for better determinism
timestamp = r.get("created_at") or r.get("timestamp") or 0
if isinstance(timestamp, str):
try:
from datetime import datetime
timestamp = datetime.fromisoformat(timestamp.replace("Z", "+00:00")).timestamp()
except ValueError:
timestamp = 0.0
timestamp = -(float(timestamp)) # Negative for descending order
# Use full content hash for true deterministic ordering
content = r.get("text", "") if isinstance(r, dict) else str(r)
content_hash = hashlib.sha256(content.encode()).hexdigest()
return (timestamp, content_hash)
return sorted(results, key=sort_key)
# Apply stable sorting to all memory results
short_term = _sort_memory_results(short_term)
long_term = _sort_memory_results(long_term)
entities = _sort_memory_results(entities)
user_mem = _sort_memory_results(user_mem)
# Add sections in order of priority
add_section("Short-term Memory Context", short_term)
add_section("Long-term Memory Context", long_term)
@@ -1713,6 +1751,52 @@ class Memory(StorageMixin, SearchMixin, MemoryCoreMixin):
return "\n".join(lines) if lines else ""
def build_cache_optimized_context(
self,
task_descr: str,
user_id: Optional[str] = None,
additional: str = "",
max_items: int = 3,
include_cache_boundary: bool = True,
include_in_output: Optional[bool] = None
) -> Dict[str, str]:
"""
Build context with cache boundary markers for prompt prefix caching optimization.
Returns a dictionary with 'stable_prefix' and 'cache_boundary' keys.
The stable prefix contains deterministically ordered content that should
remain constant between turns for effective prompt caching.
Args:
task_descr: Task description for memory search
user_id: Optional user ID for personalized memory
additional: Additional context to include in search
max_items: Maximum items per memory category
include_cache_boundary: Whether to include cache boundary marker
include_in_output: Whether to include memory content in output
Returns:
Dict with 'stable_prefix' and 'cache_boundary' keys
"""
# Cache-optimized API should include stable content unless explicitly disabled
if include_in_output is None:
include_in_output = True
# Build the stable context using existing method
stable_context = self.build_context_for_task(
task_descr=task_descr,
user_id=user_id,
additional=additional,
max_items=max_items,
include_in_output=include_in_output
)
result = {"stable_prefix": stable_context}
result["cache_boundary"] = CACHE_BOUNDARY if include_cache_boundary else ""
return result
# -------------------------------------------------------------------------
# Master Reset (Everything)
# -------------------------------------------------------------------------
@@ -17,9 +17,12 @@ Usage:
from abc import ABC, abstractmethod
from typing import Any, Callable, Dict, List, Optional, Type, get_type_hints
import inspect
import json
import logging
import copy
from .schema import annotation_to_json_schema, get_parameter_requirements
class ToolValidationError(Exception):
"""Raised when a tool fails validation."""
@@ -129,18 +132,17 @@ class BaseTool(ABC):
# Get type hint
param_type = hints.get(param_name, Any)
json_type = self._python_type_to_json(param_type)
# Build property schema
prop_schema = {"type": json_type}
# Use new schema utility for proper type handling
prop_schema = annotation_to_json_schema(param_type)
# Add description from docstring if available
# (Could parse docstring for param descriptions)
schema["properties"][param_name] = prop_schema
# Check if required (no default value)
if param.default is inspect.Parameter.empty:
# Check if required using improved logic
if get_parameter_requirements(sig, param_name):
schema["required"].append(param_name)
except Exception as e:
logging.debug(f"Could not generate schema for {self.name}: {e}")
@@ -149,28 +151,15 @@ class BaseTool(ABC):
@staticmethod
def _python_type_to_json(python_type: Type) -> str:
"""Convert Python type to JSON Schema type."""
type_map = {
str: "string",
int: "integer",
float: "number",
bool: "boolean",
list: "array",
dict: "object",
type(None): "null"
}
"""Convert Python type to JSON Schema type.
# Handle Optional, Union, etc.
origin = getattr(python_type, '__origin__', None)
if origin is not None:
# For List[X], return "array"
if origin is list:
return "array"
# For Dict[X, Y], return "object"
if origin is dict:
return "object"
return type_map.get(python_type, "string")
DEPRECATED: Use annotation_to_json_schema() from schema.py instead.
This method is kept for backward compatibility but will be removed.
"""
# Legacy fallback - delegate to new schema utility and extract type
from .schema import annotation_to_json_schema
schema = annotation_to_json_schema(python_type)
return schema.get("type", "string")
@abstractmethod
def run(self, **kwargs) -> Any:
@@ -442,5 +431,49 @@ def validate_tool_schema_consistency(tools: List[Any]) -> bool:
return True
def get_sorted_tool_schemas(tools: List[Any]) -> List[Dict[str, Any]]:
"""Get tool schemas sorted by function name for deterministic ordering.
This ensures tool schemas are always ordered consistently for prompt caching optimization.
Args:
tools: List of tool objects (BaseTool instances, callables, etc.)
Returns:
List of tool schemas sorted alphabetically by function name
Raises:
ToolValidationError: If validation fails
"""
if not tools:
return []
# First validate all tools (reuse existing validation logic)
validate_tool_schema_consistency(tools)
from .decorator import get_tool_schema
schemas = []
for tool in tools:
# Get schema from different tool types
if isinstance(tool, BaseTool):
schema = tool.get_schema()
elif hasattr(tool, 'get_schema') and callable(getattr(tool, 'get_schema')):
schema = tool.get_schema()
elif callable(tool):
schema = get_tool_schema(tool)
else:
continue # Skip invalid tools (validation already happened)
if schema:
schemas.append(schema)
# Sort schemas by function name for deterministic ordering
def sort_key(schema):
return schema.get("function", {}).get("name", "")
return sorted(schemas, key=sort_key)
# For backward compatibility - tools can also just be functions
# The @tool decorator (in decorator.py) wraps functions into BaseTool instances
@@ -31,6 +31,7 @@ import copy
from typing import Any, Callable, Dict, Optional, Union, get_type_hints
from .base import BaseTool
from .schema import annotation_to_json_schema, get_parameter_requirements
# Lazy load injected module functions to reduce import time
_injected_module = None
@@ -132,12 +133,13 @@ class FunctionTool(BaseTool):
if is_injected_type(param_type):
continue
json_type = BaseTool._python_type_to_json(param_type)
# Use new schema utility for proper type handling
prop_schema = annotation_to_json_schema(param_type)
schema["properties"][param_name] = {"type": json_type}
schema["properties"][param_name] = prop_schema
# Check if required (no default value)
if param.default is inspect.Parameter.empty:
# Check if required using improved logic
if get_parameter_requirements(sig, param_name):
schema["required"].append(param_name)
except Exception as e:
logging.debug(f"Could not generate schema for {func.__name__}: {e}")
@@ -342,11 +344,14 @@ def _schema_from_function(func: Callable) -> Dict[str, Any]:
continue
param_type = hints.get(param_name, Any)
json_type = BaseTool._python_type_to_json(param_type)
properties[param_name] = {"type": json_type}
# Use new schema utility for proper type handling
prop_schema = annotation_to_json_schema(param_type)
if param.default is inspect.Parameter.empty:
properties[param_name] = prop_schema
# Check if required using improved logic
if get_parameter_requirements(sig, param_name):
required.append(param_name)
except Exception as e:
logging.debug(f"Could not generate schema for {name}: {e}")
@@ -0,0 +1,152 @@
"""JSON Schema generation utilities for tool parameters.
This module provides a shared utility for converting Python type annotations
to JSON Schema format. It handles complex types like Optional, Union, Literal,
and Enum that were previously collapsed to simple "string" types.
Usage:
from praisonaiagents.tools.schema import annotation_to_json_schema
schema = annotation_to_json_schema(Optional[int])
# {"anyOf": [{"type": "integer"}, {"type": "null"}]}
schema = annotation_to_json_schema(Literal["fast", "deep"])
# {"type": "string", "enum": ["fast", "deep"]}
"""
import enum
import inspect
from typing import Any, Union, Literal, get_origin, get_args
def annotation_to_json_schema(annotation: Any) -> dict:
"""Convert Python type annotation to JSON Schema format.
Handles:
- Basic types (str, int, float, bool)
- Optional[T] -> {"anyOf": [T schema, {"type": "null"}]}
- Union[T1, T2] -> {"anyOf": [T1 schema, T2 schema]}
- Literal["a", "b"] -> {"type": "string", "enum": ["a", "b"]}
- Enum subclasses -> {"type": "string", "enum": [e.value for e in MyEnum]}
- List[T] -> {"type": "array", "items": T schema}
- Dict[K, V] -> {"type": "object", "additionalProperties": V schema}
Args:
annotation: Python type annotation to convert
Returns:
dict: JSON Schema representation
"""
# Handle None type
if annotation is type(None):
return {"type": "null"}
# Get typing origin and args
origin = get_origin(annotation)
args = get_args(annotation)
# Handle Union types (including Optional[T] which is Union[T, None])
if origin is Union:
schemas = [annotation_to_json_schema(arg) for arg in args]
# Check if this is Optional[T] (Union[T, None])
null_schemas = [s for s in schemas if s == {"type": "null"}]
non_null_schemas = [s for s in schemas if s != {"type": "null"}]
if null_schemas and len(non_null_schemas) == 1:
# This is Optional[T] - simplified format
return {"anyOf": [non_null_schemas[0], {"type": "null"}]}
else:
# General Union type
return {"anyOf": schemas}
# Handle Literal types
if origin is Literal or (hasattr(annotation, '__origin__') and
str(annotation.__origin__).endswith('Literal')):
# Infer type from literal values if all are the same type
values = list(args)
if values:
types_seen = set(type(v) for v in values)
if len(types_seen) == 1:
single_type = next(iter(types_seen))
type_map = {str: "string", int: "integer", float: "number", bool: "boolean"}
if single_type in type_map:
return {"type": type_map[single_type], "enum": values}
# Mixed types or non-primitive - use enum without type constraint
return {"enum": values}
# Handle Enum subclasses
if isinstance(annotation, type) and issubclass(annotation, enum.Enum):
# Infer type from enum values if all are the same type
values = [e.value for e in annotation]
if values:
types_seen = set(type(v) for v in values)
if len(types_seen) == 1:
single_type = next(iter(types_seen))
type_map = {str: "string", int: "integer", float: "number", bool: "boolean"}
if single_type in type_map:
return {"type": type_map[single_type], "enum": values}
# Mixed types or non-primitive - use enum without type constraint
return {"enum": values}
# Handle List types
if origin is list:
if args:
items_schema = annotation_to_json_schema(args[0])
return {"type": "array", "items": items_schema}
else:
return {"type": "array"}
# Handle Dict types
if origin is dict:
if len(args) >= 2:
value_schema = annotation_to_json_schema(args[1])
return {"type": "object", "additionalProperties": value_schema}
else:
return {"type": "object"}
# Handle basic types
type_map = {
str: "string",
int: "integer",
float: "number",
bool: "boolean",
list: "array",
dict: "object",
type(None): "null"
}
return {"type": type_map.get(annotation, "string")}
def get_parameter_requirements(sig: inspect.Signature, param_name: str) -> bool:
"""Determine if a parameter is required based on its signature.
A parameter is required if it has no default value, unless it's Optional.
Args:
sig: Function signature
param_name: Parameter name to check
Returns:
bool: True if parameter is required
"""
param = sig.parameters.get(param_name)
if not param:
return False
# Parameter with default is not required
if param.default is not inspect.Parameter.empty:
return False
# Check if the type annotation indicates optional
if param.annotation is not inspect.Parameter.empty:
origin = get_origin(param.annotation)
if origin is Union:
args = get_args(param.annotation)
# Optional[T] is Union[T, None]
if type(None) in args:
return False
# No default and not optional = required
return True
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "praisonaiagents"
version = "1.6.55"
version = "1.6.56"
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
readme = "README.md"
requires-python = ">=3.10"
+1 -2
View File
@@ -5,8 +5,7 @@ testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts = -v --tb=short --timeout=60
timeout_method = thread
addopts = -v --tb=short
filterwarnings =
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
+1 -1
View File
@@ -3125,7 +3125,7 @@ wheels = [
[[package]]
name = "praisonaiagents"
version = "1.6.55"
version = "1.6.56"
source = { editable = "." }
dependencies = [
{ name = "aiohttp" },
+13 -3
View File
@@ -5,10 +5,10 @@
"sourceOfTruth": "Python SDK (praisonaiagents)",
"status": "PARITY_ACHIEVED",
"summary": {
"pythonCoreFeatures": 359,
"pythonCoreFeatures": 360,
"rustFeatures": 667,
"gapCount": 80,
"parityPercentage": 185.8
"gapCount": 81,
"parityPercentage": 185.3
},
"pythonCoreSDK": {
"exports": [
@@ -275,6 +275,7 @@
"WebSearchProvider",
"Workflow",
"WorkflowContext",
"__version__",
"aembed",
"aembedding",
"aembeddings",
@@ -4374,6 +4375,15 @@
"status": "DONE",
"category": "config"
},
{
"feature": "__version__",
"python": true,
"rust": false,
"priority": "P3",
"effort": "low",
"status": "TODO",
"category": "other"
},
{
"feature": "aembed",
"python": true,
Loaded 100 of 124 files, more files were not shown because too many files have changed in this diff. Show more