mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-02-12 08:58:19 +00:00
Compare commits
39 Commits
devin/1769
...
codex/nl2s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db0c8ff468 | ||
|
|
cde33fd981 | ||
|
|
1f8cfe3282 | ||
|
|
2ed0c2c043 | ||
|
|
0341e5aee7 | ||
|
|
397d14c772 | ||
|
|
fc3e86e9a3 | ||
|
|
2882df5daf | ||
|
|
3a22e80764 | ||
|
|
9b585a934d | ||
|
|
46e1b02154 | ||
|
|
87675b49fd | ||
|
|
a3bee66be8 | ||
|
|
f6fa04528a | ||
|
|
7d498b29be | ||
|
|
1308bdee63 | ||
|
|
6bb1b178a1 | ||
|
|
fe2a4b4e40 | ||
|
|
711e7171e1 | ||
|
|
76b5f72e81 | ||
|
|
d86d43d3e0 | ||
|
|
6bfc98e960 | ||
|
|
3cc33ef6ab | ||
|
|
3fec4669af | ||
|
|
d3f424fd8f | ||
|
|
fee9445067 | ||
|
|
a3c01265ee | ||
|
|
aa7e7785bc | ||
|
|
e30645e855 | ||
|
|
c1d2801be2 | ||
|
|
6a8483fcb6 | ||
|
|
5fb602dff2 | ||
|
|
b90cff580a | ||
|
|
576b74b2ef | ||
|
|
7590d4c6e3 | ||
|
|
8c6436234b | ||
|
|
96bde4510b | ||
|
|
9d7f45376a | ||
|
|
536447ab0e |
1429
.cursorrules
1429
.cursorrules
File diff suppressed because it is too large
Load Diff
5
.github/codeql/codeql-config.yml
vendored
5
.github/codeql/codeql-config.yml
vendored
@@ -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
|
||||
|
||||
9
.github/dependabot.yml
vendored
9
.github/dependabot.yml
vendored
@@ -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:
|
||||
- "*"
|
||||
|
||||
4
.github/workflows/codeql.yml
vendored
4
.github/workflows/codeql.yml
vendored
@@ -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}}"
|
||||
|
||||
63
.github/workflows/generate-tool-specs.yml
vendored
Normal file
63
.github/workflows/generate-tool-specs.yml
vendored
Normal 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
1
.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
@@ -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/"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
61
docs/en/guides/coding-tools/agents-md.mdx
Normal file
61
docs/en/guides/coding-tools/agents-md.mdx
Normal 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 tool’s 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 Cursor’s 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 Windsurf’s native assistant, configure its project rules or instructions feature (if available) to read from `AGENTS.md` or paste the contents directly.
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
## 사용 예제
|
||||
|
||||
@@ -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>
|
||||
|
||||
## 사용 예제
|
||||
|
||||
@@ -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 시트에서 프레젠테이션으로 데이터를 가져옵니다.
|
||||
|
||||
|
||||
@@ -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 워크시트의 모든 차트를 가져옵니다.
|
||||
|
||||
|
||||
@@ -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`은 조직 계정이 필요합니다.
|
||||
|
||||
### 도움 받기
|
||||
|
||||
|
||||
@@ -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'").
|
||||
|
||||
### 도움 받기
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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이어야 합니다.
|
||||
|
||||
### 도움 받기
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
## 사용 예제
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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(
|
||||
@@ -900,11 +906,6 @@ class Agent(BaseAgent):
|
||||
self.agent_executor.tools_handler = self.tools_handler
|
||||
self.agent_executor.request_within_rpm_limit = rpm_limit_fn
|
||||
|
||||
# Clear accumulated messages and reset iterations from previous tasks
|
||||
# to prevent context pollution when agent is reused across sequential tasks
|
||||
self.agent_executor.messages = []
|
||||
self.agent_executor.iterations = 0
|
||||
|
||||
if self.agent_executor.llm:
|
||||
existing_stop = getattr(self.agent_executor.llm, "stop", [])
|
||||
self.agent_executor.llm.stop = list(
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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__(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,11 +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)
|
||||
|
||||
# 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"
|
||||
|
||||
@@ -155,7 +196,12 @@ def copy_template_files(folder_path, name, class_name, parent_folder):
|
||||
copy_template(src_file, dst_file, 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:
|
||||
@@ -189,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"
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
1017
lib/crewai/src/crewai/cli/templates/AGENTS.md
Normal file
1017
lib/crewai/src/crewai/cli/templates/AGENTS.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
1
lib/crewai/src/crewai/core/__init__.py
Normal file
1
lib/crewai/src/crewai/core/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Core crewAI components and interfaces."""
|
||||
1
lib/crewai/src/crewai/core/providers/__init__.py
Normal file
1
lib/crewai/src/crewai/core/providers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Provider interfaces for extensible crewAI components."""
|
||||
78
lib/crewai/src/crewai/core/providers/content_processor.py
Normal file
78
lib/crewai/src/crewai/core/providers/content_processor.py
Normal 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)
|
||||
489
lib/crewai/src/crewai/core/providers/human_input.py
Normal file
489
lib/crewai/src/crewai/core/providers/human_input.py
Normal 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)
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Final, Literal
|
||||
|
||||
|
||||
AND_CONDITION: Final[Literal["AND"]] = "AND"
|
||||
OR_CONDITION: Final[Literal["OR"]] = "OR"
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
16
lib/crewai/src/crewai/flow/flow_context.py
Normal file
16
lib/crewai/src/crewai/flow/flow_context.py
Normal 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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
1
lib/crewai/src/crewai/knowledge/source/utils/__init__.py
Normal file
1
lib/crewai/src/crewai/knowledge/source/utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Knowledge source utilities."""
|
||||
@@ -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}")
|
||||
@@ -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(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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.
|
||||
@@ -266,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()
|
||||
@@ -485,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 (
|
||||
@@ -538,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 (
|
||||
@@ -566,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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,6 +31,8 @@ from pydantic_core import PydanticCustomError
|
||||
from typing_extensions import Self
|
||||
|
||||
from crewai.agents.agent_builder.base_agent import BaseAgent
|
||||
from crewai.context import reset_current_task_id, set_current_task_id
|
||||
from crewai.core.providers.content_processor import process_content
|
||||
from crewai.events.event_bus import crewai_event_bus
|
||||
from crewai.events.types.task_events import (
|
||||
TaskCompletedEvent,
|
||||
@@ -496,6 +498,7 @@ class Task(BaseModel):
|
||||
tools: list[BaseTool] | None = None,
|
||||
) -> TaskOutput:
|
||||
"""Execute the task synchronously."""
|
||||
self.start_time = datetime.datetime.now()
|
||||
return self._execute_core(agent, context, tools)
|
||||
|
||||
@property
|
||||
@@ -536,6 +539,7 @@ class Task(BaseModel):
|
||||
) -> None:
|
||||
"""Execute the task asynchronously with context handling."""
|
||||
try:
|
||||
self.start_time = datetime.datetime.now()
|
||||
result = self._execute_core(agent, context, tools)
|
||||
future.set_result(result)
|
||||
except Exception as e:
|
||||
@@ -548,6 +552,7 @@ class Task(BaseModel):
|
||||
tools: list[BaseTool] | None = None,
|
||||
) -> TaskOutput:
|
||||
"""Execute the task asynchronously using native async/await."""
|
||||
self.start_time = datetime.datetime.now()
|
||||
return await self._aexecute_core(agent, context, tools)
|
||||
|
||||
async def _aexecute_core(
|
||||
@@ -557,6 +562,7 @@ class Task(BaseModel):
|
||||
tools: list[Any] | None,
|
||||
) -> TaskOutput:
|
||||
"""Run the core execution logic of the task asynchronously."""
|
||||
task_id_token = set_current_task_id(str(self.id))
|
||||
self._store_input_files()
|
||||
try:
|
||||
agent = agent or self.agent
|
||||
@@ -566,8 +572,6 @@ class Task(BaseModel):
|
||||
f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, like hierarchical."
|
||||
)
|
||||
|
||||
self.start_time = datetime.datetime.now()
|
||||
|
||||
self.prompt_context = context
|
||||
tools = tools or self.tools or []
|
||||
|
||||
@@ -579,6 +583,8 @@ class Task(BaseModel):
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
self._post_agent_execution(agent)
|
||||
|
||||
if not self._guardrails and not self._guardrail:
|
||||
pydantic_output, json_output = self._export_output(result)
|
||||
else:
|
||||
@@ -644,6 +650,7 @@ class Task(BaseModel):
|
||||
raise e # Re-raise the exception after emitting the event
|
||||
finally:
|
||||
clear_task_files(self.id)
|
||||
reset_current_task_id(task_id_token)
|
||||
|
||||
def _execute_core(
|
||||
self,
|
||||
@@ -652,6 +659,7 @@ class Task(BaseModel):
|
||||
tools: list[Any] | None,
|
||||
) -> TaskOutput:
|
||||
"""Run the core execution logic of the task."""
|
||||
task_id_token = set_current_task_id(str(self.id))
|
||||
self._store_input_files()
|
||||
try:
|
||||
agent = agent or self.agent
|
||||
@@ -661,8 +669,6 @@ class Task(BaseModel):
|
||||
f"The task '{self.description}' has no agent assigned, therefore it can't be executed directly and should be executed in a Crew using a specific process that support that, like hierarchical."
|
||||
)
|
||||
|
||||
self.start_time = datetime.datetime.now()
|
||||
|
||||
self.prompt_context = context
|
||||
tools = tools or self.tools or []
|
||||
|
||||
@@ -674,6 +680,8 @@ class Task(BaseModel):
|
||||
tools=tools,
|
||||
)
|
||||
|
||||
self._post_agent_execution(agent)
|
||||
|
||||
if not self._guardrails and not self._guardrail:
|
||||
pydantic_output, json_output = self._export_output(result)
|
||||
else:
|
||||
@@ -740,6 +748,10 @@ class Task(BaseModel):
|
||||
raise e # Re-raise the exception after emitting the event
|
||||
finally:
|
||||
clear_task_files(self.id)
|
||||
reset_current_task_id(task_id_token)
|
||||
|
||||
def _post_agent_execution(self, agent: BaseAgent) -> None:
|
||||
pass
|
||||
|
||||
def prompt(self) -> str:
|
||||
"""Generates the task prompt with optional markdown formatting.
|
||||
@@ -863,6 +875,11 @@ Follow these guidelines:
|
||||
except ValueError as e:
|
||||
raise ValueError(f"Error interpolating description: {e!s}") from e
|
||||
|
||||
self.description = process_content(self.description, {"task": self})
|
||||
self._original_expected_output = process_content(
|
||||
self._original_expected_output, {"task": self}
|
||||
)
|
||||
|
||||
try:
|
||||
self.expected_output = interpolate_only(
|
||||
input_string=self._original_expected_output, inputs=inputs
|
||||
|
||||
@@ -6,6 +6,7 @@ Classes:
|
||||
HallucinationGuardrail: Placeholder guardrail that validates task outputs.
|
||||
"""
|
||||
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
from crewai.llm import LLM
|
||||
@@ -13,32 +14,36 @@ from crewai.tasks.task_output import TaskOutput
|
||||
from crewai.utilities.logger import Logger
|
||||
|
||||
|
||||
_validate_output_hook: Callable[..., tuple[bool, Any]] | None = None
|
||||
|
||||
|
||||
class HallucinationGuardrail:
|
||||
"""Placeholder for the HallucinationGuardrail feature.
|
||||
|
||||
Attributes:
|
||||
context: The reference context that outputs would be checked against.
|
||||
context: Optional reference context that outputs would be checked against.
|
||||
llm: The language model that would be used for evaluation.
|
||||
threshold: Optional minimum faithfulness score that would be required to pass.
|
||||
tool_response: Optional tool response information that would be used in evaluation.
|
||||
|
||||
Examples:
|
||||
>>> # Basic usage with default verdict logic
|
||||
>>> # Basic usage without context (uses task expected_output as context)
|
||||
>>> guardrail = HallucinationGuardrail(llm=agent.llm)
|
||||
|
||||
>>> # With context for reference
|
||||
>>> guardrail = HallucinationGuardrail(
|
||||
... context="AI helps with various tasks including analysis and generation.",
|
||||
... llm=agent.llm,
|
||||
... context="AI helps with various tasks including analysis and generation.",
|
||||
... )
|
||||
|
||||
>>> # With custom threshold for stricter validation
|
||||
>>> strict_guardrail = HallucinationGuardrail(
|
||||
... context="Quantum computing uses qubits in superposition.",
|
||||
... llm=agent.llm,
|
||||
... threshold=8.0, # Would require score >= 8 to pass in enterprise version
|
||||
... threshold=8.0, # Require score >= 8 to pass
|
||||
... )
|
||||
|
||||
>>> # With tool response for additional context
|
||||
>>> guardrail_with_tools = HallucinationGuardrail(
|
||||
... context="The current weather data",
|
||||
... llm=agent.llm,
|
||||
... tool_response="Weather API returned: Temperature 22°C, Humidity 65%",
|
||||
... )
|
||||
@@ -46,16 +51,17 @@ class HallucinationGuardrail:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: str,
|
||||
llm: LLM,
|
||||
context: str | None = None,
|
||||
threshold: float | None = None,
|
||||
tool_response: str = "",
|
||||
):
|
||||
"""Initialize the HallucinationGuardrail placeholder.
|
||||
|
||||
Args:
|
||||
context: The reference context that outputs would be checked against.
|
||||
llm: The language model that would be used for evaluation.
|
||||
context: Optional reference context that outputs would be checked against.
|
||||
If not provided, the task's expected_output will be used as context.
|
||||
threshold: Optional minimum faithfulness score that would be required to pass.
|
||||
tool_response: Optional tool response information that would be used in evaluation.
|
||||
"""
|
||||
@@ -78,16 +84,17 @@ class HallucinationGuardrail:
|
||||
def __call__(self, task_output: TaskOutput) -> tuple[bool, Any]:
|
||||
"""Validate a task output against hallucination criteria.
|
||||
|
||||
In the open source, this method always returns that the output is valid.
|
||||
|
||||
Args:
|
||||
task_output: The output to be validated.
|
||||
|
||||
Returns:
|
||||
A tuple containing:
|
||||
- True
|
||||
- The raw task output
|
||||
- True if validation passed, False otherwise
|
||||
- The raw task output if valid, or error feedback if invalid
|
||||
"""
|
||||
if callable(_validate_output_hook):
|
||||
return _validate_output_hook(self, task_output)
|
||||
|
||||
self._logger.log(
|
||||
"warning",
|
||||
"Premium hallucination detection skipped (use for free at https://app.crewai.com)\n",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user