mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-04-20 18:02:37 +00:00
Compare commits
8 Commits
1.10.0
...
devin/1772
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
affe5709c1 | ||
|
|
3899910aa9 | ||
|
|
757a435ee3 | ||
|
|
8bfdb188f7 | ||
|
|
1bdb9496a3 | ||
|
|
979aa26c3d | ||
|
|
514c082882 | ||
|
|
c9e8068578 |
@@ -4,6 +4,106 @@ description: "Product updates, improvements, and bug fixes for CrewAI"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="Feb 27, 2026">
|
||||
## v1.10.1a1
|
||||
|
||||
[View release on GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.10.1a1)
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Features
|
||||
- Implement asynchronous invocation support in step callback methods
|
||||
- Implement lazy loading for heavy dependencies in Memory module
|
||||
|
||||
### Documentation
|
||||
- Update changelog and version for v1.10.0
|
||||
|
||||
### Refactoring
|
||||
- Refactor step callback methods to support asynchronous invocation
|
||||
- Refactor to implement lazy loading for heavy dependencies in Memory module
|
||||
|
||||
### Bug Fixes
|
||||
- Fix branch for release notes
|
||||
|
||||
## Contributors
|
||||
|
||||
@greysonlalonde, @joaomdmoura
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="Feb 27, 2026">
|
||||
## v1.10.1a1
|
||||
|
||||
[View release on GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.10.1a1)
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Refactoring
|
||||
- Refactor step callback methods to support asynchronous invocation
|
||||
- Implement lazy loading for heavy dependencies in Memory module
|
||||
|
||||
### Documentation
|
||||
- Update changelog and version for v1.10.0
|
||||
|
||||
### Bug Fixes
|
||||
- Make branch for release notes
|
||||
|
||||
## Contributors
|
||||
|
||||
@greysonlalonde, @joaomdmoura
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="Feb 26, 2026">
|
||||
## v1.10.0
|
||||
|
||||
[View release on GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.10.0)
|
||||
|
||||
## What's Changed
|
||||
|
||||
### Features
|
||||
- Enhance MCP tool resolution and related events
|
||||
- Update lancedb version and add lance-namespace packages
|
||||
- Enhance JSON argument parsing and validation in CrewAgentExecutor and BaseTool
|
||||
- Migrate CLI HTTP client from requests to httpx
|
||||
- Add versioned documentation
|
||||
- Add yanked detection for version notes
|
||||
- Implement user input handling in Flows
|
||||
- Enhance HITL self-loop functionality in human feedback integration tests
|
||||
- Add started_event_id and set in eventbus
|
||||
- Auto update tools.specs
|
||||
|
||||
### Bug Fixes
|
||||
- Validate tool kwargs even when empty to prevent cryptic TypeError
|
||||
- Preserve null types in tool parameter schemas for LLM
|
||||
- Map output_pydantic/output_json to native structured output
|
||||
- Ensure callbacks are ran/awaited if promise
|
||||
- Capture method name in exception context
|
||||
- Preserve enum type in router result; improve types
|
||||
- Fix cyclic flows silently breaking when persistence ID is passed in inputs
|
||||
- Correct CLI flag format from --skip-provider to --skip_provider
|
||||
- Ensure OpenAI tool call stream is finalized
|
||||
- Resolve complex schema $ref pointers in MCP tools
|
||||
- Enforce additionalProperties=false in schemas
|
||||
- Reject reserved script names for crew folders
|
||||
- Resolve race condition in guardrail event emission test
|
||||
|
||||
### Documentation
|
||||
- Add litellm dependency note for non-native LLM providers
|
||||
- Clarify NL2SQL security model and hardening guidance
|
||||
- Add 96 missing actions across 9 integrations
|
||||
|
||||
### Refactoring
|
||||
- Refactor crew to provider
|
||||
- Extract HITL to provider pattern
|
||||
- Improve hook typing and registration
|
||||
|
||||
## Contributors
|
||||
|
||||
@dependabot[bot], @github-actions[bot], @github-code-quality[bot], @greysonlalonde, @heitorado, @hobostay, @joaomdmoura, @johnvan7, @jonathansampson, @lorenzejay, @lucasgomide, @mattatcha, @mplachta, @nicoferdi96, @theCyberTech, @thiagomoretto, @vinibrsl
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="Jan 26, 2026">
|
||||
## v1.9.0
|
||||
|
||||
|
||||
@@ -18,77 +18,46 @@ Composio is an integration platform that allows you to connect your AI agents to
|
||||
To incorporate Composio tools into your project, follow the instructions below:
|
||||
|
||||
```shell
|
||||
pip install composio-crewai
|
||||
pip install composio composio-crewai
|
||||
pip install crewai
|
||||
```
|
||||
|
||||
After the installation is complete, either run `composio login` or export your composio API key as `COMPOSIO_API_KEY`. Get your Composio API key from [here](https://app.composio.dev)
|
||||
After the installation is complete, set your Composio API key as `COMPOSIO_API_KEY`. Get your Composio API key from [here](https://platform.composio.dev)
|
||||
|
||||
## Example
|
||||
|
||||
The following example demonstrates how to initialize the tool and execute a github action:
|
||||
|
||||
1. Initialize Composio toolset
|
||||
1. Initialize Composio with CrewAI Provider
|
||||
|
||||
```python Code
|
||||
from composio_crewai import ComposioToolSet, App, Action
|
||||
from composio_crewai import ComposioProvider
|
||||
from composio import Composio
|
||||
from crewai import Agent, Task, Crew
|
||||
|
||||
toolset = ComposioToolSet()
|
||||
composio = Composio(provider=ComposioProvider())
|
||||
```
|
||||
|
||||
2. Connect your GitHub account
|
||||
2. Create a new Composio Session and retrieve the tools
|
||||
<CodeGroup>
|
||||
```shell CLI
|
||||
composio add github
|
||||
```
|
||||
```python Code
|
||||
request = toolset.initiate_connection(app=App.GITHUB)
|
||||
print(f"Open this URL to authenticate: {request.redirectUrl}")
|
||||
```python
|
||||
session = composio.create(
|
||||
user_id="your-user-id",
|
||||
toolkits=["gmail", "github"] # optional, default is all toolkits
|
||||
)
|
||||
tools = session.tools()
|
||||
```
|
||||
Read more about sessions and user management [here](https://docs.composio.dev/docs/configuring-sessions)
|
||||
</CodeGroup>
|
||||
|
||||
3. Get Tools
|
||||
3. Authenticating users manually
|
||||
|
||||
- Retrieving all the tools from an app (not recommended for production):
|
||||
Composio automatically authenticates the users during the agent chat session. However, you can also authenticate the user manually by calling the `authorize` method.
|
||||
```python Code
|
||||
tools = toolset.get_tools(apps=[App.GITHUB])
|
||||
connection_request = session.authorize("github")
|
||||
print(f"Open this URL to authenticate: {connection_request.redirect_url}")
|
||||
```
|
||||
|
||||
- Filtering tools based on tags:
|
||||
```python Code
|
||||
tag = "users"
|
||||
|
||||
filtered_action_enums = toolset.find_actions_by_tags(
|
||||
App.GITHUB,
|
||||
tags=[tag],
|
||||
)
|
||||
|
||||
tools = toolset.get_tools(actions=filtered_action_enums)
|
||||
```
|
||||
|
||||
- Filtering tools based on use case:
|
||||
```python Code
|
||||
use_case = "Star a repository on GitHub"
|
||||
|
||||
filtered_action_enums = toolset.find_actions_by_use_case(
|
||||
App.GITHUB, use_case=use_case, advanced=False
|
||||
)
|
||||
|
||||
tools = toolset.get_tools(actions=filtered_action_enums)
|
||||
```
|
||||
<Tip>Set `advanced` to True to get actions for complex use cases</Tip>
|
||||
|
||||
- Using specific tools:
|
||||
|
||||
In this demo, we will use the `GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER` action from the GitHub app.
|
||||
```python Code
|
||||
tools = toolset.get_tools(
|
||||
actions=[Action.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER]
|
||||
)
|
||||
```
|
||||
Learn more about filtering actions [here](https://docs.composio.dev/patterns/tools/use-tools/use-specific-actions)
|
||||
|
||||
4. Define agent
|
||||
|
||||
```python Code
|
||||
@@ -116,4 +85,4 @@ crew = Crew(agents=[crewai_agent], tasks=[task])
|
||||
crew.kickoff()
|
||||
```
|
||||
|
||||
* More detailed list of tools can be found [here](https://app.composio.dev)
|
||||
* More detailed list of tools can be found [here](https://docs.composio.dev/toolkits)
|
||||
|
||||
@@ -4,6 +4,106 @@ description: "CrewAI의 제품 업데이트, 개선 사항 및 버그 수정"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="2026년 2월 27일">
|
||||
## v1.10.1a1
|
||||
|
||||
[GitHub 릴리스 보기](https://github.com/crewAIInc/crewAI/releases/tag/1.10.1a1)
|
||||
|
||||
## 변경 사항
|
||||
|
||||
### 기능
|
||||
- 단계 콜백 메서드에서 비동기 호출 지원 구현
|
||||
- 메모리 모듈의 무거운 의존성에 대한 지연 로딩 구현
|
||||
|
||||
### 문서
|
||||
- v1.10.0에 대한 변경 로그 및 버전 업데이트
|
||||
|
||||
### 리팩토링
|
||||
- 비동기 호출을 지원하기 위해 단계 콜백 메서드 리팩토링
|
||||
- 메모리 모듈의 무거운 의존성에 대한 지연 로딩을 구현하기 위해 리팩토링
|
||||
|
||||
### 버그 수정
|
||||
- 릴리스 노트의 분기 수정
|
||||
|
||||
## 기여자
|
||||
|
||||
@greysonlalonde, @joaomdmoura
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026년 2월 27일">
|
||||
## v1.10.1a1
|
||||
|
||||
[GitHub 릴리스 보기](https://github.com/crewAIInc/crewAI/releases/tag/1.10.1a1)
|
||||
|
||||
## 변경 사항
|
||||
|
||||
### 리팩토링
|
||||
- 비동기 호출을 지원하기 위해 단계 콜백 메서드 리팩토링
|
||||
- 메모리 모듈의 무거운 의존성에 대해 지연 로딩 구현
|
||||
|
||||
### 문서화
|
||||
- v1.10.0에 대한 변경 로그 및 버전 업데이트
|
||||
|
||||
### 버그 수정
|
||||
- 릴리스 노트를 위한 브랜치 생성
|
||||
|
||||
## 기여자
|
||||
|
||||
@greysonlalonde, @joaomdmoura
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026년 2월 26일">
|
||||
## v1.10.0
|
||||
|
||||
[GitHub 릴리스 보기](https://github.com/crewAIInc/crewAI/releases/tag/1.10.0)
|
||||
|
||||
## 변경 사항
|
||||
|
||||
### 기능
|
||||
- MCP 도구 해상도 및 관련 이벤트 개선
|
||||
- lancedb 버전 업데이트 및 lance-namespace 패키지 추가
|
||||
- CrewAgentExecutor 및 BaseTool에서 JSON 인수 파싱 및 검증 개선
|
||||
- CLI HTTP 클라이언트를 requests에서 httpx로 마이그레이션
|
||||
- 버전화된 문서 추가
|
||||
- 버전 노트에 대한 yanked 감지 추가
|
||||
- Flows에서 사용자 입력 처리 구현
|
||||
- 인간 피드백 통합 테스트에서 HITL 자기 루프 기능 개선
|
||||
- eventbus에 started_event_id 추가 및 설정
|
||||
- tools.specs 자동 업데이트
|
||||
|
||||
### 버그 수정
|
||||
- 빈 경우에도 도구 kwargs를 검증하여 모호한 TypeError 방지
|
||||
- LLM을 위한 도구 매개변수 스키마에서 null 타입 유지
|
||||
- output_pydantic/output_json을 네이티브 구조화된 출력으로 매핑
|
||||
- 약속이 있는 경우 콜백이 실행/대기되도록 보장
|
||||
- 예외 컨텍스트에서 메서드 이름 캡처
|
||||
- 라우터 결과에서 enum 타입 유지; 타입 개선
|
||||
- 입력으로 지속성 ID가 전달될 때 조용히 깨지는 순환 흐름 수정
|
||||
- CLI 플래그 형식을 --skip-provider에서 --skip_provider로 수정
|
||||
- OpenAI 도구 호출 스트림이 완료되도록 보장
|
||||
- MCP 도구에서 복잡한 스키마 $ref 포인터 해결
|
||||
- 스키마에서 additionalProperties=false 강제 적용
|
||||
- 크루 폴더에 대해 예약된 스크립트 이름 거부
|
||||
- 가드레일 이벤트 방출 테스트에서 경쟁 조건 해결
|
||||
|
||||
### 문서
|
||||
- 비네이티브 LLM 공급자를 위한 litellm 종속성 노트 추가
|
||||
- NL2SQL 보안 모델 및 강화 지침 명확화
|
||||
- 9개 통합에서 96개의 누락된 작업 추가
|
||||
|
||||
### 리팩토링
|
||||
- crew를 provider로 리팩토링
|
||||
- HITL을 provider 패턴으로 추출
|
||||
- 훅 타이핑 및 등록 개선
|
||||
|
||||
## 기여자
|
||||
|
||||
@dependabot[bot], @github-actions[bot], @github-code-quality[bot], @greysonlalonde, @heitorado, @hobostay, @joaomdmoura, @johnvan7, @jonathansampson, @lorenzejay, @lucasgomide, @mattatcha, @mplachta, @nicoferdi96, @theCyberTech, @thiagomoretto, @vinibrsl
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="2026년 1월 26일">
|
||||
## v1.9.0
|
||||
|
||||
|
||||
@@ -18,77 +18,46 @@ Composio는 AI 에이전트를 250개 이상의 도구와 연결할 수 있는
|
||||
Composio 도구를 프로젝트에 통합하려면 아래 지침을 따르세요:
|
||||
|
||||
```shell
|
||||
pip install composio-crewai
|
||||
pip install composio composio-crewai
|
||||
pip install crewai
|
||||
```
|
||||
|
||||
설치가 완료된 후, `composio login`을 실행하거나 Composio API 키를 `COMPOSIO_API_KEY`로 export하세요. Composio API 키는 [여기](https://app.composio.dev)에서 받을 수 있습니다.
|
||||
설치가 완료되면 Composio API 키를 `COMPOSIO_API_KEY`로 설정하세요. Composio API 키는 [여기](https://platform.composio.dev)에서 받을 수 있습니다.
|
||||
|
||||
## 예시
|
||||
|
||||
다음 예시는 도구를 초기화하고 github action을 실행하는 방법을 보여줍니다:
|
||||
다음 예시는 도구를 초기화하고 GitHub 액션을 실행하는 방법을 보여줍니다:
|
||||
|
||||
1. Composio 도구 세트 초기화
|
||||
1. CrewAI Provider와 함께 Composio 초기화
|
||||
|
||||
```python Code
|
||||
from composio_crewai import ComposioToolSet, App, Action
|
||||
from composio_crewai import ComposioProvider
|
||||
from composio import Composio
|
||||
from crewai import Agent, Task, Crew
|
||||
|
||||
toolset = ComposioToolSet()
|
||||
composio = Composio(provider=ComposioProvider())
|
||||
```
|
||||
|
||||
2. GitHub 계정 연결
|
||||
2. 새 Composio 세션을 만들고 도구 가져오기
|
||||
<CodeGroup>
|
||||
```shell CLI
|
||||
composio add github
|
||||
```
|
||||
```python Code
|
||||
request = toolset.initiate_connection(app=App.GITHUB)
|
||||
print(f"Open this URL to authenticate: {request.redirectUrl}")
|
||||
```python
|
||||
session = composio.create(
|
||||
user_id="your-user-id",
|
||||
toolkits=["gmail", "github"] # optional, default is all toolkits
|
||||
)
|
||||
tools = session.tools()
|
||||
```
|
||||
세션 및 사용자 관리에 대한 자세한 내용은 [여기](https://docs.composio.dev/docs/configuring-sessions)를 참고하세요.
|
||||
</CodeGroup>
|
||||
|
||||
3. 도구 가져오기
|
||||
3. 사용자 수동 인증하기
|
||||
|
||||
- 앱에서 모든 도구를 가져오기 (프로덕션 환경에서는 권장하지 않음):
|
||||
Composio는 에이전트 채팅 세션 중에 사용자를 자동으로 인증합니다. 하지만 `authorize` 메서드를 호출해 사용자를 수동으로 인증할 수도 있습니다.
|
||||
```python Code
|
||||
tools = toolset.get_tools(apps=[App.GITHUB])
|
||||
connection_request = session.authorize("github")
|
||||
print(f"Open this URL to authenticate: {connection_request.redirect_url}")
|
||||
```
|
||||
|
||||
- 태그를 기반으로 도구 필터링:
|
||||
```python Code
|
||||
tag = "users"
|
||||
|
||||
filtered_action_enums = toolset.find_actions_by_tags(
|
||||
App.GITHUB,
|
||||
tags=[tag],
|
||||
)
|
||||
|
||||
tools = toolset.get_tools(actions=filtered_action_enums)
|
||||
```
|
||||
|
||||
- 사용 사례를 기반으로 도구 필터링:
|
||||
```python Code
|
||||
use_case = "Star a repository on GitHub"
|
||||
|
||||
filtered_action_enums = toolset.find_actions_by_use_case(
|
||||
App.GITHUB, use_case=use_case, advanced=False
|
||||
)
|
||||
|
||||
tools = toolset.get_tools(actions=filtered_action_enums)
|
||||
```
|
||||
<Tip>`advanced`를 True로 설정하면 복잡한 사용 사례를 위한 액션을 가져올 수 있습니다</Tip>
|
||||
|
||||
- 특정 도구 사용하기:
|
||||
|
||||
이 데모에서는 GitHub 앱의 `GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER` 액션을 사용합니다.
|
||||
```python Code
|
||||
tools = toolset.get_tools(
|
||||
actions=[Action.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER]
|
||||
)
|
||||
```
|
||||
액션 필터링에 대해 더 자세한 내용을 보려면 [여기](https://docs.composio.dev/patterns/tools/use-tools/use-specific-actions)를 참고하세요.
|
||||
|
||||
4. 에이전트 정의
|
||||
|
||||
```python Code
|
||||
@@ -116,4 +85,4 @@ crew = Crew(agents=[crewai_agent], tasks=[task])
|
||||
crew.kickoff()
|
||||
```
|
||||
|
||||
* 더욱 자세한 도구 리스트는 [여기](https://app.composio.dev)에서 확인하실 수 있습니다.
|
||||
* 더욱 자세한 도구 목록은 [여기](https://docs.composio.dev/toolkits)에서 확인할 수 있습니다.
|
||||
@@ -4,6 +4,106 @@ description: "Atualizações de produto, melhorias e correções do CrewAI"
|
||||
icon: "clock"
|
||||
mode: "wide"
|
||||
---
|
||||
<Update label="27 fev 2026">
|
||||
## v1.10.1a1
|
||||
|
||||
[Ver release no GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.10.1a1)
|
||||
|
||||
## O que Mudou
|
||||
|
||||
### Funcionalidades
|
||||
- Implementar suporte a invocação assíncrona em métodos de callback de etapas
|
||||
- Implementar carregamento sob demanda para dependências pesadas no módulo de Memória
|
||||
|
||||
### Documentação
|
||||
- Atualizar changelog e versão para v1.10.0
|
||||
|
||||
### Refatoração
|
||||
- Refatorar métodos de callback de etapas para suportar invocação assíncrona
|
||||
- Refatorar para implementar carregamento sob demanda para dependências pesadas no módulo de Memória
|
||||
|
||||
### Correções de Bugs
|
||||
- Corrigir branch para notas de lançamento
|
||||
|
||||
## Contribuidores
|
||||
|
||||
@greysonlalonde, @joaomdmoura
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="27 fev 2026">
|
||||
## v1.10.1a1
|
||||
|
||||
[Ver release no GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.10.1a1)
|
||||
|
||||
## O que Mudou
|
||||
|
||||
### Refatoração
|
||||
- Refatorar métodos de callback de etapas para suportar invocação assíncrona
|
||||
- Implementar carregamento sob demanda para dependências pesadas no módulo de Memória
|
||||
|
||||
### Documentação
|
||||
- Atualizar changelog e versão para v1.10.0
|
||||
|
||||
### Correções de Bugs
|
||||
- Criar branch para notas de lançamento
|
||||
|
||||
## Contribuidores
|
||||
|
||||
@greysonlalonde, @joaomdmoura
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="26 fev 2026">
|
||||
## v1.10.0
|
||||
|
||||
[Ver release no GitHub](https://github.com/crewAIInc/crewAI/releases/tag/1.10.0)
|
||||
|
||||
## O que Mudou
|
||||
|
||||
### Recursos
|
||||
- Aprimorar a resolução da ferramenta MCP e eventos relacionados
|
||||
- Atualizar a versão do lancedb e adicionar pacotes lance-namespace
|
||||
- Aprimorar a análise e validação de argumentos JSON no CrewAgentExecutor e BaseTool
|
||||
- Migrar o cliente HTTP da CLI de requests para httpx
|
||||
- Adicionar documentação versionada
|
||||
- Adicionar detecção de versões removidas para notas de versão
|
||||
- Implementar tratamento de entrada do usuário em Flows
|
||||
- Aprimorar a funcionalidade de auto-loop HITL nos testes de integração de feedback humano
|
||||
- Adicionar started_event_id e definir no eventbus
|
||||
- Atualizar automaticamente tools.specs
|
||||
|
||||
### Correções de Bugs
|
||||
- Validar kwargs da ferramenta mesmo quando vazios para evitar TypeError crípticos
|
||||
- Preservar tipos nulos nos esquemas de parâmetros da ferramenta para LLM
|
||||
- Mapear output_pydantic/output_json para saída estruturada nativa
|
||||
- Garantir que callbacks sejam executados/aguardados se forem promessas
|
||||
- Capturar o nome do método no contexto da exceção
|
||||
- Preservar tipo enum no resultado do roteador; melhorar tipos
|
||||
- Corrigir fluxos cíclicos que quebram silenciosamente quando o ID de persistência é passado nas entradas
|
||||
- Corrigir o formato da flag da CLI de --skip-provider para --skip_provider
|
||||
- Garantir que o fluxo de chamada da ferramenta OpenAI seja finalizado
|
||||
- Resolver ponteiros $ref de esquema complexos nas ferramentas MCP
|
||||
- Impor additionalProperties=false nos esquemas
|
||||
- Rejeitar nomes de scripts reservados para pastas de equipe
|
||||
- Resolver condição de corrida no teste de emissão de eventos de guardrail
|
||||
|
||||
### Documentação
|
||||
- Adicionar nota de dependência litellm para provedores de LLM não nativos
|
||||
- Esclarecer o modelo de segurança NL2SQL e orientações de fortalecimento
|
||||
- Adicionar 96 ações ausentes em 9 integrações
|
||||
|
||||
### Refatoração
|
||||
- Refatorar crew para provider
|
||||
- Extrair HITL para padrão de provider
|
||||
- Melhorar tipagem e registro de hooks
|
||||
|
||||
## Contribuidores
|
||||
|
||||
@dependabot[bot], @github-actions[bot], @github-code-quality[bot], @greysonlalonde, @heitorado, @hobostay, @joaomdmoura, @johnvan7, @jonathansampson, @lorenzejay, @lucasgomide, @mattatcha, @mplachta, @nicoferdi96, @theCyberTech, @thiagomoretto, @vinibrsl
|
||||
|
||||
</Update>
|
||||
|
||||
<Update label="26 jan 2026">
|
||||
## v1.9.0
|
||||
|
||||
|
||||
@@ -11,84 +11,53 @@ mode: "wide"
|
||||
Composio é uma plataforma de integração que permite conectar seus agentes de IA a mais de 250 ferramentas. Os principais recursos incluem:
|
||||
|
||||
- **Autenticação de Nível Empresarial**: Suporte integrado para OAuth, Chaves de API, JWT com atualização automática de token
|
||||
- **Observabilidade Completa**: Logs detalhados de uso das ferramentas, registros de execução, e muito mais
|
||||
- **Observabilidade Completa**: Logs detalhados de uso das ferramentas, carimbos de data/hora de execução e muito mais
|
||||
|
||||
## Instalação
|
||||
|
||||
Para incorporar as ferramentas Composio em seu projeto, siga as instruções abaixo:
|
||||
|
||||
```shell
|
||||
pip install composio-crewai
|
||||
pip install composio composio-crewai
|
||||
pip install crewai
|
||||
```
|
||||
|
||||
Após a conclusão da instalação, execute `composio login` ou exporte sua chave de API do composio como `COMPOSIO_API_KEY`. Obtenha sua chave de API Composio [aqui](https://app.composio.dev)
|
||||
Após concluir a instalação, defina sua chave de API do Composio como `COMPOSIO_API_KEY`. Obtenha sua chave de API do Composio [aqui](https://platform.composio.dev)
|
||||
|
||||
## Exemplo
|
||||
|
||||
O exemplo a seguir demonstra como inicializar a ferramenta e executar uma ação do github:
|
||||
O exemplo a seguir demonstra como inicializar a ferramenta e executar uma ação do GitHub:
|
||||
|
||||
1. Inicialize o conjunto de ferramentas Composio
|
||||
1. Inicialize o Composio com o Provider do CrewAI
|
||||
|
||||
```python Code
|
||||
from composio_crewai import ComposioToolSet, App, Action
|
||||
from composio_crewai import ComposioProvider
|
||||
from composio import Composio
|
||||
from crewai import Agent, Task, Crew
|
||||
|
||||
toolset = ComposioToolSet()
|
||||
composio = Composio(provider=ComposioProvider())
|
||||
```
|
||||
|
||||
2. Conecte sua conta do GitHub
|
||||
2. Crie uma nova sessão Composio e recupere as ferramentas
|
||||
<CodeGroup>
|
||||
```shell CLI
|
||||
composio add github
|
||||
```
|
||||
```python Code
|
||||
request = toolset.initiate_connection(app=App.GITHUB)
|
||||
print(f"Open this URL to authenticate: {request.redirectUrl}")
|
||||
```python
|
||||
session = composio.create(
|
||||
user_id="your-user-id",
|
||||
toolkits=["gmail", "github"] # optional, default is all toolkits
|
||||
)
|
||||
tools = session.tools()
|
||||
```
|
||||
Leia mais sobre sessões e gerenciamento de usuários [aqui](https://docs.composio.dev/docs/configuring-sessions)
|
||||
</CodeGroup>
|
||||
|
||||
3. Obtenha ferramentas
|
||||
3. Autenticação manual dos usuários
|
||||
|
||||
- Recuperando todas as ferramentas de um app (não recomendado em produção):
|
||||
O Composio autentica automaticamente os usuários durante a sessão de chat do agente. No entanto, você também pode autenticar o usuário manualmente chamando o método `authorize`.
|
||||
```python Code
|
||||
tools = toolset.get_tools(apps=[App.GITHUB])
|
||||
connection_request = session.authorize("github")
|
||||
print(f"Open this URL to authenticate: {connection_request.redirect_url}")
|
||||
```
|
||||
|
||||
- Filtrando ferramentas com base em tags:
|
||||
```python Code
|
||||
tag = "users"
|
||||
|
||||
filtered_action_enums = toolset.find_actions_by_tags(
|
||||
App.GITHUB,
|
||||
tags=[tag],
|
||||
)
|
||||
|
||||
tools = toolset.get_tools(actions=filtered_action_enums)
|
||||
```
|
||||
|
||||
- Filtrando ferramentas com base no caso de uso:
|
||||
```python Code
|
||||
use_case = "Star a repository on GitHub"
|
||||
|
||||
filtered_action_enums = toolset.find_actions_by_use_case(
|
||||
App.GITHUB, use_case=use_case, advanced=False
|
||||
)
|
||||
|
||||
tools = toolset.get_tools(actions=filtered_action_enums)
|
||||
```
|
||||
<Tip>Defina `advanced` como True para obter ações para casos de uso complexos</Tip>
|
||||
|
||||
- Usando ferramentas específicas:
|
||||
|
||||
Neste exemplo, usaremos a ação `GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER` do app GitHub.
|
||||
```python Code
|
||||
tools = toolset.get_tools(
|
||||
actions=[Action.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER]
|
||||
)
|
||||
```
|
||||
Saiba mais sobre como filtrar ações [aqui](https://docs.composio.dev/patterns/tools/use-tools/use-specific-actions)
|
||||
|
||||
4. Defina o agente
|
||||
|
||||
```python Code
|
||||
@@ -116,4 +85,4 @@ crew = Crew(agents=[crewai_agent], tasks=[task])
|
||||
crew.kickoff()
|
||||
```
|
||||
|
||||
* Uma lista mais detalhada de ferramentas pode ser encontrada [aqui](https://app.composio.dev)
|
||||
* Uma lista mais detalhada de ferramentas pode ser encontrada [aqui](https://docs.composio.dev/toolkits)
|
||||
@@ -152,4 +152,4 @@ __all__ = [
|
||||
"wrap_file_source",
|
||||
]
|
||||
|
||||
__version__ = "1.10.0"
|
||||
__version__ = "1.10.1a1"
|
||||
|
||||
@@ -11,7 +11,7 @@ dependencies = [
|
||||
"pytube~=15.0.0",
|
||||
"requests~=2.32.5",
|
||||
"docker~=7.1.0",
|
||||
"crewai==1.10.0",
|
||||
"crewai==1.10.1a1",
|
||||
"tiktoken~=0.8.0",
|
||||
"beautifulsoup4~=4.13.4",
|
||||
"python-docx~=1.2.0",
|
||||
|
||||
@@ -291,4 +291,4 @@ __all__ = [
|
||||
"ZapierActionTools",
|
||||
]
|
||||
|
||||
__version__ = "1.10.0"
|
||||
__version__ = "1.10.1a1"
|
||||
|
||||
@@ -53,7 +53,7 @@ Repository = "https://github.com/crewAIInc/crewAI"
|
||||
|
||||
[project.optional-dependencies]
|
||||
tools = [
|
||||
"crewai-tools==1.10.0",
|
||||
"crewai-tools==1.10.1a1",
|
||||
]
|
||||
embeddings = [
|
||||
"tiktoken~=0.8.0"
|
||||
|
||||
@@ -10,7 +10,6 @@ from crewai.flow.flow import Flow
|
||||
from crewai.knowledge.knowledge import Knowledge
|
||||
from crewai.llm import LLM
|
||||
from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.memory.unified_memory import Memory
|
||||
from crewai.process import Process
|
||||
from crewai.task import Task
|
||||
from crewai.tasks.llm_guardrail import LLMGuardrail
|
||||
@@ -41,7 +40,7 @@ def _suppress_pydantic_deprecation_warnings() -> None:
|
||||
|
||||
_suppress_pydantic_deprecation_warnings()
|
||||
|
||||
__version__ = "1.10.0"
|
||||
__version__ = "1.10.1a1"
|
||||
_telemetry_submitted = False
|
||||
|
||||
|
||||
@@ -72,6 +71,25 @@ def _track_install_async() -> None:
|
||||
|
||||
|
||||
_track_install_async()
|
||||
|
||||
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
||||
"Memory": ("crewai.memory.unified_memory", "Memory"),
|
||||
}
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
"""Lazily import heavy modules (e.g. Memory → lancedb) on first access."""
|
||||
if name in _LAZY_IMPORTS:
|
||||
module_path, attr = _LAZY_IMPORTS[name]
|
||||
import importlib
|
||||
|
||||
mod = importlib.import_module(module_path)
|
||||
val = getattr(mod, attr)
|
||||
globals()[name] = val
|
||||
return val
|
||||
raise AttributeError(f"module 'crewai' has no attribute {name!r}")
|
||||
|
||||
|
||||
__all__ = [
|
||||
"LLM",
|
||||
"Agent",
|
||||
|
||||
@@ -1259,7 +1259,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
formatted_answer, tool_result
|
||||
)
|
||||
|
||||
self._invoke_step_callback(formatted_answer) # type: ignore[arg-type]
|
||||
await self._ainvoke_step_callback(formatted_answer) # type: ignore[arg-type]
|
||||
self._append_message(formatted_answer.text) # type: ignore[union-attr]
|
||||
|
||||
except OutputParserError as e:
|
||||
@@ -1374,7 +1374,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
output=answer,
|
||||
text=answer,
|
||||
)
|
||||
self._invoke_step_callback(formatted_answer)
|
||||
await self._ainvoke_step_callback(formatted_answer)
|
||||
self._append_message(answer) # Save final answer to messages
|
||||
self._show_logs(formatted_answer)
|
||||
return formatted_answer
|
||||
@@ -1386,7 +1386,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
output=answer,
|
||||
text=output_json,
|
||||
)
|
||||
self._invoke_step_callback(formatted_answer)
|
||||
await self._ainvoke_step_callback(formatted_answer)
|
||||
self._append_message(output_json)
|
||||
self._show_logs(formatted_answer)
|
||||
return formatted_answer
|
||||
@@ -1397,7 +1397,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
output=str(answer),
|
||||
text=str(answer),
|
||||
)
|
||||
self._invoke_step_callback(formatted_answer)
|
||||
await self._ainvoke_step_callback(formatted_answer)
|
||||
self._append_message(str(answer)) # Save final answer to messages
|
||||
self._show_logs(formatted_answer)
|
||||
return formatted_answer
|
||||
@@ -1491,7 +1491,7 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
def _invoke_step_callback(
|
||||
self, formatted_answer: AgentAction | AgentFinish
|
||||
) -> None:
|
||||
"""Invoke step callback.
|
||||
"""Invoke step callback (sync context).
|
||||
|
||||
Args:
|
||||
formatted_answer: Current agent response.
|
||||
@@ -1501,6 +1501,19 @@ class CrewAgentExecutor(CrewAgentExecutorMixin):
|
||||
if inspect.iscoroutine(cb_result):
|
||||
asyncio.run(cb_result)
|
||||
|
||||
async def _ainvoke_step_callback(
|
||||
self, formatted_answer: AgentAction | AgentFinish
|
||||
) -> None:
|
||||
"""Invoke step callback (async context).
|
||||
|
||||
Args:
|
||||
formatted_answer: Current agent response.
|
||||
"""
|
||||
if self.step_callback:
|
||||
cb_result = self.step_callback(formatted_answer)
|
||||
if inspect.iscoroutine(cb_result):
|
||||
await cb_result
|
||||
|
||||
def _append_message(
|
||||
self, text: str, role: Literal["user", "assistant", "system"] = "assistant"
|
||||
) -> None:
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]==1.10.0"
|
||||
"crewai[tools]==1.10.1a1"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "{{name}} using crewAI"
|
||||
authors = [{ name = "Your Name", email = "you@example.com" }]
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]==1.10.0"
|
||||
"crewai[tools]==1.10.1a1"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
|
||||
@@ -5,7 +5,7 @@ description = "Power up your crews with {{folder_name}}"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10,<3.14"
|
||||
dependencies = [
|
||||
"crewai[tools]==1.10.0"
|
||||
"crewai[tools]==1.10.1a1"
|
||||
]
|
||||
|
||||
[tool.crewai]
|
||||
|
||||
@@ -16,7 +16,7 @@ from collections.abc import (
|
||||
Sequence,
|
||||
ValuesView,
|
||||
)
|
||||
from concurrent.futures import Future
|
||||
from concurrent.futures import Future, ThreadPoolExecutor
|
||||
import copy
|
||||
import enum
|
||||
import inspect
|
||||
@@ -1739,7 +1739,12 @@ class Flow(Generic[T], metaclass=FlowMeta):
|
||||
async def _run_flow() -> Any:
|
||||
return await self.kickoff_async(inputs, input_files)
|
||||
|
||||
return asyncio.run(_run_flow())
|
||||
try:
|
||||
asyncio.get_running_loop()
|
||||
with ThreadPoolExecutor(max_workers=1) as pool:
|
||||
return pool.submit(asyncio.run, _run_flow()).result()
|
||||
except RuntimeError:
|
||||
return asyncio.run(_run_flow())
|
||||
|
||||
async def kickoff_async(
|
||||
self,
|
||||
|
||||
@@ -34,6 +34,8 @@ except ImportError:
|
||||
) from None
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
STRUCTURED_OUTPUT_TOOL_NAME = "structured_output"
|
||||
|
||||
|
||||
@@ -61,6 +63,7 @@ class GeminiCompletion(BaseLLM):
|
||||
interceptor: BaseInterceptor[Any, Any] | None = None,
|
||||
use_vertexai: bool | None = None,
|
||||
response_format: type[BaseModel] | None = None,
|
||||
thinking_config: types.ThinkingConfig | dict[str, Any] | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
"""Initialize Google Gemini chat completion client.
|
||||
@@ -93,6 +96,14 @@ class GeminiCompletion(BaseLLM):
|
||||
api_version="v1" is automatically configured.
|
||||
response_format: Pydantic model for structured output. Used as default when
|
||||
response_model is not passed to call()/acall() methods.
|
||||
thinking_config: Configuration for Gemini thinking models (e.g. gemini-2.5-pro).
|
||||
Can be a ThinkingConfig object or a dict with 'include_thoughts'
|
||||
and optionally 'thinking_budget' keys.
|
||||
When enabled, the model's reasoning/thought output is captured
|
||||
and logged. Example:
|
||||
thinking_config={"include_thoughts": True}
|
||||
thinking_config=ThinkingConfig(include_thoughts=True,
|
||||
thinking_budget=10000)
|
||||
**kwargs: Additional parameters
|
||||
"""
|
||||
if interceptor is not None:
|
||||
@@ -130,6 +141,17 @@ class GeminiCompletion(BaseLLM):
|
||||
self.tools: list[dict[str, Any]] | None = None
|
||||
self.response_format = response_format
|
||||
|
||||
# Thinking config for Gemini thinking models (e.g. gemini-2.5-pro)
|
||||
if isinstance(thinking_config, dict):
|
||||
self.thinking_config: types.ThinkingConfig | None = types.ThinkingConfig(
|
||||
**thinking_config
|
||||
)
|
||||
else:
|
||||
self.thinking_config = thinking_config
|
||||
|
||||
# Store previous thought content for multi-turn conversations
|
||||
self.previous_thoughts: list[str] = []
|
||||
|
||||
# Model-specific settings
|
||||
version_match = re.search(r"gemini-(\d+(?:\.\d+)?)", model.lower())
|
||||
self.supports_tools = bool(
|
||||
@@ -481,6 +503,10 @@ class GeminiCompletion(BaseLLM):
|
||||
if self.stop_sequences:
|
||||
config_params["stop_sequences"] = self.stop_sequences
|
||||
|
||||
# Add thinking config for thinking models (e.g. gemini-2.5-pro)
|
||||
if self.thinking_config is not None:
|
||||
config_params["thinking_config"] = self.thinking_config
|
||||
|
||||
if tools and self.supports_tools:
|
||||
gemini_tools = self._convert_tools_for_interference(tools)
|
||||
|
||||
@@ -916,6 +942,11 @@ class GeminiCompletion(BaseLLM):
|
||||
) -> tuple[str, dict[int, dict[str, Any]], dict[str, int]]:
|
||||
"""Process a single streaming chunk.
|
||||
|
||||
Instead of using ``chunk.text`` (which triggers a warning when non-text
|
||||
parts like ``function_call`` or ``thought_signature`` are present), this
|
||||
method iterates over the candidate parts directly to extract text,
|
||||
thought content, and function calls without side effects.
|
||||
|
||||
Args:
|
||||
chunk: The streaming chunk response
|
||||
full_response: Accumulated response text
|
||||
@@ -931,19 +962,31 @@ class GeminiCompletion(BaseLLM):
|
||||
if chunk.usage_metadata:
|
||||
usage_data = self._extract_token_usage(chunk)
|
||||
|
||||
if chunk.text:
|
||||
full_response += chunk.text
|
||||
self._emit_stream_chunk_event(
|
||||
chunk=chunk.text,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
response_id=response_id,
|
||||
)
|
||||
|
||||
# Iterate over parts directly to avoid the warning triggered by chunk.text
|
||||
# when non-text parts (function_call, thought_signature) are present.
|
||||
if chunk.candidates:
|
||||
candidate = chunk.candidates[0]
|
||||
if candidate.content and candidate.content.parts:
|
||||
for part in candidate.content.parts:
|
||||
# Handle thought parts from thinking models
|
||||
if getattr(part, "thought", False) and part.text:
|
||||
logger.debug(
|
||||
"Gemini thinking model thought: %s", part.text
|
||||
)
|
||||
self.previous_thoughts.append(part.text)
|
||||
continue
|
||||
|
||||
# Handle regular text parts
|
||||
if hasattr(part, "text") and part.text and not part.function_call:
|
||||
full_response += part.text
|
||||
self._emit_stream_chunk_event(
|
||||
chunk=part.text,
|
||||
from_task=from_task,
|
||||
from_agent=from_agent,
|
||||
response_id=response_id,
|
||||
)
|
||||
|
||||
# Handle function call parts
|
||||
if part.function_call:
|
||||
call_index = len(function_calls)
|
||||
call_id = f"call_{call_index}"
|
||||
@@ -1305,19 +1348,21 @@ class GeminiCompletion(BaseLLM):
|
||||
}
|
||||
return {"total_tokens": 0}
|
||||
|
||||
@staticmethod
|
||||
def _extract_text_from_response(response: GenerateContentResponse) -> str:
|
||||
def _extract_text_from_response(self, response: GenerateContentResponse) -> str:
|
||||
"""Extract text content from Gemini response without triggering warnings.
|
||||
|
||||
This method directly accesses the response parts to extract text content,
|
||||
avoiding the warning that occurs when using response.text on responses
|
||||
containing non-text parts (e.g., 'thought_signature' from thinking models).
|
||||
|
||||
Thought parts (where ``part.thought == True``) are separated from regular
|
||||
text and stored in ``self.previous_thoughts`` for downstream access.
|
||||
|
||||
Args:
|
||||
response: The Gemini API response
|
||||
|
||||
Returns:
|
||||
Concatenated text content from all text parts
|
||||
Concatenated text content from all non-thought text parts
|
||||
"""
|
||||
if not response.candidates:
|
||||
return ""
|
||||
@@ -1326,11 +1371,13 @@ class GeminiCompletion(BaseLLM):
|
||||
if not candidate.content or not candidate.content.parts:
|
||||
return ""
|
||||
|
||||
text_parts = [
|
||||
part.text
|
||||
for part in candidate.content.parts
|
||||
if hasattr(part, "text") and part.text
|
||||
]
|
||||
text_parts: list[str] = []
|
||||
for part in candidate.content.parts:
|
||||
if getattr(part, "thought", False) and part.text:
|
||||
logger.debug("Gemini thinking model thought: %s", part.text)
|
||||
self.previous_thoughts.append(part.text)
|
||||
elif hasattr(part, "text") and part.text:
|
||||
text_parts.append(part.text)
|
||||
|
||||
return "".join(text_parts)
|
||||
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
"""Memory module: unified Memory with LLM analysis and pluggable storage."""
|
||||
"""Memory module: unified Memory with LLM analysis and pluggable storage.
|
||||
|
||||
Heavy dependencies are lazily imported so that
|
||||
``import crewai`` does not initialise at runtime — critical for
|
||||
Celery pre-fork and similar deployment patterns.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from crewai.memory.encoding_flow import EncodingFlow
|
||||
from crewai.memory.memory_scope import MemoryScope, MemorySlice
|
||||
from crewai.memory.types import (
|
||||
MemoryMatch,
|
||||
@@ -10,7 +18,24 @@ from crewai.memory.types import (
|
||||
embed_text,
|
||||
embed_texts,
|
||||
)
|
||||
from crewai.memory.unified_memory import Memory
|
||||
|
||||
_LAZY_IMPORTS: dict[str, tuple[str, str]] = {
|
||||
"Memory": ("crewai.memory.unified_memory", "Memory"),
|
||||
"EncodingFlow": ("crewai.memory.encoding_flow", "EncodingFlow"),
|
||||
}
|
||||
|
||||
|
||||
def __getattr__(name: str) -> Any:
|
||||
"""Lazily import Memory / EncodingFlow to avoid pulling in lancedb at import time."""
|
||||
if name in _LAZY_IMPORTS:
|
||||
import importlib
|
||||
|
||||
module_path, attr = _LAZY_IMPORTS[name]
|
||||
mod = importlib.import_module(module_path)
|
||||
val = getattr(mod, attr)
|
||||
globals()[name] = val
|
||||
return val
|
||||
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
||||
@@ -21,7 +21,6 @@ from crewai.llms.base_llm import BaseLLM
|
||||
from crewai.memory.analyze import extract_memories_from_content
|
||||
from crewai.memory.recall_flow import RecallFlow
|
||||
from crewai.memory.storage.backend import StorageBackend
|
||||
from crewai.memory.storage.lancedb_storage import LanceDBStorage
|
||||
from crewai.memory.types import (
|
||||
MemoryConfig,
|
||||
MemoryMatch,
|
||||
@@ -148,12 +147,10 @@ class Memory:
|
||||
else None
|
||||
)
|
||||
|
||||
# Storage is initialized eagerly (local, no API key needed).
|
||||
self._storage: StorageBackend
|
||||
if storage == "lancedb":
|
||||
self._storage = LanceDBStorage()
|
||||
elif isinstance(storage, str):
|
||||
self._storage = LanceDBStorage(path=storage)
|
||||
if isinstance(storage, str):
|
||||
from crewai.memory.storage.lancedb_storage import LanceDBStorage
|
||||
|
||||
self._storage = LanceDBStorage() if storage == "lancedb" else LanceDBStorage(path=storage)
|
||||
else:
|
||||
self._storage = storage
|
||||
|
||||
|
||||
@@ -1190,3 +1190,287 @@ def test_gemini_cached_prompt_tokens_with_tools():
|
||||
# cached_prompt_tokens should be populated (may be 0 if Gemini
|
||||
# doesn't cache for this particular request, but the field should exist)
|
||||
assert usage.cached_prompt_tokens >= 0
|
||||
|
||||
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
# Tests for Gemini thinking model support (issue #4647)
|
||||
# ────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_gemini_thinking_config_dict_initialization():
|
||||
"""Test that thinking_config can be passed as a dict and is converted to ThinkingConfig."""
|
||||
from google.genai import types as genai_types
|
||||
|
||||
llm = LLM(
|
||||
model="google/gemini-2.5-flash",
|
||||
thinking_config={"include_thoughts": True},
|
||||
)
|
||||
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
assert isinstance(llm, GeminiCompletion)
|
||||
assert llm.thinking_config is not None
|
||||
assert isinstance(llm.thinking_config, genai_types.ThinkingConfig)
|
||||
assert llm.thinking_config.include_thoughts is True
|
||||
|
||||
|
||||
def test_gemini_thinking_config_object_initialization():
|
||||
"""Test that thinking_config can be passed as a ThinkingConfig object."""
|
||||
from google.genai import types as genai_types
|
||||
|
||||
tc = genai_types.ThinkingConfig(include_thoughts=True, thinking_budget=10000)
|
||||
llm = LLM(
|
||||
model="google/gemini-2.5-flash",
|
||||
thinking_config=tc,
|
||||
)
|
||||
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
assert isinstance(llm, GeminiCompletion)
|
||||
assert llm.thinking_config is tc
|
||||
assert llm.thinking_config.include_thoughts is True
|
||||
assert llm.thinking_config.thinking_budget == 10000
|
||||
|
||||
|
||||
def test_gemini_thinking_config_none_by_default():
|
||||
"""Test that thinking_config is None when not provided."""
|
||||
llm = LLM(model="google/gemini-2.0-flash-001")
|
||||
|
||||
from crewai.llms.providers.gemini.completion import GeminiCompletion
|
||||
|
||||
assert isinstance(llm, GeminiCompletion)
|
||||
assert llm.thinking_config is None
|
||||
|
||||
|
||||
def test_gemini_thinking_config_in_generation_config():
|
||||
"""Test that thinking_config is included in the GenerateContentConfig."""
|
||||
from google.genai import types as genai_types
|
||||
|
||||
llm = LLM(
|
||||
model="google/gemini-2.5-flash",
|
||||
thinking_config={"include_thoughts": True},
|
||||
)
|
||||
|
||||
config = llm._prepare_generation_config()
|
||||
assert config.thinking_config is not None
|
||||
assert isinstance(config.thinking_config, genai_types.ThinkingConfig)
|
||||
assert config.thinking_config.include_thoughts is True
|
||||
|
||||
|
||||
def test_gemini_thinking_config_not_in_generation_config_when_none():
|
||||
"""Test that thinking_config is absent from GenerateContentConfig when not set."""
|
||||
llm = LLM(model="google/gemini-2.0-flash-001")
|
||||
|
||||
config = llm._prepare_generation_config()
|
||||
assert config.thinking_config is None
|
||||
|
||||
|
||||
def test_gemini_extract_text_filters_out_thought_parts():
|
||||
"""Test that _extract_text_from_response separates thought parts from text."""
|
||||
llm = LLM(model="google/gemini-2.5-flash")
|
||||
|
||||
# Build a fake response with thought + text parts
|
||||
mock_response = MagicMock()
|
||||
thought_part = MagicMock()
|
||||
thought_part.thought = True
|
||||
thought_part.text = "Let me think about this..."
|
||||
thought_part.function_call = None
|
||||
|
||||
text_part = MagicMock()
|
||||
text_part.thought = False
|
||||
text_part.text = "The answer is 42."
|
||||
text_part.function_call = None
|
||||
|
||||
candidate = MagicMock()
|
||||
candidate.content.parts = [thought_part, text_part]
|
||||
mock_response.candidates = [candidate]
|
||||
|
||||
llm.previous_thoughts = []
|
||||
result = llm._extract_text_from_response(mock_response)
|
||||
|
||||
assert result == "The answer is 42."
|
||||
assert len(llm.previous_thoughts) == 1
|
||||
assert llm.previous_thoughts[0] == "Let me think about this..."
|
||||
|
||||
|
||||
def test_gemini_extract_text_no_thought_parts():
|
||||
"""Test _extract_text_from_response with no thought parts (normal response)."""
|
||||
llm = LLM(model="google/gemini-2.0-flash-001")
|
||||
|
||||
mock_response = MagicMock()
|
||||
text_part = MagicMock()
|
||||
text_part.thought = False
|
||||
text_part.text = "Hello world"
|
||||
text_part.function_call = None
|
||||
|
||||
candidate = MagicMock()
|
||||
candidate.content.parts = [text_part]
|
||||
mock_response.candidates = [candidate]
|
||||
|
||||
llm.previous_thoughts = []
|
||||
result = llm._extract_text_from_response(mock_response)
|
||||
|
||||
assert result == "Hello world"
|
||||
assert len(llm.previous_thoughts) == 0
|
||||
|
||||
|
||||
def test_gemini_stream_chunk_handles_thought_parts():
|
||||
"""Test that _process_stream_chunk captures thought parts and emits text parts."""
|
||||
import json as json_mod
|
||||
|
||||
llm = LLM(model="google/gemini-2.5-flash")
|
||||
llm.previous_thoughts = []
|
||||
|
||||
# Build a mock chunk with a thought part and a text part
|
||||
thought_part = MagicMock()
|
||||
thought_part.thought = True
|
||||
thought_part.text = "Reasoning step 1"
|
||||
thought_part.function_call = None
|
||||
|
||||
text_part = MagicMock()
|
||||
text_part.thought = False
|
||||
text_part.text = "Final answer"
|
||||
text_part.function_call = None
|
||||
|
||||
chunk = MagicMock()
|
||||
chunk.response_id = "resp_123"
|
||||
chunk.usage_metadata = None
|
||||
candidate = MagicMock()
|
||||
candidate.content.parts = [thought_part, text_part]
|
||||
chunk.candidates = [candidate]
|
||||
|
||||
with patch.object(llm, "_emit_stream_chunk_event"):
|
||||
full_response, function_calls, usage_data = llm._process_stream_chunk(
|
||||
chunk=chunk,
|
||||
full_response="",
|
||||
function_calls={},
|
||||
usage_data={"total_tokens": 0},
|
||||
)
|
||||
|
||||
assert full_response == "Final answer"
|
||||
assert len(llm.previous_thoughts) == 1
|
||||
assert llm.previous_thoughts[0] == "Reasoning step 1"
|
||||
|
||||
|
||||
def test_gemini_stream_chunk_handles_function_call_without_warning():
|
||||
"""Test that _process_stream_chunk handles function calls without triggering chunk.text."""
|
||||
llm = LLM(model="google/gemini-2.5-flash")
|
||||
llm.previous_thoughts = []
|
||||
|
||||
# Build a mock chunk with a function call part
|
||||
func_part = MagicMock()
|
||||
func_part.thought = False
|
||||
func_part.text = None
|
||||
func_part.function_call.name = "get_weather"
|
||||
func_part.function_call.args = {"location": "Tokyo"}
|
||||
|
||||
chunk = MagicMock()
|
||||
chunk.response_id = "resp_456"
|
||||
chunk.usage_metadata = None
|
||||
candidate = MagicMock()
|
||||
candidate.content.parts = [func_part]
|
||||
chunk.candidates = [candidate]
|
||||
|
||||
with patch.object(llm, "_emit_stream_chunk_event"):
|
||||
full_response, function_calls, usage_data = llm._process_stream_chunk(
|
||||
chunk=chunk,
|
||||
full_response="",
|
||||
function_calls={},
|
||||
usage_data={"total_tokens": 0},
|
||||
)
|
||||
|
||||
assert full_response == ""
|
||||
assert len(function_calls) == 1
|
||||
assert function_calls[0]["name"] == "get_weather"
|
||||
assert function_calls[0]["args"] == {"location": "Tokyo"}
|
||||
|
||||
|
||||
def test_gemini_stream_chunk_mixed_thought_text_and_function_call():
|
||||
"""Test _process_stream_chunk with thought, text, and function call parts."""
|
||||
llm = LLM(model="google/gemini-2.5-flash")
|
||||
llm.previous_thoughts = []
|
||||
|
||||
thought_part = MagicMock()
|
||||
thought_part.thought = True
|
||||
thought_part.text = "I need to use a tool"
|
||||
thought_part.function_call = None
|
||||
|
||||
func_part = MagicMock()
|
||||
func_part.thought = False
|
||||
func_part.text = None
|
||||
func_part.function_call.name = "search"
|
||||
func_part.function_call.args = {"query": "hello"}
|
||||
|
||||
chunk = MagicMock()
|
||||
chunk.response_id = "resp_789"
|
||||
chunk.usage_metadata = None
|
||||
candidate = MagicMock()
|
||||
candidate.content.parts = [thought_part, func_part]
|
||||
chunk.candidates = [candidate]
|
||||
|
||||
with patch.object(llm, "_emit_stream_chunk_event"):
|
||||
full_response, function_calls, usage_data = llm._process_stream_chunk(
|
||||
chunk=chunk,
|
||||
full_response="",
|
||||
function_calls={},
|
||||
usage_data={"total_tokens": 0},
|
||||
)
|
||||
|
||||
assert full_response == ""
|
||||
assert len(function_calls) == 1
|
||||
assert function_calls[0]["name"] == "search"
|
||||
assert len(llm.previous_thoughts) == 1
|
||||
assert llm.previous_thoughts[0] == "I need to use a tool"
|
||||
|
||||
|
||||
def test_gemini_previous_thoughts_accumulate_across_chunks():
|
||||
"""Test that previous_thoughts accumulate across multiple streaming chunks."""
|
||||
llm = LLM(model="google/gemini-2.5-flash")
|
||||
llm.previous_thoughts = []
|
||||
|
||||
# First chunk with thought
|
||||
thought1 = MagicMock()
|
||||
thought1.thought = True
|
||||
thought1.text = "Step 1"
|
||||
thought1.function_call = None
|
||||
chunk1 = MagicMock()
|
||||
chunk1.response_id = "resp_1"
|
||||
chunk1.usage_metadata = None
|
||||
candidate1 = MagicMock()
|
||||
candidate1.content.parts = [thought1]
|
||||
chunk1.candidates = [candidate1]
|
||||
|
||||
# Second chunk with thought + text
|
||||
thought2 = MagicMock()
|
||||
thought2.thought = True
|
||||
thought2.text = "Step 2"
|
||||
thought2.function_call = None
|
||||
text_part = MagicMock()
|
||||
text_part.thought = False
|
||||
text_part.text = "Result"
|
||||
text_part.function_call = None
|
||||
chunk2 = MagicMock()
|
||||
chunk2.response_id = "resp_1"
|
||||
chunk2.usage_metadata = None
|
||||
candidate2 = MagicMock()
|
||||
candidate2.content.parts = [thought2, text_part]
|
||||
chunk2.candidates = [candidate2]
|
||||
|
||||
with patch.object(llm, "_emit_stream_chunk_event"):
|
||||
full_response = ""
|
||||
function_calls: dict = {}
|
||||
usage_data = {"total_tokens": 0}
|
||||
|
||||
full_response, function_calls, usage_data = llm._process_stream_chunk(
|
||||
chunk=chunk1, full_response=full_response,
|
||||
function_calls=function_calls, usage_data=usage_data,
|
||||
)
|
||||
full_response, function_calls, usage_data = llm._process_stream_chunk(
|
||||
chunk=chunk2, full_response=full_response,
|
||||
function_calls=function_calls, usage_data=usage_data,
|
||||
)
|
||||
|
||||
assert full_response == "Result"
|
||||
assert len(llm.previous_thoughts) == 2
|
||||
assert llm.previous_thoughts[0] == "Step 1"
|
||||
assert llm.previous_thoughts[1] == "Step 2"
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
"""CrewAI development tools."""
|
||||
|
||||
__version__ = "1.10.0"
|
||||
__version__ = "1.10.1a1"
|
||||
|
||||
@@ -200,7 +200,7 @@ def add_docs_version(docs_json_path: Path, version: str) -> bool:
|
||||
|
||||
Args:
|
||||
docs_json_path: Path to docs/docs.json.
|
||||
version: Version string (e.g., "1.10.0").
|
||||
version: Version string (e.g., "1.10.1b1").
|
||||
|
||||
Returns:
|
||||
True if docs.json was updated, False otherwise.
|
||||
|
||||
Reference in New Issue
Block a user