Compare commits

..

39 Commits

Author SHA1 Message Date
Rip&Tear
db0c8ff468 Merge branch 'main' into codex/nl2sql-security-docs 2026-02-12 13:44:54 +08:00
Greyson LaLonde
cde33fd981 feat: add yanked detection for version notes
Some checks are pending
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Notify Downstream / notify-downstream (push) Waiting to run
2026-02-11 23:31:06 -05:00
theCyberTech
1f8cfe3282 docs: clarify NL2SQL security model and hardening guidance 2026-02-12 10:32:56 +08:00
Lorenze Jay
2ed0c2c043 imp compaction (#4399)
Some checks failed
Check Documentation Broken Links / Check broken links (push) Waiting to run
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
* imp compaction

* fix lint

* cassette gen

* cassette gen

* improve assert

* adding azure

* fix global docstring
2026-02-11 15:52:03 -08:00
Lorenze Jay
0341e5aee7 supporting prompt cache results show (#4447)
* supporting prompt cache

* droped azure tests

* fix tests

---------

Co-authored-by: Greyson LaLonde <greyson.r.lalonde@gmail.com>
2026-02-11 14:07:15 -08:00
Mike Plachta
397d14c772 fix: correct CLI flag format from --skip-provider to --skip_provider (#4462)
Update documentation to use underscore instead of hyphen in the `--skip_provider` flag across all CLI command examples for consistency with actual CLI implementation.
2026-02-11 13:51:54 -08:00
Lucas Gomide
fc3e86e9a3 docs Adding 96 missing actions across 9 integrations (#4460)
* docs: add missing integration actions from OAuth config

Sync enterprise integration docs with crewai-oauth apps.js config.
Adds ~96 missing actions across 9 integrations:
- Google Contacts: 4 contact group actions
- Google Slides: 14 slide manipulation/content actions
- Microsoft SharePoint: 27 file, Excel, and Word actions
- Microsoft Excel: 2 actions (get_used_range_metadata, get_table_data)
- Microsoft Word: 2 actions (copy_document, move_document)
- Google Docs: 27 text formatting, table, and header/footer actions
- Microsoft Outlook: 7 message and calendar event actions
- Microsoft OneDrive: 5 path-based and discovery actions
- Microsoft Teams: 8 meeting, channel, and reply actions

* docs: add missing integration actions from OAuth config

Sync pt-BR enterprise integration docs with crewai-oauth apps.js config.
Adds ~96 missing actions across 9 integrations, translated to Portuguese:
- Google Contacts: 2 contact group actions
- Google Slides: 14 slide manipulation/content actions
- Microsoft SharePoint: 27 file, Excel, and Word actions
- Microsoft Excel: 2 actions (get_used_range_metadata, get_table_data)
- Microsoft Word: 2 actions (copy_document, move_document)
- Google Docs: 27 text formatting, table, and header/footer actions
- Microsoft Outlook: 7 message and calendar event actions
- Microsoft OneDrive: 5 path-based and discovery actions
- Microsoft Teams: 8 meeting, channel, and reply actions

* docs: add missing integration actions from OAuth config

Sync Korean enterprise integration docs with crewai-oauth apps.js config.
Adds ~96 missing actions across 9 integrations, translated to Korean:
- Google Contacts: 2 contact group actions
- Google Slides: 14 slide manipulation/content actions
- Microsoft SharePoint: 27 file, Excel, and Word actions
- Microsoft Excel: 2 actions (get_used_range_metadata, get_table_data)
- Microsoft Word: 2 actions (copy_document, move_document)
- Google Docs: 27 text formatting, table, and header/footer actions
- Microsoft Outlook: 7 message and calendar event actions
- Microsoft OneDrive: 5 path-based and discovery actions
- Microsoft Teams: 8 meeting, channel, and reply actions

---------

Co-authored-by: Greyson LaLonde <greyson.r.lalonde@gmail.com>
2026-02-11 15:17:54 -05:00
Mike Plachta
2882df5daf replace old .cursorrules with AGENTS.md (#4451)
* chore: remove .cursorrules file
feat: add AGENTS.md file to any newly created file

* move the copy of the tests
2026-02-11 10:07:24 -08:00
Greyson LaLonde
3a22e80764 fix: ensure openai tool call stream is finalized
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
2026-02-11 10:02:31 -05:00
Greyson LaLonde
9b585a934d fix: pass started_event_id to crew 2026-02-11 09:30:07 -05:00
Rip&Tear
46e1b02154 chore: fix codeql coverage and action version (#4454) 2026-02-11 18:20:07 +08:00
Rip&Tear
87675b49fd test: avoid URL substring assertion in brave search test (#4453)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
2026-02-11 14:32:10 +08:00
Lucas Gomide
a3bee66be8 Address OpenSSL CVE-2025-15467 vulnerability (#4426)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
* fix(security): bump regex from 2024.9.11 to 2026.1.15

Address security vulnerability flagged in regex==2024.9.11

* bump mcp from 1.23.1 to 1.26.0

Address security vulnerability flagged in mcp==1.16.0 (resolved to 1.23.3)
2026-02-10 09:39:35 -08:00
Greyson LaLonde
f6fa04528a fix: add async HITL support and chained-router tests
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
asynchronous human-in-the-loop handling and related fixes.

- Extend human_input provider with async support: AsyncExecutorContext, handle_feedback_async, async prompt helpers (_prompt_input_async, _async_readline), and async training/regular feedback loops in SyncHumanInputProvider.
- Add async handler methods in CrewAgentExecutor and AgentExecutor (_ahandle_human_feedback, _ainvoke_loop) to integrate async provider flows.
- Change PlusAPI.get_agent to an async httpx call and adapt caller in agent_utils to run it via asyncio.run.
- Simplify listener execution in flow.Flow to correctly pass HumanFeedbackResult to listeners and unify execution path for router outcomes.
- Remove deprecated types/hitl.py definitions.
- Add tests covering chained router feedback, rejected paths, and mixed router/non-router listeners to prevent regressions.
2026-02-06 16:29:27 -05:00
Greyson LaLonde
7d498b29be fix: event ordering; flow state locks, routing
* fix: add current task id context and flow updates

introduce a context var for the current task id in `crewai.context` to track task scope. update `Flow._execute_single_listener` to return `(result, event_id)` and adjust callers to unpack it and append `FlowMethodName(str(result))` to `router_results`. set/reset the current task id at the start/end of task execution (async + sync) with minor import and call-site tweaks.

* fix: await event futures and flush event bus

call `crewai_event_bus.flush()` after crew kickoff. in `Flow`, await event handler futures instead of just collecting them: await pending `_event_futures` before finishing, await emitted futures immediately with try/except to log failures, then clear `_event_futures`. ensures handlers complete and errors surface.

* fix: continue iteration on tool completion events

expand the loop bridge listener to also trigger on tool completion events (`tool_completed` and `native_tool_completed`) so agent iteration resumes after tools finish. add a `requests.post` mock and response fixture in the liteagent test to simulate platform tool execution. refresh and sanitize vcr cassettes (updated model responses, timestamps, and header placeholders) to reflect tool-call flows and new recordings.

* fix: thread-safe state proxies & native routing

add thread-safe state proxies and refactor native tool routing.

* introduce `LockedListProxy` and `LockedDictProxy` in `flow.py` and update `StateProxy` to return them for list/dict attrs so mutations are protected by the flow lock.
* update `AgentExecutor` to use `StateProxy` on flow init, guard the messages setter with the state lock, and return a `StateProxy` from the temp state accessor.
* convert `call_llm_native_tools` into a listener (no direct routing return) and add `route_native_tool_result` to route based on state (pending tool calls, final answer, or context error).
* minor cleanup in `continue_iteration` to drop orphan listeners on init.
* update test cassettes for new native tool call responses, timestamps, and ids.

improves concurrency safety for shared state and makes native tool routing explicit.

* chore: regen cassettes

* chore: regen cassettes, remove duplicate listener call path
2026-02-06 14:02:43 -05:00
Greyson LaLonde
1308bdee63 feat: add started_event_id and set in eventbus
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
* feat: add started_event_id and set in eventbus

* chore: update additional test assumption

* fix: restore event bus handlers on context exit

fix rollback in crewai events bus so that exiting the context restores
the previous _sync_handlers, _async_handlers, _handler_dependencies, and _execution_plan_cache by assigning shallow copies of the saved dicts. previously these
were set to empty dicts on exit, which caused registered handlers and cached execution plans to be lost.
2026-02-05 21:28:23 -05:00
Greyson LaLonde
6bb1b178a1 chore: extension points
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Introduce ContextVar-backed hooks and small API/behavior changes to improve extensibility and testability.

Changes include:
- agents: mark configure_structured_output as abstract and change its parameter to task to reflect use of task metadata.
- tracing: convert _first_time_trace_hook to a ContextVar and call .get() to safely retrieve the hook.
- console formatter: add _disable_version_check ContextVar and skip version checks when set (avoids noisy checks in certain contexts).
- flow: use current_triggering_event_id variable when scheduling listener tasks to keep naming consistent.
- hallucination guardrail: make context optional, add _validate_output_hook to allow custom validation hooks, update examples and return contract to allow hooks to override behavior.
- agent utilities: add _create_plus_client_hook for injecting a Plus client (used in tests/alternate flows), ensure structured tools have current_usage_count initialized and propagate to original tool, and fall back to creating PlusAPI client when no hook is provided.
2026-02-05 12:49:54 -05:00
Greyson LaLonde
fe2a4b4e40 chore: bug fixes and more refactor
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
Refactor agent executor to delegate human interactions to a provider: add messages and ask_for_human_input properties, implement _invoke_loop and _format_feedback_message, and replace the internal iterative/training feedback logic with a call to get_provider().handle_feedback.

Make LLMGuardrail kickoff coroutine-aware by detecting coroutines and running them via asyncio.run so both sync and async agents are supported.

Make telemetry more robust by safely handling missing task.output (use empty string) and returning early if span is None before setting attributes.

Improve serialization to detect circular references via an _ancestors set, propagate it through recursive calls, and pass exclude/max_depth/_current_depth consistently to prevent infinite recursion and produce stable serializable output.
2026-02-04 21:21:54 -05:00
Greyson LaLonde
711e7171e1 chore: improve hook typing and registration
Allow hook registration to accept both typed hook types and plain callables by importing and using After*/Before*CallHookCallable types; add explicit LLMCallHookContext and ToolCallHookContext typing in crew_base. Introduce a post-initialize crew hook list and invoke hooks after Crew instance initialization. Refactor filtered hook factory functions to include precise typing and clearer local names (before_llm_hook/after_llm_hook/before_tool_hook/after_tool_hook) and register those with the instance. Update CrewInstance protocol to include _registered_hook_functions and _hooks_being_registered fields.
2026-02-04 21:16:20 -05:00
Vini Brasil
76b5f72e81 Fix tool error causing double event scope pop (#4373)
When a tool raises an error, both ToolUsageErrorEvent and
ToolUsageFinishedEvent were being emitted. Since both events pop the
event scope stack, this caused the agent scope to be incorrectly popped
along with the tool scope.
2026-02-04 20:34:08 -03:00
Greyson LaLonde
d86d43d3e0 chore: refactor crew to provider
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Enable dynamic extension exports and small behavior fixes across events and flow modules:

- events/__init__.py: Added _extension_exports and extended __getattr__ to lazily resolve registered extension values or import paths.
- events/event_bus.py: Implemented off() to unregister sync/async handlers, clean handler dependencies, and invalidate execution plan cache.
- events/listeners/tracing/utils.py: Added Callable import and _first_time_trace_hook to allow overriding first-time trace auto-collection behavior.
- events/types/tool_usage_events.py: Changed ToolUsageEvent.run_attempts default from None to 0 to avoid nullable handling.
- events/utils/console_formatter.py: Respect CREWAI_DISABLE_VERSION_CHECK env var to skip version checks in CI-like flows.
- flow/async_feedback/__init__.py: Added typing.Any import, _extension_exports and __getattr__ to support extensions via attribute lookup.

These changes add extension points and safer defaults, and provide a way to unregister event handlers.
2026-02-04 16:05:21 -05:00
Greyson LaLonde
6bfc98e960 refactor: extract hitl to provider pattern
* refactor: extract hitl to provider pattern

- add humaninputprovider protocol with setup_messages and handle_feedback
- move sync hitl logic from executor to synchuman inputprovider
- add _passthrough_exceptions extension point in agent/core.py
- create crewai.core.providers module for extensible components
- remove _ask_human_input from base_agent_executor_mixin
2026-02-04 15:40:22 -05:00
Greyson LaLonde
3cc33ef6ab fix: resolve complex schema $ref pointers in mcp tools
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
* fix: resolve complex schema $ref pointers in mcp tools

* chore: update tool specifications

* fix: adapt mcp tools; sanitize pydantic json schemas

* fix: strip nulls from json schemas and simplify mcp args

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-02-03 20:47:58 -05:00
Lorenze Jay
3fec4669af Lorenze/fix/anthropic available functions call (#4360)
* feat: enhance AnthropicCompletion to support available functions in tool execution

- Updated the `_prepare_completion_params` method to accept `available_functions` for better tool handling.
- Modified tool execution logic to directly return results from tools when `available_functions` is provided, aligning behavior with OpenAI's model.
- Added new test cases to validate the execution of tools with available functions, ensuring correct argument passing and result formatting.

This change improves the flexibility and usability of the Anthropic LLM integration, allowing for more complex interactions with tools.

* refactor: remove redundant event emission in AnthropicCompletion

* fix test

* dry up
2026-02-03 16:30:43 -08:00
dependabot[bot]
d3f424fd8f chore(deps-dev): bump types-aiofiles
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
Bumps [types-aiofiles](https://github.com/typeshed-internal/stub_uploader) from 24.1.0.20250822 to 25.1.0.20251011.
- [Commits](https://github.com/typeshed-internal/stub_uploader/commits)

---
updated-dependencies:
- dependency-name: types-aiofiles
  dependency-version: 25.1.0.20251011
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-03 12:02:28 -05:00
Matt Aitchison
fee9445067 fix: add .python-version to fix Dependabot uv updates (#4352)
Dependabot's uv updater defaults to Python 3.14.2, which is incompatible
with the project's requires-python constraint (>=3.10, <3.14). Adding
.python-version pins the Python version to 3.13 for dependency updates.

Co-authored-by: Greyson LaLonde <greyson.r.lalonde@gmail.com>
2026-02-03 10:55:01 -06:00
Greyson LaLonde
a3c01265ee feat: add version check & integrate update notices 2026-02-03 10:17:50 -05:00
Matt Aitchison
aa7e7785bc chore: group dependabot security updates into single PR (#4351)
Configure dependabot to batch security updates together while keeping
regular version updates as separate PRs.
2026-02-03 08:53:28 -06:00
Thiago Moretto
e30645e855 limit stagehand dep version to 0.5.9 due breaking changes (#4339)
* limit to 0.5.9 due breaking changes + add env vars requirements

* fix tool spec extract that was ignoring with default

* original tool spec

* update spec
2026-02-03 09:43:24 -05:00
Greyson LaLonde
c1d2801be2 fix: reject reserved script names for crew folders 2026-02-03 09:16:55 -05:00
Greyson LaLonde
6a8483fcb6 fix: resolve race condition in guardrail event emission test 2026-02-03 09:06:48 -05:00
Greyson LaLonde
5fb602dff2 fix: replace timing-based concurrency test with state tracking 2026-02-03 08:58:51 -05:00
Greyson LaLonde
b90cff580a fix: relax openai and litellm dependency constraints 2026-02-03 08:51:55 -05:00
Vini Brasil
576b74b2ef Add call_id to LLM events for correlating requests (#4281)
When monitoring LLM events, consumers need to know which events belong
to the same API call. Before this change, there was no way to correlate
LLMCallStartedEvent, LLMStreamChunkEvent, and LLMCallCompletedEvent
belonging to the same request.
2026-02-03 10:10:33 -03:00
Greyson LaLonde
7590d4c6e3 fix: enforce additionalProperties=false in schemas
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
* fix: enforce additionalProperties=false in schemas

* fix: ensure nested items have required properties
2026-02-02 22:19:04 -05:00
Sampson
8c6436234b adds additional search params (#4321)
Some checks failed
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Notify Downstream / notify-downstream (push) Has been cancelled
Introduces support for additional Brave Search API web-search parameters.
2026-02-02 11:17:02 -08:00
Lucas Gomide
96bde4510b feat: auto update tools.specs (#4341) 2026-02-02 12:52:00 -05:00
Greyson LaLonde
9d7f45376a fix: use contextvars for flow execution context 2026-02-02 11:24:02 -05:00
Thiago Moretto
536447ab0e declare stagehand package as dep for StagehandTool (#4336) 2026-02-02 09:45:47 -05:00
173 changed files with 34515 additions and 4519 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -14,13 +14,18 @@ paths-ignore:
- "lib/crewai/src/crewai/experimental/a2a/**"
paths:
# Include GitHub Actions workflows/composite actions for CodeQL actions analysis
- ".github/workflows/**"
- ".github/actions/**"
# Include all Python source code from workspace packages
- "lib/crewai/src/**"
- "lib/crewai-tools/src/**"
- "lib/crewai-files/src/**"
- "lib/devtools/src/**"
# Include tests (but exclude cassettes via paths-ignore)
- "lib/crewai/tests/**"
- "lib/crewai-tools/tests/**"
- "lib/crewai-files/tests/**"
- "lib/devtools/tests/**"
# Configure specific queries or packs if needed

View File

@@ -5,7 +5,12 @@
version: 2
updates:
- package-ecosystem: uv # See documentation for possible values
directory: "/" # Location of package manifests
- package-ecosystem: uv
directory: "/"
schedule:
interval: "weekly"
groups:
security-updates:
applies-to: security-updates
patterns:
- "*"

View File

@@ -69,7 +69,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -98,6 +98,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{matrix.language}}"

View File

@@ -0,0 +1,63 @@
name: Generate Tool Specifications
on:
pull_request:
branches:
- main
paths:
- 'lib/crewai-tools/src/crewai_tools/**'
workflow_dispatch:
permissions:
contents: write
pull-requests: write
jobs:
generate-specs:
runs-on: ubuntu-latest
env:
PYTHONUNBUFFERED: 1
steps:
- name: Generate GitHub App token
id: app-token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.CREWAI_TOOL_SPECS_APP_ID }}
private_key: ${{ secrets.CREWAI_TOOL_SPECS_PRIVATE_KEY }}
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
token: ${{ steps.app-token.outputs.token }}
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
version: "0.8.4"
python-version: "3.12"
enable-cache: true
- name: Install the project
working-directory: lib/crewai-tools
run: uv sync --dev --all-extras
- name: Generate tool specifications
working-directory: lib/crewai-tools
run: uv run python src/crewai_tools/generate_tool_specs.py
- name: Check for changes and commit
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add lib/crewai-tools/tool.specs.json
if git diff --quiet --staged; then
echo "No changes detected in tool.specs.json"
else
echo "Changes detected in tool.specs.json, committing..."
git commit -m "chore: update tool specifications"
git push
fi

1
.python-version Normal file
View File

@@ -0,0 +1 @@
3.13

View File

@@ -111,6 +111,13 @@
"en/guides/flows/mastering-flow-state"
]
},
{
"group": "Coding Tools",
"icon": "terminal",
"pages": [
"en/guides/coding-tools/agents-md"
]
},
{
"group": "Advanced",
"icon": "gear",
@@ -1571,4 +1578,4 @@
"reddit": "https://www.reddit.com/r/crewAIInc/"
}
}
}
}

View File

@@ -25,35 +25,6 @@ For file-based Knowledge Sources, make sure to place your files in a `knowledge`
Also, use relative paths from the `knowledge` directory when creating the source.
</Tip>
### Skills.md for Crews (CrewBase)
When using the `@CrewBase` decorator and project layout (e.g. from `crewai create crew`), you can add **Skills.md** files as crew-level knowledge sources. Place one markdown file per skill under:
```
src/<project>/.agents/<skill_name>/Skills.md
```
Each `Skills.md` is loaded as a knowledge source so the crew can query it via RAG at runtime. Use `get_skills_knowledge_sources()` when building your crew:
```python
from crewai.project import CrewBase, agent, crew, task
@CrewBase
class MyCrew():
# ...
@crew
def crew(self) -> Crew:
return Crew(
agents=self.agents,
tasks=self.tasks,
process=Process.sequential,
knowledge_sources=self.get_skills_knowledge_sources(),
)
```
You can combine skills with other knowledge sources: `knowledge_sources=[*self.get_skills_knowledge_sources(), other_source]`. If the `.agents` directory is missing or has no `Skills.md` files, `get_skills_knowledge_sources()` returns an empty list. To use a different directory than `.agents`, set the class attribute `skills_directory = "my_skills"`. Skills.md support requires the `docling` package (`uv add docling`).
### Vector store (RAG) client configuration
CrewAI exposes a provider-neutral RAG client abstraction for vector stores. The default provider is ChromaDB, and Qdrant is supported as well. You can switch providers using configuration utilities.

View File

@@ -224,6 +224,60 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `groupFields` (string, optional): Fields to include (e.g., 'name,memberCount,clientData'). Default: name,memberCount
</Accordion>
<Accordion title="google_contacts/get_contact_group">
**Description:** Get a specific contact group by resource name.
**Parameters:**
- `resourceName` (string, required): The resource name of the contact group (e.g., 'contactGroups/myContactGroup')
- `maxMembers` (integer, optional): Maximum number of members to include. Minimum: 0, Maximum: 20000
- `groupFields` (string, optional): Fields to include (e.g., 'name,memberCount,clientData'). Default: name,memberCount
</Accordion>
<Accordion title="google_contacts/create_contact_group">
**Description:** Create a new contact group (label).
**Parameters:**
- `name` (string, required): The name of the contact group
- `clientData` (array, optional): Client-specific data
```json
[
{
"key": "data_key",
"value": "data_value"
}
]
```
</Accordion>
<Accordion title="google_contacts/update_contact_group">
**Description:** Update a contact group's information.
**Parameters:**
- `resourceName` (string, required): The resource name of the contact group (e.g., 'contactGroups/myContactGroup')
- `name` (string, required): The name of the contact group
- `clientData` (array, optional): Client-specific data
```json
[
{
"key": "data_key",
"value": "data_value"
}
]
```
</Accordion>
<Accordion title="google_contacts/delete_contact_group">
**Description:** Delete a contact group.
**Parameters:**
- `resourceName` (string, required): The resource name of the contact group to delete (e.g., 'contactGroups/myContactGroup')
- `deleteContacts` (boolean, optional): Whether to delete contacts in the group as well. Default: false
</Accordion>
</AccordionGroup>
## Usage Examples

View File

@@ -132,6 +132,297 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `endIndex` (integer, required): The end index of the range.
</Accordion>
<Accordion title="google_docs/create_document_with_content">
**Description:** Create a new Google Document with content in one action.
**Parameters:**
- `title` (string, required): The title for the new document. Appears at the top of the document and in Google Drive.
- `content` (string, optional): The text content to insert into the document. Use `\n` for new paragraphs.
</Accordion>
<Accordion title="google_docs/append_text">
**Description:** Append text to the end of a Google Document. Automatically inserts at the document end without needing to specify an index.
**Parameters:**
- `documentId` (string, required): The document ID from create_document response or URL.
- `text` (string, required): Text to append at the end of the document. Use `\n` for new paragraphs.
</Accordion>
<Accordion title="google_docs/set_text_bold">
**Description:** Make text bold or remove bold formatting in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of text to format.
- `endIndex` (integer, required): End position of text to format (exclusive).
- `bold` (boolean, required): Set `true` to make bold, `false` to remove bold.
</Accordion>
<Accordion title="google_docs/set_text_italic">
**Description:** Make text italic or remove italic formatting in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of text to format.
- `endIndex` (integer, required): End position of text to format (exclusive).
- `italic` (boolean, required): Set `true` to make italic, `false` to remove italic.
</Accordion>
<Accordion title="google_docs/set_text_underline">
**Description:** Add or remove underline formatting from text in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of text to format.
- `endIndex` (integer, required): End position of text to format (exclusive).
- `underline` (boolean, required): Set `true` to underline, `false` to remove underline.
</Accordion>
<Accordion title="google_docs/set_text_strikethrough">
**Description:** Add or remove strikethrough formatting from text in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of text to format.
- `endIndex` (integer, required): End position of text to format (exclusive).
- `strikethrough` (boolean, required): Set `true` to add strikethrough, `false` to remove.
</Accordion>
<Accordion title="google_docs/set_font_size">
**Description:** Change the font size of text in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of text to format.
- `endIndex` (integer, required): End position of text to format (exclusive).
- `fontSize` (number, required): Font size in points. Common sizes: 10, 11, 12, 14, 16, 18, 24, 36.
</Accordion>
<Accordion title="google_docs/set_text_color">
**Description:** Change the color of text using RGB values (0-1 scale) in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of text to format.
- `endIndex` (integer, required): End position of text to format (exclusive).
- `red` (number, required): Red component (0-1). Example: `1` for full red.
- `green` (number, required): Green component (0-1). Example: `0.5` for half green.
- `blue` (number, required): Blue component (0-1). Example: `0` for no blue.
</Accordion>
<Accordion title="google_docs/create_hyperlink">
**Description:** Turn existing text into a clickable hyperlink in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of text to make into a link.
- `endIndex` (integer, required): End position of text to make into a link (exclusive).
- `url` (string, required): The URL the link should point to. Example: `"https://example.com"`.
</Accordion>
<Accordion title="google_docs/apply_heading_style">
**Description:** Apply a heading or paragraph style to a text range in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of paragraph(s) to style.
- `endIndex` (integer, required): End position of paragraph(s) to style.
- `style` (string, required): The style to apply. Enum: `NORMAL_TEXT`, `TITLE`, `SUBTITLE`, `HEADING_1`, `HEADING_2`, `HEADING_3`, `HEADING_4`, `HEADING_5`, `HEADING_6`.
</Accordion>
<Accordion title="google_docs/set_paragraph_alignment">
**Description:** Set text alignment for paragraphs in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of paragraph(s) to align.
- `endIndex` (integer, required): End position of paragraph(s) to align.
- `alignment` (string, required): Text alignment. Enum: `START` (left), `CENTER`, `END` (right), `JUSTIFIED`.
</Accordion>
<Accordion title="google_docs/set_line_spacing">
**Description:** Set line spacing for paragraphs in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of paragraph(s).
- `endIndex` (integer, required): End position of paragraph(s).
- `lineSpacing` (number, required): Line spacing as percentage. `100` = single, `115` = 1.15x, `150` = 1.5x, `200` = double.
</Accordion>
<Accordion title="google_docs/create_paragraph_bullets">
**Description:** Convert paragraphs to a bulleted or numbered list in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of paragraphs to convert to list.
- `endIndex` (integer, required): End position of paragraphs to convert to list.
- `bulletPreset` (string, required): Bullet/numbering style. Enum: `BULLET_DISC_CIRCLE_SQUARE`, `BULLET_DIAMONDX_ARROW3D_SQUARE`, `BULLET_CHECKBOX`, `BULLET_ARROW_DIAMOND_DISC`, `BULLET_STAR_CIRCLE_SQUARE`, `NUMBERED_DECIMAL_ALPHA_ROMAN`, `NUMBERED_DECIMAL_ALPHA_ROMAN_PARENS`, `NUMBERED_DECIMAL_NESTED`, `NUMBERED_UPPERALPHA_ALPHA_ROMAN`, `NUMBERED_UPPERROMAN_UPPERALPHA_DECIMAL`.
</Accordion>
<Accordion title="google_docs/delete_paragraph_bullets">
**Description:** Remove bullets or numbering from paragraphs in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `startIndex` (integer, required): Start position of list paragraphs.
- `endIndex` (integer, required): End position of list paragraphs.
</Accordion>
<Accordion title="google_docs/insert_table_with_content">
**Description:** Insert a table with content into a Google Document in one action. Provide content as a 2D array.
**Parameters:**
- `documentId` (string, required): The document ID.
- `rows` (integer, required): Number of rows in the table.
- `columns` (integer, required): Number of columns in the table.
- `index` (integer, optional): Position to insert the table. If not provided, the table is inserted at the end of the document.
- `content` (array, required): Table content as a 2D array. Each inner array is a row. Example: `[["Year", "Revenue"], ["2023", "$43B"], ["2024", "$45B"]]`.
</Accordion>
<Accordion title="google_docs/insert_table_row">
**Description:** Insert a new row above or below a reference cell in an existing table.
**Parameters:**
- `documentId` (string, required): The document ID.
- `tableStartIndex` (integer, required): The start index of the table. Get from get_document.
- `rowIndex` (integer, required): Row index (0-based) of reference cell.
- `columnIndex` (integer, optional): Column index (0-based) of reference cell. Default is `0`.
- `insertBelow` (boolean, optional): If `true`, insert below the reference row. If `false`, insert above. Default is `true`.
</Accordion>
<Accordion title="google_docs/insert_table_column">
**Description:** Insert a new column left or right of a reference cell in an existing table.
**Parameters:**
- `documentId` (string, required): The document ID.
- `tableStartIndex` (integer, required): The start index of the table.
- `rowIndex` (integer, optional): Row index (0-based) of reference cell. Default is `0`.
- `columnIndex` (integer, required): Column index (0-based) of reference cell.
- `insertRight` (boolean, optional): If `true`, insert to the right. If `false`, insert to the left. Default is `true`.
</Accordion>
<Accordion title="google_docs/delete_table_row">
**Description:** Delete a row from an existing table in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `tableStartIndex` (integer, required): The start index of the table.
- `rowIndex` (integer, required): Row index (0-based) to delete.
- `columnIndex` (integer, optional): Column index (0-based) of any cell in the row. Default is `0`.
</Accordion>
<Accordion title="google_docs/delete_table_column">
**Description:** Delete a column from an existing table in a Google Document.
**Parameters:**
- `documentId` (string, required): The document ID.
- `tableStartIndex` (integer, required): The start index of the table.
- `rowIndex` (integer, optional): Row index (0-based) of any cell in the column. Default is `0`.
- `columnIndex` (integer, required): Column index (0-based) to delete.
</Accordion>
<Accordion title="google_docs/merge_table_cells">
**Description:** Merge a range of table cells into a single cell. Content from all cells is preserved.
**Parameters:**
- `documentId` (string, required): The document ID.
- `tableStartIndex` (integer, required): The start index of the table.
- `rowIndex` (integer, required): Starting row index (0-based) for the merge.
- `columnIndex` (integer, required): Starting column index (0-based) for the merge.
- `rowSpan` (integer, required): Number of rows to merge.
- `columnSpan` (integer, required): Number of columns to merge.
</Accordion>
<Accordion title="google_docs/unmerge_table_cells">
**Description:** Unmerge previously merged table cells back into individual cells.
**Parameters:**
- `documentId` (string, required): The document ID.
- `tableStartIndex` (integer, required): The start index of the table.
- `rowIndex` (integer, required): Row index (0-based) of the merged cell.
- `columnIndex` (integer, required): Column index (0-based) of the merged cell.
- `rowSpan` (integer, required): Number of rows the merged cell spans.
- `columnSpan` (integer, required): Number of columns the merged cell spans.
</Accordion>
<Accordion title="google_docs/insert_inline_image">
**Description:** Insert an image from a public URL into a Google Document. The image must be publicly accessible, under 50MB, and in PNG/JPEG/GIF format.
**Parameters:**
- `documentId` (string, required): The document ID.
- `uri` (string, required): Public URL of the image. Must be accessible without authentication.
- `index` (integer, optional): Position to insert the image. If not provided, the image is inserted at the end of the document. Default is `1`.
</Accordion>
<Accordion title="google_docs/insert_section_break">
**Description:** Insert a section break to create document sections with different formatting.
**Parameters:**
- `documentId` (string, required): The document ID.
- `index` (integer, required): Position to insert the section break.
- `sectionType` (string, required): The type of section break. Enum: `CONTINUOUS` (stays on same page), `NEXT_PAGE` (starts a new page).
</Accordion>
<Accordion title="google_docs/create_header">
**Description:** Create a header for the document. Returns a headerId which can be used with insert_text to add header content.
**Parameters:**
- `documentId` (string, required): The document ID.
- `type` (string, optional): Header type. Enum: `DEFAULT`. Default is `DEFAULT`.
</Accordion>
<Accordion title="google_docs/create_footer">
**Description:** Create a footer for the document. Returns a footerId which can be used with insert_text to add footer content.
**Parameters:**
- `documentId` (string, required): The document ID.
- `type` (string, optional): Footer type. Enum: `DEFAULT`. Default is `DEFAULT`.
</Accordion>
<Accordion title="google_docs/delete_header">
**Description:** Delete a header from the document. Use get_document to find the headerId.
**Parameters:**
- `documentId` (string, required): The document ID.
- `headerId` (string, required): The header ID to delete. Get from get_document response.
</Accordion>
<Accordion title="google_docs/delete_footer">
**Description:** Delete a footer from the document. Use get_document to find the footerId.
**Parameters:**
- `documentId` (string, required): The document ID.
- `footerId` (string, required): The footer ID to delete. Get from get_document response.
</Accordion>
</AccordionGroup>
## Usage Examples

View File

@@ -62,6 +62,22 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="google_slides/get_presentation_metadata">
**Description:** Get lightweight metadata about a presentation (title, slide count, slide IDs). Use this first before fetching full content.
**Parameters:**
- `presentationId` (string, required): The ID of the presentation to retrieve.
</Accordion>
<Accordion title="google_slides/get_presentation_text">
**Description:** Extract all text content from a presentation. Returns slide IDs and text from shapes and tables only (no formatting).
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
</Accordion>
<Accordion title="google_slides/get_presentation">
**Description:** Retrieves a presentation by ID.
@@ -96,6 +112,15 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="google_slides/get_slide_text">
**Description:** Extract text content from a single slide. Returns only text from shapes and tables (no formatting or styling).
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `pageObjectId` (string, required): The ID of the slide/page to get text from.
</Accordion>
<Accordion title="google_slides/get_page">
**Description:** Retrieves a specific page by its ID.
@@ -114,6 +139,120 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="google_slides/create_slide">
**Description:** Add an additional blank slide to a presentation. New presentations already have one blank slide - check get_presentation_metadata first. For slides with title/body areas, use create_slide_with_layout instead.
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `insertionIndex` (integer, optional): Where to insert the slide (0-based). If omitted, adds at the end.
</Accordion>
<Accordion title="google_slides/create_slide_with_layout">
**Description:** Create a slide with a predefined layout containing placeholder areas for title, body, etc. This is better than create_slide for structured content. After creating, use get_page to find placeholder IDs, then insert text into them.
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `layout` (string, required): Layout type. One of: `BLANK`, `TITLE`, `TITLE_AND_BODY`, `TITLE_AND_TWO_COLUMNS`, `TITLE_ONLY`, `SECTION_HEADER`, `ONE_COLUMN_TEXT`, `MAIN_POINT`, `BIG_NUMBER`. TITLE_AND_BODY is best for title+description. TITLE for title-only slides. SECTION_HEADER for section dividers.
- `insertionIndex` (integer, optional): Where to insert (0-based). Omit to add at end.
</Accordion>
<Accordion title="google_slides/create_text_box">
**Description:** Create a text box on a slide with content. Use this for titles, descriptions, paragraphs - not tables. Optionally specify position (x, y) and size (width, height) in EMU units (914400 EMU = 1 inch).
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `slideId` (string, required): The ID of the slide to add the text box to.
- `text` (string, required): The text content for the text box.
- `x` (integer, optional): X position in EMU (914400 = 1 inch). Default: 914400 (1 inch from left).
- `y` (integer, optional): Y position in EMU (914400 = 1 inch). Default: 914400 (1 inch from top).
- `width` (integer, optional): Width in EMU. Default: 7315200 (~8 inches).
- `height` (integer, optional): Height in EMU. Default: 914400 (~1 inch).
</Accordion>
<Accordion title="google_slides/delete_slide">
**Description:** Remove a slide from the presentation. Use get_presentation first to find the slide ID.
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `slideId` (string, required): The object ID of the slide to delete. Get from get_presentation.
</Accordion>
<Accordion title="google_slides/duplicate_slide">
**Description:** Create a copy of an existing slide. The duplicate is inserted immediately after the original.
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `slideId` (string, required): The object ID of the slide to duplicate. Get from get_presentation.
</Accordion>
<Accordion title="google_slides/move_slides">
**Description:** Reorder slides by moving them to a new position. Slide IDs must be in their current presentation order (no duplicates).
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `slideIds` (array of strings, required): Array of slide IDs to move. Must be in current presentation order.
- `insertionIndex` (integer, required): Target position (0-based). 0 = beginning, slide count = end.
</Accordion>
<Accordion title="google_slides/insert_youtube_video">
**Description:** Embed a YouTube video on a slide. The video ID is the value after "v=" in YouTube URLs (e.g., for youtube.com/watch?v=abc123, use "abc123").
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `slideId` (string, required): The ID of the slide to add the video to. Get from get_presentation.
- `videoId` (string, required): The YouTube video ID (the value after v= in the URL).
</Accordion>
<Accordion title="google_slides/insert_drive_video">
**Description:** Embed a video from Google Drive on a slide. The file ID can be found in the Drive file URL.
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `slideId` (string, required): The ID of the slide to add the video to. Get from get_presentation.
- `fileId` (string, required): The Google Drive file ID of the video.
</Accordion>
<Accordion title="google_slides/set_slide_background_image">
**Description:** Set a background image for a slide. The image URL must be publicly accessible.
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `slideId` (string, required): The ID of the slide to set the background for. Get from get_presentation.
- `imageUrl` (string, required): Publicly accessible URL of the image to use as background.
</Accordion>
<Accordion title="google_slides/create_table">
**Description:** Create an empty table on a slide. To create a table with content, use create_table_with_content instead.
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `slideId` (string, required): The ID of the slide to add the table to. Get from get_presentation.
- `rows` (integer, required): Number of rows in the table.
- `columns` (integer, required): Number of columns in the table.
</Accordion>
<Accordion title="google_slides/create_table_with_content">
**Description:** Create a table with content in one action. Provide content as a 2D array where each inner array is a row. Example: [["Header1", "Header2"], ["Row1Col1", "Row1Col2"]].
**Parameters:**
- `presentationId` (string, required): The ID of the presentation.
- `slideId` (string, required): The ID of the slide to add the table to. Get from get_presentation.
- `rows` (integer, required): Number of rows in the table.
- `columns` (integer, required): Number of columns in the table.
- `content` (array, required): Table content as 2D array. Each inner array is a row. Example: [["Year", "Revenue"], ["2023", "$10M"]].
</Accordion>
<Accordion title="google_slides/import_data_from_sheet">
**Description:** Imports data from a Google Sheet into a presentation.

View File

@@ -169,6 +169,16 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="microsoft_excel/get_table_data">
**Description:** Get data from a specific table in an Excel worksheet.
**Parameters:**
- `file_id` (string, required): The ID of the Excel file
- `worksheet_name` (string, required): Name of the worksheet
- `table_name` (string, required): Name of the table
</Accordion>
<Accordion title="microsoft_excel/create_chart">
**Description:** Create a chart in an Excel worksheet.
@@ -201,6 +211,15 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="microsoft_excel/get_used_range_metadata">
**Description:** Get the used range metadata (dimensions only, no data) of an Excel worksheet.
**Parameters:**
- `file_id` (string, required): The ID of the Excel file
- `worksheet_name` (string, required): Name of the worksheet
</Accordion>
<Accordion title="microsoft_excel/list_charts">
**Description:** Get all charts in an Excel worksheet.

View File

@@ -151,6 +151,49 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `item_id` (string, required): The ID of the file.
</Accordion>
<Accordion title="microsoft_onedrive/list_files_by_path">
**Description:** List files and folders in a specific OneDrive path.
**Parameters:**
- `folder_path` (string, required): The folder path (e.g., 'Documents/Reports').
- `top` (integer, optional): Number of items to retrieve (max 1000). Default is `50`.
- `orderby` (string, optional): Order by field (e.g., "name asc", "lastModifiedDateTime desc"). Default is "name asc".
</Accordion>
<Accordion title="microsoft_onedrive/get_recent_files">
**Description:** Get recently accessed files from OneDrive.
**Parameters:**
- `top` (integer, optional): Number of items to retrieve (max 200). Default is `25`.
</Accordion>
<Accordion title="microsoft_onedrive/get_shared_with_me">
**Description:** Get files and folders shared with the user.
**Parameters:**
- `top` (integer, optional): Number of items to retrieve (max 200). Default is `50`.
- `orderby` (string, optional): Order by field. Default is "name asc".
</Accordion>
<Accordion title="microsoft_onedrive/get_file_by_path">
**Description:** Get information about a specific file or folder by path.
**Parameters:**
- `file_path` (string, required): The file or folder path (e.g., 'Documents/report.docx').
</Accordion>
<Accordion title="microsoft_onedrive/download_file_by_path">
**Description:** Download a file from OneDrive by its path.
**Parameters:**
- `file_path` (string, required): The file path (e.g., 'Documents/report.docx').
</Accordion>
</AccordionGroup>
## Usage Examples

View File

@@ -133,6 +133,74 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `companyName` (string, optional): Contact's company name.
</Accordion>
<Accordion title="microsoft_outlook/get_message">
**Description:** Get a specific email message by ID.
**Parameters:**
- `message_id` (string, required): The unique identifier of the message. Obtain from get_messages action.
- `select` (string, optional): Comma-separated list of properties to return. Example: "id,subject,body,from,receivedDateTime". Default is "id,subject,body,from,toRecipients,receivedDateTime".
</Accordion>
<Accordion title="microsoft_outlook/reply_to_email">
**Description:** Reply to an email message.
**Parameters:**
- `message_id` (string, required): The unique identifier of the message to reply to. Obtain from get_messages action.
- `comment` (string, required): The reply message content. Can be plain text or HTML. The original message will be quoted below this content.
</Accordion>
<Accordion title="microsoft_outlook/forward_email">
**Description:** Forward an email message.
**Parameters:**
- `message_id` (string, required): The unique identifier of the message to forward. Obtain from get_messages action.
- `to_recipients` (array, required): Array of recipient email addresses to forward to. Example: ["john@example.com", "jane@example.com"].
- `comment` (string, optional): Optional message to include above the forwarded content. Can be plain text or HTML.
</Accordion>
<Accordion title="microsoft_outlook/mark_message_read">
**Description:** Mark a message as read or unread.
**Parameters:**
- `message_id` (string, required): The unique identifier of the message. Obtain from get_messages action.
- `is_read` (boolean, required): Set to true to mark as read, false to mark as unread.
</Accordion>
<Accordion title="microsoft_outlook/delete_message">
**Description:** Delete an email message.
**Parameters:**
- `message_id` (string, required): The unique identifier of the message to delete. Obtain from get_messages action.
</Accordion>
<Accordion title="microsoft_outlook/update_event">
**Description:** Update an existing calendar event.
**Parameters:**
- `event_id` (string, required): The unique identifier of the event. Obtain from get_calendar_events action.
- `subject` (string, optional): New subject/title for the event.
- `start_time` (string, optional): New start time in ISO 8601 format (e.g., "2024-01-20T10:00:00"). REQUIRED: Must also provide start_timezone when using this field.
- `start_timezone` (string, optional): Timezone for start time. REQUIRED when updating start_time. Examples: "Pacific Standard Time", "Eastern Standard Time", "UTC".
- `end_time` (string, optional): New end time in ISO 8601 format. REQUIRED: Must also provide end_timezone when using this field.
- `end_timezone` (string, optional): Timezone for end time. REQUIRED when updating end_time. Examples: "Pacific Standard Time", "Eastern Standard Time", "UTC".
- `location` (string, optional): New location for the event.
- `body` (string, optional): New body/description for the event. Supports HTML formatting.
</Accordion>
<Accordion title="microsoft_outlook/delete_event">
**Description:** Delete a calendar event.
**Parameters:**
- `event_id` (string, required): The unique identifier of the event to delete. Obtain from get_calendar_events action.
</Accordion>
</AccordionGroup>
## Usage Examples

View File

@@ -78,6 +78,17 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="microsoft_sharepoint/get_drives">
**Description:** List all document libraries (drives) in a SharePoint site. Use this to discover available libraries before using file operations.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `top` (integer, optional): Maximum number of drives to return per page (1-999). Default is 100
- `skip_token` (string, optional): Pagination token from a previous response to fetch the next page of results
- `select` (string, optional): Comma-separated list of properties to return (e.g., 'id,name,webUrl,driveType')
</Accordion>
<Accordion title="microsoft_sharepoint/get_site_lists">
**Description:** Get all lists in a SharePoint site.
@@ -159,20 +170,317 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="microsoft_sharepoint/get_drive_items">
**Description:** Get files and folders from a SharePoint document library.
<Accordion title="microsoft_sharepoint/list_files">
**Description:** Retrieve files and folders from a SharePoint document library. By default lists the root folder, but you can navigate into subfolders by providing a folder_id.
**Parameters:**
- `site_id` (string, required): The ID of the SharePoint site
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `folder_id` (string, optional): The ID of the folder to list contents from. Use 'root' for the root folder, or provide a folder ID from a previous list_files call. Default is 'root'
- `top` (integer, optional): Maximum number of items to return per page (1-1000). Default is 50
- `skip_token` (string, optional): Pagination token from a previous response to fetch the next page of results
- `orderby` (string, optional): Sort order for results (e.g., 'name asc', 'size desc', 'lastModifiedDateTime desc'). Default is 'name asc'
- `filter` (string, optional): OData filter to narrow results (e.g., 'file ne null' for files only, 'folder ne null' for folders only)
- `select` (string, optional): Comma-separated list of fields to return (e.g., 'id,name,size,folder,file,webUrl,lastModifiedDateTime')
</Accordion>
<Accordion title="microsoft_sharepoint/delete_drive_item">
**Description:** Delete a file or folder from SharePoint document library.
<Accordion title="microsoft_sharepoint/delete_file">
**Description:** Delete a file or folder from a SharePoint document library. For folders, all contents are deleted recursively. Items are moved to the site recycle bin.
**Parameters:**
- `site_id` (string, required): The ID of the SharePoint site
- `item_id` (string, required): The ID of the file or folder to delete
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the file or folder to delete. Obtain from list_files
</Accordion>
<Accordion title="microsoft_sharepoint/list_files_by_path">
**Description:** List files and folders in a SharePoint document library folder by its path. More efficient than multiple list_files calls for deep navigation.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `folder_path` (string, required): The full path to the folder without leading/trailing slashes (e.g., 'Documents', 'Reports/2024/Q1')
- `top` (integer, optional): Maximum number of items to return per page (1-1000). Default is 50
- `skip_token` (string, optional): Pagination token from a previous response to fetch the next page of results
- `orderby` (string, optional): Sort order for results (e.g., 'name asc', 'size desc'). Default is 'name asc'
- `select` (string, optional): Comma-separated list of fields to return (e.g., 'id,name,size,folder,file,webUrl,lastModifiedDateTime')
</Accordion>
<Accordion title="microsoft_sharepoint/download_file">
**Description:** Download raw file content from a SharePoint document library. Use only for plain text files (.txt, .csv, .json). For Excel files, use the Excel-specific actions. For Word files, use get_word_document_content.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the file to download. Obtain from list_files or list_files_by_path
</Accordion>
<Accordion title="microsoft_sharepoint/get_file_info">
**Description:** Retrieve detailed metadata for a specific file or folder in a SharePoint document library, including name, size, created/modified dates, and author information.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the file or folder. Obtain from list_files or list_files_by_path
- `select` (string, optional): Comma-separated list of properties to return (e.g., 'id,name,size,createdDateTime,lastModifiedDateTime,webUrl,createdBy,lastModifiedBy')
</Accordion>
<Accordion title="microsoft_sharepoint/create_folder">
**Description:** Create a new folder in a SharePoint document library. By default creates the folder in the root; use parent_id to create subfolders.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `folder_name` (string, required): Name for the new folder. Cannot contain: \ / : * ? " < > |
- `parent_id` (string, optional): The ID of the parent folder. Use 'root' for the document library root, or provide a folder ID from list_files. Default is 'root'
</Accordion>
<Accordion title="microsoft_sharepoint/search_files">
**Description:** Search for files and folders in a SharePoint document library by keywords. Searches file names, folder names, and file contents for Office documents. Do not use wildcards or special characters.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `query` (string, required): Search keywords (e.g., 'report', 'budget 2024'). Wildcards like *.txt are not supported
- `top` (integer, optional): Maximum number of results to return per page (1-1000). Default is 50
- `skip_token` (string, optional): Pagination token from a previous response to fetch the next page of results
- `select` (string, optional): Comma-separated list of fields to return (e.g., 'id,name,size,folder,file,webUrl,lastModifiedDateTime')
</Accordion>
<Accordion title="microsoft_sharepoint/copy_file">
**Description:** Copy a file or folder to a new location within SharePoint. The original item remains unchanged. The copy operation is asynchronous for large files.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the file or folder to copy. Obtain from list_files or search_files
- `destination_folder_id` (string, required): The ID of the destination folder. Use 'root' for the root folder, or a folder ID from list_files
- `new_name` (string, optional): New name for the copy. If not provided, the original name is used
</Accordion>
<Accordion title="microsoft_sharepoint/move_file">
**Description:** Move a file or folder to a new location within SharePoint. The item is removed from its original location. For folders, all contents are moved as well.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the file or folder to move. Obtain from list_files or search_files
- `destination_folder_id` (string, required): The ID of the destination folder. Use 'root' for the root folder, or a folder ID from list_files
- `new_name` (string, optional): New name for the moved item. If not provided, the original name is kept
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_worksheets">
**Description:** List all worksheets (tabs) in an Excel workbook stored in a SharePoint document library. Use the returned worksheet name with other Excel actions.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `select` (string, optional): Comma-separated list of properties to return (e.g., 'id,name,position,visibility')
- `filter` (string, optional): OData filter expression (e.g., "visibility eq 'Visible'" to exclude hidden sheets)
- `top` (integer, optional): Maximum number of worksheets to return. Minimum: 1, Maximum: 999
- `orderby` (string, optional): Sort order (e.g., 'position asc' to return sheets in tab order)
</Accordion>
<Accordion title="microsoft_sharepoint/create_excel_worksheet">
**Description:** Create a new worksheet (tab) in an Excel workbook stored in a SharePoint document library. The new sheet is added at the end of the tab list.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `name` (string, required): Name for the new worksheet. Maximum 31 characters. Cannot contain: \ / * ? : [ ]. Must be unique within the workbook
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_range_data">
**Description:** Retrieve cell values from a specific range in an Excel worksheet stored in SharePoint. For reading all data without knowing dimensions, use get_excel_used_range instead.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet (tab) to read from. Obtain from get_excel_worksheets. Case-sensitive
- `range` (string, required): Cell range in A1 notation (e.g., 'A1:C10', 'A:C', '1:5', 'A1')
- `select` (string, optional): Comma-separated list of properties to return (e.g., 'address,values,formulas,numberFormat,text')
</Accordion>
<Accordion title="microsoft_sharepoint/update_excel_range_data">
**Description:** Write values to a specific range in an Excel worksheet stored in SharePoint. Overwrites existing cell contents. The values array dimensions must match the range dimensions exactly.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet (tab) to update. Obtain from get_excel_worksheets. Case-sensitive
- `range` (string, required): Cell range in A1 notation where values will be written (e.g., 'A1:C3' for a 3x3 block)
- `values` (array, required): 2D array of values (rows containing cells). Example for A1:B2: [["Header1", "Header2"], ["Value1", "Value2"]]. Use null to clear a cell
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_used_range_metadata">
**Description:** Return only the metadata (address and dimensions) of the used range in a worksheet, without the actual cell values. Ideal for large files to understand spreadsheet size before reading data in chunks.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet (tab) to read. Obtain from get_excel_worksheets. Case-sensitive
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_used_range">
**Description:** Retrieve all cells containing data in a worksheet stored in SharePoint. Do not use for files larger than 2MB. For large files, use get_excel_used_range_metadata first, then get_excel_range_data to read in smaller chunks.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet (tab) to read. Obtain from get_excel_worksheets. Case-sensitive
- `select` (string, optional): Comma-separated list of properties to return (e.g., 'address,values,formulas,numberFormat,text,rowCount,columnCount')
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_cell">
**Description:** Retrieve the value of a single cell by row and column index from an Excel file in SharePoint. Indices are 0-based (row 0 = Excel row 1, column 0 = column A).
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet (tab). Obtain from get_excel_worksheets. Case-sensitive
- `row` (integer, required): 0-based row index (row 0 = Excel row 1). Valid range: 0-1048575
- `column` (integer, required): 0-based column index (column 0 = A, column 1 = B). Valid range: 0-16383
- `select` (string, optional): Comma-separated list of properties to return (e.g., 'address,values,formulas,numberFormat,text')
</Accordion>
<Accordion title="microsoft_sharepoint/add_excel_table">
**Description:** Convert a cell range into a formatted Excel table with filtering, sorting, and structured data capabilities. Tables enable add_excel_table_row for appending data.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet containing the data range. Obtain from get_excel_worksheets
- `range` (string, required): Cell range to convert into a table, including headers and data (e.g., 'A1:D10' where A1:D1 contains column headers)
- `has_headers` (boolean, optional): Set to true if the first row contains column headers. Default is true
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_tables">
**Description:** List all tables in a specific Excel worksheet stored in SharePoint. Returns table properties including id, name, showHeaders, and showTotals.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet to get tables from. Obtain from get_excel_worksheets
</Accordion>
<Accordion title="microsoft_sharepoint/add_excel_table_row">
**Description:** Append a new row to the end of an Excel table in a SharePoint file. The values array must have the same number of elements as the table has columns.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet containing the table. Obtain from get_excel_worksheets
- `table_name` (string, required): Name of the table to add the row to (e.g., 'Table1'). Obtain from get_excel_tables. Case-sensitive
- `values` (array, required): Array of cell values for the new row, one per column in table order (e.g., ["John Doe", "john@example.com", 25])
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_table_data">
**Description:** Get all rows from an Excel table in a SharePoint file as a data range. Easier than get_excel_range_data when working with structured tables since you don't need to know the exact range.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet containing the table. Obtain from get_excel_worksheets
- `table_name` (string, required): Name of the table to get data from (e.g., 'Table1'). Obtain from get_excel_tables. Case-sensitive
- `select` (string, optional): Comma-separated list of properties to return (e.g., 'address,values,formulas,numberFormat,text')
</Accordion>
<Accordion title="microsoft_sharepoint/create_excel_chart">
**Description:** Create a chart visualization in an Excel worksheet stored in SharePoint from a data range. The chart is embedded in the worksheet.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet where the chart will be created. Obtain from get_excel_worksheets
- `chart_type` (string, required): Chart type (e.g., 'ColumnClustered', 'ColumnStacked', 'Line', 'LineMarkers', 'Pie', 'Bar', 'BarClustered', 'Area', 'Scatter', 'Doughnut')
- `source_data` (string, required): Data range for the chart in A1 notation, including headers (e.g., 'A1:B10')
- `series_by` (string, optional): How data series are organized: 'Auto', 'Columns', or 'Rows'. Default is 'Auto'
</Accordion>
<Accordion title="microsoft_sharepoint/list_excel_charts">
**Description:** List all charts embedded in an Excel worksheet stored in SharePoint. Returns chart properties including id, name, chartType, height, width, and position.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet to list charts from. Obtain from get_excel_worksheets
</Accordion>
<Accordion title="microsoft_sharepoint/delete_excel_worksheet">
**Description:** Permanently remove a worksheet (tab) and all its contents from an Excel workbook stored in SharePoint. Cannot be undone. A workbook must have at least one worksheet.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet to delete. Case-sensitive. All data, tables, and charts on this sheet will be permanently removed
</Accordion>
<Accordion title="microsoft_sharepoint/delete_excel_table">
**Description:** Remove a table from an Excel worksheet in SharePoint. This deletes the table structure (filtering, formatting, table features) but preserves the underlying cell data.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
- `worksheet_name` (string, required): Name of the worksheet containing the table. Obtain from get_excel_worksheets
- `table_name` (string, required): Name of the table to delete (e.g., 'Table1'). Obtain from get_excel_tables. The data in the cells will remain after table deletion
</Accordion>
<Accordion title="microsoft_sharepoint/list_excel_names">
**Description:** Retrieve all named ranges defined in an Excel workbook stored in SharePoint. Named ranges are user-defined labels for cell ranges (e.g., 'SalesData' for A1:D100).
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Excel file in SharePoint. Obtain from list_files or search_files
</Accordion>
<Accordion title="microsoft_sharepoint/get_word_document_content">
**Description:** Download and extract text content from a Word document (.docx) stored in a SharePoint document library. This is the recommended way to read Word documents from SharePoint.
**Parameters:**
- `site_id` (string, required): The full SharePoint site identifier from get_sites
- `drive_id` (string, required): The ID of the document library. Call get_drives first to get valid drive IDs
- `item_id` (string, required): The unique identifier of the Word document (.docx) in SharePoint. Obtain from list_files or search_files
</Accordion>
</AccordionGroup>

View File

@@ -108,6 +108,86 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `join_web_url` (string, required): The join web URL of the meeting to search for.
</Accordion>
<Accordion title="microsoft_teams/search_online_meetings_by_meeting_id">
**Description:** Search online meetings by external Meeting ID.
**Parameters:**
- `join_meeting_id` (string, required): The meeting ID (numeric code) that attendees use to join. This is the joinMeetingId shown in meeting invitations, not the Graph API meeting id.
</Accordion>
<Accordion title="microsoft_teams/get_meeting">
**Description:** Get details of a specific online meeting.
**Parameters:**
- `meeting_id` (string, required): The Graph API meeting ID (a long alphanumeric string). Obtain from create_meeting or search_online_meetings actions. Different from the numeric joinMeetingId.
</Accordion>
<Accordion title="microsoft_teams/get_team_members">
**Description:** Get members of a specific team.
**Parameters:**
- `team_id` (string, required): The unique identifier of the team. Obtain from get_teams action.
- `top` (integer, optional): Maximum number of members to retrieve per page (1-999). Default is `100`.
- `skip_token` (string, optional): Pagination token from a previous response. When the response includes @odata.nextLink, extract the $skiptoken parameter value and pass it here to get the next page of results.
</Accordion>
<Accordion title="microsoft_teams/create_channel">
**Description:** Create a new channel in a team.
**Parameters:**
- `team_id` (string, required): The unique identifier of the team. Obtain from get_teams action.
- `display_name` (string, required): Name of the channel as displayed in Teams. Must be unique within the team. Max 50 characters.
- `description` (string, optional): Optional description explaining the channel's purpose. Visible in channel details. Max 1024 characters.
- `membership_type` (string, optional): Channel visibility. Enum: `standard`, `private`. "standard" = visible to all team members, "private" = visible only to specifically added members. Default is `standard`.
</Accordion>
<Accordion title="microsoft_teams/get_message_replies">
**Description:** Get replies to a specific message in a channel.
**Parameters:**
- `team_id` (string, required): The unique identifier of the team. Obtain from get_teams action.
- `channel_id` (string, required): The unique identifier of the channel. Obtain from get_channels action.
- `message_id` (string, required): The unique identifier of the parent message. Obtain from get_messages action.
- `top` (integer, optional): Maximum number of replies to retrieve per page (1-50). Default is `50`.
- `skip_token` (string, optional): Pagination token from a previous response. When the response includes @odata.nextLink, extract the $skiptoken parameter value and pass it here to get the next page of results.
</Accordion>
<Accordion title="microsoft_teams/reply_to_message">
**Description:** Reply to a message in a Teams channel.
**Parameters:**
- `team_id` (string, required): The unique identifier of the team. Obtain from get_teams action.
- `channel_id` (string, required): The unique identifier of the channel. Obtain from get_channels action.
- `message_id` (string, required): The unique identifier of the message to reply to. Obtain from get_messages action.
- `message` (string, required): The reply content. For HTML, include formatting tags. For text, plain text only.
- `content_type` (string, optional): Content format. Enum: `html`, `text`. "text" for plain text, "html" for rich text with formatting. Default is `text`.
</Accordion>
<Accordion title="microsoft_teams/update_meeting">
**Description:** Update an existing online meeting.
**Parameters:**
- `meeting_id` (string, required): The unique identifier of the meeting. Obtain from create_meeting or search_online_meetings actions.
- `subject` (string, optional): New meeting title.
- `startDateTime` (string, optional): New start time in ISO 8601 format with timezone. Example: "2024-01-20T10:00:00-08:00".
- `endDateTime` (string, optional): New end time in ISO 8601 format with timezone.
</Accordion>
<Accordion title="microsoft_teams/delete_meeting">
**Description:** Delete an online meeting.
**Parameters:**
- `meeting_id` (string, required): The unique identifier of the meeting to delete. Obtain from create_meeting or search_online_meetings actions.
</Accordion>
</AccordionGroup>
## Usage Examples

View File

@@ -98,6 +98,26 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `file_id` (string, required): The ID of the document to delete.
</Accordion>
<Accordion title="microsoft_word/copy_document">
**Description:** Copy a document to a new location in OneDrive.
**Parameters:**
- `file_id` (string, required): The ID of the document to copy
- `name` (string, optional): New name for the copied document
- `parent_id` (string, optional): The ID of the destination folder (defaults to root)
</Accordion>
<Accordion title="microsoft_word/move_document">
**Description:** Move a document to a new location in OneDrive.
**Parameters:**
- `file_id` (string, required): The ID of the document to move
- `parent_id` (string, required): The ID of the destination folder
- `name` (string, optional): New name for the moved document
</Accordion>
</AccordionGroup>
## Usage Examples

View File

@@ -0,0 +1,61 @@
---
title: Coding Tools
description: Use AGENTS.md to guide coding agents and IDEs across your CrewAI projects.
icon: terminal
mode: "wide"
---
## Why AGENTS.md
`AGENTS.md` is a lightweight, repo-local instruction file that gives coding agents consistent, project-specific guidance. Keep it in the project root and treat it as the source of truth for how you want assistants to work: conventions, commands, architecture notes, and guardrails.
## Create a Project with the CLI
Use the CrewAI CLI to scaffold a project, then `AGENTS.md` will be automatically added at the root.
```bash
# Crew
crewai create crew my_crew
# Flow
crewai create flow my_flow
# Tool repository
crewai tool create my_tool
```
## Tool Setup: Point Assistants to AGENTS.md
### Codex
Codex can be guided by `AGENTS.md` files placed in your repository. Use them to supply persistent project context such as conventions, commands, and workflow expectations.
### Claude Code
Claude Code stores project memory in `CLAUDE.md`. You can bootstrap it with `/init` and edit it using `/memory`. Claude Code also supports imports inside `CLAUDE.md`, so you can add a single line like `@AGENTS.md` to pull in the shared instructions without duplicating them.
You can simply use:
```bash
mv AGENTS.md CLAUDE.md
```
### Gemini CLI and Google Antigravity
Gemini CLI and Antigravity load a project context file (default: `GEMINI.md`) from the repo root and parent directories. You can configure it to read `AGENTS.md` instead (or in addition) by setting `context.fileName` in your Gemini CLI settings. For example, set it to `AGENTS.md` only, or include both `AGENTS.md` and `GEMINI.md` if you want to keep each tools format.
You can simply use:
```bash
mv AGENTS.md GEMINI.md
```
### Cursor
Cursor supports `AGENTS.md` as a project instruction file. Place it at the project root to provide guidance for Cursors coding assistant.
### Windsurf
Claude Code provides an official integration with Windsurf. If you use Claude Code inside Windsurf, follow the Claude Code guidance above and import `AGENTS.md` from `CLAUDE.md`.
If you are using Windsurfs native assistant, configure its project rules or instructions feature (if available) to read from `AGENTS.md` or paste the contents directly.

View File

@@ -15,6 +15,29 @@ Along with that provides the ability for the Agent to update the database based
**Attention**: Make sure that the Agent has access to a Read-Replica or that is okay for the Agent to run insert/update queries on the database.
## Security Model
`NL2SQLTool` is an execution-capable tool. It runs model-generated SQL directly against the configured database connection.
This means risk depends on your deployment choices:
- Which credentials you provide in `db_uri`
- Whether untrusted input can influence prompts
- Whether you add tool-call guardrails before execution
If you route untrusted input to agents using this tool, treat it as a high-risk integration.
## Hardening Recommendations
Use all of the following in production:
- Use a read-only database user whenever possible
- Prefer a read replica for analytics/retrieval workloads
- Grant least privilege (no superuser/admin roles, no file/system-level capabilities)
- Apply database-side resource limits (statement timeout, lock timeout, cost/row limits)
- Add `before_tool_call` hooks to enforce allowed query patterns
- Enable query logging and alerting for destructive statements
## Requirements
- SqlAlchemy

View File

@@ -200,6 +200,25 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `clientData` (array, 선택사항): 클라이언트별 데이터. 각 항목은 `key` (string)와 `value` (string)가 있는 객체.
</Accordion>
<Accordion title="google_contacts/update_contact_group">
**설명:** 연락처 그룹의 정보를 업데이트합니다.
**매개변수:**
- `resourceName` (string, 필수): 연락처 그룹의 리소스 이름 (예: 'contactGroups/myContactGroup').
- `name` (string, 필수): 연락처 그룹의 이름.
- `clientData` (array, 선택사항): 클라이언트별 데이터. 각 항목은 `key` (string)와 `value` (string)가 있는 객체.
</Accordion>
<Accordion title="google_contacts/delete_contact_group">
**설명:** 연락처 그룹을 삭제합니다.
**매개변수:**
- `resourceName` (string, 필수): 삭제할 연락처 그룹의 리소스 이름 (예: 'contactGroups/myContactGroup').
- `deleteContacts` (boolean, 선택사항): 그룹 내 연락처도 삭제할지 여부. 기본값: false
</Accordion>
</AccordionGroup>
## 사용 예제

View File

@@ -131,6 +131,297 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `endIndex` (integer, 필수): 범위의 끝 인덱스.
</Accordion>
<Accordion title="google_docs/create_document_with_content">
**설명:** 내용이 포함된 새 Google 문서를 한 번에 만듭니다.
**매개변수:**
- `title` (string, 필수): 새 문서의 제목. 문서 상단과 Google Drive에 표시됩니다.
- `content` (string, 선택사항): 문서에 삽입할 텍스트 내용. 새 단락에는 `\n`을 사용하세요.
</Accordion>
<Accordion title="google_docs/append_text">
**설명:** Google 문서의 끝에 텍스트를 추가합니다. 인덱스를 지정할 필요 없이 자동으로 문서 끝에 삽입됩니다.
**매개변수:**
- `documentId` (string, 필수): create_document 응답 또는 URL에서 가져온 문서 ID.
- `text` (string, 필수): 문서 끝에 추가할 텍스트. 새 단락에는 `\n`을 사용하세요.
</Accordion>
<Accordion title="google_docs/set_text_bold">
**설명:** Google 문서에서 텍스트를 굵게 만들거나 굵게 서식을 제거합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 서식을 지정할 텍스트의 시작 위치.
- `endIndex` (integer, 필수): 서식을 지정할 텍스트의 끝 위치 (배타적).
- `bold` (boolean, 필수): 굵게 만들려면 `true`, 굵게를 제거하려면 `false`로 설정.
</Accordion>
<Accordion title="google_docs/set_text_italic">
**설명:** Google 문서에서 텍스트를 기울임꼴로 만들거나 기울임꼴 서식을 제거합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 서식을 지정할 텍스트의 시작 위치.
- `endIndex` (integer, 필수): 서식을 지정할 텍스트의 끝 위치 (배타적).
- `italic` (boolean, 필수): 기울임꼴로 만들려면 `true`, 기울임꼴을 제거하려면 `false`로 설정.
</Accordion>
<Accordion title="google_docs/set_text_underline">
**설명:** Google 문서에서 텍스트에 밑줄 서식을 추가하거나 제거합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 서식을 지정할 텍스트의 시작 위치.
- `endIndex` (integer, 필수): 서식을 지정할 텍스트의 끝 위치 (배타적).
- `underline` (boolean, 필수): 밑줄을 추가하려면 `true`, 밑줄을 제거하려면 `false`로 설정.
</Accordion>
<Accordion title="google_docs/set_text_strikethrough">
**설명:** Google 문서에서 텍스트에 취소선 서식을 추가하거나 제거합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 서식을 지정할 텍스트의 시작 위치.
- `endIndex` (integer, 필수): 서식을 지정할 텍스트의 끝 위치 (배타적).
- `strikethrough` (boolean, 필수): 취소선을 추가하려면 `true`, 제거하려면 `false`로 설정.
</Accordion>
<Accordion title="google_docs/set_font_size">
**설명:** Google 문서에서 텍스트의 글꼴 크기를 변경합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 서식을 지정할 텍스트의 시작 위치.
- `endIndex` (integer, 필수): 서식을 지정할 텍스트의 끝 위치 (배타적).
- `fontSize` (number, 필수): 포인트 단위의 글꼴 크기. 일반적인 크기: 10, 11, 12, 14, 16, 18, 24, 36.
</Accordion>
<Accordion title="google_docs/set_text_color">
**설명:** Google 문서에서 RGB 값(0-1 스케일)을 사용하여 텍스트 색상을 변경합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 서식을 지정할 텍스트의 시작 위치.
- `endIndex` (integer, 필수): 서식을 지정할 텍스트의 끝 위치 (배타적).
- `red` (number, 필수): 빨강 구성 요소 (0-1). 예: `1`은 완전한 빨강.
- `green` (number, 필수): 초록 구성 요소 (0-1). 예: `0.5`는 절반 초록.
- `blue` (number, 필수): 파랑 구성 요소 (0-1). 예: `0`은 파랑 없음.
</Accordion>
<Accordion title="google_docs/create_hyperlink">
**설명:** Google 문서에서 기존 텍스트를 클릭 가능한 하이퍼링크로 변환합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 링크로 만들 텍스트의 시작 위치.
- `endIndex` (integer, 필수): 링크로 만들 텍스트의 끝 위치 (배타적).
- `url` (string, 필수): 링크가 가리킬 URL. 예: `"https://example.com"`.
</Accordion>
<Accordion title="google_docs/apply_heading_style">
**설명:** Google 문서에서 텍스트 범위에 제목 또는 단락 스타일을 적용합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 스타일을 적용할 단락의 시작 위치.
- `endIndex` (integer, 필수): 스타일을 적용할 단락의 끝 위치.
- `style` (string, 필수): 적용할 스타일. 옵션: `NORMAL_TEXT`, `TITLE`, `SUBTITLE`, `HEADING_1`, `HEADING_2`, `HEADING_3`, `HEADING_4`, `HEADING_5`, `HEADING_6`.
</Accordion>
<Accordion title="google_docs/set_paragraph_alignment">
**설명:** Google 문서에서 단락의 텍스트 정렬을 설정합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 정렬할 단락의 시작 위치.
- `endIndex` (integer, 필수): 정렬할 단락의 끝 위치.
- `alignment` (string, 필수): 텍스트 정렬. 옵션: `START` (왼쪽), `CENTER`, `END` (오른쪽), `JUSTIFIED`.
</Accordion>
<Accordion title="google_docs/set_line_spacing">
**설명:** Google 문서에서 단락의 줄 간격을 설정합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 단락의 시작 위치.
- `endIndex` (integer, 필수): 단락의 끝 위치.
- `lineSpacing` (number, 필수): 백분율로 나타낸 줄 간격. `100` = 단일, `115` = 1.15배, `150` = 1.5배, `200` = 이중.
</Accordion>
<Accordion title="google_docs/create_paragraph_bullets">
**설명:** Google 문서에서 단락을 글머리 기호 또는 번호 매기기 목록으로 변환합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 목록으로 변환할 단락의 시작 위치.
- `endIndex` (integer, 필수): 목록으로 변환할 단락의 끝 위치.
- `bulletPreset` (string, 필수): 글머리 기호/번호 매기기 스타일. 옵션: `BULLET_DISC_CIRCLE_SQUARE`, `BULLET_DIAMONDX_ARROW3D_SQUARE`, `BULLET_CHECKBOX`, `BULLET_ARROW_DIAMOND_DISC`, `BULLET_STAR_CIRCLE_SQUARE`, `NUMBERED_DECIMAL_ALPHA_ROMAN`, `NUMBERED_DECIMAL_ALPHA_ROMAN_PARENS`, `NUMBERED_DECIMAL_NESTED`, `NUMBERED_UPPERALPHA_ALPHA_ROMAN`, `NUMBERED_UPPERROMAN_UPPERALPHA_DECIMAL`.
</Accordion>
<Accordion title="google_docs/delete_paragraph_bullets">
**설명:** Google 문서에서 단락의 글머리 기호 또는 번호 매기기를 제거합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `startIndex` (integer, 필수): 목록 단락의 시작 위치.
- `endIndex` (integer, 필수): 목록 단락의 끝 위치.
</Accordion>
<Accordion title="google_docs/insert_table_with_content">
**설명:** Google 문서에 내용이 포함된 표를 한 번에 삽입합니다. 내용은 2D 배열로 제공하세요.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `rows` (integer, 필수): 표의 행 수.
- `columns` (integer, 필수): 표의 열 수.
- `index` (integer, 선택사항): 표를 삽입할 위치. 제공하지 않으면 문서 끝에 삽입됩니다.
- `content` (array, 필수): 2D 배열로 된 표 내용. 각 내부 배열은 행입니다. 예: `[["Year", "Revenue"], ["2023", "$43B"], ["2024", "$45B"]]`.
</Accordion>
<Accordion title="google_docs/insert_table_row">
**설명:** 기존 표의 참조 셀 위 또는 아래에 새 행을 삽입합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `tableStartIndex` (integer, 필수): 표의 시작 인덱스. get_document에서 가져오세요.
- `rowIndex` (integer, 필수): 참조 셀의 행 인덱스 (0 기반).
- `columnIndex` (integer, 선택사항): 참조 셀의 열 인덱스 (0 기반). 기본값: `0`.
- `insertBelow` (boolean, 선택사항): `true`이면 참조 행 아래에, `false`이면 위에 삽입. 기본값: `true`.
</Accordion>
<Accordion title="google_docs/insert_table_column">
**설명:** 기존 표의 참조 셀 왼쪽 또는 오른쪽에 새 열을 삽입합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `tableStartIndex` (integer, 필수): 표의 시작 인덱스.
- `rowIndex` (integer, 선택사항): 참조 셀의 행 인덱스 (0 기반). 기본값: `0`.
- `columnIndex` (integer, 필수): 참조 셀의 열 인덱스 (0 기반).
- `insertRight` (boolean, 선택사항): `true`이면 오른쪽에, `false`이면 왼쪽에 삽입. 기본값: `true`.
</Accordion>
<Accordion title="google_docs/delete_table_row">
**설명:** Google 문서의 기존 표에서 행을 삭제합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `tableStartIndex` (integer, 필수): 표의 시작 인덱스.
- `rowIndex` (integer, 필수): 삭제할 행 인덱스 (0 기반).
- `columnIndex` (integer, 선택사항): 행의 아무 셀의 열 인덱스 (0 기반). 기본값: `0`.
</Accordion>
<Accordion title="google_docs/delete_table_column">
**설명:** Google 문서의 기존 표에서 열을 삭제합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `tableStartIndex` (integer, 필수): 표의 시작 인덱스.
- `rowIndex` (integer, 선택사항): 열의 아무 셀의 행 인덱스 (0 기반). 기본값: `0`.
- `columnIndex` (integer, 필수): 삭제할 열 인덱스 (0 기반).
</Accordion>
<Accordion title="google_docs/merge_table_cells">
**설명:** 표 셀 범위를 단일 셀로 병합합니다. 모든 셀의 내용이 보존됩니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `tableStartIndex` (integer, 필수): 표의 시작 인덱스.
- `rowIndex` (integer, 필수): 병합의 시작 행 인덱스 (0 기반).
- `columnIndex` (integer, 필수): 병합의 시작 열 인덱스 (0 기반).
- `rowSpan` (integer, 필수): 병합할 행 수.
- `columnSpan` (integer, 필수): 병합할 열 수.
</Accordion>
<Accordion title="google_docs/unmerge_table_cells">
**설명:** 이전에 병합된 표 셀을 개별 셀로 분리합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `tableStartIndex` (integer, 필수): 표의 시작 인덱스.
- `rowIndex` (integer, 필수): 병합된 셀의 행 인덱스 (0 기반).
- `columnIndex` (integer, 필수): 병합된 셀의 열 인덱스 (0 기반).
- `rowSpan` (integer, 필수): 병합된 셀이 차지하는 행 수.
- `columnSpan` (integer, 필수): 병합된 셀이 차지하는 열 수.
</Accordion>
<Accordion title="google_docs/insert_inline_image">
**설명:** 공개 URL에서 Google 문서에 이미지를 삽입합니다. 이미지는 공개적으로 접근 가능해야 하고, 50MB 미만이며, PNG/JPEG/GIF 형식이어야 합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `uri` (string, 필수): 이미지의 공개 URL. 인증 없이 접근 가능해야 합니다.
- `index` (integer, 선택사항): 이미지를 삽입할 위치. 제공하지 않으면 문서 끝에 삽입됩니다. 기본값: `1`.
</Accordion>
<Accordion title="google_docs/insert_section_break">
**설명:** 서로 다른 서식을 가진 문서 섹션을 만들기 위해 섹션 나누기를 삽입합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `index` (integer, 필수): 섹션 나누기를 삽입할 위치.
- `sectionType` (string, 필수): 섹션 나누기의 유형. 옵션: `CONTINUOUS` (같은 페이지에 유지), `NEXT_PAGE` (새 페이지 시작).
</Accordion>
<Accordion title="google_docs/create_header">
**설명:** 문서의 머리글을 만듭니다. insert_text를 사용하여 머리글 내용을 추가할 수 있는 headerId를 반환합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `type` (string, 선택사항): 머리글 유형. 옵션: `DEFAULT`. 기본값: `DEFAULT`.
</Accordion>
<Accordion title="google_docs/create_footer">
**설명:** 문서의 바닥글을 만듭니다. insert_text를 사용하여 바닥글 내용을 추가할 수 있는 footerId를 반환합니다.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `type` (string, 선택사항): 바닥글 유형. 옵션: `DEFAULT`. 기본값: `DEFAULT`.
</Accordion>
<Accordion title="google_docs/delete_header">
**설명:** 문서에서 머리글을 삭제합니다. headerId를 찾으려면 get_document를 사용하세요.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `headerId` (string, 필수): 삭제할 머리글 ID. get_document 응답에서 가져오세요.
</Accordion>
<Accordion title="google_docs/delete_footer">
**설명:** 문서에서 바닥글을 삭제합니다. footerId를 찾으려면 get_document를 사용하세요.
**매개변수:**
- `documentId` (string, 필수): 문서 ID.
- `footerId` (string, 필수): 삭제할 바닥글 ID. get_document 응답에서 가져오세요.
</Accordion>
</AccordionGroup>
## 사용 예제

View File

@@ -61,6 +61,22 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="google_slides/get_presentation_metadata">
**설명:** 프레젠테이션에 대한 가벼운 메타데이터(제목, 슬라이드 수, 슬라이드 ID)를 가져옵니다. 전체 콘텐츠를 가져오기 전에 먼저 사용하세요.
**매개변수:**
- `presentationId` (string, 필수): 검색할 프레젠테이션의 ID.
</Accordion>
<Accordion title="google_slides/get_presentation_text">
**설명:** 프레젠테이션에서 모든 텍스트 콘텐츠를 추출합니다. 슬라이드 ID와 도형 및 테이블의 텍스트만 반환합니다 (포맷팅 없음).
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
</Accordion>
<Accordion title="google_slides/get_presentation">
**설명:** ID로 프레젠테이션을 검색합니다.
@@ -80,6 +96,15 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="google_slides/get_slide_text">
**설명:** 단일 슬라이드에서 텍스트 콘텐츠를 추출합니다. 도형 및 테이블의 텍스트만 반환합니다 (포맷팅 또는 스타일 없음).
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `pageObjectId` (string, 필수): 텍스트를 가져올 슬라이드/페이지의 ID.
</Accordion>
<Accordion title="google_slides/get_page">
**설명:** ID로 특정 페이지를 검색합니다.
@@ -98,6 +123,120 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="google_slides/create_slide">
**설명:** 프레젠테이션에 추가 빈 슬라이드를 추가합니다. 새 프레젠테이션에는 이미 빈 슬라이드가 하나 있습니다. 먼저 get_presentation_metadata를 확인하세요. 제목/본문 영역이 있는 슬라이드는 create_slide_with_layout을 사용하세요.
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `insertionIndex` (integer, 선택사항): 슬라이드를 삽입할 위치 (0 기반). 생략하면 맨 끝에 추가됩니다.
</Accordion>
<Accordion title="google_slides/create_slide_with_layout">
**설명:** 제목, 본문 등의 플레이스홀더 영역이 있는 미리 정의된 레이아웃으로 슬라이드를 만듭니다. 구조화된 콘텐츠에는 create_slide보다 적합합니다. 생성 후 get_page로 플레이스홀더 ID를 찾고, 그 안에 텍스트를 삽입하세요.
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `layout` (string, 필수): 레이아웃 유형. 옵션: `BLANK`, `TITLE`, `TITLE_AND_BODY`, `TITLE_AND_TWO_COLUMNS`, `TITLE_ONLY`, `SECTION_HEADER`, `ONE_COLUMN_TEXT`, `MAIN_POINT`, `BIG_NUMBER`. 제목+설명은 TITLE_AND_BODY, 제목만은 TITLE, 섹션 구분은 SECTION_HEADER가 적합합니다.
- `insertionIndex` (integer, 선택사항): 삽입할 위치 (0 기반). 생략하면 맨 끝에 추가됩니다.
</Accordion>
<Accordion title="google_slides/create_text_box">
**설명:** 콘텐츠가 있는 텍스트 상자를 슬라이드에 만듭니다. 제목, 설명, 단락에 사용합니다. 테이블에는 사용하지 마세요. 선택적으로 EMU 단위로 위치(x, y)와 크기(width, height)를 지정할 수 있습니다 (914400 EMU = 1 인치).
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `slideId` (string, 필수): 텍스트 상자를 추가할 슬라이드의 ID.
- `text` (string, 필수): 텍스트 상자의 텍스트 내용.
- `x` (integer, 선택사항): EMU 단위 X 위치 (914400 = 1 인치). 기본값: 914400 (왼쪽에서 1 인치).
- `y` (integer, 선택사항): EMU 단위 Y 위치 (914400 = 1 인치). 기본값: 914400 (위에서 1 인치).
- `width` (integer, 선택사항): EMU 단위 너비. 기본값: 7315200 (약 8 인치).
- `height` (integer, 선택사항): EMU 단위 높이. 기본값: 914400 (약 1 인치).
</Accordion>
<Accordion title="google_slides/delete_slide">
**설명:** 프레젠테이션에서 슬라이드를 제거합니다. 슬라이드 ID를 찾으려면 먼저 get_presentation을 사용하세요.
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `slideId` (string, 필수): 삭제할 슬라이드의 객체 ID. get_presentation에서 가져옵니다.
</Accordion>
<Accordion title="google_slides/duplicate_slide">
**설명:** 기존 슬라이드의 복사본을 만듭니다. 복사본은 원본 바로 다음에 삽입됩니다.
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `slideId` (string, 필수): 복제할 슬라이드의 객체 ID. get_presentation에서 가져옵니다.
</Accordion>
<Accordion title="google_slides/move_slides">
**설명:** 슬라이드를 새 위치로 이동하여 순서를 변경합니다. 슬라이드 ID는 현재 프레젠테이션 순서대로 있어야 합니다 (중복 없음).
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `slideIds` (string 배열, 필수): 이동할 슬라이드 ID 배열. 현재 프레젠테이션 순서대로 있어야 합니다.
- `insertionIndex` (integer, 필수): 대상 위치 (0 기반). 0 = 맨 앞, 슬라이드 수 = 맨 끝.
</Accordion>
<Accordion title="google_slides/insert_youtube_video">
**설명:** 슬라이드에 YouTube 동영상을 삽입합니다. 동영상 ID는 YouTube URL의 "v=" 다음 값입니다 (예: youtube.com/watch?v=abc123의 경우 "abc123" 사용).
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `slideId` (string, 필수): 동영상을 추가할 슬라이드의 ID. get_presentation에서 가져옵니다.
- `videoId` (string, 필수): YouTube 동영상 ID (URL의 v= 다음 값).
</Accordion>
<Accordion title="google_slides/insert_drive_video">
**설명:** 슬라이드에 Google Drive의 동영상을 삽입합니다. 파일 ID는 Drive 파일 URL에서 찾을 수 있습니다.
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `slideId` (string, 필수): 동영상을 추가할 슬라이드의 ID. get_presentation에서 가져옵니다.
- `fileId` (string, 필수): 동영상의 Google Drive 파일 ID.
</Accordion>
<Accordion title="google_slides/set_slide_background_image">
**설명:** 슬라이드의 배경 이미지를 설정합니다. 이미지 URL은 공개적으로 액세스 가능해야 합니다.
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `slideId` (string, 필수): 배경을 설정할 슬라이드의 ID. get_presentation에서 가져옵니다.
- `imageUrl` (string, 필수): 배경으로 사용할 이미지의 공개적으로 액세스 가능한 URL.
</Accordion>
<Accordion title="google_slides/create_table">
**설명:** 슬라이드에 빈 테이블을 만듭니다. 콘텐츠가 있는 테이블을 만들려면 create_table_with_content를 사용하세요.
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `slideId` (string, 필수): 테이블을 추가할 슬라이드의 ID. get_presentation에서 가져옵니다.
- `rows` (integer, 필수): 테이블의 행 수.
- `columns` (integer, 필수): 테이블의 열 수.
</Accordion>
<Accordion title="google_slides/create_table_with_content">
**설명:** 한 번의 작업으로 콘텐츠가 있는 테이블을 만듭니다. 콘텐츠는 2D 배열로 제공하며, 각 내부 배열은 행을 나타냅니다. 예: [["Header1", "Header2"], ["Row1Col1", "Row1Col2"]].
**매개변수:**
- `presentationId` (string, 필수): 프레젠테이션의 ID.
- `slideId` (string, 필수): 테이블을 추가할 슬라이드의 ID. get_presentation에서 가져옵니다.
- `rows` (integer, 필수): 테이블의 행 수.
- `columns` (integer, 필수): 테이블의 열 수.
- `content` (array, 필수): 2D 배열 형태의 테이블 콘텐츠. 각 내부 배열은 행입니다. 예: [["Year", "Revenue"], ["2023", "$10M"]].
</Accordion>
<Accordion title="google_slides/import_data_from_sheet">
**설명:** Google 시트에서 프레젠테이션으로 데이터를 가져옵니다.

View File

@@ -148,6 +148,16 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="microsoft_excel/get_table_data">
**설명:** Excel 워크시트의 특정 테이블에서 데이터를 가져옵니다.
**매개변수:**
- `file_id` (string, 필수): Excel 파일의 ID.
- `worksheet_name` (string, 필수): 워크시트의 이름.
- `table_name` (string, 필수): 테이블의 이름.
</Accordion>
<Accordion title="microsoft_excel/create_chart">
**설명:** Excel 워크시트에 차트를 만듭니다.
@@ -180,6 +190,15 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="microsoft_excel/get_used_range_metadata">
**설명:** Excel 워크시트의 사용된 범위 메타데이터(크기만, 데이터 없음)를 가져옵니다.
**매개변수:**
- `file_id` (string, 필수): Excel 파일의 ID.
- `worksheet_name` (string, 필수): 워크시트의 이름.
</Accordion>
<Accordion title="microsoft_excel/list_charts">
**설명:** Excel 워크시트의 모든 차트를 가져옵니다.

View File

@@ -150,6 +150,49 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `item_id` (string, 필수): 파일의 ID.
</Accordion>
<Accordion title="microsoft_onedrive/list_files_by_path">
**설명:** 특정 OneDrive 경로의 파일과 폴더를 나열합니다.
**매개변수:**
- `folder_path` (string, 필수): 폴더 경로 (예: 'Documents/Reports').
- `top` (integer, 선택사항): 검색할 항목 수 (최대 1000). 기본값: 50.
- `orderby` (string, 선택사항): 필드별 정렬 (예: "name asc", "lastModifiedDateTime desc"). 기본값: "name asc".
</Accordion>
<Accordion title="microsoft_onedrive/get_recent_files">
**설명:** OneDrive에서 최근에 액세스한 파일을 가져옵니다.
**매개변수:**
- `top` (integer, 선택사항): 검색할 항목 수 (최대 200). 기본값: 25.
</Accordion>
<Accordion title="microsoft_onedrive/get_shared_with_me">
**설명:** 사용자와 공유된 파일과 폴더를 가져옵니다.
**매개변수:**
- `top` (integer, 선택사항): 검색할 항목 수 (최대 200). 기본값: 50.
- `orderby` (string, 선택사항): 필드별 정렬. 기본값: "name asc".
</Accordion>
<Accordion title="microsoft_onedrive/get_file_by_path">
**설명:** 경로로 특정 파일 또는 폴더에 대한 정보를 가져옵니다.
**매개변수:**
- `file_path` (string, 필수): 파일 또는 폴더 경로 (예: 'Documents/report.docx').
</Accordion>
<Accordion title="microsoft_onedrive/download_file_by_path">
**설명:** 경로로 OneDrive에서 파일을 다운로드합니다.
**매개변수:**
- `file_path` (string, 필수): 파일 경로 (예: 'Documents/report.docx').
</Accordion>
</AccordionGroup>
## 사용 예제
@@ -183,6 +226,62 @@ crew = Crew(
crew.kickoff()
```
### 파일 업로드 및 관리
```python
from crewai import Agent, Task, Crew
# 파일 작업에 특화된 에이전트 생성
file_operator = Agent(
role="파일 운영자",
goal="파일을 정확하게 업로드, 다운로드 및 관리",
backstory="파일 처리 및 콘텐츠 관리에 능숙한 AI 어시스턴트.",
apps=['microsoft_onedrive/upload_file', 'microsoft_onedrive/download_file', 'microsoft_onedrive/get_file_info']
)
# 파일 업로드 및 관리 작업
file_management_task = Task(
description="'report.txt'라는 이름의 텍스트 파일을 'This is a sample report for the project.' 내용으로 업로드한 다음 업로드된 파일에 대한 정보를 가져오세요.",
agent=file_operator,
expected_output="파일이 성공적으로 업로드되고 파일 정보가 검색됨."
)
crew = Crew(
agents=[file_operator],
tasks=[file_management_task]
)
crew.kickoff()
```
### 파일 정리 및 공유
```python
from crewai import Agent, Task, Crew
# 파일 정리 및 공유를 위한 에이전트 생성
file_organizer = Agent(
role="파일 정리자",
goal="파일을 정리하고 협업을 위한 공유 링크 생성",
backstory="파일 정리 및 공유 권한 관리에 뛰어난 AI 어시스턴트.",
apps=['microsoft_onedrive/search_files', 'microsoft_onedrive/move_item', 'microsoft_onedrive/share_item', 'microsoft_onedrive/create_folder']
)
# 파일 정리 및 공유 작업
organize_share_task = Task(
description="이름에 'presentation'이 포함된 파일을 검색하고, '프레젠테이션'이라는 폴더를 만든 다음, 찾은 파일을 이 폴더로 이동하고 폴더에 대한 읽기 전용 공유 링크를 생성하세요.",
agent=file_organizer,
expected_output="파일이 '프레젠테이션' 폴더로 정리되고 공유 링크가 생성됨."
)
crew = Crew(
agents=[file_organizer],
tasks=[organize_share_task]
)
crew.kickoff()
```
## 문제 해결
### 일반적인 문제
@@ -196,6 +295,30 @@ crew.kickoff()
- 파일 업로드 시 `file_name`과 `content`가 제공되는지 확인하세요.
- 바이너리 파일의 경우 내용이 Base64로 인코딩되어야 합니다.
- OneDrive에 대한 쓰기 권한이 있는지 확인하세요.
**파일/폴더 ID 문제**
- 특정 파일 또는 폴더에 액세스할 때 항목 ID가 올바른지 다시 확인하세요.
- 항목 ID는 `list_files` 또는 `search_files`와 같은 다른 작업에서 반환됩니다.
- 참조하는 항목이 존재하고 액세스 가능한지 확인하세요.
**검색 및 필터 작업**
- `search_files` 작업에 적절한 검색어를 사용하세요.
- `filter` 매개변수의 경우 올바른 OData 문법을 사용하세요.
**파일 작업 (복사/이동)**
- `move_item`의 경우 `item_id`와 `parent_id`가 모두 제공되는지 확인하세요.
- `copy_item`의 경우 `item_id`만 필요합니다. `parent_id`는 지정하지 않으면 루트로 기본 설정됩니다.
- 대상 폴더가 존재하고 액세스 가능한지 확인하세요.
**공유 링크 생성**
- 공유 링크를 만들기 전에 항목이 존재하는지 확인하세요.
- 공유 요구 사항에 따라 적절한 `type`과 `scope`를 선택하세요.
- `anonymous` 범위는 로그인 없이 액세스를 허용합니다. `organization`은 조직 계정이 필요합니다.
### 도움 받기

View File

@@ -132,6 +132,74 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `companyName` (string, 선택사항): 연락처의 회사 이름.
</Accordion>
<Accordion title="microsoft_outlook/get_message">
**설명:** ID로 특정 이메일 메시지를 가져옵니다.
**매개변수:**
- `message_id` (string, 필수): 메시지의 고유 식별자. get_messages 작업에서 얻을 수 있습니다.
- `select` (string, 선택사항): 반환할 속성의 쉼표로 구분된 목록. 예: "id,subject,body,from,receivedDateTime". 기본값: "id,subject,body,from,toRecipients,receivedDateTime".
</Accordion>
<Accordion title="microsoft_outlook/reply_to_email">
**설명:** 이메일 메시지에 회신합니다.
**매개변수:**
- `message_id` (string, 필수): 회신할 메시지의 고유 식별자. get_messages 작업에서 얻을 수 있습니다.
- `comment` (string, 필수): 회신 메시지 내용. 일반 텍스트 또는 HTML 가능. 원본 메시지가 이 내용 아래에 인용됩니다.
</Accordion>
<Accordion title="microsoft_outlook/forward_email">
**설명:** 이메일 메시지를 전달합니다.
**매개변수:**
- `message_id` (string, 필수): 전달할 메시지의 고유 식별자. get_messages 작업에서 얻을 수 있습니다.
- `to_recipients` (array, 필수): 전달할 받는 사람의 이메일 주소 배열. 예: ["john@example.com", "jane@example.com"].
- `comment` (string, 선택사항): 전달된 콘텐츠 위에 포함할 선택적 메시지. 일반 텍스트 또는 HTML 가능.
</Accordion>
<Accordion title="microsoft_outlook/mark_message_read">
**설명:** 메시지를 읽음 또는 읽지 않음으로 표시합니다.
**매개변수:**
- `message_id` (string, 필수): 메시지의 고유 식별자. get_messages 작업에서 얻을 수 있습니다.
- `is_read` (boolean, 필수): 읽음으로 표시하려면 true, 읽지 않음으로 표시하려면 false로 설정합니다.
</Accordion>
<Accordion title="microsoft_outlook/delete_message">
**설명:** 이메일 메시지를 삭제합니다.
**매개변수:**
- `message_id` (string, 필수): 삭제할 메시지의 고유 식별자. get_messages 작업에서 얻을 수 있습니다.
</Accordion>
<Accordion title="microsoft_outlook/update_event">
**설명:** 기존 캘린더 이벤트를 업데이트합니다.
**매개변수:**
- `event_id` (string, 필수): 이벤트의 고유 식별자. get_calendar_events 작업에서 얻을 수 있습니다.
- `subject` (string, 선택사항): 이벤트의 새 제목/제목.
- `start_time` (string, 선택사항): ISO 8601 형식의 새 시작 시간 (예: "2024-01-20T10:00:00"). 필수: 이 필드 사용 시 start_timezone도 제공해야 합니다.
- `start_timezone` (string, 선택사항): 시작 시간의 시간대. start_time 업데이트 시 필수. 예: "Pacific Standard Time", "Eastern Standard Time", "UTC".
- `end_time` (string, 선택사항): ISO 8601 형식의 새 종료 시간. 필수: 이 필드 사용 시 end_timezone도 제공해야 합니다.
- `end_timezone` (string, 선택사항): 종료 시간의 시간대. end_time 업데이트 시 필수. 예: "Pacific Standard Time", "Eastern Standard Time", "UTC".
- `location` (string, 선택사항): 이벤트의 새 위치.
- `body` (string, 선택사항): 이벤트의 새 본문/설명. HTML 형식 지원.
</Accordion>
<Accordion title="microsoft_outlook/delete_event">
**설명:** 캘린더 이벤트를 삭제합니다.
**매개변수:**
- `event_id` (string, 필수): 삭제할 이벤트의 고유 식별자. get_calendar_events 작업에서 얻을 수 있습니다.
</Accordion>
</AccordionGroup>
## 사용 예제
@@ -165,6 +233,62 @@ crew = Crew(
crew.kickoff()
```
### 이메일 관리 및 검색
```python
from crewai import Agent, Task, Crew
# 이메일 관리에 특화된 에이전트 생성
email_manager = Agent(
role="이메일 관리자",
goal="이메일 메시지를 검색하고 가져와 정리",
backstory="이메일 정리 및 관리에 능숙한 AI 어시스턴트.",
apps=['microsoft_outlook/get_messages']
)
# 이메일 검색 및 가져오기 작업
search_emails_task = Task(
description="최신 읽지 않은 이메일 20건을 가져와 가장 중요한 것들의 요약을 제공하세요.",
agent=email_manager,
expected_output="주요 읽지 않은 이메일의 요약과 핵심 세부 정보."
)
crew = Crew(
agents=[email_manager],
tasks=[search_emails_task]
)
crew.kickoff()
```
### 캘린더 및 연락처 관리
```python
from crewai import Agent, Task, Crew
# 캘린더 및 연락처 관리를 위한 에이전트 생성
scheduler = Agent(
role="캘린더 및 연락처 관리자",
goal="캘린더 이벤트를 관리하고 연락처 정보를 유지",
backstory="일정 관리 및 연락처 정리를 담당하는 AI 어시스턴트.",
apps=['microsoft_outlook/create_calendar_event', 'microsoft_outlook/get_calendar_events', 'microsoft_outlook/create_contact']
)
# 회의 생성 및 연락처 추가 작업
schedule_task = Task(
description="내일 오후 2시 '팀 회의' 제목으로 '회의실 A' 장소의 캘린더 이벤트를 만들고, 'john.smith@example.com' 이메일과 '프로젝트 매니저' 직책으로 'John Smith'의 새 연락처를 추가하세요.",
agent=scheduler,
expected_output="캘린더 이벤트가 생성되고 새 연락처가 추가됨."
)
crew = Crew(
agents=[scheduler],
tasks=[schedule_task]
)
crew.kickoff()
```
## 문제 해결
### 일반적인 문제
@@ -173,11 +297,29 @@ crew.kickoff()
- Microsoft 계정이 이메일, 캘린더 및 연락처 액세스에 필요한 권한을 가지고 있는지 확인하세요.
- 필요한 범위: `Mail.Read`, `Mail.Send`, `Calendars.Read`, `Calendars.ReadWrite`, `Contacts.Read`, `Contacts.ReadWrite`.
- OAuth 연결에 필요한 모든 범위가 포함되어 있는지 확인하세요.
**이메일 보내기 문제**
- `send_email`에 `to_recipients`, `subject`, `body`가 제공되는지 확인하세요.
- 이메일 주소가 올바르게 형식화되어 있는지 확인하세요.
- 계정에 `Mail.Send` 권한이 있는지 확인하세요.
**캘린더 이벤트 생성**
- `subject`, `start_datetime`, `end_datetime`이 제공되는지 확인하세요.
- 날짜/시간 필드에 적절한 ISO 8601 형식을 사용하세요 (예: '2024-01-20T10:00:00').
- 이벤트가 잘못된 시간에 표시되는 경우 시간대 설정을 확인하세요.
**연락처 관리**
- `create_contact`의 경우 필수인 `displayName`이 제공되는지 확인하세요.
- `emailAddresses`를 제공할 때 `address`와 `name` 속성이 있는 올바른 객체 형식을 사용하세요.
**검색 및 필터 문제**
- `filter` 매개변수에 올바른 OData 문법을 사용하세요.
- 날짜 필터의 경우 ISO 8601 형식을 사용하세요 (예: "receivedDateTime ge '2024-01-01T00:00:00Z'").
### 도움 받기

View File

@@ -77,6 +77,17 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="microsoft_sharepoint/get_drives">
**설명:** SharePoint 사이트의 모든 문서 라이브러리(드라이브)를 나열합니다. 파일 작업을 사용하기 전에 사용 가능한 라이브러리를 찾으려면 이 작업을 사용하세요.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `top` (integer, 선택사항): 페이지당 반환할 최대 드라이브 수 (1-999). 기본값: 100
- `skip_token` (string, 선택사항): 다음 결과 페이지를 가져오기 위한 이전 응답의 페이지네이션 토큰.
- `select` (string, 선택사항): 반환할 속성의 쉼표로 구분된 목록 (예: 'id,name,webUrl,driveType').
</Accordion>
<Accordion title="microsoft_sharepoint/get_site_lists">
**설명:** SharePoint 사이트의 모든 목록을 가져옵니다.
@@ -145,20 +156,317 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
</Accordion>
<Accordion title="microsoft_sharepoint/get_drive_items">
**설명:** SharePoint 문서 라이브러리에서 파일과 폴더를 가져옵니다.
<Accordion title="microsoft_sharepoint/list_files">
**설명:** SharePoint 문서 라이브러리에서 파일과 폴더를 가져옵니다. 기본적으로 루트 폴더를 나열하지만 folder_id를 제공하여 하위 폴더로 이동할 수 있습니다.
**매개변수:**
- `site_id` (string, 필수): SharePoint 사이트의 ID.
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `folder_id` (string, 선택사항): 내용을 나열할 폴더의 ID. 루트 폴더의 경우 'root'를 사용하거나 이전 list_files 호출에서 가져온 폴더 ID를 제공하세요. 기본값: 'root'
- `top` (integer, 선택사항): 페이지당 반환할 최대 항목 수 (1-1000). 기본값: 50
- `skip_token` (string, 선택사항): 다음 결과 페이지를 가져오기 위한 이전 응답의 페이지네이션 토큰.
- `orderby` (string, 선택사항): 결과 정렬 순서 (예: 'name asc', 'size desc', 'lastModifiedDateTime desc'). 기본값: 'name asc'
- `filter` (string, 선택사항): 결과를 좁히기 위한 OData 필터 (예: 'file ne null'은 파일만, 'folder ne null'은 폴더만).
- `select` (string, 선택사항): 반환할 필드의 쉼표로 구분된 목록 (예: 'id,name,size,folder,file,webUrl,lastModifiedDateTime').
</Accordion>
<Accordion title="microsoft_sharepoint/delete_drive_item">
**설명:** SharePoint 문서 라이브러리에서 파일 또는 폴더를 삭제합니다.
<Accordion title="microsoft_sharepoint/delete_file">
**설명:** SharePoint 문서 라이브러리에서 파일 또는 폴더를 삭제합니다. 폴더의 경우 모든 내용이 재귀적으로 삭제됩니다. 항목은 사이트 휴지통으로 이동됩니다.
**매개변수:**
- `site_id` (string, 필수): SharePoint 사이트의 ID.
- `item_id` (string, 필수): 삭제할 파일 또는 폴더의 ID.
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): 삭제할 파일 또는 폴더의 고유 식별자. list_files에서 가져오세요.
</Accordion>
<Accordion title="microsoft_sharepoint/list_files_by_path">
**설명:** 경로로 SharePoint 문서 라이브러리 폴더의 파일과 폴더를 나열합니다. 깊은 탐색을 위해 여러 list_files 호출보다 더 효율적입니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `folder_path` (string, 필수): 앞뒤 슬래시 없이 폴더의 전체 경로 (예: 'Documents', 'Reports/2024/Q1').
- `top` (integer, 선택사항): 페이지당 반환할 최대 항목 수 (1-1000). 기본값: 50
- `skip_token` (string, 선택사항): 다음 결과 페이지를 가져오기 위한 이전 응답의 페이지네이션 토큰.
- `orderby` (string, 선택사항): 결과 정렬 순서 (예: 'name asc', 'size desc'). 기본값: 'name asc'
- `select` (string, 선택사항): 반환할 필드의 쉼표로 구분된 목록 (예: 'id,name,size,folder,file,webUrl,lastModifiedDateTime').
</Accordion>
<Accordion title="microsoft_sharepoint/download_file">
**설명:** SharePoint 문서 라이브러리에서 원시 파일 내용을 다운로드합니다. 일반 텍스트 파일(.txt, .csv, .json)에만 사용하세요. Excel 파일의 경우 Excel 전용 작업을 사용하세요. Word 파일의 경우 get_word_document_content를 사용하세요.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): 다운로드할 파일의 고유 식별자. list_files 또는 list_files_by_path에서 가져오세요.
</Accordion>
<Accordion title="microsoft_sharepoint/get_file_info">
**설명:** SharePoint 문서 라이브러리의 특정 파일 또는 폴더에 대한 자세한 메타데이터를 가져옵니다. 이름, 크기, 생성/수정 날짜 및 작성자 정보가 포함됩니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): 파일 또는 폴더의 고유 식별자. list_files 또는 list_files_by_path에서 가져오세요.
- `select` (string, 선택사항): 반환할 속성의 쉼표로 구분된 목록 (예: 'id,name,size,createdDateTime,lastModifiedDateTime,webUrl,createdBy,lastModifiedBy').
</Accordion>
<Accordion title="microsoft_sharepoint/create_folder">
**설명:** SharePoint 문서 라이브러리에 새 폴더를 만듭니다. 기본적으로 루트에 폴더를 만들며 하위 폴더를 만들려면 parent_id를 사용하세요.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `folder_name` (string, 필수): 새 폴더의 이름. 사용할 수 없는 문자: \ / : * ? " < > |
- `parent_id` (string, 선택사항): 상위 폴더의 ID. 문서 라이브러리 루트의 경우 'root'를 사용하거나 list_files에서 가져온 폴더 ID를 제공하세요. 기본값: 'root'
</Accordion>
<Accordion title="microsoft_sharepoint/search_files">
**설명:** 키워드로 SharePoint 문서 라이브러리에서 파일과 폴더를 검색합니다. 파일 이름, 폴더 이름 및 Office 문서의 파일 내용을 검색합니다. 와일드카드나 특수 문자를 사용하지 마세요.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `query` (string, 필수): 검색 키워드 (예: 'report', 'budget 2024'). *.txt와 같은 와일드카드는 지원되지 않습니다.
- `top` (integer, 선택사항): 페이지당 반환할 최대 결과 수 (1-1000). 기본값: 50
- `skip_token` (string, 선택사항): 다음 결과 페이지를 가져오기 위한 이전 응답의 페이지네이션 토큰.
- `select` (string, 선택사항): 반환할 필드의 쉼표로 구분된 목록 (예: 'id,name,size,folder,file,webUrl,lastModifiedDateTime').
</Accordion>
<Accordion title="microsoft_sharepoint/copy_file">
**설명:** SharePoint 내에서 파일 또는 폴더를 새 위치로 복사합니다. 원본 항목은 변경되지 않습니다. 대용량 파일의 경우 복사 작업은 비동기적입니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): 복사할 파일 또는 폴더의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `destination_folder_id` (string, 필수): 대상 폴더의 ID. 루트 폴더의 경우 'root'를 사용하거나 list_files에서 가져온 폴더 ID를 사용하세요.
- `new_name` (string, 선택사항): 복사본의 새 이름. 제공하지 않으면 원래 이름이 사용됩니다.
</Accordion>
<Accordion title="microsoft_sharepoint/move_file">
**설명:** SharePoint 내에서 파일 또는 폴더를 새 위치로 이동합니다. 항목은 원래 위치에서 제거됩니다. 폴더의 경우 모든 내용도 함께 이동됩니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): 이동할 파일 또는 폴더의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `destination_folder_id` (string, 필수): 대상 폴더의 ID. 루트 폴더의 경우 'root'를 사용하거나 list_files에서 가져온 폴더 ID를 사용하세요.
- `new_name` (string, 선택사항): 이동된 항목의 새 이름. 제공하지 않으면 원래 이름이 유지됩니다.
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_worksheets">
**설명:** SharePoint 문서 라이브러리에 저장된 Excel 통합 문서의 모든 워크시트(탭)를 나열합니다. 반환된 워크시트 이름을 다른 Excel 작업에 사용하세요.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `select` (string, 선택사항): 반환할 속성의 쉼표로 구분된 목록 (예: 'id,name,position,visibility').
- `filter` (string, 선택사항): OData 필터 표현식 (예: "visibility eq 'Visible'"로 숨겨진 시트 제외).
- `top` (integer, 선택사항): 반환할 최대 워크시트 수. 최소: 1, 최대: 999
- `orderby` (string, 선택사항): 정렬 순서 (예: 'position asc'로 탭 순서대로 반환).
</Accordion>
<Accordion title="microsoft_sharepoint/create_excel_worksheet">
**설명:** SharePoint 문서 라이브러리에 저장된 Excel 통합 문서에 새 워크시트(탭)를 만듭니다. 새 시트는 탭 목록의 끝에 추가됩니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `name` (string, 필수): 새 워크시트의 이름. 최대 31자. 사용할 수 없는 문자: \ / * ? : [ ]. 통합 문서 내에서 고유해야 합니다.
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_range_data">
**설명:** SharePoint에 저장된 Excel 워크시트의 특정 범위에서 셀 값을 가져옵니다. 크기를 모르는 상태에서 모든 데이터를 읽으려면 대신 get_excel_used_range를 사용하세요.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 읽을 워크시트(탭)의 이름. get_excel_worksheets에서 가져오세요. 대소문자를 구분합니다.
- `range` (string, 필수): A1 표기법의 셀 범위 (예: 'A1:C10', 'A:C', '1:5', 'A1').
- `select` (string, 선택사항): 반환할 속성의 쉼표로 구분된 목록 (예: 'address,values,formulas,numberFormat,text').
</Accordion>
<Accordion title="microsoft_sharepoint/update_excel_range_data">
**설명:** SharePoint에 저장된 Excel 워크시트의 특정 범위에 값을 씁니다. 기존 셀 내용을 덮어씁니다. values 배열의 크기는 범위 크기와 정확히 일치해야 합니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 업데이트할 워크시트(탭)의 이름. get_excel_worksheets에서 가져오세요. 대소문자를 구분합니다.
- `range` (string, 필수): 값을 쓸 A1 표기법의 셀 범위 (예: 'A1:C3'은 3x3 블록).
- `values` (array, 필수): 2D 값 배열 (셀을 포함하는 행). A1:B2의 예: [["Header1", "Header2"], ["Value1", "Value2"]]. 셀을 지우려면 null을 사용하세요.
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_used_range_metadata">
**설명:** 실제 셀 값 없이 워크시트에서 사용된 범위의 메타데이터(주소 및 크기)만 반환합니다. 대용량 파일에서 데이터를 청크로 읽기 전에 스프레드시트 크기를 파악하는 데 이상적입니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 읽을 워크시트(탭)의 이름. get_excel_worksheets에서 가져오세요. 대소문자를 구분합니다.
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_used_range">
**설명:** SharePoint에 저장된 워크시트에서 데이터가 포함된 모든 셀을 가져옵니다. 2MB보다 큰 파일에는 사용하지 마세요. 대용량 파일의 경우 먼저 get_excel_used_range_metadata를 사용한 다음 get_excel_range_data로 작은 청크로 읽으세요.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 읽을 워크시트(탭)의 이름. get_excel_worksheets에서 가져오세요. 대소문자를 구분합니다.
- `select` (string, 선택사항): 반환할 속성의 쉼표로 구분된 목록 (예: 'address,values,formulas,numberFormat,text,rowCount,columnCount').
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_cell">
**설명:** SharePoint의 Excel 파일에서 행과 열 인덱스로 단일 셀의 값을 가져옵니다. 인덱스는 0 기반입니다 (행 0 = Excel 행 1, 열 0 = 열 A).
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 워크시트(탭)의 이름. get_excel_worksheets에서 가져오세요. 대소문자를 구분합니다.
- `row` (integer, 필수): 0 기반 행 인덱스 (행 0 = Excel 행 1). 유효 범위: 0-1048575
- `column` (integer, 필수): 0 기반 열 인덱스 (열 0 = A, 열 1 = B). 유효 범위: 0-16383
- `select` (string, 선택사항): 반환할 속성의 쉼표로 구분된 목록 (예: 'address,values,formulas,numberFormat,text').
</Accordion>
<Accordion title="microsoft_sharepoint/add_excel_table">
**설명:** 셀 범위를 필터링, 정렬 및 구조화된 데이터 기능이 있는 서식이 지정된 Excel 테이블로 변환합니다. 테이블을 만들면 add_excel_table_row로 데이터를 추가할 수 있습니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 데이터 범위가 포함된 워크시트의 이름. get_excel_worksheets에서 가져오세요.
- `range` (string, 필수): 헤더와 데이터를 포함하여 테이블로 변환할 셀 범위 (예: 'A1:D10'에서 A1:D1은 열 헤더).
- `has_headers` (boolean, 선택사항): 첫 번째 행에 열 헤더가 포함되어 있으면 true로 설정. 기본값: true
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_tables">
**설명:** SharePoint에 저장된 특정 Excel 워크시트의 모든 테이블을 나열합니다. id, name, showHeaders 및 showTotals를 포함한 테이블 속성을 반환합니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 테이블을 가져올 워크시트의 이름. get_excel_worksheets에서 가져오세요.
</Accordion>
<Accordion title="microsoft_sharepoint/add_excel_table_row">
**설명:** SharePoint 파일의 Excel 테이블 끝에 새 행을 추가합니다. values 배열은 테이블의 열 수와 같은 수의 요소를 가져야 합니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 테이블이 포함된 워크시트의 이름. get_excel_worksheets에서 가져오세요.
- `table_name` (string, 필수): 행을 추가할 테이블의 이름 (예: 'Table1'). get_excel_tables에서 가져오세요. 대소문자를 구분합니다.
- `values` (array, 필수): 새 행의 셀 값 배열로 테이블 순서대로 열당 하나씩 (예: ["John Doe", "john@example.com", 25]).
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_table_data">
**설명:** SharePoint 파일의 Excel 테이블에서 모든 행을 데이터 범위로 가져옵니다. 정확한 범위를 알 필요가 없으므로 구조화된 테이블 작업 시 get_excel_range_data보다 쉽습니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 테이블이 포함된 워크시트의 이름. get_excel_worksheets에서 가져오세요.
- `table_name` (string, 필수): 데이터를 가져올 테이블의 이름 (예: 'Table1'). get_excel_tables에서 가져오세요. 대소문자를 구분합니다.
- `select` (string, 선택사항): 반환할 속성의 쉼표로 구분된 목록 (예: 'address,values,formulas,numberFormat,text').
</Accordion>
<Accordion title="microsoft_sharepoint/create_excel_chart">
**설명:** SharePoint에 저장된 Excel 워크시트에 데이터 범위에서 차트 시각화를 만듭니다. 차트는 워크시트에 포함됩니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 차트를 만들 워크시트의 이름. get_excel_worksheets에서 가져오세요.
- `chart_type` (string, 필수): 차트 유형 (예: 'ColumnClustered', 'ColumnStacked', 'Line', 'LineMarkers', 'Pie', 'Bar', 'BarClustered', 'Area', 'Scatter', 'Doughnut').
- `source_data` (string, 필수): 헤더를 포함한 A1 표기법의 차트 데이터 범위 (예: 'A1:B10').
- `series_by` (string, 선택사항): 데이터 계열 구성 방법: 'Auto', 'Columns' 또는 'Rows'. 기본값: 'Auto'
</Accordion>
<Accordion title="microsoft_sharepoint/list_excel_charts">
**설명:** SharePoint에 저장된 Excel 워크시트에 포함된 모든 차트를 나열합니다. id, name, chartType, height, width 및 position을 포함한 차트 속성을 반환합니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 차트를 나열할 워크시트의 이름. get_excel_worksheets에서 가져오세요.
</Accordion>
<Accordion title="microsoft_sharepoint/delete_excel_worksheet">
**설명:** SharePoint에 저장된 Excel 통합 문서에서 워크시트(탭)와 모든 내용을 영구적으로 제거합니다. 실행 취소할 수 없습니다. 통합 문서에는 최소 하나의 워크시트가 있어야 합니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 삭제할 워크시트의 이름. 대소문자를 구분합니다. 이 시트의 모든 데이터, 테이블 및 차트가 영구적으로 제거됩니다.
</Accordion>
<Accordion title="microsoft_sharepoint/delete_excel_table">
**설명:** SharePoint의 Excel 워크시트에서 테이블을 제거합니다. 테이블 구조(필터링, 서식, 테이블 기능)는 삭제되지만 기본 셀 데이터는 보존됩니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
- `worksheet_name` (string, 필수): 테이블이 포함된 워크시트의 이름. get_excel_worksheets에서 가져오세요.
- `table_name` (string, 필수): 삭제할 테이블의 이름 (예: 'Table1'). get_excel_tables에서 가져오세요. 테이블 삭제 후에도 셀의 데이터는 유지됩니다.
</Accordion>
<Accordion title="microsoft_sharepoint/list_excel_names">
**설명:** SharePoint에 저장된 Excel 통합 문서에 정의된 모든 명명된 범위를 가져옵니다. 명명된 범위는 셀 범위에 대한 사용자 정의 레이블입니다 (예: 'SalesData'는 A1:D100을 가리킴).
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Excel 파일의 고유 식별자. list_files 또는 search_files에서 가져오세요.
</Accordion>
<Accordion title="microsoft_sharepoint/get_word_document_content">
**설명:** SharePoint 문서 라이브러리에 저장된 Word 문서(.docx)에서 텍스트 내용을 다운로드하고 추출합니다. SharePoint에서 Word 문서를 읽는 권장 방법입니다.
**매개변수:**
- `site_id` (string, 필수): get_sites에서 가져온 전체 SharePoint 사이트 식별자.
- `drive_id` (string, 필수): 문서 라이브러리의 ID. 먼저 get_drives를 호출하여 유효한 드라이브 ID를 가져오세요.
- `item_id` (string, 필수): SharePoint에 있는 Word 문서(.docx)의 고유 식별자. list_files 또는 search_files에서 가져오세요.
</Accordion>
</AccordionGroup>

View File

@@ -107,6 +107,86 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `join_web_url` (string, 필수): 검색할 회의의 웹 참가 URL.
</Accordion>
<Accordion title="microsoft_teams/search_online_meetings_by_meeting_id">
**설명:** 외부 Meeting ID로 온라인 회의를 검색합니다.
**매개변수:**
- `join_meeting_id` (string, 필수): 참석자가 참가할 때 사용하는 회의 ID(숫자 코드). 회의 초대에 표시되는 joinMeetingId이며, Graph API meeting id가 아닙니다.
</Accordion>
<Accordion title="microsoft_teams/get_meeting">
**설명:** 특정 온라인 회의의 세부 정보를 가져옵니다.
**매개변수:**
- `meeting_id` (string, 필수): Graph API 회의 ID(긴 영숫자 문자열). create_meeting 또는 search_online_meetings 작업에서 얻을 수 있습니다. 숫자 joinMeetingId와 다릅니다.
</Accordion>
<Accordion title="microsoft_teams/get_team_members">
**설명:** 특정 팀의 멤버를 가져옵니다.
**매개변수:**
- `team_id` (string, 필수): 팀의 고유 식별자. get_teams 작업에서 얻을 수 있습니다.
- `top` (integer, 선택사항): 페이지당 검색할 멤버 수 (1-999). 기본값: 100.
- `skip_token` (string, 선택사항): 이전 응답의 페이지네이션 토큰. 응답에 @odata.nextLink가 포함된 경우 $skiptoken 매개변수 값을 추출하여 여기에 전달하면 다음 페이지 결과를 가져올 수 있습니다.
</Accordion>
<Accordion title="microsoft_teams/create_channel">
**설명:** 팀에 새 채널을 만듭니다.
**매개변수:**
- `team_id` (string, 필수): 팀의 고유 식별자. get_teams 작업에서 얻을 수 있습니다.
- `display_name` (string, 필수): Teams에 표시되는 채널 이름. 팀 내에서 고유해야 합니다. 최대 50자.
- `description` (string, 선택사항): 채널 목적을 설명하는 선택적 설명. 채널 세부 정보에 표시됩니다. 최대 1024자.
- `membership_type` (string, 선택사항): 채널 가시성. 옵션: standard, private. "standard" = 모든 팀 멤버에게 표시, "private" = 명시적으로 추가된 멤버에게만 표시. 기본값: standard.
</Accordion>
<Accordion title="microsoft_teams/get_message_replies">
**설명:** 채널의 특정 메시지에 대한 회신을 가져옵니다.
**매개변수:**
- `team_id` (string, 필수): 팀의 고유 식별자. get_teams 작업에서 얻을 수 있습니다.
- `channel_id` (string, 필수): 채널의 고유 식별자. get_channels 작업에서 얻을 수 있습니다.
- `message_id` (string, 필수): 상위 메시지의 고유 식별자. get_messages 작업에서 얻을 수 있습니다.
- `top` (integer, 선택사항): 페이지당 검색할 회신 수 (1-50). 기본값: 50.
- `skip_token` (string, 선택사항): 이전 응답의 페이지네이션 토큰. 응답에 @odata.nextLink가 포함된 경우 $skiptoken 매개변수 값을 추출하여 여기에 전달하면 다음 페이지 결과를 가져올 수 있습니다.
</Accordion>
<Accordion title="microsoft_teams/reply_to_message">
**설명:** Teams 채널의 메시지에 회신합니다.
**매개변수:**
- `team_id` (string, 필수): 팀의 고유 식별자. get_teams 작업에서 얻을 수 있습니다.
- `channel_id` (string, 필수): 채널의 고유 식별자. get_channels 작업에서 얻을 수 있습니다.
- `message_id` (string, 필수): 회신할 메시지의 고유 식별자. get_messages 작업에서 얻을 수 있습니다.
- `message` (string, 필수): 회신 내용. HTML의 경우 서식 태그 포함. 텍스트의 경우 일반 텍스트만.
- `content_type` (string, 선택사항): 콘텐츠 형식. 옵션: html, text. "text"는 일반 텍스트, "html"은 서식이 있는 리치 텍스트. 기본값: text.
</Accordion>
<Accordion title="microsoft_teams/update_meeting">
**설명:** 기존 온라인 회의를 업데이트합니다.
**매개변수:**
- `meeting_id` (string, 필수): 회의의 고유 식별자. create_meeting 또는 search_online_meetings 작업에서 얻을 수 있습니다.
- `subject` (string, 선택사항): 새 회의 제목.
- `startDateTime` (string, 선택사항): 시간대가 포함된 ISO 8601 형식의 새 시작 시간. 예: "2024-01-20T10:00:00-08:00".
- `endDateTime` (string, 선택사항): 시간대가 포함된 ISO 8601 형식의 새 종료 시간.
</Accordion>
<Accordion title="microsoft_teams/delete_meeting">
**설명:** 온라인 회의를 삭제합니다.
**매개변수:**
- `meeting_id` (string, 필수): 삭제할 회의의 고유 식별자. create_meeting 또는 search_online_meetings 작업에서 얻을 수 있습니다.
</Accordion>
</AccordionGroup>
## 사용 예제
@@ -140,6 +220,62 @@ crew = Crew(
crew.kickoff()
```
### 메시징 및 커뮤니케이션
```python
from crewai import Agent, Task, Crew
# 메시징에 특화된 에이전트 생성
messenger = Agent(
role="Teams 메신저",
goal="Teams 채널에서 메시지 전송 및 검색",
backstory="팀 커뮤니케이션 및 메시지 관리에 능숙한 AI 어시스턴트.",
apps=['microsoft_teams/send_message', 'microsoft_teams/get_messages']
)
# 메시지 전송 및 최근 메시지 검색 작업
messaging_task = Task(
description="'your_team_id' 팀의 General 채널에 'Hello team! This is an automated update from our AI assistant.' 메시지를 보낸 다음 해당 채널의 최근 10개 메시지를 검색하세요.",
agent=messenger,
expected_output="메시지가 성공적으로 전송되고 최근 메시지가 검색됨."
)
crew = Crew(
agents=[messenger],
tasks=[messaging_task]
)
crew.kickoff()
```
### 회의 관리
```python
from crewai import Agent, Task, Crew
# 회의 관리를 위한 에이전트 생성
meeting_scheduler = Agent(
role="회의 스케줄러",
goal="Teams 회의 생성 및 관리",
backstory="회의 일정 관리 및 정리를 담당하는 AI 어시스턴트.",
apps=['microsoft_teams/create_meeting', 'microsoft_teams/search_online_meetings_by_join_url']
)
# 회의 생성 작업
schedule_meeting_task = Task(
description="내일 오전 10시에 1시간 동안 진행되는 '주간 팀 동기화' 제목의 Teams 회의를 생성하세요 (시간대가 포함된 적절한 ISO 8601 형식 사용).",
agent=meeting_scheduler,
expected_output="회의 세부 정보와 함께 Teams 회의가 성공적으로 생성됨."
)
crew = Crew(
agents=[meeting_scheduler],
tasks=[schedule_meeting_task]
)
crew.kickoff()
```
## 문제 해결
### 일반적인 문제
@@ -148,11 +284,35 @@ crew.kickoff()
- Microsoft 계정이 Teams 액세스에 필요한 권한을 가지고 있는지 확인하세요.
- 필요한 범위: `Team.ReadBasic.All`, `Channel.ReadBasic.All`, `ChannelMessage.Send`, `ChannelMessage.Read.All`, `OnlineMeetings.ReadWrite`, `OnlineMeetings.Read`.
- OAuth 연결에 필요한 모든 범위가 포함되어 있는지 확인하세요.
**팀 및 채널 액세스**
- 액세스하려는 팀의 멤버인지 확인하세요.
- 팀 및 채널 ID가 올바른지 다시 확인하세요.
- 팀 및 채널 ID는 `get_teams` 및 `get_channels` 작업을 사용하여 얻을 수 있습니다.
**메시지 전송 문제**
- `send_message`에 `team_id`, `channel_id`, `message`가 제공되는지 확인하세요.
- 지정된 채널에 메시지를 보낼 권한이 있는지 확인하세요.
- 메시지 형식에 따라 적절한 `content_type`(text 또는 html)을 선택하세요.
**회의 생성**
- `subject`, `startDateTime`, `endDateTime`이 제공되는지 확인하세요.
- 날짜/시간 필드에 시간대가 포함된 적절한 ISO 8601 형식을 사용하세요 (예: '2024-01-20T10:00:00-08:00').
- 회의 시간이 미래인지 확인하세요.
**메시지 검색 제한**
- `get_messages` 작업은 요청당 최대 50개 메시지만 검색할 수 있습니다.
- 메시지는 역시간순(최신순)으로 반환됩니다.
**회의 검색**
- `search_online_meetings_by_join_url`의 경우 참가 URL이 정확하고 올바르게 형식화되어 있는지 확인하세요.
- URL은 완전한 Teams 회의 참가 URL이어야 합니다.
### 도움 받기

View File

@@ -97,6 +97,26 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=your_enterprise_token
- `file_id` (string, 필수): 삭제할 문서의 ID.
</Accordion>
<Accordion title="microsoft_word/copy_document">
**설명:** OneDrive의 새 위치에 문서를 복사합니다.
**매개변수:**
- `file_id` (string, 필수): 복사할 문서의 ID.
- `name` (string, 선택사항): 복사된 문서의 새 이름.
- `parent_id` (string, 선택사항): 대상 폴더의 ID (기본값: 루트).
</Accordion>
<Accordion title="microsoft_word/move_document">
**설명:** OneDrive의 새 위치로 문서를 이동합니다.
**매개변수:**
- `file_id` (string, 필수): 이동할 문서의 ID.
- `parent_id` (string, 필수): 대상 폴더의 ID.
- `name` (string, 선택사항): 이동된 문서의 새 이름.
</Accordion>
</AccordionGroup>
## 사용 예제

View File

@@ -200,6 +200,25 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
- `clientData` (array, opcional): Dados específicos do cliente. Cada item é um objeto com `key` (string) e `value` (string).
</Accordion>
<Accordion title="google_contacts/update_contact_group">
**Descrição:** Atualizar informações de um grupo de contatos.
**Parâmetros:**
- `resourceName` (string, obrigatório): O nome do recurso do grupo de contatos (ex: 'contactGroups/myContactGroup').
- `name` (string, obrigatório): O nome do grupo de contatos.
- `clientData` (array, opcional): Dados específicos do cliente. Cada item é um objeto com `key` (string) e `value` (string).
</Accordion>
<Accordion title="google_contacts/delete_contact_group">
**Descrição:** Excluir um grupo de contatos.
**Parâmetros:**
- `resourceName` (string, obrigatório): O nome do recurso do grupo de contatos a excluir (ex: 'contactGroups/myContactGroup').
- `deleteContacts` (boolean, opcional): Se os contatos do grupo também devem ser excluídos. Padrão: false
</Accordion>
</AccordionGroup>
## Exemplos de Uso

View File

@@ -131,6 +131,297 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
- `endIndex` (integer, obrigatório): O índice final do intervalo.
</Accordion>
<Accordion title="google_docs/create_document_with_content">
**Descrição:** Criar um novo documento do Google com conteúdo em uma única ação.
**Parâmetros:**
- `title` (string, obrigatório): O título para o novo documento. Aparece no topo do documento e no Google Drive.
- `content` (string, opcional): O conteúdo de texto a inserir no documento. Use `\n` para novos parágrafos.
</Accordion>
<Accordion title="google_docs/append_text">
**Descrição:** Adicionar texto ao final de um documento do Google. Insere automaticamente no final do documento sem necessidade de especificar um índice.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento obtido da resposta de create_document ou URL.
- `text` (string, obrigatório): Texto a adicionar ao final do documento. Use `\n` para novos parágrafos.
</Accordion>
<Accordion title="google_docs/set_text_bold">
**Descrição:** Aplicar ou remover formatação de negrito em texto de um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial do texto a formatar.
- `endIndex` (integer, obrigatório): Posição final do texto a formatar (exclusivo).
- `bold` (boolean, obrigatório): Defina `true` para aplicar negrito, `false` para remover negrito.
</Accordion>
<Accordion title="google_docs/set_text_italic">
**Descrição:** Aplicar ou remover formatação de itálico em texto de um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial do texto a formatar.
- `endIndex` (integer, obrigatório): Posição final do texto a formatar (exclusivo).
- `italic` (boolean, obrigatório): Defina `true` para aplicar itálico, `false` para remover itálico.
</Accordion>
<Accordion title="google_docs/set_text_underline">
**Descrição:** Adicionar ou remover formatação de sublinhado em texto de um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial do texto a formatar.
- `endIndex` (integer, obrigatório): Posição final do texto a formatar (exclusivo).
- `underline` (boolean, obrigatório): Defina `true` para sublinhar, `false` para remover sublinhado.
</Accordion>
<Accordion title="google_docs/set_text_strikethrough">
**Descrição:** Adicionar ou remover formatação de tachado em texto de um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial do texto a formatar.
- `endIndex` (integer, obrigatório): Posição final do texto a formatar (exclusivo).
- `strikethrough` (boolean, obrigatório): Defina `true` para adicionar tachado, `false` para remover.
</Accordion>
<Accordion title="google_docs/set_font_size">
**Descrição:** Alterar o tamanho da fonte do texto em um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial do texto a formatar.
- `endIndex` (integer, obrigatório): Posição final do texto a formatar (exclusivo).
- `fontSize` (number, obrigatório): Tamanho da fonte em pontos. Tamanhos comuns: 10, 11, 12, 14, 16, 18, 24, 36.
</Accordion>
<Accordion title="google_docs/set_text_color">
**Descrição:** Alterar a cor do texto usando valores RGB (escala 0-1) em um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial do texto a formatar.
- `endIndex` (integer, obrigatório): Posição final do texto a formatar (exclusivo).
- `red` (number, obrigatório): Componente vermelho (0-1). Exemplo: `1` para vermelho total.
- `green` (number, obrigatório): Componente verde (0-1). Exemplo: `0.5` para metade verde.
- `blue` (number, obrigatório): Componente azul (0-1). Exemplo: `0` para sem azul.
</Accordion>
<Accordion title="google_docs/create_hyperlink">
**Descrição:** Transformar texto existente em um hyperlink clicável em um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial do texto a transformar em link.
- `endIndex` (integer, obrigatório): Posição final do texto a transformar em link (exclusivo).
- `url` (string, obrigatório): A URL para a qual o link deve apontar. Exemplo: `"https://example.com"`.
</Accordion>
<Accordion title="google_docs/apply_heading_style">
**Descrição:** Aplicar um estilo de título ou parágrafo a um intervalo de texto em um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial do(s) parágrafo(s) a estilizar.
- `endIndex` (integer, obrigatório): Posição final do(s) parágrafo(s) a estilizar.
- `style` (string, obrigatório): O estilo a aplicar. Opções: `NORMAL_TEXT`, `TITLE`, `SUBTITLE`, `HEADING_1`, `HEADING_2`, `HEADING_3`, `HEADING_4`, `HEADING_5`, `HEADING_6`.
</Accordion>
<Accordion title="google_docs/set_paragraph_alignment">
**Descrição:** Definir o alinhamento de texto para parágrafos em um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial do(s) parágrafo(s) a alinhar.
- `endIndex` (integer, obrigatório): Posição final do(s) parágrafo(s) a alinhar.
- `alignment` (string, obrigatório): Alinhamento do texto. Opções: `START` (esquerda), `CENTER`, `END` (direita), `JUSTIFIED`.
</Accordion>
<Accordion title="google_docs/set_line_spacing">
**Descrição:** Definir o espaçamento entre linhas para parágrafos em um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial do(s) parágrafo(s).
- `endIndex` (integer, obrigatório): Posição final do(s) parágrafo(s).
- `lineSpacing` (number, obrigatório): Espaçamento entre linhas como porcentagem. `100` = simples, `115` = 1.15x, `150` = 1.5x, `200` = duplo.
</Accordion>
<Accordion title="google_docs/create_paragraph_bullets">
**Descrição:** Converter parágrafos em uma lista com marcadores ou numerada em um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial dos parágrafos a converter em lista.
- `endIndex` (integer, obrigatório): Posição final dos parágrafos a converter em lista.
- `bulletPreset` (string, obrigatório): Estilo de marcadores/numeração. Opções: `BULLET_DISC_CIRCLE_SQUARE`, `BULLET_DIAMONDX_ARROW3D_SQUARE`, `BULLET_CHECKBOX`, `BULLET_ARROW_DIAMOND_DISC`, `BULLET_STAR_CIRCLE_SQUARE`, `NUMBERED_DECIMAL_ALPHA_ROMAN`, `NUMBERED_DECIMAL_ALPHA_ROMAN_PARENS`, `NUMBERED_DECIMAL_NESTED`, `NUMBERED_UPPERALPHA_ALPHA_ROMAN`, `NUMBERED_UPPERROMAN_UPPERALPHA_DECIMAL`.
</Accordion>
<Accordion title="google_docs/delete_paragraph_bullets">
**Descrição:** Remover marcadores ou numeração de parágrafos em um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `startIndex` (integer, obrigatório): Posição inicial dos parágrafos de lista.
- `endIndex` (integer, obrigatório): Posição final dos parágrafos de lista.
</Accordion>
<Accordion title="google_docs/insert_table_with_content">
**Descrição:** Inserir uma tabela com conteúdo em um documento do Google em uma única ação. Forneça o conteúdo como um array 2D.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `rows` (integer, obrigatório): Número de linhas na tabela.
- `columns` (integer, obrigatório): Número de colunas na tabela.
- `index` (integer, opcional): Posição para inserir a tabela. Se não fornecido, a tabela é inserida no final do documento.
- `content` (array, obrigatório): Conteúdo da tabela como um array 2D. Cada array interno é uma linha. Exemplo: `[["Ano", "Receita"], ["2023", "$43B"], ["2024", "$45B"]]`.
</Accordion>
<Accordion title="google_docs/insert_table_row">
**Descrição:** Inserir uma nova linha acima ou abaixo de uma célula de referência em uma tabela existente.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `tableStartIndex` (integer, obrigatório): O índice inicial da tabela. Obtenha de get_document.
- `rowIndex` (integer, obrigatório): Índice da linha (baseado em 0) da célula de referência.
- `columnIndex` (integer, opcional): Índice da coluna (baseado em 0) da célula de referência. Padrão: `0`.
- `insertBelow` (boolean, opcional): Se `true`, insere abaixo da linha de referência. Se `false`, insere acima. Padrão: `true`.
</Accordion>
<Accordion title="google_docs/insert_table_column">
**Descrição:** Inserir uma nova coluna à esquerda ou à direita de uma célula de referência em uma tabela existente.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `tableStartIndex` (integer, obrigatório): O índice inicial da tabela.
- `rowIndex` (integer, opcional): Índice da linha (baseado em 0) da célula de referência. Padrão: `0`.
- `columnIndex` (integer, obrigatório): Índice da coluna (baseado em 0) da célula de referência.
- `insertRight` (boolean, opcional): Se `true`, insere à direita. Se `false`, insere à esquerda. Padrão: `true`.
</Accordion>
<Accordion title="google_docs/delete_table_row">
**Descrição:** Excluir uma linha de uma tabela existente em um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `tableStartIndex` (integer, obrigatório): O índice inicial da tabela.
- `rowIndex` (integer, obrigatório): Índice da linha (baseado em 0) a excluir.
- `columnIndex` (integer, opcional): Índice da coluna (baseado em 0) de qualquer célula na linha. Padrão: `0`.
</Accordion>
<Accordion title="google_docs/delete_table_column">
**Descrição:** Excluir uma coluna de uma tabela existente em um documento do Google.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `tableStartIndex` (integer, obrigatório): O índice inicial da tabela.
- `rowIndex` (integer, opcional): Índice da linha (baseado em 0) de qualquer célula na coluna. Padrão: `0`.
- `columnIndex` (integer, obrigatório): Índice da coluna (baseado em 0) a excluir.
</Accordion>
<Accordion title="google_docs/merge_table_cells">
**Descrição:** Mesclar um intervalo de células de tabela em uma única célula. O conteúdo de todas as células é preservado.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `tableStartIndex` (integer, obrigatório): O índice inicial da tabela.
- `rowIndex` (integer, obrigatório): Índice da linha inicial (baseado em 0) para a mesclagem.
- `columnIndex` (integer, obrigatório): Índice da coluna inicial (baseado em 0) para a mesclagem.
- `rowSpan` (integer, obrigatório): Número de linhas a mesclar.
- `columnSpan` (integer, obrigatório): Número de colunas a mesclar.
</Accordion>
<Accordion title="google_docs/unmerge_table_cells">
**Descrição:** Desfazer a mesclagem de células de tabela previamente mescladas, retornando-as a células individuais.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `tableStartIndex` (integer, obrigatório): O índice inicial da tabela.
- `rowIndex` (integer, obrigatório): Índice da linha (baseado em 0) da célula mesclada.
- `columnIndex` (integer, obrigatório): Índice da coluna (baseado em 0) da célula mesclada.
- `rowSpan` (integer, obrigatório): Número de linhas que a célula mesclada abrange.
- `columnSpan` (integer, obrigatório): Número de colunas que a célula mesclada abrange.
</Accordion>
<Accordion title="google_docs/insert_inline_image">
**Descrição:** Inserir uma imagem de uma URL pública em um documento do Google. A imagem deve ser publicamente acessível, ter menos de 50MB e estar no formato PNG/JPEG/GIF.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `uri` (string, obrigatório): URL pública da imagem. Deve ser acessível sem autenticação.
- `index` (integer, opcional): Posição para inserir a imagem. Se não fornecido, a imagem é inserida no final do documento. Padrão: `1`.
</Accordion>
<Accordion title="google_docs/insert_section_break">
**Descrição:** Inserir uma quebra de seção para criar seções de documento com formatação diferente.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `index` (integer, obrigatório): Posição para inserir a quebra de seção.
- `sectionType` (string, obrigatório): O tipo de quebra de seção. Opções: `CONTINUOUS` (permanece na mesma página), `NEXT_PAGE` (inicia uma nova página).
</Accordion>
<Accordion title="google_docs/create_header">
**Descrição:** Criar um cabeçalho para o documento. Retorna um headerId que pode ser usado com insert_text para adicionar conteúdo ao cabeçalho.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `type` (string, opcional): Tipo de cabeçalho. Opções: `DEFAULT`. Padrão: `DEFAULT`.
</Accordion>
<Accordion title="google_docs/create_footer">
**Descrição:** Criar um rodapé para o documento. Retorna um footerId que pode ser usado com insert_text para adicionar conteúdo ao rodapé.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `type` (string, opcional): Tipo de rodapé. Opções: `DEFAULT`. Padrão: `DEFAULT`.
</Accordion>
<Accordion title="google_docs/delete_header">
**Descrição:** Excluir um cabeçalho do documento. Use get_document para encontrar o headerId.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `headerId` (string, obrigatório): O ID do cabeçalho a excluir. Obtenha da resposta de get_document.
</Accordion>
<Accordion title="google_docs/delete_footer">
**Descrição:** Excluir um rodapé do documento. Use get_document para encontrar o footerId.
**Parâmetros:**
- `documentId` (string, obrigatório): O ID do documento.
- `footerId` (string, obrigatório): O ID do rodapé a excluir. Obtenha da resposta de get_document.
</Accordion>
</AccordionGroup>
## Exemplos de Uso

View File

@@ -61,6 +61,22 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
</Accordion>
<Accordion title="google_slides/get_presentation_metadata">
**Descrição:** Obter metadados leves de uma apresentação (título, número de slides, IDs dos slides). Use isso primeiro antes de recuperar o conteúdo completo.
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação a ser recuperada.
</Accordion>
<Accordion title="google_slides/get_presentation_text">
**Descrição:** Extrair todo o conteúdo de texto de uma apresentação. Retorna IDs dos slides e texto de formas e tabelas apenas (sem formatação).
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
</Accordion>
<Accordion title="google_slides/get_presentation">
**Descrição:** Recupera uma apresentação por ID.
@@ -80,6 +96,15 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
</Accordion>
<Accordion title="google_slides/get_slide_text">
**Descrição:** Extrair conteúdo de texto de um único slide. Retorna apenas texto de formas e tabelas (sem formatação ou estilo).
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `pageObjectId` (string, obrigatório): O ID do slide/página para obter o texto.
</Accordion>
<Accordion title="google_slides/get_page">
**Descrição:** Recupera uma página específica por seu ID.
@@ -98,6 +123,120 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
</Accordion>
<Accordion title="google_slides/create_slide">
**Descrição:** Adicionar um slide em branco adicional a uma apresentação. Novas apresentações já possuem um slide em branco - verifique get_presentation_metadata primeiro. Para slides com áreas de título/corpo, use create_slide_with_layout.
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `insertionIndex` (integer, opcional): Onde inserir o slide (baseado em 0). Se omitido, adiciona no final.
</Accordion>
<Accordion title="google_slides/create_slide_with_layout">
**Descrição:** Criar um slide com layout predefinido contendo áreas de espaço reservado para título, corpo, etc. Melhor que create_slide para conteúdo estruturado. Após criar, use get_page para encontrar os IDs de espaço reservado, depois insira texto neles.
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `layout` (string, obrigatório): Tipo de layout. Um de: `BLANK`, `TITLE`, `TITLE_AND_BODY`, `TITLE_AND_TWO_COLUMNS`, `TITLE_ONLY`, `SECTION_HEADER`, `ONE_COLUMN_TEXT`, `MAIN_POINT`, `BIG_NUMBER`. TITLE_AND_BODY é melhor para título+descrição. TITLE para slides apenas com título. SECTION_HEADER para divisores de seção.
- `insertionIndex` (integer, opcional): Onde inserir (baseado em 0). Se omitido, adiciona no final.
</Accordion>
<Accordion title="google_slides/create_text_box">
**Descrição:** Criar uma caixa de texto em um slide com conteúdo. Use para títulos, descrições, parágrafos - não para tabelas. Opcionalmente especifique posição (x, y) e tamanho (width, height) em unidades EMU (914400 EMU = 1 polegada).
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `slideId` (string, obrigatório): O ID do slide para adicionar a caixa de texto.
- `text` (string, obrigatório): O conteúdo de texto da caixa de texto.
- `x` (integer, opcional): Posição X em EMU (914400 = 1 polegada). Padrão: 914400 (1 polegada da esquerda).
- `y` (integer, opcional): Posição Y em EMU (914400 = 1 polegada). Padrão: 914400 (1 polegada do topo).
- `width` (integer, opcional): Largura em EMU. Padrão: 7315200 (~8 polegadas).
- `height` (integer, opcional): Altura em EMU. Padrão: 914400 (~1 polegada).
</Accordion>
<Accordion title="google_slides/delete_slide">
**Descrição:** Remover um slide de uma apresentação. Use get_presentation primeiro para encontrar o ID do slide.
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `slideId` (string, obrigatório): O ID do objeto do slide a excluir. Obtenha de get_presentation.
</Accordion>
<Accordion title="google_slides/duplicate_slide">
**Descrição:** Criar uma cópia de um slide existente. A duplicata é inserida imediatamente após o original.
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `slideId` (string, obrigatório): O ID do objeto do slide a duplicar. Obtenha de get_presentation.
</Accordion>
<Accordion title="google_slides/move_slides">
**Descrição:** Reordenar slides movendo-os para uma nova posição. Os IDs dos slides devem estar na ordem atual da apresentação (sem duplicatas).
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `slideIds` (array de strings, obrigatório): Array de IDs dos slides a mover. Obrigatoriamente na ordem atual da apresentação.
- `insertionIndex` (integer, obrigatório): Posição de destino (baseado em 0). 0 = início, número de slides = final.
</Accordion>
<Accordion title="google_slides/insert_youtube_video">
**Descrição:** Incorporar um vídeo do YouTube em um slide. O ID do vídeo é o valor após "v=" nas URLs do YouTube (ex: para youtube.com/watch?v=abc123, use "abc123").
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `slideId` (string, obrigatório): O ID do slide para adicionar o vídeo. Obtenha de get_presentation.
- `videoId` (string, obrigatório): O ID do vídeo do YouTube (o valor após v= na URL).
</Accordion>
<Accordion title="google_slides/insert_drive_video">
**Descrição:** Incorporar um vídeo do Google Drive em um slide. O ID do arquivo pode ser encontrado na URL do arquivo no Drive.
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `slideId` (string, obrigatório): O ID do slide para adicionar o vídeo. Obtenha de get_presentation.
- `fileId` (string, obrigatório): O ID do arquivo do Google Drive do vídeo.
</Accordion>
<Accordion title="google_slides/set_slide_background_image">
**Descrição:** Definir uma imagem de fundo para um slide. A URL da imagem deve ser publicamente acessível.
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `slideId` (string, obrigatório): O ID do slide para definir o fundo. Obtenha de get_presentation.
- `imageUrl` (string, obrigatório): URL publicamente acessível da imagem a usar como fundo.
</Accordion>
<Accordion title="google_slides/create_table">
**Descrição:** Criar uma tabela vazia em um slide. Para criar uma tabela com conteúdo, use create_table_with_content.
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `slideId` (string, obrigatório): O ID do slide para adicionar a tabela. Obtenha de get_presentation.
- `rows` (integer, obrigatório): Número de linhas na tabela.
- `columns` (integer, obrigatório): Número de colunas na tabela.
</Accordion>
<Accordion title="google_slides/create_table_with_content">
**Descrição:** Criar uma tabela com conteúdo em uma única ação. Forneça o conteúdo como uma matriz 2D onde cada array interno é uma linha. Exemplo: [["Cabeçalho1", "Cabeçalho2"], ["Linha1Col1", "Linha1Col2"]].
**Parâmetros:**
- `presentationId` (string, obrigatório): O ID da apresentação.
- `slideId` (string, obrigatório): O ID do slide para adicionar a tabela. Obtenha de get_presentation.
- `rows` (integer, obrigatório): Número de linhas na tabela.
- `columns` (integer, obrigatório): Número de colunas na tabela.
- `content` (array, obrigatório): Conteúdo da tabela como matriz 2D. Cada array interno é uma linha. Exemplo: [["Ano", "Receita"], ["2023", "$10M"]].
</Accordion>
<Accordion title="google_slides/import_data_from_sheet">
**Descrição:** Importa dados de uma planilha do Google para uma apresentação.

View File

@@ -148,6 +148,16 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
</Accordion>
<Accordion title="microsoft_excel/get_table_data">
**Descrição:** Obter dados de uma tabela específica em uma planilha do Excel.
**Parâmetros:**
- `file_id` (string, obrigatório): O ID do arquivo Excel.
- `worksheet_name` (string, obrigatório): Nome da planilha.
- `table_name` (string, obrigatório): Nome da tabela.
</Accordion>
<Accordion title="microsoft_excel/create_chart">
**Descrição:** Criar um gráfico em uma planilha do Excel.
@@ -180,6 +190,15 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
</Accordion>
<Accordion title="microsoft_excel/get_used_range_metadata">
**Descrição:** Obter os metadados do intervalo usado (apenas dimensões, sem dados) de uma planilha do Excel.
**Parâmetros:**
- `file_id` (string, obrigatório): O ID do arquivo Excel.
- `worksheet_name` (string, obrigatório): Nome da planilha.
</Accordion>
<Accordion title="microsoft_excel/list_charts">
**Descrição:** Obter todos os gráficos em uma planilha do Excel.

View File

@@ -150,6 +150,49 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
- `item_id` (string, obrigatório): O ID do arquivo.
</Accordion>
<Accordion title="microsoft_onedrive/list_files_by_path">
**Descrição:** Listar arquivos e pastas em um caminho específico do OneDrive.
**Parâmetros:**
- `folder_path` (string, obrigatório): O caminho da pasta (ex: 'Documents/Reports').
- `top` (integer, opcional): Número de itens a recuperar (máx 1000). Padrão: 50.
- `orderby` (string, opcional): Ordenar por campo (ex: "name asc", "lastModifiedDateTime desc"). Padrão: "name asc".
</Accordion>
<Accordion title="microsoft_onedrive/get_recent_files">
**Descrição:** Obter arquivos acessados recentemente no OneDrive.
**Parâmetros:**
- `top` (integer, opcional): Número de itens a recuperar (máx 200). Padrão: 25.
</Accordion>
<Accordion title="microsoft_onedrive/get_shared_with_me">
**Descrição:** Obter arquivos e pastas compartilhados com o usuário.
**Parâmetros:**
- `top` (integer, opcional): Número de itens a recuperar (máx 200). Padrão: 50.
- `orderby` (string, opcional): Ordenar por campo. Padrão: "name asc".
</Accordion>
<Accordion title="microsoft_onedrive/get_file_by_path">
**Descrição:** Obter informações sobre um arquivo ou pasta específica pelo caminho.
**Parâmetros:**
- `file_path` (string, obrigatório): O caminho do arquivo ou pasta (ex: 'Documents/report.docx').
</Accordion>
<Accordion title="microsoft_onedrive/download_file_by_path">
**Descrição:** Baixar um arquivo do OneDrive pelo seu caminho.
**Parâmetros:**
- `file_path` (string, obrigatório): O caminho do arquivo (ex: 'Documents/report.docx').
</Accordion>
</AccordionGroup>
## Exemplos de Uso

View File

@@ -132,6 +132,74 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
- `companyName` (string, opcional): Nome da empresa do contato.
</Accordion>
<Accordion title="microsoft_outlook/get_message">
**Descrição:** Obter uma mensagem de email específica por ID.
**Parâmetros:**
- `message_id` (string, obrigatório): O identificador único da mensagem. Obter pela ação get_messages.
- `select` (string, opcional): Lista separada por vírgulas de propriedades a retornar. Exemplo: "id,subject,body,from,receivedDateTime". Padrão: "id,subject,body,from,toRecipients,receivedDateTime".
</Accordion>
<Accordion title="microsoft_outlook/reply_to_email">
**Descrição:** Responder a uma mensagem de email.
**Parâmetros:**
- `message_id` (string, obrigatório): O identificador único da mensagem a responder. Obter pela ação get_messages.
- `comment` (string, obrigatório): O conteúdo da mensagem de resposta. Pode ser texto simples ou HTML. A mensagem original será citada abaixo deste conteúdo.
</Accordion>
<Accordion title="microsoft_outlook/forward_email">
**Descrição:** Encaminhar uma mensagem de email.
**Parâmetros:**
- `message_id` (string, obrigatório): O identificador único da mensagem a encaminhar. Obter pela ação get_messages.
- `to_recipients` (array, obrigatório): Array de endereços de email dos destinatários. Exemplo: ["john@example.com", "jane@example.com"].
- `comment` (string, opcional): Mensagem opcional a incluir acima do conteúdo encaminhado. Pode ser texto simples ou HTML.
</Accordion>
<Accordion title="microsoft_outlook/mark_message_read">
**Descrição:** Marcar uma mensagem como lida ou não lida.
**Parâmetros:**
- `message_id` (string, obrigatório): O identificador único da mensagem. Obter pela ação get_messages.
- `is_read` (boolean, obrigatório): Definir como true para marcar como lida, false para marcar como não lida.
</Accordion>
<Accordion title="microsoft_outlook/delete_message">
**Descrição:** Excluir uma mensagem de email.
**Parâmetros:**
- `message_id` (string, obrigatório): O identificador único da mensagem a excluir. Obter pela ação get_messages.
</Accordion>
<Accordion title="microsoft_outlook/update_event">
**Descrição:** Atualizar um evento de calendário existente.
**Parâmetros:**
- `event_id` (string, obrigatório): O identificador único do evento. Obter pela ação get_calendar_events.
- `subject` (string, opcional): Novo assunto/título do evento.
- `start_time` (string, opcional): Nova hora de início no formato ISO 8601 (ex: "2024-01-20T10:00:00"). OBRIGATÓRIO: Também deve fornecer start_timezone ao usar este campo.
- `start_timezone` (string, opcional): Fuso horário da hora de início. OBRIGATÓRIO ao atualizar start_time. Exemplos: "Pacific Standard Time", "Eastern Standard Time", "UTC".
- `end_time` (string, opcional): Nova hora de término no formato ISO 8601. OBRIGATÓRIO: Também deve fornecer end_timezone ao usar este campo.
- `end_timezone` (string, opcional): Fuso horário da hora de término. OBRIGATÓRIO ao atualizar end_time. Exemplos: "Pacific Standard Time", "Eastern Standard Time", "UTC".
- `location` (string, opcional): Novo local do evento.
- `body` (string, opcional): Novo corpo/descrição do evento. Suporta formatação HTML.
</Accordion>
<Accordion title="microsoft_outlook/delete_event">
**Descrição:** Excluir um evento de calendário.
**Parâmetros:**
- `event_id` (string, obrigatório): O identificador único do evento a excluir. Obter pela ação get_calendar_events.
</Accordion>
</AccordionGroup>
## Exemplos de Uso

View File

@@ -77,6 +77,17 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
</Accordion>
<Accordion title="microsoft_sharepoint/get_drives">
**Descrição:** Listar todas as bibliotecas de documentos (drives) em um site do SharePoint. Use isto para descobrir bibliotecas disponíveis antes de usar operações de arquivo.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `top` (integer, opcional): Número máximo de drives a retornar por página (1-999). Padrão: 100
- `skip_token` (string, opcional): Token de paginação de uma resposta anterior para buscar a próxima página de resultados.
- `select` (string, opcional): Lista de propriedades separadas por vírgula para retornar (ex: 'id,name,webUrl,driveType').
</Accordion>
<Accordion title="microsoft_sharepoint/get_site_lists">
**Descrição:** Obter todas as listas em um site do SharePoint.
@@ -145,20 +156,317 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
</Accordion>
<Accordion title="microsoft_sharepoint/get_drive_items">
**Descrição:** Obter arquivos e pastas de uma biblioteca de documentos do SharePoint.
<Accordion title="microsoft_sharepoint/list_files">
**Descrição:** Recuperar arquivos e pastas de uma biblioteca de documentos do SharePoint. Por padrão, lista a pasta raiz, mas você pode navegar em subpastas fornecendo um folder_id.
**Parâmetros:**
- `site_id` (string, obrigatório): O ID do site do SharePoint.
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `folder_id` (string, opcional): O ID da pasta para listar o conteúdo. Use 'root' para a pasta raiz, ou forneça um ID de pasta de uma chamada anterior de list_files. Padrão: 'root'
- `top` (integer, opcional): Número máximo de itens a retornar por página (1-1000). Padrão: 50
- `skip_token` (string, opcional): Token de paginação de uma resposta anterior para buscar a próxima página de resultados.
- `orderby` (string, opcional): Ordem de classificação dos resultados (ex: 'name asc', 'size desc', 'lastModifiedDateTime desc'). Padrão: 'name asc'
- `filter` (string, opcional): Filtro OData para restringir resultados (ex: 'file ne null' apenas para arquivos, 'folder ne null' apenas para pastas).
- `select` (string, opcional): Lista de campos separados por vírgula para retornar (ex: 'id,name,size,folder,file,webUrl,lastModifiedDateTime').
</Accordion>
<Accordion title="microsoft_sharepoint/delete_drive_item">
**Descrição:** Excluir um arquivo ou pasta da biblioteca de documentos do SharePoint.
<Accordion title="microsoft_sharepoint/delete_file">
**Descrição:** Excluir um arquivo ou pasta de uma biblioteca de documentos do SharePoint. Para pastas, todo o conteúdo é excluído recursivamente. Os itens são movidos para a lixeira do site.
**Parâmetros:**
- `site_id` (string, obrigatório): O ID do site do SharePoint.
- `item_id` (string, obrigatório): O ID do arquivo ou pasta a excluir.
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo ou pasta a excluir. Obtenha de list_files.
</Accordion>
<Accordion title="microsoft_sharepoint/list_files_by_path">
**Descrição:** Listar arquivos e pastas em uma pasta de biblioteca de documentos do SharePoint pelo caminho. Mais eficiente do que múltiplas chamadas list_files para navegação profunda.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `folder_path` (string, obrigatório): O caminho completo para a pasta sem barras iniciais/finais (ex: 'Documents', 'Reports/2024/Q1').
- `top` (integer, opcional): Número máximo de itens a retornar por página (1-1000). Padrão: 50
- `skip_token` (string, opcional): Token de paginação de uma resposta anterior para buscar a próxima página de resultados.
- `orderby` (string, opcional): Ordem de classificação dos resultados (ex: 'name asc', 'size desc'). Padrão: 'name asc'
- `select` (string, opcional): Lista de campos separados por vírgula para retornar (ex: 'id,name,size,folder,file,webUrl,lastModifiedDateTime').
</Accordion>
<Accordion title="microsoft_sharepoint/download_file">
**Descrição:** Baixar conteúdo bruto de um arquivo de uma biblioteca de documentos do SharePoint. Use apenas para arquivos de texto simples (.txt, .csv, .json). Para arquivos Excel, use as ações específicas de Excel. Para arquivos Word, use get_word_document_content.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo a baixar. Obtenha de list_files ou list_files_by_path.
</Accordion>
<Accordion title="microsoft_sharepoint/get_file_info">
**Descrição:** Recuperar metadados detalhados de um arquivo ou pasta específico em uma biblioteca de documentos do SharePoint, incluindo nome, tamanho, datas de criação/modificação e informações do autor.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo ou pasta. Obtenha de list_files ou list_files_by_path.
- `select` (string, opcional): Lista de propriedades separadas por vírgula para retornar (ex: 'id,name,size,createdDateTime,lastModifiedDateTime,webUrl,createdBy,lastModifiedBy').
</Accordion>
<Accordion title="microsoft_sharepoint/create_folder">
**Descrição:** Criar uma nova pasta em uma biblioteca de documentos do SharePoint. Por padrão, cria a pasta na raiz; use parent_id para criar subpastas.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `folder_name` (string, obrigatório): Nome para a nova pasta. Não pode conter: \ / : * ? " < > |
- `parent_id` (string, opcional): O ID da pasta pai. Use 'root' para a raiz da biblioteca de documentos, ou forneça um ID de pasta de list_files. Padrão: 'root'
</Accordion>
<Accordion title="microsoft_sharepoint/search_files">
**Descrição:** Pesquisar arquivos e pastas em uma biblioteca de documentos do SharePoint por palavras-chave. Pesquisa nomes de arquivos, nomes de pastas e conteúdo de arquivos para documentos Office. Não use curingas ou caracteres especiais.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `query` (string, obrigatório): Palavras-chave de pesquisa (ex: 'relatório', 'orçamento 2024'). Curingas como *.txt não são suportados.
- `top` (integer, opcional): Número máximo de resultados a retornar por página (1-1000). Padrão: 50
- `skip_token` (string, opcional): Token de paginação de uma resposta anterior para buscar a próxima página de resultados.
- `select` (string, opcional): Lista de campos separados por vírgula para retornar (ex: 'id,name,size,folder,file,webUrl,lastModifiedDateTime').
</Accordion>
<Accordion title="microsoft_sharepoint/copy_file">
**Descrição:** Copiar um arquivo ou pasta para um novo local dentro do SharePoint. O item original permanece inalterado. A operação de cópia é assíncrona para arquivos grandes.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo ou pasta a copiar. Obtenha de list_files ou search_files.
- `destination_folder_id` (string, obrigatório): O ID da pasta de destino. Use 'root' para a pasta raiz, ou um ID de pasta de list_files.
- `new_name` (string, opcional): Novo nome para a cópia. Se não fornecido, o nome original é usado.
</Accordion>
<Accordion title="microsoft_sharepoint/move_file">
**Descrição:** Mover um arquivo ou pasta para um novo local dentro do SharePoint. O item é removido de sua localização original. Para pastas, todo o conteúdo é movido também.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo ou pasta a mover. Obtenha de list_files ou search_files.
- `destination_folder_id` (string, obrigatório): O ID da pasta de destino. Use 'root' para a pasta raiz, ou um ID de pasta de list_files.
- `new_name` (string, opcional): Novo nome para o item movido. Se não fornecido, o nome original é mantido.
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_worksheets">
**Descrição:** Listar todas as planilhas (abas) em uma pasta de trabalho Excel armazenada em uma biblioteca de documentos do SharePoint. Use o nome da planilha retornado com outras ações de Excel.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `select` (string, opcional): Lista de propriedades separadas por vírgula para retornar (ex: 'id,name,position,visibility').
- `filter` (string, opcional): Expressão de filtro OData (ex: "visibility eq 'Visible'" para excluir planilhas ocultas).
- `top` (integer, opcional): Número máximo de planilhas a retornar. Mínimo: 1, Máximo: 999
- `orderby` (string, opcional): Ordem de classificação (ex: 'position asc' para retornar planilhas na ordem das abas).
</Accordion>
<Accordion title="microsoft_sharepoint/create_excel_worksheet">
**Descrição:** Criar uma nova planilha (aba) em uma pasta de trabalho Excel armazenada em uma biblioteca de documentos do SharePoint. A nova planilha é adicionada no final da lista de abas.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `name` (string, obrigatório): Nome para a nova planilha. Máximo de 31 caracteres. Não pode conter: \ / * ? : [ ]. Deve ser único na pasta de trabalho.
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_range_data">
**Descrição:** Recuperar valores de células de um intervalo específico em uma planilha Excel armazenada no SharePoint. Para ler todos os dados sem saber as dimensões, use get_excel_used_range em vez disso.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha (aba) para leitura. Obtenha de get_excel_worksheets. Sensível a maiúsculas e minúsculas.
- `range` (string, obrigatório): Intervalo de células em notação A1 (ex: 'A1:C10', 'A:C', '1:5', 'A1').
- `select` (string, opcional): Lista de propriedades separadas por vírgula para retornar (ex: 'address,values,formulas,numberFormat,text').
</Accordion>
<Accordion title="microsoft_sharepoint/update_excel_range_data">
**Descrição:** Escrever valores em um intervalo específico em uma planilha Excel armazenada no SharePoint. Sobrescreve o conteúdo existente das células. As dimensões do array de valores devem corresponder exatamente às dimensões do intervalo.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha (aba) a atualizar. Obtenha de get_excel_worksheets. Sensível a maiúsculas e minúsculas.
- `range` (string, obrigatório): Intervalo de células em notação A1 onde os valores serão escritos (ex: 'A1:C3' para um bloco 3x3).
- `values` (array, obrigatório): Array 2D de valores (linhas contendo células). Exemplo para A1:B2: [["Cabeçalho1", "Cabeçalho2"], ["Valor1", "Valor2"]]. Use null para limpar uma célula.
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_used_range_metadata">
**Descrição:** Retornar apenas os metadados (endereço e dimensões) do intervalo utilizado em uma planilha, sem os valores reais das células. Ideal para arquivos grandes para entender o tamanho da planilha antes de ler dados em blocos.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha (aba) para leitura. Obtenha de get_excel_worksheets. Sensível a maiúsculas e minúsculas.
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_used_range">
**Descrição:** Recuperar todas as células contendo dados em uma planilha armazenada no SharePoint. Não use para arquivos maiores que 2MB. Para arquivos grandes, use get_excel_used_range_metadata primeiro, depois get_excel_range_data para ler em blocos menores.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha (aba) para leitura. Obtenha de get_excel_worksheets. Sensível a maiúsculas e minúsculas.
- `select` (string, opcional): Lista de propriedades separadas por vírgula para retornar (ex: 'address,values,formulas,numberFormat,text,rowCount,columnCount').
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_cell">
**Descrição:** Recuperar o valor de uma única célula por índice de linha e coluna de um arquivo Excel no SharePoint. Os índices são baseados em 0 (linha 0 = linha 1 do Excel, coluna 0 = coluna A).
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha (aba). Obtenha de get_excel_worksheets. Sensível a maiúsculas e minúsculas.
- `row` (integer, obrigatório): Índice de linha baseado em 0 (linha 0 = linha 1 do Excel). Intervalo válido: 0-1048575
- `column` (integer, obrigatório): Índice de coluna baseado em 0 (coluna 0 = A, coluna 1 = B). Intervalo válido: 0-16383
- `select` (string, opcional): Lista de propriedades separadas por vírgula para retornar (ex: 'address,values,formulas,numberFormat,text').
</Accordion>
<Accordion title="microsoft_sharepoint/add_excel_table">
**Descrição:** Converter um intervalo de células em uma tabela Excel formatada com recursos de filtragem, classificação e dados estruturados. Tabelas habilitam add_excel_table_row para adicionar dados.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha contendo o intervalo de dados. Obtenha de get_excel_worksheets.
- `range` (string, obrigatório): Intervalo de células para converter em tabela, incluindo cabeçalhos e dados (ex: 'A1:D10' onde A1:D1 contém cabeçalhos de coluna).
- `has_headers` (boolean, opcional): Defina como true se a primeira linha contém cabeçalhos de coluna. Padrão: true
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_tables">
**Descrição:** Listar todas as tabelas em uma planilha Excel específica armazenada no SharePoint. Retorna propriedades da tabela incluindo id, name, showHeaders e showTotals.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha para obter tabelas. Obtenha de get_excel_worksheets.
</Accordion>
<Accordion title="microsoft_sharepoint/add_excel_table_row">
**Descrição:** Adicionar uma nova linha ao final de uma tabela Excel em um arquivo do SharePoint. O array de valores deve ter o mesmo número de elementos que o número de colunas da tabela.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha contendo a tabela. Obtenha de get_excel_worksheets.
- `table_name` (string, obrigatório): Nome da tabela para adicionar a linha (ex: 'Table1'). Obtenha de get_excel_tables. Sensível a maiúsculas e minúsculas.
- `values` (array, obrigatório): Array de valores de células para a nova linha, um por coluna na ordem da tabela (ex: ["João Silva", "joao@exemplo.com", 25]).
</Accordion>
<Accordion title="microsoft_sharepoint/get_excel_table_data">
**Descrição:** Obter todas as linhas de uma tabela Excel em um arquivo do SharePoint como um intervalo de dados. Mais fácil do que get_excel_range_data ao trabalhar com tabelas estruturadas, pois não é necessário saber o intervalo exato.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha contendo a tabela. Obtenha de get_excel_worksheets.
- `table_name` (string, obrigatório): Nome da tabela para obter dados (ex: 'Table1'). Obtenha de get_excel_tables. Sensível a maiúsculas e minúsculas.
- `select` (string, opcional): Lista de propriedades separadas por vírgula para retornar (ex: 'address,values,formulas,numberFormat,text').
</Accordion>
<Accordion title="microsoft_sharepoint/create_excel_chart">
**Descrição:** Criar uma visualização de gráfico em uma planilha Excel armazenada no SharePoint a partir de um intervalo de dados. O gráfico é incorporado na planilha.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha onde o gráfico será criado. Obtenha de get_excel_worksheets.
- `chart_type` (string, obrigatório): Tipo de gráfico (ex: 'ColumnClustered', 'ColumnStacked', 'Line', 'LineMarkers', 'Pie', 'Bar', 'BarClustered', 'Area', 'Scatter', 'Doughnut').
- `source_data` (string, obrigatório): Intervalo de dados para o gráfico em notação A1, incluindo cabeçalhos (ex: 'A1:B10').
- `series_by` (string, opcional): Como as séries de dados são organizadas: 'Auto', 'Columns' ou 'Rows'. Padrão: 'Auto'
</Accordion>
<Accordion title="microsoft_sharepoint/list_excel_charts">
**Descrição:** Listar todos os gráficos incorporados em uma planilha Excel armazenada no SharePoint. Retorna propriedades do gráfico incluindo id, name, chartType, height, width e position.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha para listar gráficos. Obtenha de get_excel_worksheets.
</Accordion>
<Accordion title="microsoft_sharepoint/delete_excel_worksheet">
**Descrição:** Remover permanentemente uma planilha (aba) e todo seu conteúdo de uma pasta de trabalho Excel armazenada no SharePoint. Não pode ser desfeito. Uma pasta de trabalho deve ter pelo menos uma planilha.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha a excluir. Sensível a maiúsculas e minúsculas. Todos os dados, tabelas e gráficos nesta planilha serão permanentemente removidos.
</Accordion>
<Accordion title="microsoft_sharepoint/delete_excel_table">
**Descrição:** Remover uma tabela de uma planilha Excel no SharePoint. Isto exclui a estrutura da tabela (filtragem, formatação, recursos de tabela) mas preserva os dados subjacentes das células.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
- `worksheet_name` (string, obrigatório): Nome da planilha contendo a tabela. Obtenha de get_excel_worksheets.
- `table_name` (string, obrigatório): Nome da tabela a excluir (ex: 'Table1'). Obtenha de get_excel_tables. Os dados nas células permanecerão após a exclusão da tabela.
</Accordion>
<Accordion title="microsoft_sharepoint/list_excel_names">
**Descrição:** Recuperar todos os intervalos nomeados definidos em uma pasta de trabalho Excel armazenada no SharePoint. Intervalos nomeados são rótulos definidos pelo usuário para intervalos de células (ex: 'DadosVendas' para A1:D100).
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do arquivo Excel no SharePoint. Obtenha de list_files ou search_files.
</Accordion>
<Accordion title="microsoft_sharepoint/get_word_document_content">
**Descrição:** Baixar e extrair conteúdo de texto de um documento Word (.docx) armazenado em uma biblioteca de documentos do SharePoint. Esta é a maneira recomendada de ler documentos Word do SharePoint.
**Parâmetros:**
- `site_id` (string, obrigatório): O identificador completo do site SharePoint obtido de get_sites.
- `drive_id` (string, obrigatório): O ID da biblioteca de documentos. Chame get_drives primeiro para obter IDs de drive válidos.
- `item_id` (string, obrigatório): O identificador único do documento Word (.docx) no SharePoint. Obtenha de list_files ou search_files.
</Accordion>
</AccordionGroup>

View File

@@ -107,6 +107,86 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
- `join_web_url` (string, obrigatório): A URL de participação na web da reunião a pesquisar.
</Accordion>
<Accordion title="microsoft_teams/search_online_meetings_by_meeting_id">
**Descrição:** Pesquisar reuniões online por ID externo da reunião.
**Parâmetros:**
- `join_meeting_id` (string, obrigatório): O ID da reunião (código numérico) que os participantes usam para entrar. É o joinMeetingId exibido nos convites da reunião, não o meeting id da API Graph.
</Accordion>
<Accordion title="microsoft_teams/get_meeting">
**Descrição:** Obter detalhes de uma reunião online específica.
**Parâmetros:**
- `meeting_id` (string, obrigatório): O ID da reunião na API Graph (string alfanumérica longa). Obter pelas ações create_meeting ou search_online_meetings. Diferente do joinMeetingId numérico.
</Accordion>
<Accordion title="microsoft_teams/get_team_members">
**Descrição:** Obter membros de uma equipe específica.
**Parâmetros:**
- `team_id` (string, obrigatório): O identificador único da equipe. Obter pela ação get_teams.
- `top` (integer, opcional): Número máximo de membros a recuperar por página (1-999). Padrão: 100.
- `skip_token` (string, opcional): Token de paginação de uma resposta anterior. Quando a resposta incluir @odata.nextLink, extraia o valor do parâmetro $skiptoken e passe aqui para obter a próxima página de resultados.
</Accordion>
<Accordion title="microsoft_teams/create_channel">
**Descrição:** Criar um novo canal em uma equipe.
**Parâmetros:**
- `team_id` (string, obrigatório): O identificador único da equipe. Obter pela ação get_teams.
- `display_name` (string, obrigatório): Nome do canal exibido no Teams. Deve ser único na equipe. Máx 50 caracteres.
- `description` (string, opcional): Descrição opcional explicando o propósito do canal. Visível nos detalhes do canal. Máx 1024 caracteres.
- `membership_type` (string, opcional): Visibilidade do canal. Opções: standard, private. "standard" = visível para todos os membros da equipe, "private" = visível apenas para membros adicionados especificamente. Padrão: standard.
</Accordion>
<Accordion title="microsoft_teams/get_message_replies">
**Descrição:** Obter respostas a uma mensagem específica em um canal.
**Parâmetros:**
- `team_id` (string, obrigatório): O identificador único da equipe. Obter pela ação get_teams.
- `channel_id` (string, obrigatório): O identificador único do canal. Obter pela ação get_channels.
- `message_id` (string, obrigatório): O identificador único da mensagem pai. Obter pela ação get_messages.
- `top` (integer, opcional): Número máximo de respostas a recuperar por página (1-50). Padrão: 50.
- `skip_token` (string, opcional): Token de paginação de uma resposta anterior. Quando a resposta incluir @odata.nextLink, extraia o valor do parâmetro $skiptoken e passe aqui para obter a próxima página de resultados.
</Accordion>
<Accordion title="microsoft_teams/reply_to_message">
**Descrição:** Responder a uma mensagem em um canal do Teams.
**Parâmetros:**
- `team_id` (string, obrigatório): O identificador único da equipe. Obter pela ação get_teams.
- `channel_id` (string, obrigatório): O identificador único do canal. Obter pela ação get_channels.
- `message_id` (string, obrigatório): O identificador único da mensagem a responder. Obter pela ação get_messages.
- `message` (string, obrigatório): O conteúdo da resposta. Para HTML, inclua tags de formatação. Para texto, use apenas texto simples.
- `content_type` (string, opcional): Formato do conteúdo. Opções: html, text. "text" para texto simples, "html" para texto rico com formatação. Padrão: text.
</Accordion>
<Accordion title="microsoft_teams/update_meeting">
**Descrição:** Atualizar uma reunião online existente.
**Parâmetros:**
- `meeting_id` (string, obrigatório): O identificador único da reunião. Obter pelas ações create_meeting ou search_online_meetings.
- `subject` (string, opcional): Novo título da reunião.
- `startDateTime` (string, opcional): Nova hora de início no formato ISO 8601 com fuso horário. Exemplo: "2024-01-20T10:00:00-08:00".
- `endDateTime` (string, opcional): Nova hora de término no formato ISO 8601 com fuso horário.
</Accordion>
<Accordion title="microsoft_teams/delete_meeting">
**Descrição:** Excluir uma reunião online.
**Parâmetros:**
- `meeting_id` (string, obrigatório): O identificador único da reunião a excluir. Obter pelas ações create_meeting ou search_online_meetings.
</Accordion>
</AccordionGroup>
## Exemplos de Uso

View File

@@ -97,6 +97,26 @@ CREWAI_PLATFORM_INTEGRATION_TOKEN=seu_enterprise_token
- `file_id` (string, obrigatório): O ID do documento a excluir.
</Accordion>
<Accordion title="microsoft_word/copy_document">
**Descrição:** Copiar um documento para um novo local no OneDrive.
**Parâmetros:**
- `file_id` (string, obrigatório): O ID do documento a copiar.
- `name` (string, opcional): Novo nome para o documento copiado.
- `parent_id` (string, opcional): O ID da pasta de destino (padrão: raiz).
</Accordion>
<Accordion title="microsoft_word/move_document">
**Descrição:** Mover um documento para um novo local no OneDrive.
**Parâmetros:**
- `file_id` (string, obrigatório): O ID do documento a mover.
- `parent_id` (string, obrigatório): O ID da pasta de destino.
- `name` (string, opcional): Novo nome para o documento movido.
</Accordion>
</AccordionGroup>
## Exemplos de Uso

View File

@@ -2,29 +2,95 @@
from __future__ import annotations
from collections.abc import Callable
import logging
from typing import TYPE_CHECKING, Any
from crewai.tools import BaseTool
from crewai.utilities.pydantic_schema_utils import create_model_from_schema
from crewai.utilities.string_utils import sanitize_tool_name
from pydantic import BaseModel
from crewai_tools.adapters.tool_collection import ToolCollection
logger = logging.getLogger(__name__)
if TYPE_CHECKING:
from mcp import StdioServerParameters
from mcpadapt.core import MCPAdapt
from mcpadapt.crewai_adapter import CrewAIAdapter
from mcp.types import CallToolResult, TextContent, Tool
from mcpadapt.core import MCPAdapt, ToolAdapter
logger = logging.getLogger(__name__)
try:
from mcp import StdioServerParameters
from mcpadapt.core import MCPAdapt
from mcpadapt.crewai_adapter import CrewAIAdapter
from mcp.types import CallToolResult, TextContent, Tool
from mcpadapt.core import MCPAdapt, ToolAdapter
class CrewAIToolAdapter(ToolAdapter):
"""Adapter that creates CrewAI tools with properly normalized JSON schemas.
This adapter bypasses mcpadapt's model creation which adds invalid null values
to field schemas, instead using CrewAI's own schema utilities.
"""
def adapt(
self,
func: Callable[[dict[str, Any] | None], CallToolResult],
mcp_tool: Tool,
) -> BaseTool:
"""Adapt a MCP tool to a CrewAI tool.
Args:
func: The function to call when the tool is invoked.
mcp_tool: The MCP tool definition to adapt.
Returns:
A CrewAI BaseTool instance.
"""
tool_name = sanitize_tool_name(mcp_tool.name)
tool_description = mcp_tool.description or ""
args_model = create_model_from_schema(mcp_tool.inputSchema)
class CrewAIMCPTool(BaseTool):
name: str = tool_name
description: str = tool_description
args_schema: type[BaseModel] = args_model
def _run(self, **kwargs: Any) -> Any:
result = func(kwargs)
if len(result.content) == 1:
first_content = result.content[0]
if isinstance(first_content, TextContent):
return first_content.text
return str(first_content)
return str(
[
content.text
for content in result.content
if isinstance(content, TextContent)
]
)
def _generate_description(self) -> None:
schema = self.args_schema.model_json_schema()
schema.pop("$defs", None)
self.description = (
f"Tool Name: {self.name}\n"
f"Tool Arguments: {schema}\n"
f"Tool Description: {self.description}"
)
return CrewAIMCPTool()
async def async_adapt(self, afunc: Any, mcp_tool: Tool) -> Any:
"""Async adaptation is not supported by CrewAI."""
raise NotImplementedError("async is not supported by the CrewAI framework.")
MCP_AVAILABLE = True
except ImportError:
except ImportError as e:
logger.debug(f"MCP packages not available: {e}")
MCP_AVAILABLE = False
@@ -34,9 +100,6 @@ class MCPServerAdapter:
Note: tools can only be accessed after the server has been started with the
`start()` method.
Attributes:
tools: The CrewAI tools available from the MCP server.
Usage:
# context manager + stdio
with MCPServerAdapter(...) as tools:
@@ -89,7 +152,9 @@ class MCPServerAdapter:
super().__init__()
self._adapter = None
self._tools = None
self._tool_names = list(tool_names) if tool_names else None
self._tool_names = (
[sanitize_tool_name(name) for name in tool_names] if tool_names else None
)
if not MCP_AVAILABLE:
import click
@@ -100,7 +165,7 @@ class MCPServerAdapter:
import subprocess
try:
subprocess.run(["uv", "add", "mcp crewai-tools[mcp]"], check=True) # noqa: S607
subprocess.run(["uv", "add", "mcp crewai-tools'[mcp]'"], check=True) # noqa: S607
except subprocess.CalledProcessError as e:
raise ImportError("Failed to install mcp package") from e
@@ -112,7 +177,7 @@ class MCPServerAdapter:
try:
self._serverparams = serverparams
self._adapter = MCPAdapt(
self._serverparams, CrewAIAdapter(), connect_timeout
self._serverparams, CrewAIToolAdapter(), connect_timeout
)
self.start()
@@ -124,13 +189,13 @@ class MCPServerAdapter:
logger.error(f"Error during stop cleanup: {stop_e}")
raise RuntimeError(f"Failed to initialize MCP Adapter: {e}") from e
def start(self):
def start(self) -> None:
"""Start the MCP server and initialize the tools."""
self._tools = self._adapter.__enter__()
self._tools = self._adapter.__enter__() # type: ignore[union-attr]
def stop(self):
def stop(self) -> None:
"""Stop the MCP server."""
self._adapter.__exit__(None, None, None)
self._adapter.__exit__(None, None, None) # type: ignore[union-attr]
@property
def tools(self) -> ToolCollection[BaseTool]:
@@ -152,12 +217,19 @@ class MCPServerAdapter:
return tools_collection.filter_by_names(self._tool_names)
return tools_collection
def __enter__(self):
"""Enter the context manager. Note that `__init__()` already starts the MCP server.
So tools should already be available.
def __enter__(self) -> ToolCollection[BaseTool]:
"""Enter the context manager.
Note that `__init__()` already starts the MCP server,
so tools should already be available.
"""
return self.tools
def __exit__(self, exc_type, exc_value, traceback):
def __exit__(
self,
exc_type: type[BaseException] | None,
exc_value: BaseException | None,
traceback: Any,
) -> None:
"""Exit the context manager."""
return self._adapter.__exit__(exc_type, exc_value, traceback)
self._adapter.__exit__(exc_type, exc_value, traceback) # type: ignore[union-attr]

View File

@@ -8,8 +8,9 @@ from typing import Any
from crewai.tools.base_tool import BaseTool, EnvVar
from pydantic import BaseModel
from pydantic.fields import FieldInfo
from pydantic.json_schema import GenerateJsonSchema
from pydantic_core import PydanticOmit
from pydantic_core import PydanticOmit, PydanticUndefined
from crewai_tools import tools
@@ -44,6 +45,9 @@ class ToolSpecExtractor:
schema = self._unwrap_schema(core_schema)
fields = schema.get("schema", {}).get("fields", {})
# Use model_fields to get defaults (handles both default and default_factory)
model_fields = tool_class.model_fields
tool_info = {
"name": tool_class.__name__,
"humanized_name": self._extract_field_default(
@@ -54,9 +58,9 @@ class ToolSpecExtractor:
).strip(),
"run_params_schema": self._extract_params(fields.get("args_schema")),
"init_params_schema": self._extract_init_params(tool_class),
"env_vars": self._extract_env_vars(fields.get("env_vars")),
"package_dependencies": self._extract_field_default(
fields.get("package_dependencies"), fallback=[]
"env_vars": self._extract_env_vars_from_model_fields(model_fields),
"package_dependencies": self._extract_package_deps_from_model_fields(
model_fields
),
}
@@ -103,10 +107,27 @@ class ToolSpecExtractor:
return {}
@staticmethod
def _extract_env_vars(
env_vars_field: dict[str, Any] | None,
def _get_field_default(field: FieldInfo | None) -> Any:
"""Get default value from a FieldInfo, handling both default and default_factory."""
if not field:
return None
default_value = field.default
if default_value is PydanticUndefined or default_value is None:
if field.default_factory:
return field.default_factory()
return None
return default_value
@staticmethod
def _extract_env_vars_from_model_fields(
model_fields: dict[str, FieldInfo],
) -> list[dict[str, Any]]:
if not env_vars_field:
default_value = ToolSpecExtractor._get_field_default(
model_fields.get("env_vars")
)
if not default_value:
return []
return [
@@ -116,10 +137,22 @@ class ToolSpecExtractor:
"required": env_var.required,
"default": env_var.default,
}
for env_var in env_vars_field.get("schema", {}).get("default", [])
for env_var in default_value
if isinstance(env_var, EnvVar)
]
@staticmethod
def _extract_package_deps_from_model_fields(
model_fields: dict[str, FieldInfo],
) -> list[str]:
default_value = ToolSpecExtractor._get_field_default(
model_fields.get("package_dependencies")
)
if not isinstance(default_value, list):
return []
return default_value
@staticmethod
def _extract_init_params(tool_class: type[BaseTool]) -> dict[str, Any]:
ignored_init_params = [
@@ -152,7 +185,7 @@ class ToolSpecExtractor:
if __name__ == "__main__":
output_file = Path(__file__).parent / "tool.specs.json"
output_file = Path(__file__).parent.parent.parent / "tool.specs.json"
extractor = ToolSpecExtractor()
extractor.extract_all_tools()

View File

@@ -1,12 +1,17 @@
from datetime import datetime
import json
import os
import time
from typing import Any, ClassVar
from typing import Annotated, Any, ClassVar, Literal
from crewai.tools import BaseTool, EnvVar
from dotenv import load_dotenv
from pydantic import BaseModel, Field
from pydantic.types import StringConstraints
import requests
load_dotenv()
def _save_results_to_file(content: str) -> None:
"""Saves the search results to a file."""
@@ -15,37 +20,72 @@ def _save_results_to_file(content: str) -> None:
file.write(content)
class BraveSearchToolSchema(BaseModel):
"""Input for BraveSearchTool."""
FreshnessPreset = Literal["pd", "pw", "pm", "py"]
FreshnessRange = Annotated[
str, StringConstraints(pattern=r"^\d{4}-\d{2}-\d{2}to\d{4}-\d{2}-\d{2}$")
]
Freshness = FreshnessPreset | FreshnessRange
SafeSearch = Literal["off", "moderate", "strict"]
search_query: str = Field(
..., description="Mandatory search query you want to use to search the internet"
class BraveSearchToolSchema(BaseModel):
"""Input for BraveSearchTool"""
query: str = Field(..., description="Search query to perform")
country: str | None = Field(
default=None,
description="Country code for geo-targeting (e.g., 'US', 'BR').",
)
search_language: str | None = Field(
default=None,
description="Language code for the search results (e.g., 'en', 'es').",
)
count: int | None = Field(
default=None,
description="The maximum number of results to return. Actual number may be less.",
)
offset: int | None = Field(
default=None, description="Skip the first N result sets/pages. Max is 9."
)
safesearch: SafeSearch | None = Field(
default=None,
description="Filter out explicit content. Options: off/moderate/strict",
)
spellcheck: bool | None = Field(
default=None,
description="Attempt to correct spelling errors in the search query.",
)
freshness: Freshness | None = Field(
default=None,
description="Enforce freshness of results. Options: pd/pw/pm/py, or YYYY-MM-DDtoYYYY-MM-DD",
)
text_decorations: bool | None = Field(
default=None,
description="Include markup to highlight search terms in the results.",
)
extra_snippets: bool | None = Field(
default=None,
description="Include up to 5 text snippets for each page if possible.",
)
operators: bool | None = Field(
default=None,
description="Whether to apply search operators (e.g., site:example.com).",
)
# TODO: Extend support to additional endpoints (e.g., /images, /news, etc.)
class BraveSearchTool(BaseTool):
"""BraveSearchTool - A tool for performing web searches using the Brave Search API.
"""A tool that performs web searches using the Brave Search API."""
This module provides functionality to search the internet using Brave's Search API,
supporting customizable result counts and country-specific searches.
Dependencies:
- requests
- pydantic
- python-dotenv (for API key management)
"""
name: str = "Brave Web Search the internet"
name: str = "Brave Search"
description: str = (
"A tool that can be used to search the internet with a search_query."
"A tool that performs web searches using the Brave Search API. "
"Results are returned as structured JSON data."
)
args_schema: type[BaseModel] = BraveSearchToolSchema
search_url: str = "https://api.search.brave.com/res/v1/web/search"
country: str | None = ""
n_results: int = 10
save_file: bool = False
_last_request_time: ClassVar[float] = 0
_min_request_interval: ClassVar[float] = 1.0 # seconds
env_vars: list[EnvVar] = Field(
default_factory=lambda: [
EnvVar(
@@ -55,6 +95,9 @@ class BraveSearchTool(BaseTool):
),
]
)
# Rate limiting parameters
_last_request_time: ClassVar[float] = 0
_min_request_interval: ClassVar[float] = 1.0 # seconds
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -73,19 +116,64 @@ class BraveSearchTool(BaseTool):
self._min_request_interval - (current_time - self._last_request_time)
)
BraveSearchTool._last_request_time = time.time()
# Construct and send the request
try:
search_query = kwargs.get("search_query") or kwargs.get("query")
if not search_query:
raise ValueError("Search query is required")
# Maintain both "search_query" and "query" for backwards compatibility
query = kwargs.get("search_query") or kwargs.get("query")
if not query:
raise ValueError("Query is required")
payload = {"q": query}
if country := kwargs.get("country"):
payload["country"] = country
if search_language := kwargs.get("search_language"):
payload["search_language"] = search_language
# Fallback to deprecated n_results parameter if no count is provided
count = kwargs.get("count")
if count is not None:
payload["count"] = count
else:
payload["count"] = self.n_results
# Offset may be 0, so avoid truthiness check
offset = kwargs.get("offset")
if offset is not None:
payload["offset"] = offset
if safesearch := kwargs.get("safesearch"):
payload["safesearch"] = safesearch
save_file = kwargs.get("save_file", self.save_file)
n_results = kwargs.get("n_results", self.n_results)
if freshness := kwargs.get("freshness"):
payload["freshness"] = freshness
payload = {"q": search_query, "count": n_results}
# Boolean parameters
spellcheck = kwargs.get("spellcheck")
if spellcheck is not None:
payload["spellcheck"] = spellcheck
if self.country != "":
payload["country"] = self.country
text_decorations = kwargs.get("text_decorations")
if text_decorations is not None:
payload["text_decorations"] = text_decorations
extra_snippets = kwargs.get("extra_snippets")
if extra_snippets is not None:
payload["extra_snippets"] = extra_snippets
operators = kwargs.get("operators")
if operators is not None:
payload["operators"] = operators
# Limit the result types to "web" since there is presently no
# handling of other types like "discussions", "faq", "infobox",
# "news", "videos", or "locations".
payload["result_filter"] = "web"
# Setup Request Headers
headers = {
"X-Subscription-Token": os.environ["BRAVE_API_KEY"],
"Accept": "application/json",
@@ -97,25 +185,32 @@ class BraveSearchTool(BaseTool):
response.raise_for_status() # Handle non-200 responses
results = response.json()
# TODO: Handle other result types like "discussions", "faq", etc.
web_results_items = []
if "web" in results:
results = results["web"]["results"]
string = []
for result in results:
try:
string.append(
"\n".join(
[
f"Title: {result['title']}",
f"Link: {result['url']}",
f"Snippet: {result['description']}",
"---",
]
)
)
except KeyError: # noqa: PERF203
continue
web_results = results["web"]["results"]
content = "\n".join(string)
for result in web_results:
url = result.get("url")
title = result.get("title")
# If, for whatever reason, this entry does not have a title
# or url, skip it.
if not url or not title:
continue
item = {
"url": url,
"title": title,
}
description = result.get("description")
if description:
item["description"] = description
snippets = result.get("extra_snippets")
if snippets:
item["snippets"] = snippets
web_results_items.append(item)
content = json.dumps(web_results_items)
except requests.RequestException as e:
return f"Error performing search: {e!s}"
except KeyError as e:

View File

@@ -8,6 +8,29 @@ This enables multiple workflows like having an Agent to access the database fetc
**Attention**: Make sure that the Agent has access to a Read-Replica or that is okay for the Agent to run insert/update queries on the database.
## Security Model
`NL2SQLTool` is an execution-capable tool. It runs model-generated SQL directly against the configured database connection.
Risk depends on deployment choices:
- Which credentials are used in `db_uri`
- Whether untrusted input can influence prompts
- Whether tool-call guardrails are enforced before execution
If untrusted input can reach this tool, treat the integration as high risk.
## Hardening Recommendations
Use all of the following in production:
- Use a read-only database user whenever possible
- Prefer a read replica for analytics/retrieval workloads
- Grant least privilege (no superuser/admin roles, no file/system-level capabilities)
- Apply database-side resource limits (statement timeout, lock timeout, cost/row limits)
- Add `before_tool_call` hooks to enforce allowed query patterns
- Enable query logging and alerting for destructive statements
## Requirements
- SqlAlchemy

View File

@@ -4,7 +4,7 @@ import os
import re
from typing import Any
from crewai.tools import BaseTool
from crewai.tools import BaseTool, EnvVar
from pydantic import BaseModel, Field
@@ -137,6 +137,21 @@ class StagehandTool(BaseTool):
- 'observe': For finding elements in a specific area
"""
args_schema: type[BaseModel] = StagehandToolSchema
package_dependencies: list[str] = Field(default_factory=lambda: ["stagehand<=0.5.9"])
env_vars: list[EnvVar] = Field(
default_factory=lambda: [
EnvVar(
name="BROWSERBASE_API_KEY",
description="API key for Browserbase services",
required=False,
),
EnvVar(
name="BROWSERBASE_PROJECT_ID",
description="Project ID for Browserbase services",
required=False,
),
]
)
# Stagehand configuration
api_key: str | None = None

View File

@@ -23,23 +23,26 @@ class MockTool(BaseTool):
)
my_parameter: str = Field("This is default value", description="What a description")
my_parameter_bool: bool = Field(False)
# Use default_factory like real tools do (not direct default)
package_dependencies: list[str] = Field(
["this-is-a-required-package", "another-required-package"], description=""
default_factory=lambda: ["this-is-a-required-package", "another-required-package"]
)
env_vars: list[EnvVar] = Field(
default_factory=lambda: [
EnvVar(
name="SERPER_API_KEY",
description="API key for Serper",
required=True,
default=None,
),
EnvVar(
name="API_RATE_LIMIT",
description="API rate limit",
required=False,
default="100",
),
]
)
env_vars: list[EnvVar] = [
EnvVar(
name="SERPER_API_KEY",
description="API key for Serper",
required=True,
default=None,
),
EnvVar(
name="API_RATE_LIMIT",
description="API rate limit",
required=False,
default="100",
),
]
@pytest.fixture

View File

@@ -1,8 +1,10 @@
import json
from unittest.mock import patch
from crewai_tools.tools.brave_search_tool.brave_search_tool import BraveSearchTool
import pytest
from crewai_tools.tools.brave_search_tool.brave_search_tool import BraveSearchTool
@pytest.fixture
def brave_tool():
@@ -30,16 +32,46 @@ def test_brave_tool_search(mock_get, brave_tool):
}
mock_get.return_value.json.return_value = mock_response
result = brave_tool.run(search_query="test")
assert "Test Title" in result
assert "http://test.com" in result
result = brave_tool.run(query="test")
data = json.loads(result)
assert isinstance(data, list)
assert len(data) >= 1
assert data[0]["title"] == "Test Title"
assert data[0]["url"] == "http://test.com"
def test_brave_tool():
tool = BraveSearchTool(
n_results=2,
)
tool.run(search_query="ChatGPT")
@patch("requests.get")
def test_brave_tool(mock_get):
mock_response = {
"web": {
"results": [
{
"title": "Brave Browser",
"url": "https://brave.com",
"description": "Brave Browser description",
}
]
}
}
mock_get.return_value.json.return_value = mock_response
tool = BraveSearchTool(n_results=2)
result = tool.run(query="Brave Browser")
assert result is not None
# Parse JSON so we can examine the structure
data = json.loads(result)
assert isinstance(data, list)
assert len(data) >= 1
# First item should have expected fields: title, url, and description
first = data[0]
assert "title" in first
assert first["title"] == "Brave Browser"
assert "url" in first
assert first["url"] == "https://brave.com"
assert "description" in first
assert first["description"] == "Brave Browser description"
if __name__ == "__main__":

File diff suppressed because it is too large Load Diff

View File

@@ -10,11 +10,11 @@ requires-python = ">=3.10, <3.14"
dependencies = [
# Core Dependencies
"pydantic~=2.11.9",
"openai~=1.83.0",
"openai>=1.83.0,<3",
"instructor>=1.3.3",
# Text Processing
"pdfplumber~=0.11.4",
"regex~=2024.9.11",
"regex~=2026.1.15",
# Telemetry and Monitoring
"opentelemetry-api~=1.34.0",
"opentelemetry-sdk~=1.34.0",
@@ -36,7 +36,7 @@ dependencies = [
"json5~=0.10.0",
"portalocker~=2.7.0",
"pydantic-settings~=2.10.1",
"mcp~=1.23.1",
"mcp~=1.26.0",
"uv~=0.9.13",
"aiosqlite~=0.21.0",
]
@@ -78,7 +78,7 @@ voyageai = [
"voyageai~=0.3.5",
]
litellm = [
"litellm~=1.74.9",
"litellm>=1.74.9,<3",
]
bedrock = [
"boto3~=1.40.45",

View File

@@ -118,6 +118,8 @@ MCP_TOOL_EXECUTION_TIMEOUT: Final[int] = 30
MCP_DISCOVERY_TIMEOUT: Final[int] = 15
MCP_MAX_RETRIES: Final[int] = 3
_passthrough_exceptions: tuple[type[Exception], ...] = ()
# Simple in-memory cache for MCP tool schemas (duration: 5 minutes)
_mcp_schema_cache: dict[str, Any] = {}
_cache_ttl: Final[int] = 300 # 5 minutes
@@ -479,6 +481,8 @@ class Agent(BaseAgent):
),
)
raise e
if isinstance(e, _passthrough_exceptions):
raise
self._times_executed += 1
if self._times_executed > self.max_retry_limit:
crewai_event_bus.emit(
@@ -711,6 +715,8 @@ class Agent(BaseAgent):
),
)
raise e
if isinstance(e, _passthrough_exceptions):
raise
self._times_executed += 1
if self._times_executed > self.max_retry_limit:
crewai_event_bus.emit(

View File

@@ -37,9 +37,10 @@ class BaseAgentAdapter(BaseAgent, ABC):
tools: Optional list of BaseTool instances to be configured
"""
def configure_structured_output(self, structured_output: Any) -> None:
@abstractmethod
def configure_structured_output(self, task: Any) -> None:
"""Configure the structured output for the specific agent implementation.
Args:
structured_output: The structured output to be configured
task: The task object containing output format specifications.
"""

View File

@@ -16,6 +16,7 @@ from crewai.agents.agent_adapters.openai_agents.protocols import (
)
from crewai.tools import BaseTool
from crewai.utilities.import_utils import require
from crewai.utilities.pydantic_schema_utils import force_additional_properties_false
from crewai.utilities.string_utils import sanitize_tool_name
@@ -135,7 +136,9 @@ class OpenAIAgentToolAdapter(BaseToolAdapter):
for tool in tools:
schema: dict[str, Any] = tool.args_schema.model_json_schema()
schema.update({"additionalProperties": False, "type": "object"})
schema = force_additional_properties_false(schema)
schema.update({"type": "object"})
openai_tool: OpenAIFunctionTool = cast(
OpenAIFunctionTool,

View File

@@ -4,7 +4,6 @@ import time
from typing import TYPE_CHECKING
from crewai.agents.parser import AgentFinish
from crewai.events.event_listener import event_listener
from crewai.memory.entity.entity_memory_item import EntityMemoryItem
from crewai.memory.long_term.long_term_memory_item import LongTermMemoryItem
from crewai.utilities.converter import ConverterError
@@ -138,52 +137,3 @@ class CrewAgentExecutorMixin:
content="Long term memory is enabled, but entity memory is not enabled. Please configure entity memory or set memory=True to automatically enable it.",
color="bold_yellow",
)
def _ask_human_input(self, final_answer: str) -> str:
"""Prompt human input with mode-appropriate messaging.
Note: The final answer is already displayed via the AgentLogsExecutionEvent
panel, so we only show the feedback prompt here.
"""
from rich.panel import Panel
from rich.text import Text
formatter = event_listener.formatter
formatter.pause_live_updates()
try:
# Training mode prompt (single iteration)
if self.crew and getattr(self.crew, "_train", False):
prompt_text = (
"TRAINING MODE: Provide feedback to improve the agent's performance.\n\n"
"This will be used to train better versions of the agent.\n"
"Please provide detailed feedback about the result quality and reasoning process."
)
title = "🎓 Training Feedback Required"
# Regular human-in-the-loop prompt (multiple iterations)
else:
prompt_text = (
"Provide feedback on the Final Result above.\n\n"
"• If you are happy with the result, simply hit Enter without typing anything.\n"
"• Otherwise, provide specific improvement requests.\n"
"• You can provide multiple rounds of feedback until satisfied."
)
title = "💬 Human Feedback Required"
content = Text()
content.append(prompt_text, style="yellow")
prompt_panel = Panel(
content,
title=title,
border_style="yellow",
padding=(1, 2),
)
formatter.console.print(prompt_panel)
response = input()
if response.strip() != "":
formatter.console.print("\n[cyan]Processing your feedback...[/cyan]")
return response
finally:
formatter.resume_live_updates()

View File

@@ -19,6 +19,7 @@ from crewai.agents.parser import (
AgentFinish,
OutputParserError,
)
from crewai.core.providers.human_input import ExecutorContext, get_provider
from crewai.events.event_bus import crewai_event_bus
from crewai.events.types.logging_events import (
AgentLogsExecutionEvent,
@@ -175,15 +176,16 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
"""
return self.llm.supports_stop_words() if self.llm else False
def invoke(self, inputs: dict[str, Any]) -> dict[str, Any]:
"""Execute the agent with given inputs.
def _setup_messages(self, inputs: dict[str, Any]) -> None:
"""Set up messages for the agent execution.
Args:
inputs: Input dictionary containing prompt variables.
Returns:
Dictionary with agent output.
"""
provider = get_provider()
if provider.setup_messages(cast(ExecutorContext, cast(object, self))):
return
if "system" in self.prompt:
system_prompt = self._format_prompt(
cast(str, self.prompt.get("system", "")), inputs
@@ -197,6 +199,19 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
user_prompt = self._format_prompt(self.prompt.get("prompt", ""), inputs)
self.messages.append(format_message_for_llm(user_prompt))
provider.post_setup_messages(cast(ExecutorContext, cast(object, self)))
def invoke(self, inputs: dict[str, Any]) -> dict[str, Any]:
"""Execute the agent with given inputs.
Args:
inputs: Input dictionary containing prompt variables.
Returns:
Dictionary with agent output.
"""
self._setup_messages(inputs)
self._inject_multimodal_files(inputs)
self._show_start_logs()
@@ -799,6 +814,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
agent_key=agent_key,
),
)
error_event_emitted = False
track_delegation_if_needed(func_name, args_dict, self.task)
@@ -881,6 +897,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
error=e,
),
)
error_event_emitted = True
elif max_usage_reached and original_tool:
# Return error message when max usage limit is reached
result = f"Tool '{func_name}' has reached its usage limit of {original_tool.max_usage_count} times and cannot be used anymore."
@@ -908,20 +925,20 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
color="red",
)
# Emit tool usage finished event
crewai_event_bus.emit(
self,
event=ToolUsageFinishedEvent(
output=result,
tool_name=func_name,
tool_args=args_dict,
from_agent=self.agent,
from_task=self.task,
agent_key=agent_key,
started_at=started_at,
finished_at=datetime.now(),
),
)
if not error_event_emitted:
crewai_event_bus.emit(
self,
event=ToolUsageFinishedEvent(
output=result,
tool_name=func_name,
tool_args=args_dict,
from_agent=self.agent,
from_task=self.task,
agent_key=agent_key,
started_at=started_at,
finished_at=datetime.now(),
),
)
# Append tool result message
tool_message: LLMMessage = {
@@ -970,18 +987,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
Returns:
Dictionary with agent output.
"""
if "system" in self.prompt:
system_prompt = self._format_prompt(
cast(str, self.prompt.get("system", "")), inputs
)
user_prompt = self._format_prompt(
cast(str, self.prompt.get("user", "")), inputs
)
self.messages.append(format_message_for_llm(system_prompt, role="system"))
self.messages.append(format_message_for_llm(user_prompt))
else:
user_prompt = self._format_prompt(self.prompt.get("prompt", ""), inputs)
self.messages.append(format_message_for_llm(user_prompt))
self._setup_messages(inputs)
await self._ainject_multimodal_files(inputs)
@@ -1003,7 +1009,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
raise
if self.ask_for_human_input:
formatted_answer = self._handle_human_feedback(formatted_answer)
formatted_answer = await self._ahandle_human_feedback(formatted_answer)
self._create_short_term_memory(formatted_answer)
self._create_long_term_memory(formatted_answer)
@@ -1491,7 +1497,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
return prompt.replace("{tools}", inputs["tools"])
def _handle_human_feedback(self, formatted_answer: AgentFinish) -> AgentFinish:
"""Process human feedback.
"""Process human feedback via the configured provider.
Args:
formatted_answer: Initial agent result.
@@ -1499,17 +1505,22 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
Returns:
Final answer after feedback.
"""
output_str = (
formatted_answer.output
if isinstance(formatted_answer.output, str)
else formatted_answer.output.model_dump_json()
)
human_feedback = self._ask_human_input(output_str)
provider = get_provider()
return provider.handle_feedback(formatted_answer, self)
if self._is_training_mode():
return self._handle_training_feedback(formatted_answer, human_feedback)
async def _ahandle_human_feedback(
self, formatted_answer: AgentFinish
) -> AgentFinish:
"""Process human feedback asynchronously via the configured provider.
return self._handle_regular_feedback(formatted_answer, human_feedback)
Args:
formatted_answer: Initial agent result.
Returns:
Final answer after feedback.
"""
provider = get_provider()
return await provider.handle_feedback_async(formatted_answer, self)
def _is_training_mode(self) -> bool:
"""Check if training mode is active.
@@ -1519,74 +1530,18 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
"""
return bool(self.crew and self.crew._train)
def _handle_training_feedback(
self, initial_answer: AgentFinish, feedback: str
) -> AgentFinish:
"""Process training feedback.
def _format_feedback_message(self, feedback: str) -> LLMMessage:
"""Format feedback as a message for the LLM.
Args:
initial_answer: Initial agent output.
feedback: Training feedback.
feedback: User feedback string.
Returns:
Improved answer.
Formatted message dict.
"""
self._handle_crew_training_output(initial_answer, feedback)
self.messages.append(
format_message_for_llm(
self._i18n.slice("feedback_instructions").format(feedback=feedback)
)
return format_message_for_llm(
self._i18n.slice("feedback_instructions").format(feedback=feedback)
)
improved_answer = self._invoke_loop()
self._handle_crew_training_output(improved_answer)
self.ask_for_human_input = False
return improved_answer
def _handle_regular_feedback(
self, current_answer: AgentFinish, initial_feedback: str
) -> AgentFinish:
"""Process regular feedback iteratively.
Args:
current_answer: Current agent output.
initial_feedback: Initial user feedback.
Returns:
Final answer after iterations.
"""
feedback = initial_feedback
answer = current_answer
while self.ask_for_human_input:
# If the user provides a blank response, assume they are happy with the result
if feedback.strip() == "":
self.ask_for_human_input = False
else:
answer = self._process_feedback_iteration(feedback)
output_str = (
answer.output
if isinstance(answer.output, str)
else answer.output.model_dump_json()
)
feedback = self._ask_human_input(output_str)
return answer
def _process_feedback_iteration(self, feedback: str) -> AgentFinish:
"""Process single feedback iteration.
Args:
feedback: User feedback.
Returns:
Updated agent response.
"""
self.messages.append(
format_message_for_llm(
self._i18n.slice("feedback_instructions").format(feedback=feedback)
)
)
return self._invoke_loop()
@classmethod
def __get_pydantic_core_schema__(

View File

@@ -1,10 +1,13 @@
from typing import Any
DEFAULT_CREWAI_ENTERPRISE_URL = "https://app.crewai.com"
CREWAI_ENTERPRISE_DEFAULT_OAUTH2_PROVIDER = "workos"
CREWAI_ENTERPRISE_DEFAULT_OAUTH2_AUDIENCE = "client_01JNJQWBJ4SPFN3SWJM5T7BDG8"
CREWAI_ENTERPRISE_DEFAULT_OAUTH2_CLIENT_ID = "client_01JYT06R59SP0NXYGD994NFXXX"
CREWAI_ENTERPRISE_DEFAULT_OAUTH2_DOMAIN = "login.crewai.com"
ENV_VARS = {
ENV_VARS: dict[str, list[dict[str, Any]]] = {
"openai": [
{
"prompt": "Enter your OPENAI API key (press Enter to skip)",
@@ -112,7 +115,7 @@ ENV_VARS = {
}
PROVIDERS = [
PROVIDERS: list[str] = [
"openai",
"anthropic",
"gemini",
@@ -127,7 +130,7 @@ PROVIDERS = [
"sambanova",
]
MODELS = {
MODELS: dict[str, list[str]] = {
"openai": [
"gpt-4",
"gpt-4.1",

View File

@@ -3,6 +3,7 @@ import shutil
import sys
import click
import tomli
from crewai.cli.constants import ENV_VARS, MODELS
from crewai.cli.provider import (
@@ -13,7 +14,31 @@ from crewai.cli.provider import (
from crewai.cli.utils import copy_template, load_env_vars, write_env_file
def create_folder_structure(name, parent_folder=None):
def get_reserved_script_names() -> set[str]:
"""Get reserved script names from pyproject.toml template.
Returns:
Set of reserved script names that would conflict with crew folder names.
"""
package_dir = Path(__file__).parent
template_path = package_dir / "templates" / "crew" / "pyproject.toml"
with open(template_path, "r") as f:
template_content = f.read()
template_content = template_content.replace("{{folder_name}}", "_placeholder_")
template_content = template_content.replace("{{name}}", "placeholder")
template_content = template_content.replace("{{crew_name}}", "Placeholder")
template_data = tomli.loads(template_content)
script_names = set(template_data.get("project", {}).get("scripts", {}).keys())
script_names.discard("_placeholder_")
return script_names
def create_folder_structure(
name: str, parent_folder: str | None = None
) -> tuple[Path, str, str]:
import keyword
import re
@@ -51,6 +76,14 @@ def create_folder_structure(name, parent_folder=None):
f"Project name '{name}' would generate invalid Python module name '{folder_name}'"
)
reserved_names = get_reserved_script_names()
if folder_name in reserved_names:
raise ValueError(
f"Project name '{name}' would generate folder name '{folder_name}' which is reserved. "
f"Reserved names are: {', '.join(sorted(reserved_names))}. "
"Please choose a different name."
)
class_name = name.replace("_", " ").replace("-", " ").title().replace(" ", "")
class_name = re.sub(r"[^a-zA-Z0-9_]", "", class_name)
@@ -110,14 +143,19 @@ def create_folder_structure(name, parent_folder=None):
(folder_path / "src" / folder_name).mkdir(parents=True)
(folder_path / "src" / folder_name / "tools").mkdir(parents=True)
(folder_path / "src" / folder_name / "config").mkdir(parents=True)
(folder_path / "src" / folder_name / ".agents" / "research").mkdir(
parents=True
)
# Copy AGENTS.md to project root (top-level projects only)
package_dir = Path(__file__).parent
agents_md_src = package_dir / "templates" / "AGENTS.md"
if agents_md_src.exists():
shutil.copy2(agents_md_src, folder_path / "AGENTS.md")
return folder_path, folder_name, class_name
def copy_template_files(folder_path, name, class_name, parent_folder):
def copy_template_files(
folder_path: Path, name: str, class_name: str, parent_folder: str | None
) -> None:
package_dir = Path(__file__).parent
templates_dir = package_dir / "templates" / "crew"
@@ -157,15 +195,13 @@ def copy_template_files(folder_path, name, class_name, parent_folder):
dst_file = src_folder / file_name
copy_template(src_file, dst_file, name, class_name, folder_path.name)
# Copy Skills.md from .agents/research template
skills_src = templates_dir / ".agents" / "research" / "Skills.md"
skills_dst = src_folder / ".agents" / "research" / "Skills.md"
if skills_src.exists():
skills_dst.parent.mkdir(parents=True, exist_ok=True)
copy_template(skills_src, skills_dst, name, class_name, folder_path.name)
def create_crew(name, provider=None, skip_provider=False, parent_folder=None):
def create_crew(
name: str,
provider: str | None = None,
skip_provider: bool = False,
parent_folder: str | None = None,
) -> None:
folder_path, folder_name, class_name = create_folder_structure(name, parent_folder)
env_vars = load_env_vars(folder_path)
if not skip_provider:
@@ -199,7 +235,9 @@ def create_crew(name, provider=None, skip_provider=False, parent_folder=None):
if selected_provider is None: # User typed 'q'
click.secho("Exiting...", fg="yellow")
sys.exit(0)
if selected_provider: # Valid selection
if selected_provider and isinstance(
selected_provider, str
): # Valid selection
break
click.secho(
"No provider selected. Please try again or press 'q' to exit.", fg="red"

View File

@@ -1,3 +1,4 @@
import shutil
from pathlib import Path
import click
@@ -34,6 +35,11 @@ def create_flow(name):
package_dir = Path(__file__).parent
templates_dir = package_dir / "templates" / "flow"
# Copy AGENTS.md to project root
agents_md_src = package_dir / "templates" / "AGENTS.md"
if agents_md_src.exists():
shutil.copy2(agents_md_src, project_root / "AGENTS.md")
# List of template files to copy
root_template_files = [".gitignore", "pyproject.toml", "README.md"]
src_template_files = ["__init__.py", "main.py"]

View File

@@ -1,6 +1,8 @@
import os
from typing import Any
from urllib.parse import urljoin
import os
import httpx
import requests
from crewai.cli.config import Settings
@@ -33,7 +35,11 @@ class PlusAPI:
if settings.org_uuid:
self.headers["X-Crewai-Organization-Id"] = settings.org_uuid
self.base_url = os.getenv("CREWAI_PLUS_URL") or str(settings.enterprise_base_url) or DEFAULT_CREWAI_ENTERPRISE_URL
self.base_url = (
os.getenv("CREWAI_PLUS_URL")
or str(settings.enterprise_base_url)
or DEFAULT_CREWAI_ENTERPRISE_URL
)
def _make_request(
self, method: str, endpoint: str, **kwargs: Any
@@ -49,8 +55,10 @@ class PlusAPI:
def get_tool(self, handle: str) -> requests.Response:
return self._make_request("GET", f"{self.TOOLS_RESOURCE}/{handle}")
def get_agent(self, handle: str) -> requests.Response:
return self._make_request("GET", f"{self.AGENTS_RESOURCE}/{handle}")
async def get_agent(self, handle: str) -> httpx.Response:
url = urljoin(self.base_url, f"{self.AGENTS_RESOURCE}/{handle}")
async with httpx.AsyncClient() as client:
return await client.get(url, headers=self.headers)
def publish_tool(
self,

View File

@@ -1,8 +1,10 @@
from collections import defaultdict
from collections.abc import Sequence
import json
import os
from pathlib import Path
import time
from typing import Any
import certifi
import click
@@ -11,16 +13,15 @@ import requests
from crewai.cli.constants import JSON_URL, MODELS, PROVIDERS
def select_choice(prompt_message, choices):
"""
Presents a list of choices to the user and prompts them to select one.
def select_choice(prompt_message: str, choices: Sequence[str]) -> str | None:
"""Presents a list of choices to the user and prompts them to select one.
Args:
- prompt_message (str): The message to display to the user before presenting the choices.
- choices (list): A list of options to present to the user.
prompt_message: The message to display to the user before presenting the choices.
choices: A list of options to present to the user.
Returns:
- str: The selected choice from the list, or None if the user chooses to quit.
The selected choice from the list, or None if the user chooses to quit.
"""
provider_models = get_provider_data()
@@ -52,16 +53,14 @@ def select_choice(prompt_message, choices):
)
def select_provider(provider_models):
"""
Presents a list of providers to the user and prompts them to select one.
def select_provider(provider_models: dict[str, list[str]]) -> str | None | bool:
"""Presents a list of providers to the user and prompts them to select one.
Args:
- provider_models (dict): A dictionary of provider models.
provider_models: A dictionary of provider models.
Returns:
- str: The selected provider
- None: If user explicitly quits
The selected provider, None if user explicitly quits, or False if no selection.
"""
predefined_providers = [p.lower() for p in PROVIDERS]
all_providers = sorted(set(predefined_providers + list(provider_models.keys())))
@@ -80,16 +79,15 @@ def select_provider(provider_models):
return provider.lower() if provider else False
def select_model(provider, provider_models):
"""
Presents a list of models for a given provider to the user and prompts them to select one.
def select_model(provider: str, provider_models: dict[str, list[str]]) -> str | None:
"""Presents a list of models for a given provider to the user and prompts them to select one.
Args:
- provider (str): The provider for which to select a model.
- provider_models (dict): A dictionary of provider models.
provider: The provider for which to select a model.
provider_models: A dictionary of provider models.
Returns:
- str: The selected model, or None if the operation is aborted or an invalid selection is made.
The selected model, or None if the operation is aborted or an invalid selection is made.
"""
predefined_providers = [p.lower() for p in PROVIDERS]
@@ -107,16 +105,17 @@ def select_model(provider, provider_models):
)
def load_provider_data(cache_file, cache_expiry):
"""
Loads provider data from a cache file if it exists and is not expired. If the cache is expired or corrupted, it fetches the data from the web.
def load_provider_data(cache_file: Path, cache_expiry: int) -> dict[str, Any] | None:
"""Loads provider data from a cache file if it exists and is not expired.
If the cache is expired or corrupted, it fetches the data from the web.
Args:
- cache_file (Path): The path to the cache file.
- cache_expiry (int): The cache expiry time in seconds.
cache_file: The path to the cache file.
cache_expiry: The cache expiry time in seconds.
Returns:
- dict or None: The loaded provider data or None if the operation fails.
The loaded provider data or None if the operation fails.
"""
current_time = time.time()
if (
@@ -137,32 +136,31 @@ def load_provider_data(cache_file, cache_expiry):
return fetch_provider_data(cache_file)
def read_cache_file(cache_file):
"""
Reads and returns the JSON content from a cache file. Returns None if the file contains invalid JSON.
def read_cache_file(cache_file: Path) -> dict[str, Any] | None:
"""Reads and returns the JSON content from a cache file.
Args:
- cache_file (Path): The path to the cache file.
cache_file: The path to the cache file.
Returns:
- dict or None: The JSON content of the cache file or None if the JSON is invalid.
The JSON content of the cache file or None if the JSON is invalid.
"""
try:
with open(cache_file, "r") as f:
return json.load(f)
data: dict[str, Any] = json.load(f)
return data
except json.JSONDecodeError:
return None
def fetch_provider_data(cache_file):
"""
Fetches provider data from a specified URL and caches it to a file.
def fetch_provider_data(cache_file: Path) -> dict[str, Any] | None:
"""Fetches provider data from a specified URL and caches it to a file.
Args:
- cache_file (Path): The path to the cache file.
cache_file: The path to the cache file.
Returns:
- dict or None: The fetched provider data or None if the operation fails.
The fetched provider data or None if the operation fails.
"""
ssl_config = os.environ["SSL_CERT_FILE"] = certifi.where()
@@ -180,36 +178,39 @@ def fetch_provider_data(cache_file):
return None
def download_data(response):
"""
Downloads data from a given HTTP response and returns the JSON content.
def download_data(response: requests.Response) -> dict[str, Any]:
"""Downloads data from a given HTTP response and returns the JSON content.
Args:
- response (requests.Response): The HTTP response object.
response: The HTTP response object.
Returns:
- dict: The JSON content of the response.
The JSON content of the response.
"""
total_size = int(response.headers.get("content-length", 0))
block_size = 8192
data_chunks = []
data_chunks: list[bytes] = []
bar: Any
with click.progressbar(
length=total_size, label="Downloading", show_pos=True
) as progress_bar:
) as bar:
for chunk in response.iter_content(block_size):
if chunk:
data_chunks.append(chunk)
progress_bar.update(len(chunk))
bar.update(len(chunk))
data_content = b"".join(data_chunks)
return json.loads(data_content.decode("utf-8"))
result: dict[str, Any] = json.loads(data_content.decode("utf-8"))
return result
def get_provider_data():
"""
Retrieves provider data from a cache file, filters out models based on provider criteria, and returns a dictionary of providers mapped to their models.
def get_provider_data() -> dict[str, list[str]] | None:
"""Retrieves provider data from a cache file.
Filters out models based on provider criteria, and returns a dictionary of providers
mapped to their models.
Returns:
- dict or None: A dictionary of providers mapped to their models or None if the operation fails.
A dictionary of providers mapped to their models or None if the operation fails.
"""
cache_dir = Path.home() / ".crewai"
cache_dir.mkdir(exist_ok=True)

File diff suppressed because it is too large Load Diff

View File

@@ -54,18 +54,11 @@ class {{crew_name}}():
"""Creates the {{crew_name}} crew"""
# To learn how to add knowledge sources to your crew, check out the documentation:
# https://docs.crewai.com/concepts/knowledge#what-is-knowledge
# Skills.md files under .agents/<skill_name>/ are loaded via get_skills_knowledge_sources()
skills_sources = (
self.get_skills_knowledge_sources()
if hasattr(self, "get_skills_knowledge_sources")
else []
)
return Crew(
agents=self.agents, # Automatically created by the @agent decorator
tasks=self.tasks, # Automatically created by the @task decorator
process=Process.sequential,
verbose=True,
knowledge_sources=skills_sources,
# process=Process.hierarchical, # In case you wanna use that instead https://docs.crewai.com/how-to/Hierarchical/
)

View File

@@ -2,6 +2,7 @@ import base64
from json import JSONDecodeError
import os
from pathlib import Path
import shutil
import subprocess
import tempfile
from typing import Any
@@ -55,6 +56,11 @@ class ToolCommand(BaseCommand, PlusAPIMixin):
tree_find_and_replace(project_root, "{{folder_name}}", folder_name)
tree_find_and_replace(project_root, "{{class_name}}", class_name)
# Copy AGENTS.md to project root
agents_md_src = Path(__file__).parent.parent / "templates" / "AGENTS.md"
if agents_md_src.exists():
shutil.copy2(agents_md_src, project_root / "AGENTS.md")
old_directory = os.getcwd()
os.chdir(project_root)
try:

View File

@@ -1,6 +1,215 @@
"""Version utilities for CrewAI CLI."""
from collections.abc import Mapping
from datetime import datetime, timedelta
from functools import lru_cache
import importlib.metadata
import json
from pathlib import Path
from typing import Any
from urllib import request
from urllib.error import URLError
import appdirs
from packaging.version import InvalidVersion, Version, parse
@lru_cache(maxsize=1)
def _get_cache_file() -> Path:
"""Get the path to the version cache file.
Cached to avoid repeated filesystem operations.
"""
cache_dir = Path(appdirs.user_cache_dir("crewai"))
cache_dir.mkdir(parents=True, exist_ok=True)
return cache_dir / "version_cache.json"
def get_crewai_version() -> str:
"""Get the version number of CrewAI running the CLI"""
"""Get the version number of CrewAI running the CLI."""
return importlib.metadata.version("crewai")
def _is_cache_valid(cache_data: Mapping[str, Any]) -> bool:
"""Check if the cache is still valid, less than 24 hours old."""
if "timestamp" not in cache_data:
return False
try:
cache_time = datetime.fromisoformat(str(cache_data["timestamp"]))
return datetime.now() - cache_time < timedelta(hours=24)
except (ValueError, TypeError):
return False
def _find_latest_non_yanked_version(
releases: Mapping[str, list[dict[str, Any]]],
) -> str | None:
"""Find the latest non-yanked version from PyPI releases data.
Args:
releases: PyPI releases dict mapping version strings to file info lists.
Returns:
The latest non-yanked version string, or None if all versions are yanked.
"""
best_version: Version | None = None
best_version_str: str | None = None
for version_str, files in releases.items():
try:
v = parse(version_str)
except InvalidVersion:
continue
if v.is_prerelease or v.is_devrelease:
continue
if not files:
continue
all_yanked = all(f.get("yanked", False) for f in files)
if all_yanked:
continue
if best_version is None or v > best_version:
best_version = v
best_version_str = version_str
return best_version_str
def _is_version_yanked(
version_str: str,
releases: Mapping[str, list[dict[str, Any]]],
) -> tuple[bool, str]:
"""Check if a specific version is yanked.
Args:
version_str: The version string to check.
releases: PyPI releases dict mapping version strings to file info lists.
Returns:
Tuple of (is_yanked, yanked_reason).
"""
files = releases.get(version_str, [])
if not files:
return False, ""
all_yanked = all(f.get("yanked", False) for f in files)
if not all_yanked:
return False, ""
for f in files:
reason = f.get("yanked_reason", "")
if reason:
return True, str(reason)
return True, ""
def get_latest_version_from_pypi(timeout: int = 2) -> str | None:
"""Get the latest non-yanked version of CrewAI from PyPI.
Args:
timeout: Request timeout in seconds.
Returns:
Latest non-yanked version string or None if unable to fetch.
"""
cache_file = _get_cache_file()
if cache_file.exists():
try:
cache_data = json.loads(cache_file.read_text())
if _is_cache_valid(cache_data) and "current_version" in cache_data:
version: str | None = cache_data.get("version")
return version
except (json.JSONDecodeError, OSError):
pass
try:
with request.urlopen(
"https://pypi.org/pypi/crewai/json", timeout=timeout
) as response:
data = json.loads(response.read())
releases: dict[str, list[dict[str, Any]]] = data["releases"]
latest_version = _find_latest_non_yanked_version(releases)
current_version = get_crewai_version()
is_yanked, yanked_reason = _is_version_yanked(current_version, releases)
cache_data = {
"version": latest_version,
"timestamp": datetime.now().isoformat(),
"current_version": current_version,
"current_version_yanked": is_yanked,
"current_version_yanked_reason": yanked_reason,
}
cache_file.write_text(json.dumps(cache_data))
return latest_version
except (URLError, json.JSONDecodeError, KeyError, OSError):
return None
def is_current_version_yanked() -> tuple[bool, str]:
"""Check if the currently installed version has been yanked on PyPI.
Reads from cache if available, otherwise triggers a fetch.
Returns:
Tuple of (is_yanked, yanked_reason).
"""
cache_file = _get_cache_file()
if cache_file.exists():
try:
cache_data = json.loads(cache_file.read_text())
if _is_cache_valid(cache_data) and "current_version" in cache_data:
current = get_crewai_version()
if cache_data.get("current_version") == current:
return (
bool(cache_data.get("current_version_yanked", False)),
str(cache_data.get("current_version_yanked_reason", "")),
)
except (json.JSONDecodeError, OSError):
pass
get_latest_version_from_pypi()
try:
cache_data = json.loads(cache_file.read_text())
return (
bool(cache_data.get("current_version_yanked", False)),
str(cache_data.get("current_version_yanked_reason", "")),
)
except (json.JSONDecodeError, OSError):
return False, ""
def check_version() -> tuple[str, str | None]:
"""Check current and latest versions.
Returns:
Tuple of (current_version, latest_version).
latest_version is None if unable to fetch from PyPI.
"""
current = get_crewai_version()
latest = get_latest_version_from_pypi()
return current, latest
def is_newer_version_available() -> tuple[bool, str, str | None]:
"""Check if a newer version is available.
Returns:
Tuple of (is_newer, current_version, latest_version).
"""
current, latest = check_version()
if latest is None:
return False, current, None
try:
return parse(latest) > parse(current), current, latest
except (InvalidVersion, TypeError):
return False, current, latest

View File

@@ -43,3 +43,23 @@ def platform_context(integration_token: str) -> Generator[None, Any, None]:
yield
finally:
_platform_integration_token.reset(token)
_current_task_id: contextvars.ContextVar[str | None] = contextvars.ContextVar(
"current_task_id", default=None
)
def set_current_task_id(task_id: str | None) -> contextvars.Token[str | None]:
"""Set the current task ID in the context. Returns a token for reset."""
return _current_task_id.set(task_id)
def reset_current_task_id(token: contextvars.Token[str | None]) -> None:
"""Reset the current task ID to its previous value."""
_current_task_id.reset(token)
def get_current_task_id() -> str | None:
"""Get the current task ID from the context."""
return _current_task_id.get()

View File

@@ -0,0 +1 @@
"""Core crewAI components and interfaces."""

View File

@@ -0,0 +1 @@
"""Provider interfaces for extensible crewAI components."""

View File

@@ -0,0 +1,78 @@
"""Content processor provider for extensible content processing."""
from contextvars import ContextVar
from typing import Any, Protocol, runtime_checkable
@runtime_checkable
class ContentProcessorProvider(Protocol):
"""Protocol for content processing during task execution."""
def process(self, content: str, context: dict[str, Any] | None = None) -> str:
"""Process content before use.
Args:
content: The content to process.
context: Optional context information.
Returns:
The processed content.
"""
...
class NoOpContentProcessor:
"""Default processor that returns content unchanged."""
def process(self, content: str, context: dict[str, Any] | None = None) -> str:
"""Return content unchanged.
Args:
content: The content to process.
context: Optional context information (unused).
Returns:
The original content unchanged.
"""
return content
_content_processor: ContextVar[ContentProcessorProvider | None] = ContextVar(
"_content_processor", default=None
)
_default_processor = NoOpContentProcessor()
def get_processor() -> ContentProcessorProvider:
"""Get the current content processor.
Returns:
The registered content processor or the default no-op processor.
"""
processor = _content_processor.get()
if processor is not None:
return processor
return _default_processor
def set_processor(processor: ContentProcessorProvider) -> None:
"""Set the content processor for the current context.
Args:
processor: The content processor to use.
"""
_content_processor.set(processor)
def process_content(content: str, context: dict[str, Any] | None = None) -> str:
"""Process content using the registered processor.
Args:
content: The content to process.
context: Optional context information.
Returns:
The processed content.
"""
return get_processor().process(content, context)

View File

@@ -0,0 +1,489 @@
"""Human input provider for HITL (Human-in-the-Loop) flows."""
from __future__ import annotations
import asyncio
from contextvars import ContextVar, Token
import sys
from typing import TYPE_CHECKING, Protocol, runtime_checkable
if TYPE_CHECKING:
from crewai.agent.core import Agent
from crewai.agents.parser import AgentFinish
from crewai.crew import Crew
from crewai.llms.base_llm import BaseLLM
from crewai.task import Task
from crewai.utilities.types import LLMMessage
class ExecutorContext(Protocol):
"""Context interface for human input providers to interact with executor."""
task: Task | None
crew: Crew | None
messages: list[LLMMessage]
ask_for_human_input: bool
llm: BaseLLM
agent: Agent
def _invoke_loop(self) -> AgentFinish:
"""Invoke the agent loop and return the result."""
...
def _is_training_mode(self) -> bool:
"""Check if training mode is active."""
...
def _handle_crew_training_output(
self,
result: AgentFinish,
human_feedback: str | None = None,
) -> None:
"""Handle training output."""
...
def _format_feedback_message(self, feedback: str) -> LLMMessage:
"""Format feedback as a message."""
...
class AsyncExecutorContext(ExecutorContext, Protocol):
"""Extended context for executors that support async invocation."""
async def _ainvoke_loop(self) -> AgentFinish:
"""Invoke the agent loop asynchronously and return the result."""
...
@runtime_checkable
class HumanInputProvider(Protocol):
"""Protocol for human input handling.
Implementations handle the full feedback flow:
- Sync: prompt user, loop until satisfied
- Async: use non-blocking I/O and async invoke loop
"""
def setup_messages(self, context: ExecutorContext) -> bool:
"""Set up messages for execution.
Called before standard message setup. Allows providers to handle
conversation resumption or other custom message initialization.
Args:
context: Executor context with messages list to modify.
Returns:
True if messages were set up (skip standard setup),
False to use standard setup.
"""
...
def post_setup_messages(self, context: ExecutorContext) -> None:
"""Called after standard message setup.
Allows providers to modify messages after standard setup completes.
Only called when setup_messages returned False.
Args:
context: Executor context with messages list to modify.
"""
...
def handle_feedback(
self,
formatted_answer: AgentFinish,
context: ExecutorContext,
) -> AgentFinish:
"""Handle the full human feedback flow synchronously.
Args:
formatted_answer: The agent's current answer.
context: Executor context for callbacks.
Returns:
The final answer after feedback processing.
Raises:
Exception: Async implementations may raise to signal external handling.
"""
...
async def handle_feedback_async(
self,
formatted_answer: AgentFinish,
context: AsyncExecutorContext,
) -> AgentFinish:
"""Handle the full human feedback flow asynchronously.
Uses non-blocking I/O for user prompts and async invoke loop
for agent re-execution.
Args:
formatted_answer: The agent's current answer.
context: Async executor context for callbacks.
Returns:
The final answer after feedback processing.
"""
...
@staticmethod
def _get_output_string(answer: AgentFinish) -> str:
"""Extract output string from answer.
Args:
answer: The agent's finished answer.
Returns:
String representation of the output.
"""
if isinstance(answer.output, str):
return answer.output
return answer.output.model_dump_json()
class SyncHumanInputProvider(HumanInputProvider):
"""Default human input provider with sync and async support."""
def setup_messages(self, context: ExecutorContext) -> bool:
"""Use standard message setup.
Args:
context: Executor context (unused).
Returns:
False to use standard setup.
"""
return False
def post_setup_messages(self, context: ExecutorContext) -> None:
"""No-op for sync provider.
Args:
context: Executor context (unused).
"""
def handle_feedback(
self,
formatted_answer: AgentFinish,
context: ExecutorContext,
) -> AgentFinish:
"""Handle feedback synchronously with terminal prompts.
Args:
formatted_answer: The agent's current answer.
context: Executor context for callbacks.
Returns:
The final answer after feedback processing.
"""
feedback = self._prompt_input(context.crew)
if context._is_training_mode():
return self._handle_training_feedback(formatted_answer, feedback, context)
return self._handle_regular_feedback(formatted_answer, feedback, context)
async def handle_feedback_async(
self,
formatted_answer: AgentFinish,
context: AsyncExecutorContext,
) -> AgentFinish:
"""Handle feedback asynchronously without blocking the event loop.
Args:
formatted_answer: The agent's current answer.
context: Async executor context for callbacks.
Returns:
The final answer after feedback processing.
"""
feedback = await self._prompt_input_async(context.crew)
if context._is_training_mode():
return await self._handle_training_feedback_async(
formatted_answer, feedback, context
)
return await self._handle_regular_feedback_async(
formatted_answer, feedback, context
)
# ── Sync helpers ──────────────────────────────────────────────────
@staticmethod
def _handle_training_feedback(
initial_answer: AgentFinish,
feedback: str,
context: ExecutorContext,
) -> AgentFinish:
"""Process training feedback (single iteration).
Args:
initial_answer: The agent's initial answer.
feedback: Human feedback string.
context: Executor context for callbacks.
Returns:
Improved answer after processing feedback.
"""
context._handle_crew_training_output(initial_answer, feedback)
context.messages.append(context._format_feedback_message(feedback))
improved_answer = context._invoke_loop()
context._handle_crew_training_output(improved_answer)
context.ask_for_human_input = False
return improved_answer
def _handle_regular_feedback(
self,
current_answer: AgentFinish,
initial_feedback: str,
context: ExecutorContext,
) -> AgentFinish:
"""Process regular feedback with iteration loop.
Args:
current_answer: The agent's current answer.
initial_feedback: Initial human feedback string.
context: Executor context for callbacks.
Returns:
Final answer after all feedback iterations.
"""
feedback = initial_feedback
answer = current_answer
while context.ask_for_human_input:
if feedback.strip() == "":
context.ask_for_human_input = False
else:
context.messages.append(context._format_feedback_message(feedback))
answer = context._invoke_loop()
feedback = self._prompt_input(context.crew)
return answer
# ── Async helpers ─────────────────────────────────────────────────
@staticmethod
async def _handle_training_feedback_async(
initial_answer: AgentFinish,
feedback: str,
context: AsyncExecutorContext,
) -> AgentFinish:
"""Process training feedback asynchronously (single iteration).
Args:
initial_answer: The agent's initial answer.
feedback: Human feedback string.
context: Async executor context for callbacks.
Returns:
Improved answer after processing feedback.
"""
context._handle_crew_training_output(initial_answer, feedback)
context.messages.append(context._format_feedback_message(feedback))
improved_answer = await context._ainvoke_loop()
context._handle_crew_training_output(improved_answer)
context.ask_for_human_input = False
return improved_answer
async def _handle_regular_feedback_async(
self,
current_answer: AgentFinish,
initial_feedback: str,
context: AsyncExecutorContext,
) -> AgentFinish:
"""Process regular feedback with async iteration loop.
Args:
current_answer: The agent's current answer.
initial_feedback: Initial human feedback string.
context: Async executor context for callbacks.
Returns:
Final answer after all feedback iterations.
"""
feedback = initial_feedback
answer = current_answer
while context.ask_for_human_input:
if feedback.strip() == "":
context.ask_for_human_input = False
else:
context.messages.append(context._format_feedback_message(feedback))
answer = await context._ainvoke_loop()
feedback = await self._prompt_input_async(context.crew)
return answer
# ── I/O ───────────────────────────────────────────────────────────
@staticmethod
def _prompt_input(crew: Crew | None) -> str:
"""Show rich panel and prompt for input.
Args:
crew: The crew instance for context.
Returns:
User input string from terminal.
"""
from rich.panel import Panel
from rich.text import Text
from crewai.events.event_listener import event_listener
formatter = event_listener.formatter
formatter.pause_live_updates()
try:
if crew and getattr(crew, "_train", False):
prompt_text = (
"TRAINING MODE: Provide feedback to improve the agent's performance.\n\n"
"This will be used to train better versions of the agent.\n"
"Please provide detailed feedback about the result quality and reasoning process."
)
title = "🎓 Training Feedback Required"
else:
prompt_text = (
"Provide feedback on the Final Result above.\n\n"
"• If you are happy with the result, simply hit Enter without typing anything.\n"
"• Otherwise, provide specific improvement requests.\n"
"• You can provide multiple rounds of feedback until satisfied."
)
title = "💬 Human Feedback Required"
content = Text()
content.append(prompt_text, style="yellow")
prompt_panel = Panel(
content,
title=title,
border_style="yellow",
padding=(1, 2),
)
formatter.console.print(prompt_panel)
response = input()
if response.strip() != "":
formatter.console.print("\n[cyan]Processing your feedback...[/cyan]")
return response
finally:
formatter.resume_live_updates()
@staticmethod
async def _prompt_input_async(crew: Crew | None) -> str:
"""Show rich panel and prompt for input without blocking the event loop.
Args:
crew: The crew instance for context.
Returns:
User input string from terminal.
"""
from rich.panel import Panel
from rich.text import Text
from crewai.events.event_listener import event_listener
formatter = event_listener.formatter
formatter.pause_live_updates()
try:
if crew and getattr(crew, "_train", False):
prompt_text = (
"TRAINING MODE: Provide feedback to improve the agent's performance.\n\n"
"This will be used to train better versions of the agent.\n"
"Please provide detailed feedback about the result quality and reasoning process."
)
title = "🎓 Training Feedback Required"
else:
prompt_text = (
"Provide feedback on the Final Result above.\n\n"
"• If you are happy with the result, simply hit Enter without typing anything.\n"
"• Otherwise, provide specific improvement requests.\n"
"• You can provide multiple rounds of feedback until satisfied."
)
title = "💬 Human Feedback Required"
content = Text()
content.append(prompt_text, style="yellow")
prompt_panel = Panel(
content,
title=title,
border_style="yellow",
padding=(1, 2),
)
formatter.console.print(prompt_panel)
response = await _async_readline()
if response.strip() != "":
formatter.console.print("\n[cyan]Processing your feedback...[/cyan]")
return response
finally:
formatter.resume_live_updates()
async def _async_readline() -> str:
"""Read a line from stdin using the event loop's native I/O.
Falls back to asyncio.to_thread on platforms where piping stdin
is unsupported.
Returns:
The line read from stdin, with trailing newline stripped.
"""
loop = asyncio.get_running_loop()
try:
reader = asyncio.StreamReader()
protocol = asyncio.StreamReaderProtocol(reader)
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
raw = await reader.readline()
return raw.decode().rstrip("\n")
except (OSError, NotImplementedError, ValueError):
return await asyncio.to_thread(input)
_provider: ContextVar[HumanInputProvider | None] = ContextVar(
"human_input_provider",
default=None,
)
def get_provider() -> HumanInputProvider:
"""Get the current human input provider.
Returns:
The current provider, or a new SyncHumanInputProvider if none set.
"""
provider = _provider.get()
if provider is None:
initialized_provider = SyncHumanInputProvider()
set_provider(initialized_provider)
return initialized_provider
return provider
def set_provider(provider: HumanInputProvider) -> Token[HumanInputProvider | None]:
"""Set the human input provider for the current context.
Args:
provider: The provider to use.
Returns:
Token that can be used to reset to previous value.
"""
return _provider.set(provider)
def reset_provider(token: Token[HumanInputProvider | None]) -> None:
"""Reset the provider to its previous value.
Args:
token: Token returned from set_provider.
"""
_provider.reset(token)

View File

@@ -187,6 +187,7 @@ class Crew(FlowTrackable, BaseModel):
_task_output_handler: TaskOutputStorageHandler = PrivateAttr(
default_factory=TaskOutputStorageHandler
)
_kickoff_event_id: str | None = PrivateAttr(default=None)
name: str | None = Field(default="crew")
cache: bool = Field(default=True)
@@ -751,19 +752,28 @@ class Crew(FlowTrackable, BaseModel):
for after_callback in self.after_kickoff_callbacks:
result = after_callback(result)
result = self._post_kickoff(result)
self.usage_metrics = self.calculate_usage_metrics()
return result
except Exception as e:
crewai_event_bus.emit(
self,
CrewKickoffFailedEvent(error=str(e), crew_name=self.name),
CrewKickoffFailedEvent(
error=str(e),
crew_name=self.name,
started_event_id=self._kickoff_event_id,
),
)
raise
finally:
clear_files(self.id)
detach(token)
def _post_kickoff(self, result: CrewOutput) -> CrewOutput:
return result
def kickoff_for_each(
self,
inputs: list[dict[str, Any]],
@@ -936,13 +946,19 @@ class Crew(FlowTrackable, BaseModel):
for after_callback in self.after_kickoff_callbacks:
result = after_callback(result)
result = self._post_kickoff(result)
self.usage_metrics = self.calculate_usage_metrics()
return result
except Exception as e:
crewai_event_bus.emit(
self,
CrewKickoffFailedEvent(error=str(e), crew_name=self.name),
CrewKickoffFailedEvent(
error=str(e),
crew_name=self.name,
started_event_id=self._kickoff_event_id,
),
)
raise
finally:
@@ -1181,6 +1197,9 @@ class Crew(FlowTrackable, BaseModel):
self.manager_agent = manager
manager.crew = self
def _get_execution_start_index(self, tasks: list[Task]) -> int | None:
return None
def _execute_tasks(
self,
tasks: list[Task],
@@ -1197,6 +1216,9 @@ class Crew(FlowTrackable, BaseModel):
Returns:
CrewOutput: Final output of the crew
"""
custom_start = self._get_execution_start_index(tasks)
if custom_start is not None:
start_index = custom_start
task_outputs: list[TaskOutput] = []
futures: list[tuple[Task, Future[TaskOutput], int]] = []
@@ -1305,8 +1327,10 @@ class Crew(FlowTrackable, BaseModel):
if files:
supported_types: list[str] = []
if agent and agent.llm and agent.llm.supports_multimodal():
provider = getattr(agent.llm, "provider", None) or getattr(
agent.llm, "model", "openai"
provider = (
getattr(agent.llm, "provider", None)
or getattr(agent.llm, "model", None)
or "openai"
)
api = getattr(agent.llm, "api", None)
supported_types = get_supported_content_types(provider, api)
@@ -1502,12 +1526,14 @@ class Crew(FlowTrackable, BaseModel):
final_string_output = final_task_output.raw
self._finish_execution(final_string_output)
self.token_usage = self.calculate_usage_metrics()
crewai_event_bus.flush()
crewai_event_bus.emit(
self,
CrewKickoffCompletedEvent(
crew_name=self.name,
output=final_task_output,
total_tokens=self.token_usage.total_tokens,
started_event_id=self._kickoff_event_id,
),
)
@@ -2011,7 +2037,13 @@ class Crew(FlowTrackable, BaseModel):
@staticmethod
def _show_tracing_disabled_message() -> None:
"""Show a message when tracing is disabled."""
from crewai.events.listeners.tracing.utils import has_user_declined_tracing
from crewai.events.listeners.tracing.utils import (
has_user_declined_tracing,
should_suppress_tracing_messages,
)
if should_suppress_tracing_messages():
return
console = Console()

View File

@@ -265,10 +265,9 @@ def prepare_kickoff(
normalized = {}
normalized = before_callback(normalized)
future = crewai_event_bus.emit(
crew,
CrewKickoffStartedEvent(crew_name=crew.name, inputs=normalized),
)
started_event = CrewKickoffStartedEvent(crew_name=crew.name, inputs=normalized)
crew._kickoff_event_id = started_event.event_id
future = crewai_event_bus.emit(crew, started_event)
if future is not None:
try:
future.result()

View File

@@ -195,6 +195,7 @@ __all__ = [
"ToolUsageFinishedEvent",
"ToolUsageStartedEvent",
"ToolValidateInputErrorEvent",
"_extension_exports",
"crewai_event_bus",
]
@@ -210,14 +211,29 @@ _AGENT_EVENT_MAPPING = {
"LiteAgentExecutionStartedEvent": "crewai.events.types.agent_events",
}
_extension_exports: dict[str, Any] = {}
def __getattr__(name: str) -> Any:
"""Lazy import for agent events to avoid circular imports."""
"""Lazy import for agent events and registered extensions."""
if name in _AGENT_EVENT_MAPPING:
import importlib
module_path = _AGENT_EVENT_MAPPING[name]
module = importlib.import_module(module_path)
return getattr(module, name)
if name in _extension_exports:
import importlib
value = _extension_exports[name]
if isinstance(value, str):
module_path, _, attr_name = value.rpartition(".")
if module_path:
module = importlib.import_module(module_path)
return getattr(module, attr_name)
return importlib.import_module(value)
return value
msg = f"module {__name__!r} has no attribute {name!r}"
raise AttributeError(msg)

View File

@@ -63,6 +63,7 @@ class BaseEvent(BaseModel):
parent_event_id: str | None = None
previous_event_id: str | None = None
triggered_by_event_id: str | None = None
started_event_id: str | None = None
emission_sequence: int | None = None
def to_json(self, exclude: set[str] | None = None) -> Serializable:

View File

@@ -227,6 +227,39 @@ class CrewAIEventsBus:
return decorator
def off(
self,
event_type: type[BaseEvent],
handler: Callable[..., Any],
) -> None:
"""Unregister an event handler for a specific event type.
Args:
event_type: The event class to stop listening for
handler: The handler function to unregister
"""
with self._rwlock.w_locked():
if event_type in self._sync_handlers:
existing_sync = self._sync_handlers[event_type]
if handler in existing_sync:
self._sync_handlers[event_type] = existing_sync - {handler}
if not self._sync_handlers[event_type]:
del self._sync_handlers[event_type]
if event_type in self._async_handlers:
existing_async = self._async_handlers[event_type]
if handler in existing_async:
self._async_handlers[event_type] = existing_async - {handler}
if not self._async_handlers[event_type]:
del self._async_handlers[event_type]
if event_type in self._handler_dependencies:
self._handler_dependencies[event_type].pop(handler, None)
if not self._handler_dependencies[event_type]:
del self._handler_dependencies[event_type]
self._execution_plan_cache.pop(event_type, None)
def _call_handlers(
self,
source: Any,
@@ -374,7 +407,8 @@ class CrewAIEventsBus:
if popped is None:
handle_empty_pop(event_type_name)
else:
_, popped_type = popped
popped_event_id, popped_type = popped
event.started_event_id = popped_event_id
expected_start = VALID_EVENT_PAIRS.get(event_type_name)
if expected_start and popped_type and popped_type != expected_start:
handle_mismatch(event_type_name, popped_type, expected_start)
@@ -536,24 +570,52 @@ class CrewAIEventsBus:
... # Do stuff...
... # Handlers are cleared after the context
"""
with self._rwlock.w_locked():
prev_sync = self._sync_handlers
prev_async = self._async_handlers
prev_deps = self._handler_dependencies
prev_cache = self._execution_plan_cache
self._sync_handlers = {}
self._async_handlers = {}
self._handler_dependencies = {}
self._execution_plan_cache = {}
with self._rwlock.r_locked():
saved_sync: dict[type[BaseEvent], frozenset[SyncHandler]] = dict(
self._sync_handlers
)
saved_async: dict[type[BaseEvent], frozenset[AsyncHandler]] = dict(
self._async_handlers
)
saved_deps: dict[type[BaseEvent], dict[Handler, list[Depends[Any]]]] = {
event_type: dict(handlers)
for event_type, handlers in self._handler_dependencies.items()
}
for event_type, sync_handlers in saved_sync.items():
for sync_handler in sync_handlers:
self.off(event_type, sync_handler)
for event_type, async_handlers in saved_async.items():
for async_handler in async_handlers:
self.off(event_type, async_handler)
try:
yield
finally:
with self._rwlock.w_locked():
self._sync_handlers = prev_sync
self._async_handlers = prev_async
self._handler_dependencies = prev_deps
self._execution_plan_cache = prev_cache
with self._rwlock.r_locked():
current_sync = dict(self._sync_handlers)
current_async = dict(self._async_handlers)
for event_type, cur_sync in current_sync.items():
orig_sync = saved_sync.get(event_type, frozenset())
for new_handler in cur_sync - orig_sync:
self.off(event_type, new_handler)
for event_type, cur_async in current_async.items():
orig_async = saved_async.get(event_type, frozenset())
for new_async_handler in cur_async - orig_async:
self.off(event_type, new_async_handler)
for event_type, sync_handlers in saved_sync.items():
for sync_handler in sync_handlers:
deps = saved_deps.get(event_type, {}).get(sync_handler)
self._register_handler(event_type, sync_handler, deps)
for event_type, async_handlers in saved_async.items():
for async_handler in async_handlers:
deps = saved_deps.get(event_type, {}).get(async_handler)
self._register_handler(event_type, async_handler, deps)
def shutdown(self, wait: bool = True) -> None:
"""Gracefully shutdown the event loop and wait for all tasks to finish.

View File

@@ -797,7 +797,13 @@ class TraceCollectionListener(BaseEventListener):
from rich.console import Console
from rich.panel import Panel
from crewai.events.listeners.tracing.utils import has_user_declined_tracing
from crewai.events.listeners.tracing.utils import (
has_user_declined_tracing,
should_suppress_tracing_messages,
)
if should_suppress_tracing_messages():
return
console = Console()

View File

@@ -1,3 +1,4 @@
from collections.abc import Callable
from contextvars import ContextVar, Token
from datetime import datetime
import getpass
@@ -26,6 +27,35 @@ logger = logging.getLogger(__name__)
_tracing_enabled: ContextVar[bool | None] = ContextVar("_tracing_enabled", default=None)
_first_time_trace_hook: ContextVar[Callable[[], bool] | None] = ContextVar(
"_first_time_trace_hook", default=None
)
_suppress_tracing_messages: ContextVar[bool] = ContextVar(
"_suppress_tracing_messages", default=False
)
def set_suppress_tracing_messages(suppress: bool) -> object:
"""Set whether to suppress tracing-related console messages.
Args:
suppress: True to suppress messages, False to show them.
Returns:
A token that can be used to restore the previous value.
"""
return _suppress_tracing_messages.set(suppress)
def should_suppress_tracing_messages() -> bool:
"""Check if tracing messages should be suppressed.
Returns:
True if messages should be suppressed, False otherwise.
"""
return _suppress_tracing_messages.get()
def should_enable_tracing(*, override: bool | None = None) -> bool:
"""Determine if tracing should be enabled.
@@ -407,10 +437,13 @@ def truncate_messages(
def should_auto_collect_first_time_traces() -> bool:
"""True if we should auto-collect traces for first-time user.
Returns:
True if first-time user AND telemetry not disabled AND tracing not explicitly enabled, False otherwise.
"""
hook = _first_time_trace_hook.get()
if hook is not None:
return hook()
if _is_test_environment():
return False
@@ -432,6 +465,9 @@ def prompt_user_for_trace_viewing(timeout_seconds: int = 20) -> bool:
if _is_test_environment():
return False
if should_suppress_tracing_messages():
return False
try:
import threading

View File

@@ -10,6 +10,7 @@ class LLMEventBase(BaseEvent):
from_task: Any | None = None
from_agent: Any | None = None
model: str | None = None
call_id: str
def __init__(self, **data: Any) -> None:
if data.get("from_task"):

View File

@@ -16,7 +16,7 @@ class ToolUsageEvent(BaseEvent):
tool_name: str
tool_args: dict[str, Any] | str
tool_class: str | None = None
run_attempts: int | None = None
run_attempts: int = 0
delegations: int | None = None
agent: Any | None = None
task_name: str | None = None
@@ -26,7 +26,7 @@ class ToolUsageEvent(BaseEvent):
model_config = ConfigDict(arbitrary_types_allowed=True)
def __init__(self, **data):
def __init__(self, **data: Any) -> None:
if data.get("from_task"):
task = data["from_task"]
data["task_id"] = str(task.id)
@@ -96,10 +96,10 @@ class ToolExecutionErrorEvent(BaseEvent):
type: str = "tool_execution_error"
tool_name: str
tool_args: dict[str, Any]
tool_class: Callable
tool_class: Callable[..., Any]
agent: Any | None = None
def __init__(self, **data):
def __init__(self, **data: Any) -> None:
super().__init__(**data)
# Set fingerprint data from the agent
if self.agent and hasattr(self.agent, "fingerprint") and self.agent.fingerprint:

View File

@@ -1,11 +1,45 @@
from contextvars import ContextVar
import os
import threading
from typing import Any, ClassVar
from typing import Any, ClassVar, cast
from rich.console import Console
from rich.live import Live
from rich.panel import Panel
from rich.text import Text
from crewai.cli.version import is_current_version_yanked, is_newer_version_available
_disable_version_check: ContextVar[bool] = ContextVar(
"_disable_version_check", default=False
)
_suppress_console_output: ContextVar[bool] = ContextVar(
"_suppress_console_output", default=False
)
def set_suppress_console_output(suppress: bool) -> object:
"""Set whether to suppress all console output.
Args:
suppress: True to suppress output, False to show it.
Returns:
A token that can be used to restore the previous value.
"""
return _suppress_console_output.set(suppress)
def should_suppress_console_output() -> bool:
"""Check if console output should be suppressed.
Returns:
True if output should be suppressed, False otherwise.
"""
return _suppress_console_output.get()
class ConsoleFormatter:
tool_usage_counts: ClassVar[dict[str, int]] = {}
@@ -35,13 +69,72 @@ class ConsoleFormatter:
padding=(1, 2),
)
def _show_version_update_message_if_needed(self) -> None:
"""Show version update message if a newer version is available.
Only displays when verbose mode is enabled and not running in CI/CD.
"""
if not self.verbose:
return
if _disable_version_check.get():
return
if os.getenv("CI", "").lower() in ("true", "1"):
return
if os.getenv("CREWAI_DISABLE_VERSION_CHECK", "").lower() in ("true", "1"):
return
try:
is_newer, current, latest = is_newer_version_available()
if is_newer and latest:
message = f"""A new version of CrewAI is available!
Current version: {current}
Latest version: {latest}
To update, run: uv sync --upgrade-package crewai"""
panel = Panel(
message,
title="✨ Update Available ✨",
border_style="yellow",
padding=(1, 2),
)
self.console.print(panel)
self.console.print()
is_yanked, yanked_reason = is_current_version_yanked()
if is_yanked:
yanked_message = f"Version {current} has been yanked from PyPI."
if yanked_reason:
yanked_message += f"\nReason: {yanked_reason}"
yanked_message += "\n\nTo update, run: uv sync --upgrade-package crewai"
yanked_panel = Panel(
yanked_message,
title="Yanked Version",
border_style="red",
padding=(1, 2),
)
self.console.print(yanked_panel)
self.console.print()
except Exception: # noqa: S110
# Silently ignore errors in version check - it's non-critical
pass
def _show_tracing_disabled_message_if_needed(self) -> None:
"""Show tracing disabled message if tracing is not enabled."""
from crewai.events.listeners.tracing.utils import (
has_user_declined_tracing,
is_tracing_enabled_in_context,
should_suppress_tracing_messages,
)
if should_suppress_tracing_messages():
return
if not is_tracing_enabled_in_context():
if has_user_declined_tracing():
message = """Info: Tracing is disabled.
@@ -93,6 +186,8 @@ To enable tracing, do any one of these:
def print(self, *args: Any, **kwargs: Any) -> None:
"""Print to console. Simplified to only handle panel-based output."""
if should_suppress_console_output():
return
# Skip blank lines during streaming
if len(args) == 0 and self._is_streaming:
return
@@ -176,9 +271,10 @@ To enable tracing, do any one of these:
if not self.verbose:
return
# Reset the crew completion event for this new crew execution
ConsoleFormatter.crew_completion_printed.clear()
self._show_version_update_message_if_needed()
content = self.create_status_content(
"Crew Execution Started",
crew_name,
@@ -237,6 +333,8 @@ To enable tracing, do any one of these:
def handle_flow_started(self, flow_name: str, flow_id: str) -> None:
"""Show flow started panel."""
self._show_version_update_message_if_needed()
content = Text()
content.append("Flow Started\n", style="blue bold")
content.append("Name: ", style="white")
@@ -446,6 +544,9 @@ To enable tracing, do any one of these:
if not self.verbose:
return
if should_suppress_console_output():
return
self._is_streaming = True
self._last_stream_call_type = call_type
@@ -885,7 +986,7 @@ To enable tracing, do any one of these:
is_a2a_delegation = False
try:
output_data = json.loads(formatted_answer.output)
output_data = json.loads(cast(str, formatted_answer.output))
if isinstance(output_data, dict):
if output_data.get("is_a2a") is True:
is_a2a_delegation = True

View File

@@ -18,6 +18,7 @@ from crewai.agents.parser import (
AgentFinish,
OutputParserError,
)
from crewai.core.providers.human_input import get_provider
from crewai.events.event_bus import crewai_event_bus
from crewai.events.listeners.tracing.utils import (
is_tracing_enabled_in_context,
@@ -31,7 +32,8 @@ from crewai.events.types.tool_usage_events import (
ToolUsageFinishedEvent,
ToolUsageStartedEvent,
)
from crewai.flow.flow import Flow, listen, or_, router, start
from crewai.flow.flow import Flow, StateProxy, listen, or_, router, start
from crewai.flow.types import FlowMethodName
from crewai.hooks.llm_hooks import (
get_after_llm_call_hooks,
get_before_llm_call_hooks,
@@ -41,7 +43,12 @@ from crewai.hooks.tool_hooks import (
get_after_tool_call_hooks,
get_before_tool_call_hooks,
)
from crewai.hooks.types import AfterLLMCallHookType, BeforeLLMCallHookType
from crewai.hooks.types import (
AfterLLMCallHookCallable,
AfterLLMCallHookType,
BeforeLLMCallHookCallable,
BeforeLLMCallHookType,
)
from crewai.utilities.agent_utils import (
convert_tools_to_openai_schema,
enforce_rpm_limit,
@@ -191,8 +198,12 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
self._instance_id = str(uuid4())[:8]
self.before_llm_call_hooks: list[BeforeLLMCallHookType] = []
self.after_llm_call_hooks: list[AfterLLMCallHookType] = []
self.before_llm_call_hooks: list[
BeforeLLMCallHookType | BeforeLLMCallHookCallable
] = []
self.after_llm_call_hooks: list[
AfterLLMCallHookType | AfterLLMCallHookCallable
] = []
self.before_llm_call_hooks.extend(get_before_llm_call_hooks())
self.after_llm_call_hooks.extend(get_after_llm_call_hooks())
@@ -207,6 +218,71 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
)
self._state = AgentReActState()
@property
def messages(self) -> list[LLMMessage]:
"""Delegate to state for ExecutorContext conformance."""
return self._state.messages
@messages.setter
def messages(self, value: list[LLMMessage]) -> None:
"""Delegate to state for ExecutorContext conformance."""
if self._flow_initialized and hasattr(self, "_state_lock"):
with self._state_lock:
self._state.messages = value
else:
self._state.messages = value
@property
def ask_for_human_input(self) -> bool:
"""Delegate to state for ExecutorContext conformance."""
return self._state.ask_for_human_input
@ask_for_human_input.setter
def ask_for_human_input(self, value: bool) -> None:
"""Delegate to state for ExecutorContext conformance."""
self._state.ask_for_human_input = value
def _invoke_loop(self) -> AgentFinish:
"""Invoke the agent loop and return the result.
Required by ExecutorContext protocol.
"""
self._state.iterations = 0
self._state.is_finished = False
self._state.current_answer = None
self.kickoff()
answer = self._state.current_answer
if not isinstance(answer, AgentFinish):
raise RuntimeError("Agent loop did not produce a final answer")
return answer
async def _ainvoke_loop(self) -> AgentFinish:
"""Invoke the agent loop asynchronously and return the result.
Required by AsyncExecutorContext protocol.
"""
self._state.iterations = 0
self._state.is_finished = False
self._state.current_answer = None
await self.akickoff()
answer = self._state.current_answer
if not isinstance(answer, AgentFinish):
raise RuntimeError("Agent loop did not produce a final answer")
return answer
def _format_feedback_message(self, feedback: str) -> LLMMessage:
"""Format feedback as a message for the LLM.
Required by ExecutorContext protocol.
"""
return format_message_for_llm(
self._i18n.slice("feedback_instructions").format(feedback=feedback)
)
def _ensure_flow_initialized(self) -> None:
"""Ensure Flow.__init__() has been called.
@@ -298,18 +374,10 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
Flow initialization is deferred to prevent event emission during agent setup.
Returns the temporary state until invoke() is called.
"""
if self._flow_initialized and hasattr(self, "_state_lock"):
return StateProxy(self._state, self._state_lock) # type: ignore[return-value]
return self._state
@property
def messages(self) -> list[LLMMessage]:
"""Compatibility property for mixin - returns state messages."""
return self._state.messages
@messages.setter
def messages(self, value: list[LLMMessage]) -> None:
"""Set state messages."""
self._state.messages = value
@property
def iterations(self) -> int:
"""Compatibility property for mixin - returns state iterations."""
@@ -416,15 +484,14 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
raise
@listen("continue_reasoning_native")
def call_llm_native_tools(
self,
) -> Literal["native_tool_calls", "native_finished", "context_error"]:
def call_llm_native_tools(self) -> None:
"""Execute LLM call with native function calling.
Always calls the LLM so it can read reflection prompts and decide
whether to provide a final answer or request more tools.
Returns routing decision based on whether tool calls or final answer.
Note: This is a listener, not a router. The route_native_tool_result
router fires after this to determine the next step based on state.
"""
try:
# Clear pending tools - LLM will decide what to do next after reading
@@ -454,8 +521,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
if isinstance(answer, list) and answer and self._is_tool_call_list(answer):
# Store tool calls for sequential processing
self.state.pending_tool_calls = list(answer)
return "native_tool_calls"
return # Router will check pending_tool_calls
if isinstance(answer, BaseModel):
self.state.current_answer = AgentFinish(
@@ -465,7 +531,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
)
self._invoke_step_callback(self.state.current_answer)
self._append_message_to_state(answer.model_dump_json())
return "native_finished"
return # Router will check current_answer
# Text response - this is the final answer
if isinstance(answer, str):
@@ -476,8 +542,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
)
self._invoke_step_callback(self.state.current_answer)
self._append_message_to_state(answer)
return "native_finished"
return # Router will check current_answer
# Unexpected response type, treat as final answer
self.state.current_answer = AgentFinish(
@@ -487,13 +552,12 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
)
self._invoke_step_callback(self.state.current_answer)
self._append_message_to_state(str(answer))
return "native_finished"
# Router will check current_answer
except Exception as e:
if is_context_length_exceeded(e):
self._last_context_error = e
return "context_error"
return # Router will check _last_context_error
if e.__class__.__module__.startswith("litellm"):
raise e
handle_unknown_error(self._printer, e, verbose=self.agent.verbose)
@@ -506,6 +570,22 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
return "execute_tool"
return "agent_finished"
@router(call_llm_native_tools)
def route_native_tool_result(
self,
) -> Literal["native_tool_calls", "native_finished", "context_error"]:
"""Route based on LLM response for native tool calling.
Checks state set by call_llm_native_tools to determine next step.
This router is needed because only router return values trigger
downstream listeners.
"""
if self._last_context_error is not None:
return "context_error"
if self.state.pending_tool_calls:
return "native_tool_calls"
return "native_finished"
@listen("execute_tool")
def execute_tool_action(self) -> Literal["tool_completed", "tool_result_is_final"]:
"""Execute the tool action and handle the result."""
@@ -689,6 +769,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
agent_key=agent_key,
),
)
error_event_emitted = False
track_delegation_if_needed(func_name, args_dict, self.task)
@@ -764,6 +845,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
error=e,
),
)
error_event_emitted = True
elif max_usage_reached and original_tool:
# Return error message when max usage limit is reached
result = f"Tool '{func_name}' has reached its usage limit of {original_tool.max_usage_count} times and cannot be used anymore."
@@ -792,20 +874,20 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
color="red",
)
# Emit tool usage finished event
crewai_event_bus.emit(
self,
event=ToolUsageFinishedEvent(
output=result,
tool_name=func_name,
tool_args=args_dict,
from_agent=self.agent,
from_task=self.task,
agent_key=agent_key,
started_at=started_at,
finished_at=datetime.now(),
),
)
if not error_event_emitted:
crewai_event_bus.emit(
self,
event=ToolUsageFinishedEvent(
output=result,
tool_name=func_name,
tool_args=args_dict,
from_agent=self.agent,
from_task=self.task,
agent_key=agent_key,
started_at=started_at,
finished_at=datetime.now(),
),
)
# Append tool result message
tool_message: LLMMessage = {
@@ -861,9 +943,11 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
self.state.iterations += 1
return "initialized"
@listen("initialized")
@listen(or_("initialized", "tool_completed", "native_tool_completed"))
def continue_iteration(self) -> Literal["check_iteration"]:
"""Bridge listener that connects iteration loop back to iteration check."""
if self._flow_initialized:
self._discard_or_listener(FlowMethodName("continue_iteration"))
return "check_iteration"
@router(or_(initialize_reasoning, continue_iteration))
@@ -1105,7 +1189,7 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
)
if self.state.ask_for_human_input:
formatted_answer = self._handle_human_feedback(formatted_answer)
formatted_answer = await self._ahandle_human_feedback(formatted_answer)
self._create_short_term_memory(formatted_answer)
self._create_long_term_memory(formatted_answer)
@@ -1319,17 +1403,22 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
Returns:
Final answer after feedback.
"""
output_str = (
str(formatted_answer.output)
if isinstance(formatted_answer.output, BaseModel)
else formatted_answer.output
)
human_feedback = self._ask_human_input(output_str)
provider = get_provider()
return provider.handle_feedback(formatted_answer, self)
if self._is_training_mode():
return self._handle_training_feedback(formatted_answer, human_feedback)
async def _ahandle_human_feedback(
self, formatted_answer: AgentFinish
) -> AgentFinish:
"""Process human feedback asynchronously and refine answer.
return self._handle_regular_feedback(formatted_answer, human_feedback)
Args:
formatted_answer: Initial agent result.
Returns:
Final answer after feedback.
"""
provider = get_provider()
return await provider.handle_feedback_async(formatted_answer, self)
def _is_training_mode(self) -> bool:
"""Check if training mode is active.
@@ -1339,101 +1428,6 @@ class AgentExecutor(Flow[AgentReActState], CrewAgentExecutorMixin):
"""
return bool(self.crew and self.crew._train)
def _handle_training_feedback(
self, initial_answer: AgentFinish, feedback: str
) -> AgentFinish:
"""Process training feedback and generate improved answer.
Args:
initial_answer: Initial agent output.
feedback: Training feedback.
Returns:
Improved answer.
"""
self._handle_crew_training_output(initial_answer, feedback)
self.state.messages.append(
format_message_for_llm(
self._i18n.slice("feedback_instructions").format(feedback=feedback)
)
)
# Re-run flow for improved answer
self.state.iterations = 0
self.state.is_finished = False
self.state.current_answer = None
self.kickoff()
# Get improved answer from state
improved_answer = self.state.current_answer
if not isinstance(improved_answer, AgentFinish):
raise RuntimeError(
"Training feedback iteration did not produce final answer"
)
self._handle_crew_training_output(improved_answer)
self.state.ask_for_human_input = False
return improved_answer
def _handle_regular_feedback(
self, current_answer: AgentFinish, initial_feedback: str
) -> AgentFinish:
"""Process regular feedback iteratively until user is satisfied.
Args:
current_answer: Current agent output.
initial_feedback: Initial user feedback.
Returns:
Final answer after iterations.
"""
feedback = initial_feedback
answer = current_answer
while self.state.ask_for_human_input:
if feedback.strip() == "":
self.state.ask_for_human_input = False
else:
answer = self._process_feedback_iteration(feedback)
output_str = (
str(answer.output)
if isinstance(answer.output, BaseModel)
else answer.output
)
feedback = self._ask_human_input(output_str)
return answer
def _process_feedback_iteration(self, feedback: str) -> AgentFinish:
"""Process a single feedback iteration and generate updated response.
Args:
feedback: User feedback.
Returns:
Updated agent response.
"""
self.state.messages.append(
format_message_for_llm(
self._i18n.slice("feedback_instructions").format(feedback=feedback)
)
)
# Re-run flow
self.state.iterations = 0
self.state.is_finished = False
self.state.current_answer = None
self.kickoff()
# Get answer from state
answer = self.state.current_answer
if not isinstance(answer, AgentFinish):
raise RuntimeError("Feedback iteration did not produce final answer")
return answer
@classmethod
def __get_pydantic_core_schema__(
cls, _source_type: Any, _handler: GetCoreSchemaHandler

View File

@@ -8,11 +8,13 @@ Example:
from crewai.flow import Flow, start, human_feedback
from crewai.flow.async_feedback import HumanFeedbackProvider, HumanFeedbackPending
class SlackProvider(HumanFeedbackProvider):
def request_feedback(self, context, flow):
self.send_slack_notification(context)
raise HumanFeedbackPending(context=context)
class MyFlow(Flow):
@start()
@human_feedback(
@@ -26,16 +28,30 @@ Example:
```
"""
from typing import Any
from crewai.flow.async_feedback.providers import ConsoleProvider
from crewai.flow.async_feedback.types import (
HumanFeedbackPending,
HumanFeedbackProvider,
PendingFeedbackContext,
)
from crewai.flow.async_feedback.providers import ConsoleProvider
__all__ = [
"ConsoleProvider",
"HumanFeedbackPending",
"HumanFeedbackProvider",
"PendingFeedbackContext",
"_extension_exports",
]
_extension_exports: dict[str, Any] = {}
def __getattr__(name: str) -> Any:
"""Support extensions via dynamic attribute lookup."""
if name in _extension_exports:
return _extension_exports[name]
msg = f"module {__name__!r} has no attribute {name!r}"
raise AttributeError(msg)

View File

@@ -6,10 +6,11 @@ provider that collects feedback via console input.
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any
from crewai.flow.async_feedback.types import PendingFeedbackContext
if TYPE_CHECKING:
from crewai.flow.flow import Flow
@@ -27,6 +28,7 @@ class ConsoleProvider:
```python
from crewai.flow.async_feedback import ConsoleProvider
# Explicitly use console provider
@human_feedback(
message="Review this:",
@@ -49,7 +51,7 @@ class ConsoleProvider:
def request_feedback(
self,
context: PendingFeedbackContext,
flow: Flow,
flow: Flow[Any],
) -> str:
"""Request feedback via console input (blocking).

View File

@@ -10,6 +10,7 @@ from dataclasses import dataclass, field
from datetime import datetime
from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
if TYPE_CHECKING:
from crewai.flow.flow import Flow
@@ -155,7 +156,7 @@ class HumanFeedbackPending(Exception): # noqa: N818 - Not an error, a control f
callback_info={
"slack_channel": "#reviews",
"thread_id": ticket_id,
}
},
)
```
"""
@@ -232,7 +233,7 @@ class HumanFeedbackProvider(Protocol):
callback_info={
"channel": self.channel,
"thread_id": thread_id,
}
},
)
```
"""
@@ -240,7 +241,7 @@ class HumanFeedbackProvider(Protocol):
def request_feedback(
self,
context: PendingFeedbackContext,
flow: Flow,
flow: Flow[Any],
) -> str:
"""Request feedback from a human.

View File

@@ -1,4 +1,5 @@
from typing import Final, Literal
AND_CONDITION: Final[Literal["AND"]] = "AND"
OR_CONDITION: Final[Literal["OR"]] = "OR"

View File

@@ -7,7 +7,14 @@ for building event-driven workflows with conditional execution and routing.
from __future__ import annotations
import asyncio
from collections.abc import Callable, Sequence
from collections.abc import (
Callable,
ItemsView,
Iterator,
KeysView,
Sequence,
ValuesView,
)
from concurrent.futures import Future
import copy
import inspect
@@ -45,6 +52,7 @@ from crewai.events.listeners.tracing.utils import (
has_user_declined_tracing,
set_tracing_enabled,
should_enable_tracing,
should_suppress_tracing_messages,
)
from crewai.events.types.flow_events import (
FlowCreatedEvent,
@@ -58,6 +66,7 @@ from crewai.events.types.flow_events import (
MethodExecutionStartedEvent,
)
from crewai.flow.constants import AND_CONDITION, OR_CONDITION
from crewai.flow.flow_context import current_flow_id, current_flow_request_id
from crewai.flow.flow_wrappers import (
FlowCondition,
FlowConditions,
@@ -407,6 +416,132 @@ def and_(*conditions: str | FlowCondition | Callable[..., Any]) -> FlowCondition
return {"type": AND_CONDITION, "conditions": processed_conditions}
class LockedListProxy(Generic[T]):
"""Thread-safe proxy for list operations.
Wraps a list and uses a lock for all mutating operations.
"""
def __init__(self, lst: list[T], lock: threading.Lock) -> None:
self._list = lst
self._lock = lock
def append(self, item: T) -> None:
with self._lock:
self._list.append(item)
def extend(self, items: list[T]) -> None:
with self._lock:
self._list.extend(items)
def insert(self, index: int, item: T) -> None:
with self._lock:
self._list.insert(index, item)
def remove(self, item: T) -> None:
with self._lock:
self._list.remove(item)
def pop(self, index: int = -1) -> T:
with self._lock:
return self._list.pop(index)
def clear(self) -> None:
with self._lock:
self._list.clear()
def __setitem__(self, index: int, value: T) -> None:
with self._lock:
self._list[index] = value
def __delitem__(self, index: int) -> None:
with self._lock:
del self._list[index]
def __getitem__(self, index: int) -> T:
return self._list[index]
def __len__(self) -> int:
return len(self._list)
def __iter__(self) -> Iterator[T]:
return iter(self._list)
def __contains__(self, item: object) -> bool:
return item in self._list
def __repr__(self) -> str:
return repr(self._list)
def __bool__(self) -> bool:
return bool(self._list)
class LockedDictProxy(Generic[T]):
"""Thread-safe proxy for dict operations.
Wraps a dict and uses a lock for all mutating operations.
"""
def __init__(self, d: dict[str, T], lock: threading.Lock) -> None:
self._dict = d
self._lock = lock
def __setitem__(self, key: str, value: T) -> None:
with self._lock:
self._dict[key] = value
def __delitem__(self, key: str) -> None:
with self._lock:
del self._dict[key]
def pop(self, key: str, *default: T) -> T:
with self._lock:
return self._dict.pop(key, *default)
def update(self, other: dict[str, T]) -> None:
with self._lock:
self._dict.update(other)
def clear(self) -> None:
with self._lock:
self._dict.clear()
def setdefault(self, key: str, default: T) -> T:
with self._lock:
return self._dict.setdefault(key, default)
def __getitem__(self, key: str) -> T:
return self._dict[key]
def __len__(self) -> int:
return len(self._dict)
def __iter__(self) -> Iterator[str]:
return iter(self._dict)
def __contains__(self, key: object) -> bool:
return key in self._dict
def keys(self) -> KeysView[str]:
return self._dict.keys()
def values(self) -> ValuesView[T]:
return self._dict.values()
def items(self) -> ItemsView[str, T]:
return self._dict.items()
def get(self, key: str, default: T | None = None) -> T | None:
return self._dict.get(key, default)
def __repr__(self) -> str:
return repr(self._dict)
def __bool__(self) -> bool:
return bool(self._dict)
class StateProxy(Generic[T]):
"""Proxy that provides thread-safe access to flow state.
@@ -421,7 +556,13 @@ class StateProxy(Generic[T]):
object.__setattr__(self, "_proxy_lock", lock)
def __getattr__(self, name: str) -> Any:
return getattr(object.__getattribute__(self, "_proxy_state"), name)
value = getattr(object.__getattribute__(self, "_proxy_state"), name)
lock = object.__getattribute__(self, "_proxy_lock")
if isinstance(value, list):
return LockedListProxy(value, lock)
if isinstance(value, dict):
return LockedDictProxy(value, lock)
return value
def __setattr__(self, name: str, value: Any) -> None:
if name in ("_proxy_state", "_proxy_lock"):
@@ -1540,6 +1681,13 @@ class Flow(Generic[T], metaclass=FlowMeta):
ctx = baggage.set_baggage("flow_input_files", input_files or {}, context=ctx)
flow_token = attach(ctx)
flow_id_token = None
request_id_token = None
if current_flow_id.get() is None:
flow_id_token = current_flow_id.set(self.flow_id)
if current_flow_request_id.get() is None:
request_id_token = current_flow_request_id.set(self.flow_id)
try:
# Reset flow state for fresh execution unless restoring from persistence
is_restoring = inputs and "id" in inputs and self._persistence is not None
@@ -1584,7 +1732,6 @@ class Flow(Generic[T], metaclass=FlowMeta):
reset_emission_counter()
reset_last_event_id()
# Emit FlowStartedEvent and log the start of the flow.
if not self.suppress_flow_events:
future = crewai_event_bus.emit(
self,
@@ -1595,7 +1742,10 @@ class Flow(Generic[T], metaclass=FlowMeta):
),
)
if future:
self._event_futures.append(future)
try:
await asyncio.wrap_future(future)
except Exception:
logger.warning("FlowStartedEvent handler failed", exc_info=True)
self._log_flow_event(
f"Flow started with ID: {self.flow_id}", color="bold magenta"
)
@@ -1687,6 +1837,12 @@ class Flow(Generic[T], metaclass=FlowMeta):
final_output = self._method_outputs[-1] if self._method_outputs else None
if self._event_futures:
await asyncio.gather(
*[asyncio.wrap_future(f) for f in self._event_futures]
)
self._event_futures.clear()
if not self.suppress_flow_events:
future = crewai_event_bus.emit(
self,
@@ -1698,13 +1854,12 @@ class Flow(Generic[T], metaclass=FlowMeta):
),
)
if future:
self._event_futures.append(future)
if self._event_futures:
await asyncio.gather(
*[asyncio.wrap_future(f) for f in self._event_futures]
)
self._event_futures.clear()
try:
await asyncio.wrap_future(future)
except Exception:
logger.warning(
"FlowFinishedEvent handler failed", exc_info=True
)
if not self.suppress_flow_events:
trace_listener = TraceCollectionListener()
@@ -1717,6 +1872,10 @@ class Flow(Generic[T], metaclass=FlowMeta):
return final_output
finally:
if request_id_token is not None:
current_flow_request_id.reset(request_id_token)
if flow_id_token is not None:
current_flow_id.reset(flow_id_token)
detach(flow_token)
async def akickoff(
@@ -1775,40 +1934,14 @@ class Flow(Generic[T], metaclass=FlowMeta):
await self._execute_listeners(start_method_name, result, finished_event_id)
# Then execute listeners for the router result (e.g., "approved")
router_result_trigger = FlowMethodName(str(result))
listeners_for_result = self._find_triggered_methods(
router_result_trigger, router_only=False
listener_result = (
self.last_human_feedback
if self.last_human_feedback is not None
else result
)
await self._execute_listeners(
router_result_trigger, listener_result, finished_event_id
)
if listeners_for_result:
# Pass the HumanFeedbackResult if available
listener_result = (
self.last_human_feedback
if self.last_human_feedback is not None
else result
)
racing_group = self._get_racing_group_for_listeners(
listeners_for_result
)
if racing_group:
racing_members, _ = racing_group
other_listeners = [
name
for name in listeners_for_result
if name not in racing_members
]
await self._execute_racing_listeners(
racing_members,
other_listeners,
listener_result,
finished_event_id,
)
else:
tasks = [
self._execute_single_listener(
listener_name, listener_result, finished_event_id
)
for listener_name in listeners_for_result
]
await asyncio.gather(*tasks)
else:
await self._execute_listeners(start_method_name, result, finished_event_id)
@@ -2014,15 +2147,14 @@ class Flow(Generic[T], metaclass=FlowMeta):
router_input = router_result_to_feedback.get(
str(current_trigger), current_result
)
current_triggering_event_id = await self._execute_single_listener(
(
router_result,
current_triggering_event_id,
) = await self._execute_single_listener(
router_name, router_input, current_triggering_event_id
)
# After executing router, the router's result is the path
router_result = (
self._method_outputs[-1] if self._method_outputs else None
)
if router_result: # Only add non-None results
router_results.append(router_result)
router_results.append(FlowMethodName(str(router_result)))
# If this was a human_feedback router, map the outcome to the feedback
if self.last_human_feedback is not None:
router_result_to_feedback[str(router_result)] = (
@@ -2062,12 +2194,14 @@ class Flow(Generic[T], metaclass=FlowMeta):
racing_members,
other_listeners,
listener_result,
triggering_event_id,
current_triggering_event_id,
)
else:
tasks = [
self._execute_single_listener(
listener_name, listener_result, triggering_event_id
listener_name,
listener_result,
current_triggering_event_id,
)
for listener_name in listeners_triggered
]
@@ -2250,7 +2384,7 @@ class Flow(Generic[T], metaclass=FlowMeta):
listener_name: FlowMethodName,
result: Any,
triggering_event_id: str | None = None,
) -> str | None:
) -> tuple[Any, str | None]:
"""Executes a single listener method with proper event handling.
This internal method manages the execution of an individual listener,
@@ -2263,8 +2397,9 @@ class Flow(Generic[T], metaclass=FlowMeta):
used for causal chain tracking.
Returns:
The event_id of the MethodExecutionFinishedEvent emitted by this listener,
or None if events are suppressed.
A tuple of (listener_result, event_id) where listener_result is the return
value of the listener method and event_id is the MethodExecutionFinishedEvent
id, or (None, None) if skipped during resumption.
Note:
- Inspects method signature to determine if it accepts the trigger result
@@ -2290,7 +2425,7 @@ class Flow(Generic[T], metaclass=FlowMeta):
):
# This conditional start was executed, continue its chain
await self._execute_start_method(start_method_name)
return None
return (None, None)
# For cyclic flows, clear from completed to allow re-execution
self._completed_methods.discard(listener_name)
# Also clear from fired OR listeners for cyclic flows
@@ -2328,46 +2463,7 @@ class Flow(Generic[T], metaclass=FlowMeta):
listener_name, listener_result, finished_event_id
)
# If this listener is also a router (e.g., has @human_feedback with emit),
# we need to trigger listeners for the router result as well
if listener_name in self._routers and listener_result is not None:
router_result_trigger = FlowMethodName(str(listener_result))
listeners_for_result = self._find_triggered_methods(
router_result_trigger, router_only=False
)
if listeners_for_result:
# Pass the HumanFeedbackResult if available
feedback_result = (
self.last_human_feedback
if self.last_human_feedback is not None
else listener_result
)
racing_group = self._get_racing_group_for_listeners(
listeners_for_result
)
if racing_group:
racing_members, _ = racing_group
other_listeners = [
name
for name in listeners_for_result
if name not in racing_members
]
await self._execute_racing_listeners(
racing_members,
other_listeners,
feedback_result,
finished_event_id,
)
else:
tasks = [
self._execute_single_listener(
name, feedback_result, finished_event_id
)
for name in listeners_for_result
]
await asyncio.gather(*tasks)
return finished_event_id
return (listener_result, finished_event_id)
except Exception as e:
# Don't log HumanFeedbackPending as an error - it's expected control flow
@@ -2614,6 +2710,8 @@ class Flow(Generic[T], metaclass=FlowMeta):
@staticmethod
def _show_tracing_disabled_message() -> None:
"""Show a message when tracing is disabled."""
if should_suppress_tracing_messages():
return
console = Console()

View File

@@ -8,6 +8,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from crewai.flow.async_feedback.types import HumanFeedbackProvider

View File

@@ -0,0 +1,16 @@
"""Flow execution context management.
This module provides context variables for tracking flow execution state across
async boundaries and nested function calls.
"""
import contextvars
current_flow_request_id: contextvars.ContextVar[str | None] = contextvars.ContextVar(
"flow_request_id", default=None
)
current_flow_id: contextvars.ContextVar[str | None] = contextvars.ContextVar(
"flow_id", default=None
)

View File

@@ -1,46 +1,22 @@
import inspect
from typing import Any
from pydantic import BaseModel, Field, InstanceOf, model_validator
from pydantic import BaseModel, model_validator
from typing_extensions import Self
from crewai.flow.flow import Flow
from crewai.flow.flow_context import current_flow_id, current_flow_request_id
class FlowTrackable(BaseModel):
"""Mixin that tracks the Flow instance that instantiated the object, e.g. a
Flow instance that created a Crew or Agent.
"""Mixin that tracks flow execution context for objects created within flows.
Automatically finds and stores a reference to the parent Flow instance by
inspecting the call stack.
When a Crew or Agent is instantiated inside a flow execution, this mixin
automatically captures the flow ID and request ID from context variables,
enabling proper tracking and association with the parent flow execution.
"""
parent_flow: InstanceOf[Flow[Any]] | None = Field(
default=None,
description="The parent flow of the instance, if it was created inside a flow.",
)
@model_validator(mode="after")
def _set_parent_flow(self) -> Self:
max_depth = 8
frame = inspect.currentframe()
try:
if frame is None:
return self
frame = frame.f_back
for _ in range(max_depth):
if frame is None:
break
candidate = frame.f_locals.get("self")
if isinstance(candidate, Flow):
self.parent_flow = candidate
break
frame = frame.f_back
finally:
del frame
def _set_flow_context(self) -> Self:
request_id = current_flow_request_id.get()
if request_id:
self._request_id = request_id
self._flow_id = current_flow_id.get()
return self

View File

@@ -11,6 +11,7 @@ Example (synchronous, default):
```python
from crewai.flow import Flow, start, listen, human_feedback
class ReviewFlow(Flow):
@start()
@human_feedback(
@@ -32,11 +33,13 @@ Example (asynchronous with custom provider):
from crewai.flow import Flow, start, human_feedback
from crewai.flow.async_feedback import HumanFeedbackProvider, HumanFeedbackPending
class SlackProvider(HumanFeedbackProvider):
def request_feedback(self, context, flow):
self.send_notification(context)
raise HumanFeedbackPending(context=context)
class ReviewFlow(Flow):
@start()
@human_feedback(
@@ -229,6 +232,7 @@ def human_feedback(
def review_document(self):
return document_content
@listen("approved")
def publish(self):
print(f"Publishing: {self.last_human_feedback.output}")
@@ -265,7 +269,7 @@ def human_feedback(
def decorator(func: F) -> F:
"""Inner decorator that wraps the function."""
def _request_feedback(flow_instance: Flow, method_output: Any) -> str:
def _request_feedback(flow_instance: Flow[Any], method_output: Any) -> str:
"""Request feedback using provider or default console."""
from crewai.flow.async_feedback.types import PendingFeedbackContext
@@ -291,19 +295,16 @@ def human_feedback(
effective_provider = flow_config.hitl_provider
if effective_provider is not None:
# Use provider (may raise HumanFeedbackPending for async providers)
return effective_provider.request_feedback(context, flow_instance)
else:
# Use default console input (local development)
return flow_instance._request_human_feedback(
message=message,
output=method_output,
metadata=metadata,
emit=emit,
)
return flow_instance._request_human_feedback(
message=message,
output=method_output,
metadata=metadata,
emit=emit,
)
def _process_feedback(
flow_instance: Flow,
flow_instance: Flow[Any],
method_output: Any,
raw_feedback: str,
) -> HumanFeedbackResult | str:
@@ -319,12 +320,14 @@ def human_feedback(
# No default and no feedback - use first outcome
collapsed_outcome = emit[0]
elif emit:
# Collapse feedback to outcome using LLM
collapsed_outcome = flow_instance._collapse_to_outcome(
feedback=raw_feedback,
outcomes=emit,
llm=llm,
)
if llm is not None:
collapsed_outcome = flow_instance._collapse_to_outcome(
feedback=raw_feedback,
outcomes=emit,
llm=llm,
)
else:
collapsed_outcome = emit[0]
# Create result
result = HumanFeedbackResult(
@@ -349,7 +352,7 @@ def human_feedback(
if asyncio.iscoroutinefunction(func):
# Async wrapper
@wraps(func)
async def async_wrapper(self: Flow, *args: Any, **kwargs: Any) -> Any:
async def async_wrapper(self: Flow[Any], *args: Any, **kwargs: Any) -> Any:
# Execute the original method
method_output = await func(self, *args, **kwargs)
@@ -363,7 +366,7 @@ def human_feedback(
else:
# Sync wrapper
@wraps(func)
def sync_wrapper(self: Flow, *args: Any, **kwargs: Any) -> Any:
def sync_wrapper(self: Flow[Any], *args: Any, **kwargs: Any) -> Any:
# Execute the original method
method_output = func(self, *args, **kwargs)
@@ -397,11 +400,10 @@ def human_feedback(
)
wrapper.__is_flow_method__ = True
# Make it a router if emit specified
if emit:
wrapper.__is_router__ = True
wrapper.__router_paths__ = list(emit)
return wrapper # type: ignore[return-value]
return wrapper # type: ignore[no-any-return]
return decorator

View File

@@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Any
from pydantic import BaseModel
if TYPE_CHECKING:
from crewai.flow.async_feedback.types import PendingFeedbackContext
@@ -103,4 +104,3 @@ class FlowPersistence(ABC):
Args:
flow_uuid: Unique identifier for the flow instance
"""
pass

View File

@@ -15,6 +15,7 @@ from pydantic import BaseModel
from crewai.flow.persistence.base import FlowPersistence
from crewai.utilities.paths import db_storage_path
if TYPE_CHECKING:
from crewai.flow.async_feedback.types import PendingFeedbackContext
@@ -176,7 +177,8 @@ class SQLiteFlowPersistence(FlowPersistence):
row = cursor.fetchone()
if row:
return json.loads(row[0])
result = json.loads(row[0])
return result if isinstance(result, dict) else None
return None
def save_pending_feedback(
@@ -196,7 +198,6 @@ class SQLiteFlowPersistence(FlowPersistence):
state_data: Current state data
"""
# Import here to avoid circular imports
from crewai.flow.async_feedback.types import PendingFeedbackContext
# Convert state_data to dict
if isinstance(state_data, BaseModel):

View File

@@ -3,7 +3,12 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any, cast
from crewai.events.event_listener import event_listener
from crewai.hooks.types import AfterLLMCallHookType, BeforeLLMCallHookType
from crewai.hooks.types import (
AfterLLMCallHookCallable,
AfterLLMCallHookType,
BeforeLLMCallHookCallable,
BeforeLLMCallHookType,
)
from crewai.utilities.printer import Printer
@@ -149,12 +154,12 @@ class LLMCallHookContext:
event_listener.formatter.resume_live_updates()
_before_llm_call_hooks: list[BeforeLLMCallHookType] = []
_after_llm_call_hooks: list[AfterLLMCallHookType] = []
_before_llm_call_hooks: list[BeforeLLMCallHookType | BeforeLLMCallHookCallable] = []
_after_llm_call_hooks: list[AfterLLMCallHookType | AfterLLMCallHookCallable] = []
def register_before_llm_call_hook(
hook: BeforeLLMCallHookType,
hook: BeforeLLMCallHookType | BeforeLLMCallHookCallable,
) -> None:
"""Register a global before_llm_call hook.
@@ -190,7 +195,7 @@ def register_before_llm_call_hook(
def register_after_llm_call_hook(
hook: AfterLLMCallHookType,
hook: AfterLLMCallHookType | AfterLLMCallHookCallable,
) -> None:
"""Register a global after_llm_call hook.
@@ -217,7 +222,9 @@ def register_after_llm_call_hook(
_after_llm_call_hooks.append(hook)
def get_before_llm_call_hooks() -> list[BeforeLLMCallHookType]:
def get_before_llm_call_hooks() -> list[
BeforeLLMCallHookType | BeforeLLMCallHookCallable
]:
"""Get all registered global before_llm_call hooks.
Returns:
@@ -226,7 +233,7 @@ def get_before_llm_call_hooks() -> list[BeforeLLMCallHookType]:
return _before_llm_call_hooks.copy()
def get_after_llm_call_hooks() -> list[AfterLLMCallHookType]:
def get_after_llm_call_hooks() -> list[AfterLLMCallHookType | AfterLLMCallHookCallable]:
"""Get all registered global after_llm_call hooks.
Returns:
@@ -236,7 +243,7 @@ def get_after_llm_call_hooks() -> list[AfterLLMCallHookType]:
def unregister_before_llm_call_hook(
hook: BeforeLLMCallHookType,
hook: BeforeLLMCallHookType | BeforeLLMCallHookCallable,
) -> bool:
"""Unregister a specific global before_llm_call hook.
@@ -262,7 +269,7 @@ def unregister_before_llm_call_hook(
def unregister_after_llm_call_hook(
hook: AfterLLMCallHookType,
hook: AfterLLMCallHookType | AfterLLMCallHookCallable,
) -> bool:
"""Unregister a specific global after_llm_call hook.

View File

@@ -3,7 +3,12 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any
from crewai.events.event_listener import event_listener
from crewai.hooks.types import AfterToolCallHookType, BeforeToolCallHookType
from crewai.hooks.types import (
AfterToolCallHookCallable,
AfterToolCallHookType,
BeforeToolCallHookCallable,
BeforeToolCallHookType,
)
from crewai.utilities.printer import Printer
@@ -112,12 +117,12 @@ class ToolCallHookContext:
# Global hook registries
_before_tool_call_hooks: list[BeforeToolCallHookType] = []
_after_tool_call_hooks: list[AfterToolCallHookType] = []
_before_tool_call_hooks: list[BeforeToolCallHookType | BeforeToolCallHookCallable] = []
_after_tool_call_hooks: list[AfterToolCallHookType | AfterToolCallHookCallable] = []
def register_before_tool_call_hook(
hook: BeforeToolCallHookType,
hook: BeforeToolCallHookType | BeforeToolCallHookCallable,
) -> None:
"""Register a global before_tool_call hook.
@@ -154,7 +159,7 @@ def register_before_tool_call_hook(
def register_after_tool_call_hook(
hook: AfterToolCallHookType,
hook: AfterToolCallHookType | AfterToolCallHookCallable,
) -> None:
"""Register a global after_tool_call hook.
@@ -184,7 +189,9 @@ def register_after_tool_call_hook(
_after_tool_call_hooks.append(hook)
def get_before_tool_call_hooks() -> list[BeforeToolCallHookType]:
def get_before_tool_call_hooks() -> list[
BeforeToolCallHookType | BeforeToolCallHookCallable
]:
"""Get all registered global before_tool_call hooks.
Returns:
@@ -193,7 +200,9 @@ def get_before_tool_call_hooks() -> list[BeforeToolCallHookType]:
return _before_tool_call_hooks.copy()
def get_after_tool_call_hooks() -> list[AfterToolCallHookType]:
def get_after_tool_call_hooks() -> list[
AfterToolCallHookType | AfterToolCallHookCallable
]:
"""Get all registered global after_tool_call hooks.
Returns:
@@ -203,7 +212,7 @@ def get_after_tool_call_hooks() -> list[AfterToolCallHookType]:
def unregister_before_tool_call_hook(
hook: BeforeToolCallHookType,
hook: BeforeToolCallHookType | BeforeToolCallHookCallable,
) -> bool:
"""Unregister a specific global before_tool_call hook.
@@ -229,7 +238,7 @@ def unregister_before_tool_call_hook(
def unregister_after_tool_call_hook(
hook: AfterToolCallHookType,
hook: AfterToolCallHookType | AfterToolCallHookCallable,
) -> bool:
"""Unregister a specific global after_tool_call hook.

View File

@@ -0,0 +1 @@
"""Knowledge source utilities."""

View File

@@ -0,0 +1,70 @@
"""Helper utilities for knowledge sources."""
from typing import Any, ClassVar
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
from crewai.knowledge.source.csv_knowledge_source import CSVKnowledgeSource
from crewai.knowledge.source.excel_knowledge_source import ExcelKnowledgeSource
from crewai.knowledge.source.json_knowledge_source import JSONKnowledgeSource
from crewai.knowledge.source.pdf_knowledge_source import PDFKnowledgeSource
from crewai.knowledge.source.text_file_knowledge_source import TextFileKnowledgeSource
class SourceHelper:
"""Helper class for creating and managing knowledge sources."""
SUPPORTED_FILE_TYPES: ClassVar[list[str]] = [
".csv",
".pdf",
".json",
".txt",
".xlsx",
".xls",
]
_FILE_TYPE_MAP: ClassVar[dict[str, type[BaseKnowledgeSource]]] = {
".csv": CSVKnowledgeSource,
".pdf": PDFKnowledgeSource,
".json": JSONKnowledgeSource,
".txt": TextFileKnowledgeSource,
".xlsx": ExcelKnowledgeSource,
".xls": ExcelKnowledgeSource,
}
@classmethod
def is_supported_file(cls, file_path: str) -> bool:
"""Check if a file type is supported.
Args:
file_path: Path to the file.
Returns:
True if the file type is supported.
"""
return file_path.lower().endswith(tuple(cls.SUPPORTED_FILE_TYPES))
@classmethod
def get_source(
cls, file_path: str, metadata: dict[str, Any] | None = None
) -> BaseKnowledgeSource:
"""Create appropriate KnowledgeSource based on file extension.
Args:
file_path: Path to the file.
metadata: Optional metadata to attach to the source.
Returns:
The appropriate KnowledgeSource instance.
Raises:
ValueError: If the file type is not supported.
"""
if not cls.is_supported_file(file_path):
raise ValueError(f"Unsupported file type: {file_path}")
lower_path = file_path.lower()
for ext, source_cls in cls._FILE_TYPE_MAP.items():
if lower_path.endswith(ext):
return source_cls(file_path=[file_path], metadata=metadata)
raise ValueError(f"Unsupported file type: {file_path}")

View File

@@ -37,7 +37,7 @@ from crewai.events.types.tool_usage_events import (
ToolUsageFinishedEvent,
ToolUsageStartedEvent,
)
from crewai.llms.base_llm import BaseLLM
from crewai.llms.base_llm import BaseLLM, get_current_call_id, llm_call_context
from crewai.llms.constants import (
ANTHROPIC_MODELS,
AZURE_MODELS,
@@ -770,7 +770,7 @@ class LLM(BaseLLM):
chunk_content = None
response_id = None
if hasattr(chunk,'id'):
if hasattr(chunk, "id"):
response_id = chunk.id
# Safely extract content from various chunk formats
@@ -827,7 +827,7 @@ class LLM(BaseLLM):
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_id=response_id
response_id=response_id,
)
if result is not None:
@@ -849,7 +849,8 @@ class LLM(BaseLLM):
from_task=from_task,
from_agent=from_agent,
call_type=LLMCallType.LLM_CALL,
response_id=response_id
response_id=response_id,
call_id=get_current_call_id(),
),
)
# --- 4) Fallback to non-streaming if no content received
@@ -1015,7 +1016,10 @@ class LLM(BaseLLM):
crewai_event_bus.emit(
self,
event=LLMCallFailedEvent(
error=str(e), from_task=from_task, from_agent=from_agent
error=str(e),
from_task=from_task,
from_agent=from_agent,
call_id=get_current_call_id(),
),
)
raise Exception(f"Failed to get streaming response: {e!s}") from e
@@ -1048,7 +1052,8 @@ class LLM(BaseLLM):
from_task=from_task,
from_agent=from_agent,
call_type=LLMCallType.TOOL_CALL,
response_id=response_id
response_id=response_id,
call_id=get_current_call_id(),
),
)
@@ -1476,7 +1481,8 @@ class LLM(BaseLLM):
chunk=chunk_content,
from_task=from_task,
from_agent=from_agent,
response_id=response_id
response_id=response_id,
call_id=get_current_call_id(),
),
)
@@ -1619,7 +1625,12 @@ class LLM(BaseLLM):
logging.error(f"Error executing function '{function_name}': {e}")
crewai_event_bus.emit(
self,
event=LLMCallFailedEvent(error=f"Tool execution error: {e!s}"),
event=LLMCallFailedEvent(
error=f"Tool execution error: {e!s}",
from_task=from_task,
from_agent=from_agent,
call_id=get_current_call_id(),
),
)
crewai_event_bus.emit(
self,
@@ -1669,108 +1680,117 @@ class LLM(BaseLLM):
ValueError: If response format is not supported
LLMContextLengthExceededError: If input exceeds model's context limit
"""
crewai_event_bus.emit(
self,
event=LLMCallStartedEvent(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
model=self.model,
),
)
with llm_call_context() as call_id:
crewai_event_bus.emit(
self,
event=LLMCallStartedEvent(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
model=self.model,
call_id=call_id,
),
)
# --- 2) Validate parameters before proceeding with the call
self._validate_call_params()
# --- 2) Validate parameters before proceeding with the call
self._validate_call_params()
# --- 3) Convert string messages to proper format if needed
if isinstance(messages, str):
messages = [{"role": "user", "content": messages}]
# --- 4) Handle O1 model special case (system messages not supported)
if "o1" in self.model.lower():
for message in messages:
if message.get("role") == "system":
msg_role: Literal["assistant"] = "assistant"
message["role"] = msg_role
# --- 3) Convert string messages to proper format if needed
if isinstance(messages, str):
messages = [{"role": "user", "content": messages}]
# --- 4) Handle O1 model special case (system messages not supported)
if "o1" in self.model.lower():
for message in messages:
if message.get("role") == "system":
msg_role: Literal["assistant"] = "assistant"
message["role"] = msg_role
if not self._invoke_before_llm_call_hooks(messages, from_agent):
raise ValueError("LLM call blocked by before_llm_call hook")
if not self._invoke_before_llm_call_hooks(messages, from_agent):
raise ValueError("LLM call blocked by before_llm_call hook")
# --- 5) Set up callbacks if provided
with suppress_warnings():
if callbacks and len(callbacks) > 0:
self.set_callbacks(callbacks)
try:
# --- 6) Prepare parameters for the completion call
params = self._prepare_completion_params(messages, tools)
# --- 7) Make the completion call and handle response
if self.stream:
result = self._handle_streaming_response(
params=params,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
else:
result = self._handle_non_streaming_response(
params=params,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
if isinstance(result, str):
result = self._invoke_after_llm_call_hooks(
messages, result, from_agent
)
return result
except LLMContextLengthExceededError:
# Re-raise LLMContextLengthExceededError as it should be handled
# by the CrewAgentExecutor._invoke_loop method, which can then decide
# whether to summarize the content or abort based on the respect_context_window flag
raise
except Exception as e:
unsupported_stop = "Unsupported parameter" in str(
e
) and "'stop'" in str(e)
if unsupported_stop:
if (
"additional_drop_params" in self.additional_params
and isinstance(
self.additional_params["additional_drop_params"], list
# --- 5) Set up callbacks if provided
with suppress_warnings():
if callbacks and len(callbacks) > 0:
self.set_callbacks(callbacks)
try:
# --- 6) Prepare parameters for the completion call
params = self._prepare_completion_params(messages, tools)
# --- 7) Make the completion call and handle response
if self.stream:
result = self._handle_streaming_response(
params=params,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
):
self.additional_params["additional_drop_params"].append("stop")
else:
self.additional_params = {"additional_drop_params": ["stop"]}
result = self._handle_non_streaming_response(
params=params,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
logging.info("Retrying LLM call without the unsupported 'stop'")
if isinstance(result, str):
result = self._invoke_after_llm_call_hooks(
messages, result, from_agent
)
return self.call(
messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
return result
except LLMContextLengthExceededError:
# Re-raise LLMContextLengthExceededError as it should be handled
# by the CrewAgentExecutor._invoke_loop method, which can then decide
# whether to summarize the content or abort based on the respect_context_window flag
raise
except Exception as e:
unsupported_stop = "Unsupported parameter" in str(
e
) and "'stop'" in str(e)
if unsupported_stop:
if (
"additional_drop_params" in self.additional_params
and isinstance(
self.additional_params["additional_drop_params"], list
)
):
self.additional_params["additional_drop_params"].append(
"stop"
)
else:
self.additional_params = {
"additional_drop_params": ["stop"]
}
logging.info("Retrying LLM call without the unsupported 'stop'")
return self.call(
messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
crewai_event_bus.emit(
self,
event=LLMCallFailedEvent(
error=str(e),
from_task=from_task,
from_agent=from_agent,
call_id=get_current_call_id(),
),
)
crewai_event_bus.emit(
self,
event=LLMCallFailedEvent(
error=str(e), from_task=from_task, from_agent=from_agent
),
)
raise
raise
async def acall(
self,
@@ -1808,43 +1828,54 @@ class LLM(BaseLLM):
ValueError: If response format is not supported
LLMContextLengthExceededError: If input exceeds model's context limit
"""
crewai_event_bus.emit(
self,
event=LLMCallStartedEvent(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
model=self.model,
),
)
with llm_call_context() as call_id:
crewai_event_bus.emit(
self,
event=LLMCallStartedEvent(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
model=self.model,
call_id=call_id,
),
)
self._validate_call_params()
self._validate_call_params()
if isinstance(messages, str):
messages = [{"role": "user", "content": messages}]
if isinstance(messages, str):
messages = [{"role": "user", "content": messages}]
# Process file attachments asynchronously before preparing params
messages = await self._aprocess_message_files(messages)
# Process file attachments asynchronously before preparing params
messages = await self._aprocess_message_files(messages)
if "o1" in self.model.lower():
for message in messages:
if message.get("role") == "system":
msg_role: Literal["assistant"] = "assistant"
message["role"] = msg_role
if "o1" in self.model.lower():
for message in messages:
if message.get("role") == "system":
msg_role: Literal["assistant"] = "assistant"
message["role"] = msg_role
with suppress_warnings():
if callbacks and len(callbacks) > 0:
self.set_callbacks(callbacks)
try:
params = self._prepare_completion_params(
messages, tools, skip_file_processing=True
)
with suppress_warnings():
if callbacks and len(callbacks) > 0:
self.set_callbacks(callbacks)
try:
params = self._prepare_completion_params(
messages, tools, skip_file_processing=True
)
if self.stream:
return await self._ahandle_streaming_response(
if self.stream:
return await self._ahandle_streaming_response(
params=params,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
return await self._ahandle_non_streaming_response(
params=params,
callbacks=callbacks,
available_functions=available_functions,
@@ -1852,52 +1883,50 @@ class LLM(BaseLLM):
from_agent=from_agent,
response_model=response_model,
)
except LLMContextLengthExceededError:
raise
except Exception as e:
unsupported_stop = "Unsupported parameter" in str(
e
) and "'stop'" in str(e)
return await self._ahandle_non_streaming_response(
params=params,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
except LLMContextLengthExceededError:
raise
except Exception as e:
unsupported_stop = "Unsupported parameter" in str(
e
) and "'stop'" in str(e)
if unsupported_stop:
if (
"additional_drop_params" in self.additional_params
and isinstance(
self.additional_params["additional_drop_params"], list
)
):
self.additional_params["additional_drop_params"].append(
"stop"
)
else:
self.additional_params = {
"additional_drop_params": ["stop"]
}
if unsupported_stop:
if (
"additional_drop_params" in self.additional_params
and isinstance(
self.additional_params["additional_drop_params"], list
logging.info("Retrying LLM call without the unsupported 'stop'")
return await self.acall(
messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
):
self.additional_params["additional_drop_params"].append("stop")
else:
self.additional_params = {"additional_drop_params": ["stop"]}
logging.info("Retrying LLM call without the unsupported 'stop'")
return await self.acall(
messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
crewai_event_bus.emit(
self,
event=LLMCallFailedEvent(
error=str(e),
from_task=from_task,
from_agent=from_agent,
call_id=get_current_call_id(),
),
)
crewai_event_bus.emit(
self,
event=LLMCallFailedEvent(
error=str(e), from_task=from_task, from_agent=from_agent
),
)
raise
raise
def _handle_emit_call_events(
self,
@@ -1925,6 +1954,7 @@ class LLM(BaseLLM):
from_task=from_task,
from_agent=from_agent,
model=self.model,
call_id=get_current_call_id(),
),
)

View File

@@ -7,11 +7,15 @@ in CrewAI, including common functionality for native SDK implementations.
from __future__ import annotations
from abc import ABC, abstractmethod
from collections.abc import Generator
from contextlib import contextmanager
import contextvars
from datetime import datetime
import json
import logging
import re
from typing import TYPE_CHECKING, Any, Final
import uuid
from pydantic import BaseModel
@@ -50,6 +54,32 @@ DEFAULT_CONTEXT_WINDOW_SIZE: Final[int] = 4096
DEFAULT_SUPPORTS_STOP_WORDS: Final[bool] = True
_JSON_EXTRACTION_PATTERN: Final[re.Pattern[str]] = re.compile(r"\{.*}", re.DOTALL)
_current_call_id: contextvars.ContextVar[str | None] = contextvars.ContextVar(
"_current_call_id", default=None
)
@contextmanager
def llm_call_context() -> Generator[str, None, None]:
"""Context manager that establishes an LLM call scope with a unique call_id."""
call_id = str(uuid.uuid4())
token = _current_call_id.set(call_id)
try:
yield call_id
finally:
_current_call_id.reset(token)
def get_current_call_id() -> str:
"""Get current call_id from context"""
call_id = _current_call_id.get()
if call_id is None:
logging.warning(
"LLM event emitted outside call context - generating fallback call_id"
)
return str(uuid.uuid4())
return call_id
class BaseLLM(ABC):
"""Abstract base class for LLM implementations.
@@ -351,6 +381,7 @@ class BaseLLM(ABC):
from_task=from_task,
from_agent=from_agent,
model=self.model,
call_id=get_current_call_id(),
),
)
@@ -374,6 +405,7 @@ class BaseLLM(ABC):
from_task=from_task,
from_agent=from_agent,
model=self.model,
call_id=get_current_call_id(),
),
)
@@ -394,6 +426,7 @@ class BaseLLM(ABC):
from_task=from_task,
from_agent=from_agent,
model=self.model,
call_id=get_current_call_id(),
),
)
@@ -428,6 +461,7 @@ class BaseLLM(ABC):
from_agent=from_agent,
call_type=call_type,
response_id=response_id,
call_id=get_current_call_id(),
),
)

View File

@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Any, Final, Literal, TypeGuard, cast
from pydantic import BaseModel
from crewai.events.types.llm_events import LLMCallType
from crewai.llms.base_llm import BaseLLM
from crewai.llms.base_llm import BaseLLM, llm_call_context
from crewai.llms.hooks.transport import AsyncHTTPTransport, HTTPTransport
from crewai.utilities.agent_utils import is_context_length_exceeded
from crewai.utilities.exceptions.context_window_exceeding_exception import (
@@ -266,35 +266,46 @@ class AnthropicCompletion(BaseLLM):
Returns:
Chat completion response or tool call result
"""
try:
# Emit call started event
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
with llm_call_context():
try:
# Emit call started event
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
# Format messages for Anthropic
formatted_messages, system_message = self._format_messages_for_anthropic(
messages
)
# Format messages for Anthropic
formatted_messages, system_message = (
self._format_messages_for_anthropic(messages)
)
if not self._invoke_before_llm_call_hooks(formatted_messages, from_agent):
raise ValueError("LLM call blocked by before_llm_call hook")
if not self._invoke_before_llm_call_hooks(
formatted_messages, from_agent
):
raise ValueError("LLM call blocked by before_llm_call hook")
# Prepare completion parameters
completion_params = self._prepare_completion_params(
formatted_messages, system_message, tools
)
# Prepare completion parameters
completion_params = self._prepare_completion_params(
formatted_messages, system_message, tools, available_functions
)
effective_response_model = response_model or self.response_format
effective_response_model = response_model or self.response_format
# Handle streaming vs non-streaming
if self.stream:
return self._handle_streaming_completion(
# Handle streaming vs non-streaming
if self.stream:
return self._handle_streaming_completion(
completion_params,
available_functions,
from_task,
from_agent,
effective_response_model,
)
return self._handle_completion(
completion_params,
available_functions,
from_task,
@@ -302,21 +313,13 @@ class AnthropicCompletion(BaseLLM):
effective_response_model,
)
return self._handle_completion(
completion_params,
available_functions,
from_task,
from_agent,
effective_response_model,
)
except Exception as e:
error_msg = f"Anthropic API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
except Exception as e:
error_msg = f"Anthropic API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
async def acall(
self,
@@ -342,28 +345,37 @@ class AnthropicCompletion(BaseLLM):
Returns:
Chat completion response or tool call result
"""
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
with llm_call_context():
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
formatted_messages, system_message = self._format_messages_for_anthropic(
messages
)
formatted_messages, system_message = (
self._format_messages_for_anthropic(messages)
)
completion_params = self._prepare_completion_params(
formatted_messages, system_message, tools
)
completion_params = self._prepare_completion_params(
formatted_messages, system_message, tools, available_functions
)
effective_response_model = response_model or self.response_format
effective_response_model = response_model or self.response_format
if self.stream:
return await self._ahandle_streaming_completion(
if self.stream:
return await self._ahandle_streaming_completion(
completion_params,
available_functions,
from_task,
from_agent,
effective_response_model,
)
return await self._ahandle_completion(
completion_params,
available_functions,
from_task,
@@ -371,27 +383,20 @@ class AnthropicCompletion(BaseLLM):
effective_response_model,
)
return await self._ahandle_completion(
completion_params,
available_functions,
from_task,
from_agent,
effective_response_model,
)
except Exception as e:
error_msg = f"Anthropic API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
except Exception as e:
error_msg = f"Anthropic API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
def _prepare_completion_params(
self,
messages: list[LLMMessage],
system_message: str | None = None,
tools: list[dict[str, Any]] | None = None,
available_functions: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Prepare parameters for Anthropic messages API.
@@ -399,6 +404,8 @@ class AnthropicCompletion(BaseLLM):
messages: Formatted messages for Anthropic
system_message: Extracted system message
tools: Tool definitions
available_functions: Available functions for tool calling. When provided
with a single tool, tool_choice is automatically set to force tool use.
Returns:
Parameters dictionary for Anthropic API
@@ -424,7 +431,13 @@ class AnthropicCompletion(BaseLLM):
# Handle tools for Claude 3+
if tools and self.supports_tools:
params["tools"] = self._convert_tools_for_interference(tools)
converted_tools = self._convert_tools_for_interference(tools)
params["tools"] = converted_tools
if available_functions and len(converted_tools) == 1:
tool_name = converted_tools[0].get("name")
if tool_name and tool_name in available_functions:
params["tool_choice"] = {"type": "tool", "name": tool_name}
if self.thinking:
if isinstance(self.thinking, AnthropicThinkingConfig):
@@ -726,15 +739,11 @@ class AnthropicCompletion(BaseLLM):
)
return list(tool_uses)
# Handle tool use conversation flow internally
return self._handle_tool_use_conversation(
response,
tool_uses,
params,
available_functions,
from_task,
from_agent,
result = self._execute_first_tool(
tool_uses, available_functions, from_task, from_agent
)
if result is not None:
return result
content = ""
thinking_blocks: list[ThinkingBlock] = []
@@ -935,14 +944,12 @@ class AnthropicCompletion(BaseLLM):
if not available_functions:
return list(tool_uses)
return self._handle_tool_use_conversation(
final_message,
tool_uses,
params,
available_functions,
from_task,
from_agent,
# Execute first tool and return result directly
result = self._execute_first_tool(
tool_uses, available_functions, from_task, from_agent
)
if result is not None:
return result
full_response = self._apply_stop_words(full_response)
@@ -1001,6 +1008,41 @@ class AnthropicCompletion(BaseLLM):
return tool_results
def _execute_first_tool(
self,
tool_uses: list[ToolUseBlock | BetaToolUseBlock],
available_functions: dict[str, Any],
from_task: Any | None = None,
from_agent: Any | None = None,
) -> Any | None:
"""Execute the first tool from the tool_uses list and return its result.
This is used when available_functions is provided, to directly execute
the tool and return its result (matching OpenAI behavior for use cases
like reasoning_handler).
Args:
tool_uses: List of tool use blocks from Claude's response
available_functions: Available functions for tool calling
from_task: Task that initiated the call
from_agent: Agent that initiated the call
Returns:
The result of the first tool execution, or None if execution failed
"""
tool_use = tool_uses[0]
function_name = tool_use.name
function_args = cast(dict[str, Any], tool_use.input)
return self._handle_tool_execution(
function_name=function_name,
function_args=function_args,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
# TODO: we drop this
def _handle_tool_use_conversation(
self,
initial_response: Message | BetaMessage,
@@ -1216,14 +1258,11 @@ class AnthropicCompletion(BaseLLM):
)
return list(tool_uses)
return await self._ahandle_tool_use_conversation(
response,
tool_uses,
params,
available_functions,
from_task,
from_agent,
result = self._execute_first_tool(
tool_uses, available_functions, from_task, from_agent
)
if result is not None:
return result
content = ""
if response.content:
@@ -1404,14 +1443,11 @@ class AnthropicCompletion(BaseLLM):
if not available_functions:
return list(tool_uses)
return await self._ahandle_tool_use_conversation(
final_message,
tool_uses,
params,
available_functions,
from_task,
from_agent,
result = self._execute_first_tool(
tool_uses, available_functions, from_task, from_agent
)
if result is not None:
return result
full_response = self._apply_stop_words(full_response)
@@ -1544,10 +1580,12 @@ class AnthropicCompletion(BaseLLM):
usage = response.usage
input_tokens = getattr(usage, "input_tokens", 0)
output_tokens = getattr(usage, "output_tokens", 0)
cache_read_tokens = getattr(usage, "cache_read_input_tokens", 0) or 0
return {
"input_tokens": input_tokens,
"output_tokens": output_tokens,
"total_tokens": input_tokens + output_tokens,
"cached_prompt_tokens": cache_read_tokens,
}
return {"total_tokens": 0}

View File

@@ -43,7 +43,7 @@ try:
)
from crewai.events.types.llm_events import LLMCallType
from crewai.llms.base_llm import BaseLLM
from crewai.llms.base_llm import BaseLLM, llm_call_context
except ImportError:
raise ImportError(
@@ -293,32 +293,44 @@ class AzureCompletion(BaseLLM):
Returns:
Chat completion response or tool call result
"""
try:
# Emit call started event
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
effective_response_model = response_model or self.response_format
with llm_call_context():
try:
# Emit call started event
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
# Format messages for Azure
formatted_messages = self._format_messages_for_azure(messages)
effective_response_model = response_model or self.response_format
if not self._invoke_before_llm_call_hooks(formatted_messages, from_agent):
raise ValueError("LLM call blocked by before_llm_call hook")
# Format messages for Azure
formatted_messages = self._format_messages_for_azure(messages)
# Prepare completion parameters
completion_params = self._prepare_completion_params(
formatted_messages, tools, effective_response_model
)
if not self._invoke_before_llm_call_hooks(
formatted_messages, from_agent
):
raise ValueError("LLM call blocked by before_llm_call hook")
# Handle streaming vs non-streaming
if self.stream:
return self._handle_streaming_completion(
# Prepare completion parameters
completion_params = self._prepare_completion_params(
formatted_messages, tools, effective_response_model
)
# Handle streaming vs non-streaming
if self.stream:
return self._handle_streaming_completion(
completion_params,
available_functions,
from_task,
from_agent,
effective_response_model,
)
return self._handle_completion(
completion_params,
available_functions,
from_task,
@@ -326,16 +338,8 @@ class AzureCompletion(BaseLLM):
effective_response_model,
)
return self._handle_completion(
completion_params,
available_functions,
from_task,
from_agent,
effective_response_model,
)
except Exception as e:
return self._handle_api_error(e, from_task, from_agent) # type: ignore[func-returns-value]
except Exception as e:
return self._handle_api_error(e, from_task, from_agent) # type: ignore[func-returns-value]
async def acall( # type: ignore[return]
self,
@@ -361,25 +365,35 @@ class AzureCompletion(BaseLLM):
Returns:
Chat completion response or tool call result
"""
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
effective_response_model = response_model or self.response_format
with llm_call_context():
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
formatted_messages = self._format_messages_for_azure(messages)
effective_response_model = response_model or self.response_format
completion_params = self._prepare_completion_params(
formatted_messages, tools, effective_response_model
)
formatted_messages = self._format_messages_for_azure(messages)
if self.stream:
return await self._ahandle_streaming_completion(
completion_params = self._prepare_completion_params(
formatted_messages, tools, effective_response_model
)
if self.stream:
return await self._ahandle_streaming_completion(
completion_params,
available_functions,
from_task,
from_agent,
effective_response_model,
)
return await self._ahandle_completion(
completion_params,
available_functions,
from_task,
@@ -387,16 +401,8 @@ class AzureCompletion(BaseLLM):
effective_response_model,
)
return await self._ahandle_completion(
completion_params,
available_functions,
from_task,
from_agent,
effective_response_model,
)
except Exception as e:
self._handle_api_error(e, from_task, from_agent)
except Exception as e:
self._handle_api_error(e, from_task, from_agent)
def _prepare_completion_params(
self,
@@ -419,8 +425,9 @@ class AzureCompletion(BaseLLM):
"stream": self.stream,
}
model_extras: dict[str, Any] = {}
if self.stream:
params["model_extras"] = {"stream_options": {"include_usage": True}}
model_extras["stream_options"] = {"include_usage": True}
if response_model and self.is_openai_model:
model_description = generate_model_description(response_model)
@@ -458,6 +465,13 @@ class AzureCompletion(BaseLLM):
params["tools"] = self._convert_tools_for_interference(tools)
params["tool_choice"] = "auto"
prompt_cache_key = self.additional_params.get("prompt_cache_key")
if prompt_cache_key:
model_extras["prompt_cache_key"] = prompt_cache_key
if model_extras:
params["model_extras"] = model_extras
additional_params = self.additional_params
additional_drop_params = additional_params.get("additional_drop_params")
drop_params = additional_params.get("drop_params")
@@ -1057,10 +1071,15 @@ class AzureCompletion(BaseLLM):
"""Extract token usage from Azure response."""
if hasattr(response, "usage") and response.usage:
usage = response.usage
cached_tokens = 0
prompt_details = getattr(usage, "prompt_tokens_details", None)
if prompt_details:
cached_tokens = getattr(prompt_details, "cached_tokens", 0) or 0
return {
"prompt_tokens": getattr(usage, "prompt_tokens", 0),
"completion_tokens": getattr(usage, "completion_tokens", 0),
"total_tokens": getattr(usage, "total_tokens", 0),
"cached_prompt_tokens": cached_tokens,
}
return {"total_tokens": 0}

View File

@@ -11,7 +11,7 @@ from pydantic import BaseModel
from typing_extensions import Required
from crewai.events.types.llm_events import LLMCallType
from crewai.llms.base_llm import BaseLLM
from crewai.llms.base_llm import BaseLLM, llm_call_context
from crewai.utilities.agent_utils import is_context_length_exceeded
from crewai.utilities.exceptions.context_window_exceeding_exception import (
LLMContextLengthExceededError,
@@ -378,77 +378,90 @@ class BedrockCompletion(BaseLLM):
"""Call AWS Bedrock Converse API."""
effective_response_model = response_model or self.response_format
try:
# Emit call started event
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
# Format messages for Converse API
formatted_messages, system_message = self._format_messages_for_converse(
messages
)
if not self._invoke_before_llm_call_hooks(formatted_messages, from_agent):
raise ValueError("LLM call blocked by before_llm_call hook")
# Prepare request body
body: BedrockConverseRequestBody = {
"inferenceConfig": self._get_inference_config(),
}
# Add system message if present
if system_message:
body["system"] = cast(
"list[SystemContentBlockTypeDef]",
cast(object, [{"text": system_message}]),
with llm_call_context():
try:
# Emit call started event
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
# Add tool config if present or if messages contain tool content
# Bedrock requires toolConfig when messages have toolUse/toolResult
if tools:
tool_config: ToolConfigurationTypeDef = {
"tools": cast(
"Sequence[ToolTypeDef]",
cast(object, self._format_tools_for_converse(tools)),
)
# Format messages for Converse API
formatted_messages, system_message = self._format_messages_for_converse(
messages
)
if not self._invoke_before_llm_call_hooks(
formatted_messages, from_agent
):
raise ValueError("LLM call blocked by before_llm_call hook")
# Prepare request body
body: BedrockConverseRequestBody = {
"inferenceConfig": self._get_inference_config(),
}
body["toolConfig"] = tool_config
elif self._messages_contain_tool_content(formatted_messages):
# Create minimal toolConfig from tool history in messages
tools_from_history = self._extract_tools_from_message_history(
formatted_messages
)
if tools_from_history:
body["toolConfig"] = cast(
"ToolConfigurationTypeDef",
cast(object, {"tools": tools_from_history}),
# Add system message if present
if system_message:
body["system"] = cast(
"list[SystemContentBlockTypeDef]",
cast(object, [{"text": system_message}]),
)
# Add optional advanced features if configured
if self.guardrail_config:
guardrail_config: GuardrailConfigurationTypeDef = cast(
"GuardrailConfigurationTypeDef", cast(object, self.guardrail_config)
)
body["guardrailConfig"] = guardrail_config
# Add tool config if present or if messages contain tool content
# Bedrock requires toolConfig when messages have toolUse/toolResult
if tools:
tool_config: ToolConfigurationTypeDef = {
"tools": cast(
"Sequence[ToolTypeDef]",
cast(object, self._format_tools_for_converse(tools)),
)
}
body["toolConfig"] = tool_config
elif self._messages_contain_tool_content(formatted_messages):
# Create minimal toolConfig from tool history in messages
tools_from_history = self._extract_tools_from_message_history(
formatted_messages
)
if tools_from_history:
body["toolConfig"] = cast(
"ToolConfigurationTypeDef",
cast(object, {"tools": tools_from_history}),
)
if self.additional_model_request_fields:
body["additionalModelRequestFields"] = (
self.additional_model_request_fields
)
# Add optional advanced features if configured
if self.guardrail_config:
guardrail_config: GuardrailConfigurationTypeDef = cast(
"GuardrailConfigurationTypeDef",
cast(object, self.guardrail_config),
)
body["guardrailConfig"] = guardrail_config
if self.additional_model_response_field_paths:
body["additionalModelResponseFieldPaths"] = (
self.additional_model_response_field_paths
)
if self.additional_model_request_fields:
body["additionalModelRequestFields"] = (
self.additional_model_request_fields
)
if self.stream:
return self._handle_streaming_converse(
if self.additional_model_response_field_paths:
body["additionalModelResponseFieldPaths"] = (
self.additional_model_response_field_paths
)
if self.stream:
return self._handle_streaming_converse(
formatted_messages,
body,
available_functions,
from_task,
from_agent,
effective_response_model,
)
return self._handle_converse(
formatted_messages,
body,
available_functions,
@@ -457,26 +470,17 @@ class BedrockCompletion(BaseLLM):
effective_response_model,
)
return self._handle_converse(
formatted_messages,
body,
available_functions,
from_task,
from_agent,
effective_response_model,
)
except Exception as e:
if is_context_length_exceeded(e):
logging.error(f"Context window exceeded: {e}")
raise LLMContextLengthExceededError(str(e)) from e
except Exception as e:
if is_context_length_exceeded(e):
logging.error(f"Context window exceeded: {e}")
raise LLMContextLengthExceededError(str(e)) from e
error_msg = f"AWS Bedrock API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
error_msg = f"AWS Bedrock API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
async def acall(
self,
@@ -514,69 +518,80 @@ class BedrockCompletion(BaseLLM):
'Install with: uv add "crewai[bedrock-async]"'
)
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
formatted_messages, system_message = self._format_messages_for_converse(
messages
)
body: BedrockConverseRequestBody = {
"inferenceConfig": self._get_inference_config(),
}
if system_message:
body["system"] = cast(
"list[SystemContentBlockTypeDef]",
cast(object, [{"text": system_message}]),
with llm_call_context():
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
# Add tool config if present or if messages contain tool content
# Bedrock requires toolConfig when messages have toolUse/toolResult
if tools:
tool_config: ToolConfigurationTypeDef = {
"tools": cast(
"Sequence[ToolTypeDef]",
cast(object, self._format_tools_for_converse(tools)),
)
formatted_messages, system_message = self._format_messages_for_converse(
messages
)
body: BedrockConverseRequestBody = {
"inferenceConfig": self._get_inference_config(),
}
body["toolConfig"] = tool_config
elif self._messages_contain_tool_content(formatted_messages):
# Create minimal toolConfig from tool history in messages
tools_from_history = self._extract_tools_from_message_history(
formatted_messages
)
if tools_from_history:
body["toolConfig"] = cast(
"ToolConfigurationTypeDef",
cast(object, {"tools": tools_from_history}),
if system_message:
body["system"] = cast(
"list[SystemContentBlockTypeDef]",
cast(object, [{"text": system_message}]),
)
if self.guardrail_config:
guardrail_config: GuardrailConfigurationTypeDef = cast(
"GuardrailConfigurationTypeDef", cast(object, self.guardrail_config)
)
body["guardrailConfig"] = guardrail_config
# Add tool config if present or if messages contain tool content
# Bedrock requires toolConfig when messages have toolUse/toolResult
if tools:
tool_config: ToolConfigurationTypeDef = {
"tools": cast(
"Sequence[ToolTypeDef]",
cast(object, self._format_tools_for_converse(tools)),
)
}
body["toolConfig"] = tool_config
elif self._messages_contain_tool_content(formatted_messages):
# Create minimal toolConfig from tool history in messages
tools_from_history = self._extract_tools_from_message_history(
formatted_messages
)
if tools_from_history:
body["toolConfig"] = cast(
"ToolConfigurationTypeDef",
cast(object, {"tools": tools_from_history}),
)
if self.additional_model_request_fields:
body["additionalModelRequestFields"] = (
self.additional_model_request_fields
)
if self.guardrail_config:
guardrail_config: GuardrailConfigurationTypeDef = cast(
"GuardrailConfigurationTypeDef",
cast(object, self.guardrail_config),
)
body["guardrailConfig"] = guardrail_config
if self.additional_model_response_field_paths:
body["additionalModelResponseFieldPaths"] = (
self.additional_model_response_field_paths
)
if self.additional_model_request_fields:
body["additionalModelRequestFields"] = (
self.additional_model_request_fields
)
if self.stream:
return await self._ahandle_streaming_converse(
if self.additional_model_response_field_paths:
body["additionalModelResponseFieldPaths"] = (
self.additional_model_response_field_paths
)
if self.stream:
return await self._ahandle_streaming_converse(
formatted_messages,
body,
available_functions,
from_task,
from_agent,
effective_response_model,
)
return await self._ahandle_converse(
formatted_messages,
body,
available_functions,
@@ -585,26 +600,17 @@ class BedrockCompletion(BaseLLM):
effective_response_model,
)
return await self._ahandle_converse(
formatted_messages,
body,
available_functions,
from_task,
from_agent,
effective_response_model,
)
except Exception as e:
if is_context_length_exceeded(e):
logging.error(f"Context window exceeded: {e}")
raise LLMContextLengthExceededError(str(e)) from e
except Exception as e:
if is_context_length_exceeded(e):
logging.error(f"Context window exceeded: {e}")
raise LLMContextLengthExceededError(str(e)) from e
error_msg = f"AWS Bedrock API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
error_msg = f"AWS Bedrock API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
def _handle_converse(
self,

View File

@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, Any, Literal, cast
from pydantic import BaseModel
from crewai.events.types.llm_events import LLMCallType
from crewai.llms.base_llm import BaseLLM
from crewai.llms.base_llm import BaseLLM, llm_call_context
from crewai.utilities.agent_utils import is_context_length_exceeded
from crewai.utilities.exceptions.context_window_exceeding_exception import (
LLMContextLengthExceededError,
@@ -293,33 +293,45 @@ class GeminiCompletion(BaseLLM):
Returns:
Chat completion response or tool call result
"""
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
self.tools = tools
effective_response_model = response_model or self.response_format
with llm_call_context():
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
self.tools = tools
effective_response_model = response_model or self.response_format
formatted_content, system_instruction = self._format_messages_for_gemini(
messages
)
formatted_content, system_instruction = (
self._format_messages_for_gemini(messages)
)
messages_for_hooks = self._convert_contents_to_dict(formatted_content)
messages_for_hooks = self._convert_contents_to_dict(formatted_content)
if not self._invoke_before_llm_call_hooks(messages_for_hooks, from_agent):
raise ValueError("LLM call blocked by before_llm_call hook")
if not self._invoke_before_llm_call_hooks(
messages_for_hooks, from_agent
):
raise ValueError("LLM call blocked by before_llm_call hook")
config = self._prepare_generation_config(
system_instruction, tools, effective_response_model
)
config = self._prepare_generation_config(
system_instruction, tools, effective_response_model
)
if self.stream:
return self._handle_streaming_completion(
if self.stream:
return self._handle_streaming_completion(
formatted_content,
config,
available_functions,
from_task,
from_agent,
effective_response_model,
)
return self._handle_completion(
formatted_content,
config,
available_functions,
@@ -328,29 +340,20 @@ class GeminiCompletion(BaseLLM):
effective_response_model,
)
return self._handle_completion(
formatted_content,
config,
available_functions,
from_task,
from_agent,
effective_response_model,
)
except APIError as e:
error_msg = f"Google Gemini API error: {e.code} - {e.message}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
except Exception as e:
error_msg = f"Google Gemini API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
except APIError as e:
error_msg = f"Google Gemini API error: {e.code} - {e.message}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
except Exception as e:
error_msg = f"Google Gemini API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
async def acall(
self,
@@ -376,28 +379,38 @@ class GeminiCompletion(BaseLLM):
Returns:
Chat completion response or tool call result
"""
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
self.tools = tools
effective_response_model = response_model or self.response_format
with llm_call_context():
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
self.tools = tools
effective_response_model = response_model or self.response_format
formatted_content, system_instruction = self._format_messages_for_gemini(
messages
)
formatted_content, system_instruction = (
self._format_messages_for_gemini(messages)
)
config = self._prepare_generation_config(
system_instruction, tools, effective_response_model
)
config = self._prepare_generation_config(
system_instruction, tools, effective_response_model
)
if self.stream:
return await self._ahandle_streaming_completion(
if self.stream:
return await self._ahandle_streaming_completion(
formatted_content,
config,
available_functions,
from_task,
from_agent,
effective_response_model,
)
return await self._ahandle_completion(
formatted_content,
config,
available_functions,
@@ -406,29 +419,20 @@ class GeminiCompletion(BaseLLM):
effective_response_model,
)
return await self._ahandle_completion(
formatted_content,
config,
available_functions,
from_task,
from_agent,
effective_response_model,
)
except APIError as e:
error_msg = f"Google Gemini API error: {e.code} - {e.message}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
except Exception as e:
error_msg = f"Google Gemini API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
except APIError as e:
error_msg = f"Google Gemini API error: {e.code} - {e.message}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
except Exception as e:
error_msg = f"Google Gemini API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
def _prepare_generation_config(
self,
@@ -1291,11 +1295,13 @@ class GeminiCompletion(BaseLLM):
"""Extract token usage from Gemini response."""
if response.usage_metadata:
usage = response.usage_metadata
cached_tokens = getattr(usage, "cached_content_token_count", 0) or 0
return {
"prompt_token_count": getattr(usage, "prompt_token_count", 0),
"candidates_token_count": getattr(usage, "candidates_token_count", 0),
"total_token_count": getattr(usage, "total_token_count", 0),
"total_tokens": getattr(usage, "total_token_count", 0),
"cached_prompt_tokens": cached_tokens,
}
return {"total_tokens": 0}

View File

@@ -17,7 +17,7 @@ from openai.types.responses import Response
from pydantic import BaseModel
from crewai.events.types.llm_events import LLMCallType
from crewai.llms.base_llm import BaseLLM
from crewai.llms.base_llm import BaseLLM, llm_call_context
from crewai.llms.hooks.transport import AsyncHTTPTransport, HTTPTransport
from crewai.utilities.agent_utils import is_context_length_exceeded
from crewai.utilities.exceptions.context_window_exceeding_exception import (
@@ -382,23 +382,35 @@ class OpenAICompletion(BaseLLM):
Returns:
Completion response or tool call result.
"""
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
with llm_call_context():
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
formatted_messages = self._format_messages(messages)
formatted_messages = self._format_messages(messages)
if not self._invoke_before_llm_call_hooks(formatted_messages, from_agent):
raise ValueError("LLM call blocked by before_llm_call hook")
if not self._invoke_before_llm_call_hooks(
formatted_messages, from_agent
):
raise ValueError("LLM call blocked by before_llm_call hook")
if self.api == "responses":
return self._call_responses(
if self.api == "responses":
return self._call_responses(
messages=formatted_messages,
tools=tools,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
return self._call_completions(
messages=formatted_messages,
tools=tools,
available_functions=available_functions,
@@ -407,22 +419,13 @@ class OpenAICompletion(BaseLLM):
response_model=response_model,
)
return self._call_completions(
messages=formatted_messages,
tools=tools,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
except Exception as e:
error_msg = f"OpenAI API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
except Exception as e:
error_msg = f"OpenAI API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
def _call_completions(
self,
@@ -479,20 +482,30 @@ class OpenAICompletion(BaseLLM):
Returns:
Completion response or tool call result.
"""
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
with llm_call_context():
try:
self._emit_call_started_event(
messages=messages,
tools=tools,
callbacks=callbacks,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
formatted_messages = self._format_messages(messages)
formatted_messages = self._format_messages(messages)
if self.api == "responses":
return await self._acall_responses(
if self.api == "responses":
return await self._acall_responses(
messages=formatted_messages,
tools=tools,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
return await self._acall_completions(
messages=formatted_messages,
tools=tools,
available_functions=available_functions,
@@ -501,22 +514,13 @@ class OpenAICompletion(BaseLLM):
response_model=response_model,
)
return await self._acall_completions(
messages=formatted_messages,
tools=tools,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
response_model=response_model,
)
except Exception as e:
error_msg = f"OpenAI API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
except Exception as e:
error_msg = f"OpenAI API call failed: {e!s}"
logging.error(error_msg)
self._emit_call_failed_event(
error=error_msg, from_task=from_task, from_agent=from_agent
)
raise
async def _acall_completions(
self,
@@ -1090,11 +1094,7 @@ class OpenAICompletion(BaseLLM):
if reasoning_items:
self._last_reasoning_items = reasoning_items
if event.response and event.response.usage:
usage = {
"prompt_tokens": event.response.usage.input_tokens,
"completion_tokens": event.response.usage.output_tokens,
"total_tokens": event.response.usage.total_tokens,
}
usage = self._extract_responses_token_usage(event.response)
self._track_token_usage_internal(usage)
# If parse_tool_outputs is enabled, return structured result
@@ -1218,11 +1218,7 @@ class OpenAICompletion(BaseLLM):
if reasoning_items:
self._last_reasoning_items = reasoning_items
if event.response and event.response.usage:
usage = {
"prompt_tokens": event.response.usage.input_tokens,
"completion_tokens": event.response.usage.output_tokens,
"total_tokens": event.response.usage.total_tokens,
}
usage = self._extract_responses_token_usage(event.response)
self._track_token_usage_internal(usage)
# If parse_tool_outputs is enabled, return structured result
@@ -1306,11 +1302,18 @@ class OpenAICompletion(BaseLLM):
def _extract_responses_token_usage(self, response: Response) -> dict[str, Any]:
"""Extract token usage from Responses API response."""
if response.usage:
return {
result = {
"prompt_tokens": response.usage.input_tokens,
"completion_tokens": response.usage.output_tokens,
"total_tokens": response.usage.total_tokens,
}
# Extract cached prompt tokens from input_tokens_details
input_details = getattr(response.usage, "input_tokens_details", None)
if input_details:
result["cached_prompt_tokens"] = (
getattr(input_details, "cached_tokens", 0) or 0
)
return result
return {"total_tokens": 0}
def _extract_builtin_tool_outputs(self, response: Response) -> ResponsesAPIResult:
@@ -1521,13 +1524,16 @@ class OpenAICompletion(BaseLLM):
) -> list[dict[str, Any]]:
"""Convert CrewAI tool format to OpenAI function calling format."""
from crewai.llms.providers.utils.common import safe_tool_conversion
from crewai.utilities.pydantic_schema_utils import (
force_additional_properties_false,
)
openai_tools = []
for tool in tools:
name, description, parameters = safe_tool_conversion(tool, "OpenAI")
openai_tool = {
openai_tool: dict[str, Any] = {
"type": "function",
"function": {
"name": name,
@@ -1537,10 +1543,11 @@ class OpenAICompletion(BaseLLM):
}
if parameters:
if isinstance(parameters, dict):
openai_tool["function"]["parameters"] = parameters # type: ignore
else:
openai_tool["function"]["parameters"] = dict(parameters)
params_dict = (
parameters if isinstance(parameters, dict) else dict(parameters)
)
params_dict = force_additional_properties_false(params_dict)
openai_tool["function"]["parameters"] = params_dict
openai_tools.append(openai_tool)
return openai_tools
@@ -1688,6 +1695,99 @@ class OpenAICompletion(BaseLLM):
return content
def _finalize_streaming_response(
self,
full_response: str,
tool_calls: dict[int, dict[str, Any]],
usage_data: dict[str, int],
params: dict[str, Any],
available_functions: dict[str, Any] | None = None,
from_task: Any | None = None,
from_agent: Any | None = None,
) -> str | list[dict[str, Any]]:
"""Finalize a streaming response with usage tracking, tool call handling, and events.
Args:
full_response: The accumulated text response from the stream.
tool_calls: Accumulated tool calls from the stream, keyed by index.
usage_data: Token usage data from the stream.
params: The completion parameters containing messages.
available_functions: Available functions for tool calling.
from_task: Task that initiated the call.
from_agent: Agent that initiated the call.
Returns:
Tool calls list when tools were invoked without available_functions,
tool execution result when available_functions is provided,
or the text response string.
"""
self._track_token_usage_internal(usage_data)
if tool_calls and not available_functions:
tool_calls_list = [
{
"id": call_data["id"],
"type": "function",
"function": {
"name": call_data["name"],
"arguments": call_data["arguments"],
},
"index": call_data["index"],
}
for call_data in tool_calls.values()
]
self._emit_call_completed_event(
response=tool_calls_list,
call_type=LLMCallType.TOOL_CALL,
from_task=from_task,
from_agent=from_agent,
messages=params["messages"],
)
return tool_calls_list
if tool_calls and available_functions:
for call_data in tool_calls.values():
function_name = call_data["name"]
arguments = call_data["arguments"]
if not function_name or not arguments:
continue
if function_name not in available_functions:
logging.warning(
f"Function '{function_name}' not found in available functions"
)
continue
try:
function_args = json.loads(arguments)
except json.JSONDecodeError as e:
logging.error(f"Failed to parse streamed tool arguments: {e}")
continue
result = self._handle_tool_execution(
function_name=function_name,
function_args=function_args,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
if result is not None:
return result
full_response = self._apply_stop_words(full_response)
self._emit_call_completed_event(
response=full_response,
call_type=LLMCallType.LLM_CALL,
from_task=from_task,
from_agent=from_agent,
messages=params["messages"],
)
return full_response
def _handle_streaming_completion(
self,
params: dict[str, Any],
@@ -1695,7 +1795,7 @@ class OpenAICompletion(BaseLLM):
from_task: Any | None = None,
from_agent: Any | None = None,
response_model: type[BaseModel] | None = None,
) -> str | BaseModel:
) -> str | list[dict[str, Any]] | BaseModel:
"""Handle streaming chat completion."""
full_response = ""
tool_calls: dict[int, dict[str, Any]] = {}
@@ -1812,54 +1912,20 @@ class OpenAICompletion(BaseLLM):
response_id=response_id_stream,
)
self._track_token_usage_internal(usage_data)
if tool_calls and available_functions:
for call_data in tool_calls.values():
function_name = call_data["name"]
arguments = call_data["arguments"]
# Skip if function name is empty or arguments are empty
if not function_name or not arguments:
continue
# Check if function exists in available functions
if function_name not in available_functions:
logging.warning(
f"Function '{function_name}' not found in available functions"
)
continue
try:
function_args = json.loads(arguments)
except json.JSONDecodeError as e:
logging.error(f"Failed to parse streamed tool arguments: {e}")
continue
result = self._handle_tool_execution(
function_name=function_name,
function_args=function_args,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
if result is not None:
return result
full_response = self._apply_stop_words(full_response)
self._emit_call_completed_event(
response=full_response,
call_type=LLMCallType.LLM_CALL,
result = self._finalize_streaming_response(
full_response=full_response,
tool_calls=tool_calls,
usage_data=usage_data,
params=params,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
messages=params["messages"],
)
return self._invoke_after_llm_call_hooks(
params["messages"], full_response, from_agent
)
if isinstance(result, str):
return self._invoke_after_llm_call_hooks(
params["messages"], result, from_agent
)
return result
async def _ahandle_completion(
self,
@@ -2008,7 +2074,7 @@ class OpenAICompletion(BaseLLM):
from_task: Any | None = None,
from_agent: Any | None = None,
response_model: type[BaseModel] | None = None,
) -> str | BaseModel:
) -> str | list[dict[str, Any]] | BaseModel:
"""Handle async streaming chat completion."""
full_response = ""
tool_calls: dict[int, dict[str, Any]] = {}
@@ -2134,51 +2200,16 @@ class OpenAICompletion(BaseLLM):
response_id=response_id_stream,
)
self._track_token_usage_internal(usage_data)
if tool_calls and available_functions:
for call_data in tool_calls.values():
function_name = call_data["name"]
arguments = call_data["arguments"]
if not function_name or not arguments:
continue
if function_name not in available_functions:
logging.warning(
f"Function '{function_name}' not found in available functions"
)
continue
try:
function_args = json.loads(arguments)
except json.JSONDecodeError as e:
logging.error(f"Failed to parse streamed tool arguments: {e}")
continue
result = self._handle_tool_execution(
function_name=function_name,
function_args=function_args,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
)
if result is not None:
return result
full_response = self._apply_stop_words(full_response)
self._emit_call_completed_event(
response=full_response,
call_type=LLMCallType.LLM_CALL,
return self._finalize_streaming_response(
full_response=full_response,
tool_calls=tool_calls,
usage_data=usage_data,
params=params,
available_functions=available_functions,
from_task=from_task,
from_agent=from_agent,
messages=params["messages"],
)
return full_response
def supports_function_calling(self) -> bool:
"""Check if the model supports function calling."""
return not self.is_o1_model
@@ -2232,11 +2263,18 @@ class OpenAICompletion(BaseLLM):
"""Extract token usage from OpenAI ChatCompletion or ChatCompletionChunk response."""
if hasattr(response, "usage") and response.usage:
usage = response.usage
return {
result = {
"prompt_tokens": getattr(usage, "prompt_tokens", 0),
"completion_tokens": getattr(usage, "completion_tokens", 0),
"total_tokens": getattr(usage, "total_tokens", 0),
}
# Extract cached prompt tokens from prompt_tokens_details
prompt_details = getattr(usage, "prompt_tokens_details", None)
if prompt_details:
result["cached_prompt_tokens"] = (
getattr(prompt_details, "cached_tokens", 0) or 0
)
return result
return {"total_tokens": 0}
def _format_messages(self, messages: str | list[LLMMessage]) -> list[LLMMessage]:

View File

@@ -27,6 +27,8 @@ if TYPE_CHECKING:
from crewai import Agent, Task
from crewai.agents.cache.cache_handler import CacheHandler
from crewai.crews.crew_output import CrewOutput
from crewai.hooks.llm_hooks import LLMCallHookContext
from crewai.hooks.tool_hooks import ToolCallHookContext
from crewai.project.wrappers import (
CrewInstance,
OutputJsonClass,
@@ -34,6 +36,8 @@ if TYPE_CHECKING:
)
from crewai.tasks.task_output import TaskOutput
_post_initialize_crew_hooks: list[Callable[[Any], None]] = []
class AgentConfig(TypedDict, total=False):
"""Type definition for agent configuration dictionary.
@@ -178,57 +182,6 @@ def _set_mcp_params(cls: type[CrewClass]) -> None:
cls.mcp_connect_timeout = getattr(cls, "mcp_connect_timeout", 30)
def _set_skills_params(cls: type[CrewClass]) -> None:
"""Set the skills directory path for the crew class.
Args:
cls: Crew class to configure.
"""
cls.skills_directory = getattr(cls, "skills_directory", ".agents")
def get_skills_knowledge_sources(self: CrewInstance) -> list[Any]:
"""Discover Skills.md files under .agents/<skill_name>/ and return them as knowledge sources.
Looks for src/<project>/.agents/<skill_name>/Skills.md (relative to the crew class
base_directory). Each found file is wrapped in a CrewDoclingSource so the crew can
query it via RAG. Requires the docling package; if not installed, returns an empty list.
Returns:
List of knowledge sources (CrewDoclingSource instances), or empty list if
.agents is missing, has no Skills.md files, or docling is not installed.
"""
skills_dir_name = getattr(self, "skills_directory", ".agents")
skills_dir = self.base_directory / skills_dir_name
if not skills_dir.exists() or not skills_dir.is_dir():
return []
try:
from crewai.knowledge.source.base_knowledge_source import BaseKnowledgeSource
from crewai.knowledge.source.crew_docling_source import CrewDoclingSource
except ImportError:
logging.warning(
"Skills.md support requires the docling package. "
"Install it with: uv add docling"
)
return []
sources: list[Any] = []
for subdir in sorted(skills_dir.iterdir()):
if not subdir.is_dir():
continue
skills_md = subdir / "Skills.md"
if skills_md.exists():
try:
source = CrewDoclingSource(file_paths=[skills_md])
sources.append(source)
except Exception as e:
logging.warning(
f"Could not create knowledge source for {skills_md}: {e}"
)
return sources
def _is_string_list(value: list[str] | list[BaseTool]) -> TypeGuard[list[str]]:
"""Type guard to check if list contains strings rather than BaseTool instances.
@@ -317,6 +270,9 @@ class CrewBaseMeta(type):
instance.map_all_agent_variables()
instance.map_all_task_variables()
for hook in _post_initialize_crew_hooks:
hook(instance)
original_methods = {
name: method
for name, method in cls.__dict__.items()
@@ -536,47 +492,61 @@ def _register_crew_hooks(instance: CrewInstance, cls: type) -> None:
if has_agent_filter:
agents_filter = hook_method._filter_agents
def make_filtered_before_llm(bound_fn, agents_list):
def filtered(context):
def make_filtered_before_llm(
bound_fn: Callable[[LLMCallHookContext], bool | None],
agents_list: list[str],
) -> Callable[[LLMCallHookContext], bool | None]:
def filtered(context: LLMCallHookContext) -> bool | None:
if context.agent and context.agent.role not in agents_list:
return None
return bound_fn(context)
return filtered
final_hook = make_filtered_before_llm(bound_hook, agents_filter)
before_llm_hook = make_filtered_before_llm(bound_hook, agents_filter)
else:
final_hook = bound_hook
before_llm_hook = bound_hook
register_before_llm_call_hook(final_hook)
instance._registered_hook_functions.append(("before_llm_call", final_hook))
register_before_llm_call_hook(before_llm_hook)
instance._registered_hook_functions.append(
("before_llm_call", before_llm_hook)
)
if hasattr(hook_method, "is_after_llm_call_hook"):
if has_agent_filter:
agents_filter = hook_method._filter_agents
def make_filtered_after_llm(bound_fn, agents_list):
def filtered(context):
def make_filtered_after_llm(
bound_fn: Callable[[LLMCallHookContext], str | None],
agents_list: list[str],
) -> Callable[[LLMCallHookContext], str | None]:
def filtered(context: LLMCallHookContext) -> str | None:
if context.agent and context.agent.role not in agents_list:
return None
return bound_fn(context)
return filtered
final_hook = make_filtered_after_llm(bound_hook, agents_filter)
after_llm_hook = make_filtered_after_llm(bound_hook, agents_filter)
else:
final_hook = bound_hook
after_llm_hook = bound_hook
register_after_llm_call_hook(final_hook)
instance._registered_hook_functions.append(("after_llm_call", final_hook))
register_after_llm_call_hook(after_llm_hook)
instance._registered_hook_functions.append(
("after_llm_call", after_llm_hook)
)
if hasattr(hook_method, "is_before_tool_call_hook"):
if has_tool_filter or has_agent_filter:
tools_filter = getattr(hook_method, "_filter_tools", None)
agents_filter = getattr(hook_method, "_filter_agents", None)
def make_filtered_before_tool(bound_fn, tools_list, agents_list):
def filtered(context):
def make_filtered_before_tool(
bound_fn: Callable[[ToolCallHookContext], bool | None],
tools_list: list[str] | None,
agents_list: list[str] | None,
) -> Callable[[ToolCallHookContext], bool | None]:
def filtered(context: ToolCallHookContext) -> bool | None:
if tools_list and context.tool_name not in tools_list:
return None
if (
@@ -589,22 +559,28 @@ def _register_crew_hooks(instance: CrewInstance, cls: type) -> None:
return filtered
final_hook = make_filtered_before_tool(
before_tool_hook = make_filtered_before_tool(
bound_hook, tools_filter, agents_filter
)
else:
final_hook = bound_hook
before_tool_hook = bound_hook
register_before_tool_call_hook(final_hook)
instance._registered_hook_functions.append(("before_tool_call", final_hook))
register_before_tool_call_hook(before_tool_hook)
instance._registered_hook_functions.append(
("before_tool_call", before_tool_hook)
)
if hasattr(hook_method, "is_after_tool_call_hook"):
if has_tool_filter or has_agent_filter:
tools_filter = getattr(hook_method, "_filter_tools", None)
agents_filter = getattr(hook_method, "_filter_agents", None)
def make_filtered_after_tool(bound_fn, tools_list, agents_list):
def filtered(context):
def make_filtered_after_tool(
bound_fn: Callable[[ToolCallHookContext], str | None],
tools_list: list[str] | None,
agents_list: list[str] | None,
) -> Callable[[ToolCallHookContext], str | None]:
def filtered(context: ToolCallHookContext) -> str | None:
if tools_list and context.tool_name not in tools_list:
return None
if (
@@ -617,14 +593,16 @@ def _register_crew_hooks(instance: CrewInstance, cls: type) -> None:
return filtered
final_hook = make_filtered_after_tool(
after_tool_hook = make_filtered_after_tool(
bound_hook, tools_filter, agents_filter
)
else:
final_hook = bound_hook
after_tool_hook = bound_hook
register_after_tool_call_hook(final_hook)
instance._registered_hook_functions.append(("after_tool_call", final_hook))
register_after_tool_call_hook(after_tool_hook)
instance._registered_hook_functions.append(
("after_tool_call", after_tool_hook)
)
instance._hooks_being_registered = False
@@ -782,7 +760,6 @@ _CLASS_SETUP_FUNCTIONS: tuple[Callable[[type[CrewClass]], None], ...] = (
_set_base_directory,
_set_config_paths,
_set_mcp_params,
_set_skills_params,
)
_METHODS_TO_INJECT = (
@@ -791,7 +768,6 @@ _METHODS_TO_INJECT = (
_load_config,
load_configurations,
staticmethod(load_yaml),
get_skills_knowledge_sources,
map_all_agent_variables,
_map_agent_variables,
map_all_task_variables,

View File

@@ -72,6 +72,8 @@ class CrewInstance(Protocol):
__crew_metadata__: CrewMetadata
_mcp_server_adapter: Any
_all_methods: dict[str, Callable[..., Any]]
_registered_hook_functions: list[tuple[str, Callable[..., Any]]]
_hooks_being_registered: bool
agents: list[Agent]
tasks: list[Task]
base_directory: Path
@@ -81,10 +83,8 @@ class CrewInstance(Protocol):
tasks_config: dict[str, Any]
mcp_server_params: Any
mcp_connect_timeout: int
skills_directory: str
def load_configurations(self) -> None: ...
def get_skills_knowledge_sources(self) -> list[Any]: ...
def map_all_agent_variables(self) -> None: ...
def map_all_task_variables(self) -> None: ...
def close_mcp_server(self, instance: Self, outputs: CrewOutput) -> CrewOutput: ...
@@ -124,10 +124,8 @@ class CrewClass(Protocol):
original_tasks_config_path: str
mcp_server_params: Any
mcp_connect_timeout: int
skills_directory: str
close_mcp_server: Callable[..., Any]
get_mcp_tools: Callable[..., list[BaseTool]]
get_skills_knowledge_sources: Callable[..., list[Any]]
_load_config: Callable[..., dict[str, Any]]
load_configurations: Callable[..., None]
load_yaml: Callable[..., dict[str, Any]]

Some files were not shown because too many files have changed in this diff Show More