mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
Merge branch 'main' into fix/anthropic-nested-schema-type-conversion
This commit is contained in:
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "1.24.1"
|
||||
".": "1.25.1"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
||||
"last-release-sha": "9f7d5b3f1476234e552b783415527cc4bac55b39",
|
||||
"packages": {
|
||||
".": {
|
||||
"release-type": "python",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Cherry-picks a commit from main to the release/candidate branch.
|
||||
# Use this to include bug fixes in an in-progress release.
|
||||
# Step 3 (optional): Cherry-picks a commit from main to the release/candidate branch.
|
||||
# Use between step 1 and step 4 to include bug fixes in an in-progress release.
|
||||
name: "Release: Cherry-pick"
|
||||
|
||||
on:
|
||||
@@ -42,5 +42,5 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh workflow run release-please.yml --repo ${{ github.repository }}
|
||||
gh workflow run release-please.yml --repo ${{ github.repository }} --ref release/candidate
|
||||
echo "Triggered Release Please workflow"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Starts the release process by creating a release/candidate branch.
|
||||
# Triggers release-please to generate a changelog PR.
|
||||
# Step 1: Starts the release process by creating a release/candidate branch.
|
||||
# Generates a changelog PR for review (step 2).
|
||||
name: "Release: Cut"
|
||||
|
||||
on:
|
||||
@@ -42,5 +42,5 @@ jobs:
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
gh workflow run release-please.yml --repo ${{ github.repository }}
|
||||
gh workflow run release-please.yml --repo ${{ github.repository }} --ref release/candidate
|
||||
echo "Triggered Release Please workflow"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Triggers when release-please PR is merged to release/candidate.
|
||||
# Creates the final release/v{version} branch and deletes the candidate branch.
|
||||
# Step 4: Triggers when the changelog PR is merged to release/candidate.
|
||||
# Records last-release-sha and renames release/candidate to release/v{version}.
|
||||
name: "Release: Finalize"
|
||||
|
||||
on:
|
||||
@@ -33,6 +33,8 @@ jobs:
|
||||
if: steps.check.outputs.is_release_pr == 'true'
|
||||
with:
|
||||
ref: release/candidate
|
||||
token: ${{ secrets.RELEASE_PAT }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Extract version from manifest
|
||||
if: steps.check.outputs.is_release_pr == 'true'
|
||||
@@ -42,18 +44,33 @@ jobs:
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "Extracted version: $VERSION"
|
||||
|
||||
- name: Create release branch
|
||||
- name: Configure git identity from RELEASE_PAT
|
||||
if: steps.check.outputs.is_release_pr == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
||||
run: |
|
||||
git checkout -b "release/v${{ steps.version.outputs.version }}"
|
||||
git push origin "release/v${{ steps.version.outputs.version }}"
|
||||
echo "Created branch: release/v${{ steps.version.outputs.version }}"
|
||||
USER_JSON=$(gh api user)
|
||||
git config user.name "$(echo "$USER_JSON" | jq -r '.login')"
|
||||
git config user.email "$(echo "$USER_JSON" | jq -r '.id')+$(echo "$USER_JSON" | jq -r '.login')@users.noreply.github.com"
|
||||
|
||||
- name: Delete release/candidate branch
|
||||
- name: Record last-release-sha for release-please
|
||||
if: steps.check.outputs.is_release_pr == 'true'
|
||||
run: |
|
||||
git push origin --delete release/candidate
|
||||
echo "Deleted branch: release/candidate"
|
||||
git fetch origin main
|
||||
CUT_SHA=$(git merge-base origin/main HEAD)
|
||||
echo "Release was cut from main at: $CUT_SHA"
|
||||
jq --arg sha "$CUT_SHA" '. + {"last-release-sha": $sha}' \
|
||||
.github/release-please-config.json > tmp.json && mv tmp.json .github/release-please-config.json
|
||||
git add .github/release-please-config.json
|
||||
git commit -m "chore: update last-release-sha for next release"
|
||||
git push origin release/candidate
|
||||
|
||||
- name: Rename release/candidate to release/v{version}
|
||||
if: steps.check.outputs.is_release_pr == 'true'
|
||||
run: |
|
||||
VERSION="v${{ steps.version.outputs.version }}"
|
||||
git push origin "release/candidate:refs/heads/release/$VERSION" ":release/candidate"
|
||||
echo "Renamed release/candidate to release/$VERSION"
|
||||
|
||||
- name: Update PR label to tagged
|
||||
if: steps.check.outputs.is_release_pr == 'true'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Runs release-please to create/update a PR with version bump and changelog.
|
||||
# Triggered by pushes to release/candidate or manually.
|
||||
# Triggered automatically by step 1 (cut) or step 3 (cherry-pick).
|
||||
name: "Release: Please"
|
||||
|
||||
on:
|
||||
@@ -18,11 +18,25 @@ jobs:
|
||||
if: "!startsWith(github.event.head_commit.message, 'chore(release')"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check if release/candidate still exists
|
||||
id: check
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
if gh api repos/${{ github.repository }}/branches/release/candidate --silent 2>/dev/null; then
|
||||
echo "exists=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "release/candidate branch no longer exists, skipping"
|
||||
echo "exists=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
with:
|
||||
ref: release/candidate
|
||||
|
||||
- uses: googleapis/release-please-action@v4
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
with:
|
||||
token: ${{ secrets.RELEASE_PAT }}
|
||||
config-file: .github/release-please-config.json
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Builds and publishes the package to PyPI from a release branch.
|
||||
# Creates a merge-back PR to sync release changes to main.
|
||||
# Step 6: Builds and publishes the package to PyPI from a release/v{version} branch.
|
||||
# Creates a merge-back PR (step 7) to sync release changes to main.
|
||||
name: "Release: Publish to PyPi"
|
||||
|
||||
on:
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## [1.25.1](https://github.com/google/adk-python/compare/v1.25.0...v1.25.1) (2026-02-18)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix pickling lock errors in McpSessionManager ([4e2d615](https://github.com/google/adk-python/commit/4e2d6159ae3552954aaae295fef3e09118502898))
|
||||
|
||||
## [1.25.0](https://github.com/google/adk-python/compare/v1.24.1...v1.25.0) (2026-02-11)
|
||||
|
||||
### Features
|
||||
|
||||
@@ -38,7 +38,9 @@ BIGQUERY_AGENT_NAME = "adk_sample_bigquery_agent"
|
||||
# tool read-only) or PROTECTED (only allows writes in the anonymous dataset of a
|
||||
# BigQuery session) write mode.
|
||||
tool_config = BigQueryToolConfig(
|
||||
write_mode=WriteMode.ALLOWED, application_name=BIGQUERY_AGENT_NAME
|
||||
write_mode=WriteMode.ALLOWED,
|
||||
application_name=BIGQUERY_AGENT_NAME,
|
||||
max_query_result_rows=50,
|
||||
)
|
||||
|
||||
if CREDENTIALS_TYPE == AuthCredentialTypes.OAUTH2:
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from . import agent
|
||||
@@ -0,0 +1,27 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""LiteLLM sample agent for SSE text streaming."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from google.adk import Agent
|
||||
from google.adk.models.lite_llm import LiteLlm
|
||||
|
||||
root_agent = Agent(
|
||||
name='litellm_streaming_agent',
|
||||
model=LiteLlm(model='gemini/gemini-2.5-flash'),
|
||||
description='A LiteLLM agent used for streaming text responses.',
|
||||
instruction='You are a verbose assistant',
|
||||
)
|
||||
@@ -0,0 +1,107 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Runs the LiteLLM streaming sample with SSE enabled."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from google.adk.agents.run_config import RunConfig
|
||||
from google.adk.agents.run_config import StreamingMode
|
||||
from google.adk.cli.utils import logs
|
||||
from google.adk.runners import InMemoryRunner
|
||||
from google.genai import types
|
||||
|
||||
from . import agent
|
||||
|
||||
load_dotenv(override=True)
|
||||
logs.log_to_tmp_folder()
|
||||
|
||||
|
||||
async def _run_prompt(
|
||||
*,
|
||||
runner: InMemoryRunner,
|
||||
user_id: str,
|
||||
session_id: str,
|
||||
prompt: str,
|
||||
) -> None:
|
||||
"""Runs one prompt and prints partial chunks in real time."""
|
||||
content = types.Content(
|
||||
role='user',
|
||||
parts=[types.Part.from_text(text=prompt)],
|
||||
)
|
||||
|
||||
print(f'User: {prompt}')
|
||||
print('Agent: ', end='', flush=True)
|
||||
saw_text = False
|
||||
saw_partial_text = False
|
||||
|
||||
# For `adk web`, enable the `Streaming` toggle in the UI to get
|
||||
# partial SSE responses similar to this script.
|
||||
async for event in runner.run_async(
|
||||
user_id=user_id,
|
||||
session_id=session_id,
|
||||
new_message=content,
|
||||
run_config=RunConfig(streaming_mode=StreamingMode.SSE),
|
||||
):
|
||||
if not event.content:
|
||||
continue
|
||||
text = ''.join(part.text for part in event.content.parts if part.text)
|
||||
if not text:
|
||||
continue
|
||||
|
||||
if event.partial:
|
||||
print(text, end='', flush=True)
|
||||
saw_text = True
|
||||
saw_partial_text = True
|
||||
continue
|
||||
|
||||
# With SSE mode, ADK emits a final aggregated event after partial chunks.
|
||||
if not saw_partial_text:
|
||||
print(text, end='', flush=True)
|
||||
saw_text = True
|
||||
|
||||
if saw_text:
|
||||
print()
|
||||
else:
|
||||
print('(no text response)')
|
||||
print('------------------------------------')
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
app_name = 'litellm_streaming_demo'
|
||||
user_id = 'user_1'
|
||||
runner = InMemoryRunner(agent=agent.root_agent, app_name=app_name)
|
||||
session = await runner.session_service.create_session(
|
||||
app_name=app_name,
|
||||
user_id=user_id,
|
||||
)
|
||||
prompts = [
|
||||
'Write an essay about the roman empire',
|
||||
'Now summarize the essay into one sentence.',
|
||||
]
|
||||
|
||||
for prompt in prompts:
|
||||
await _run_prompt(
|
||||
runner=runner,
|
||||
user_id=user_id,
|
||||
session_id=session.id,
|
||||
prompt=prompt,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
@@ -52,9 +52,7 @@ root_agent = Agent(
|
||||
model="gemini-2.5-flash",
|
||||
name="skill_user_agent",
|
||||
description="An agent that can use specialized skills.",
|
||||
instruction=(
|
||||
"You are a helpful assistant that can leverage skills to perform tasks."
|
||||
),
|
||||
instruction=skill_toolset.DEFAULT_SKILL_SYSTEM_INSTRUCTION,
|
||||
tools=[
|
||||
my_skill_toolset,
|
||||
],
|
||||
|
||||
+2
-1
@@ -56,7 +56,7 @@ dependencies = [
|
||||
"opentelemetry-resourcedetector-gcp>=1.9.0a0, <2.0.0",
|
||||
"opentelemetry-sdk>=1.36.0, <1.40.0",
|
||||
"pyarrow>=14.0.0",
|
||||
"pydantic>=2.0, <3.0.0", # For data validation/models
|
||||
"pydantic>=2.7.0, <3.0.0", # For data validation/models
|
||||
"python-dateutil>=2.9.0.post0, <3.0.0", # For Vertext AI Session Service
|
||||
"python-dotenv>=1.0.0, <2.0.0", # To manage environment variables
|
||||
"requests>=2.32.4, <3.0.0",
|
||||
@@ -108,6 +108,7 @@ community = [
|
||||
|
||||
eval = [
|
||||
# go/keep-sorted start
|
||||
"Jinja2>=3.1.4,<4.0.0", # For eval template rendering
|
||||
"google-cloud-aiplatform[evaluation]>=1.100.0",
|
||||
"pandas>=2.2.3",
|
||||
"rouge-score>=0.1.2",
|
||||
|
||||
@@ -15,8 +15,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from . import version
|
||||
from .agents.context import Context
|
||||
from .agents.llm_agent import Agent
|
||||
from .runners import Runner
|
||||
|
||||
__version__ = version.__version__
|
||||
__all__ = ["Agent", "Runner"]
|
||||
__all__ = ["Agent", "Context", "Runner"]
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
from .base_agent import BaseAgent
|
||||
from .context import Context
|
||||
from .invocation_context import InvocationContext
|
||||
from .live_request_queue import LiveRequest
|
||||
from .live_request_queue import LiveRequestQueue
|
||||
@@ -27,6 +28,7 @@ from .sequential_agent import SequentialAgent
|
||||
__all__ = [
|
||||
'Agent',
|
||||
'BaseAgent',
|
||||
'Context',
|
||||
'LlmAgent',
|
||||
'LoopAgent',
|
||||
'McpInstructionProvider',
|
||||
|
||||
@@ -14,240 +14,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from collections.abc import Sequence
|
||||
from typing import Any
|
||||
from typing import Optional
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from typing_extensions import override
|
||||
|
||||
from .context import Context
|
||||
# Keep ReadonlyContext for backward compatibility
|
||||
from .readonly_context import ReadonlyContext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from google.genai import types
|
||||
|
||||
from ..artifacts.base_artifact_service import ArtifactVersion
|
||||
from ..auth.auth_credential import AuthCredential
|
||||
from ..auth.auth_tool import AuthConfig
|
||||
from ..events.event import Event
|
||||
from ..events.event_actions import EventActions
|
||||
from ..sessions.state import State
|
||||
from .invocation_context import InvocationContext
|
||||
|
||||
|
||||
class CallbackContext(ReadonlyContext):
|
||||
"""The context of various callbacks within an agent run."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
invocation_context: InvocationContext,
|
||||
*,
|
||||
event_actions: Optional[EventActions] = None,
|
||||
) -> None:
|
||||
super().__init__(invocation_context)
|
||||
|
||||
from ..events.event_actions import EventActions
|
||||
from ..sessions.state import State
|
||||
|
||||
self._event_actions = event_actions or EventActions()
|
||||
self._state = State(
|
||||
value=invocation_context.session.state,
|
||||
delta=self._event_actions.state_delta,
|
||||
)
|
||||
|
||||
@property
|
||||
@override
|
||||
def state(self) -> State:
|
||||
"""The delta-aware state of the current session.
|
||||
|
||||
For any state change, you can mutate this object directly,
|
||||
e.g. `ctx.state['foo'] = 'bar'`
|
||||
"""
|
||||
return self._state
|
||||
|
||||
async def load_artifact(
|
||||
self, filename: str, version: Optional[int] = None
|
||||
) -> Optional[types.Part]:
|
||||
"""Loads an artifact attached to the current session.
|
||||
|
||||
Args:
|
||||
filename: The filename of the artifact.
|
||||
version: The version of the artifact. If None, the latest version will be
|
||||
returned.
|
||||
|
||||
Returns:
|
||||
The artifact.
|
||||
"""
|
||||
if self._invocation_context.artifact_service is None:
|
||||
raise ValueError("Artifact service is not initialized.")
|
||||
return await self._invocation_context.artifact_service.load_artifact(
|
||||
app_name=self._invocation_context.app_name,
|
||||
user_id=self._invocation_context.user_id,
|
||||
session_id=self._invocation_context.session.id,
|
||||
filename=filename,
|
||||
version=version,
|
||||
)
|
||||
|
||||
async def save_artifact(
|
||||
self,
|
||||
filename: str,
|
||||
artifact: types.Part,
|
||||
custom_metadata: Optional[dict[str, Any]] = None,
|
||||
) -> int:
|
||||
"""Saves an artifact and records it as delta for the current session.
|
||||
|
||||
Args:
|
||||
filename: The filename of the artifact.
|
||||
artifact: The artifact to save.
|
||||
custom_metadata: Custom metadata to associate with the artifact.
|
||||
|
||||
Returns:
|
||||
The version of the artifact.
|
||||
"""
|
||||
if self._invocation_context.artifact_service is None:
|
||||
raise ValueError("Artifact service is not initialized.")
|
||||
version = await self._invocation_context.artifact_service.save_artifact(
|
||||
app_name=self._invocation_context.app_name,
|
||||
user_id=self._invocation_context.user_id,
|
||||
session_id=self._invocation_context.session.id,
|
||||
filename=filename,
|
||||
artifact=artifact,
|
||||
custom_metadata=custom_metadata,
|
||||
)
|
||||
self._event_actions.artifact_delta[filename] = version
|
||||
return version
|
||||
|
||||
async def get_artifact_version(
|
||||
self, filename: str, version: Optional[int] = None
|
||||
) -> Optional[ArtifactVersion]:
|
||||
"""Gets artifact version info.
|
||||
|
||||
Args:
|
||||
filename: The filename of the artifact.
|
||||
version: The version of the artifact. If None, the latest version will be
|
||||
returned.
|
||||
|
||||
Returns:
|
||||
The artifact version info.
|
||||
"""
|
||||
if self._invocation_context.artifact_service is None:
|
||||
raise ValueError("Artifact service is not initialized.")
|
||||
return await self._invocation_context.artifact_service.get_artifact_version(
|
||||
app_name=self._invocation_context.app_name,
|
||||
user_id=self._invocation_context.user_id,
|
||||
session_id=self._invocation_context.session.id,
|
||||
filename=filename,
|
||||
version=version,
|
||||
)
|
||||
|
||||
async def list_artifacts(self) -> list[str]:
|
||||
"""Lists the filenames of the artifacts attached to the current session."""
|
||||
if self._invocation_context.artifact_service is None:
|
||||
raise ValueError("Artifact service is not initialized.")
|
||||
return await self._invocation_context.artifact_service.list_artifact_keys(
|
||||
app_name=self._invocation_context.app_name,
|
||||
user_id=self._invocation_context.user_id,
|
||||
session_id=self._invocation_context.session.id,
|
||||
)
|
||||
|
||||
async def save_credential(self, auth_config: AuthConfig) -> None:
|
||||
"""Saves a credential to the credential service.
|
||||
|
||||
Args:
|
||||
auth_config: The authentication configuration containing the credential.
|
||||
"""
|
||||
if self._invocation_context.credential_service is None:
|
||||
raise ValueError("Credential service is not initialized.")
|
||||
await self._invocation_context.credential_service.save_credential(
|
||||
auth_config, self
|
||||
)
|
||||
|
||||
async def load_credential(
|
||||
self, auth_config: AuthConfig
|
||||
) -> Optional[AuthCredential]:
|
||||
"""Loads a credential from the credential service.
|
||||
|
||||
Args:
|
||||
auth_config: The authentication configuration for the credential.
|
||||
|
||||
Returns:
|
||||
The loaded credential, or None if not found.
|
||||
"""
|
||||
if self._invocation_context.credential_service is None:
|
||||
raise ValueError("Credential service is not initialized.")
|
||||
return await self._invocation_context.credential_service.load_credential(
|
||||
auth_config, self
|
||||
)
|
||||
|
||||
def get_auth_response(
|
||||
self, auth_config: AuthConfig
|
||||
) -> Optional[AuthCredential]:
|
||||
"""Gets the auth response credential from session state.
|
||||
|
||||
This method retrieves an authentication credential that was previously
|
||||
stored in session state after a user completed an OAuth flow or other
|
||||
authentication process.
|
||||
|
||||
Args:
|
||||
auth_config: The authentication configuration for the credential.
|
||||
|
||||
Returns:
|
||||
The auth credential from the auth response, or None if not found.
|
||||
"""
|
||||
from ..auth.auth_handler import AuthHandler
|
||||
|
||||
return AuthHandler(auth_config).get_auth_response(self.state)
|
||||
|
||||
async def add_session_to_memory(self) -> None:
|
||||
"""Triggers memory generation for the current session.
|
||||
|
||||
This method saves the current session's events to the memory service,
|
||||
enabling the agent to recall information from past interactions.
|
||||
|
||||
Raises:
|
||||
ValueError: If memory service is not available.
|
||||
|
||||
Example:
|
||||
```python
|
||||
async def my_after_agent_callback(callback_context: CallbackContext):
|
||||
# Save conversation to memory at the end of each interaction
|
||||
await callback_context.add_session_to_memory()
|
||||
```
|
||||
"""
|
||||
if self._invocation_context.memory_service is None:
|
||||
raise ValueError(
|
||||
"Cannot add session to memory: memory service is not available."
|
||||
)
|
||||
await self._invocation_context.memory_service.add_session_to_memory(
|
||||
self._invocation_context.session
|
||||
)
|
||||
|
||||
async def add_events_to_memory(
|
||||
self,
|
||||
*,
|
||||
events: Sequence[Event],
|
||||
custom_metadata: Mapping[str, object] | None = None,
|
||||
) -> None:
|
||||
"""Adds an explicit list of events to the memory service.
|
||||
|
||||
Uses this callback's current session identifiers as memory scope.
|
||||
|
||||
Args:
|
||||
events: Explicit events to add to memory.
|
||||
custom_metadata: Optional standard metadata for memory generation.
|
||||
|
||||
Raises:
|
||||
ValueError: If memory service is not available.
|
||||
"""
|
||||
if self._invocation_context.memory_service is None:
|
||||
raise ValueError(
|
||||
"Cannot add events to memory: memory service is not available."
|
||||
)
|
||||
await self._invocation_context.memory_service.add_events_to_memory(
|
||||
app_name=self._invocation_context.session.app_name,
|
||||
user_id=self._invocation_context.session.user_id,
|
||||
session_id=self._invocation_context.session.id,
|
||||
events=events,
|
||||
custom_metadata=custom_metadata,
|
||||
)
|
||||
# CallbackContext is unified into Context
|
||||
CallbackContext = Context
|
||||
|
||||
@@ -32,6 +32,7 @@ if TYPE_CHECKING:
|
||||
from ..events.event import Event
|
||||
from ..events.event_actions import EventActions
|
||||
from ..memory.base_memory_service import SearchMemoryResponse
|
||||
from ..memory.memory_entry import MemoryEntry
|
||||
from ..sessions.state import State
|
||||
from ..tools.tool_confirmation import ToolConfirmation
|
||||
from .invocation_context import InvocationContext
|
||||
@@ -76,11 +77,21 @@ class Context(ReadonlyContext):
|
||||
"""The function call id of the current tool call."""
|
||||
return self._function_call_id
|
||||
|
||||
@function_call_id.setter
|
||||
def function_call_id(self, value: str | None) -> None:
|
||||
"""Sets the function call id of the current tool call."""
|
||||
self._function_call_id = value
|
||||
|
||||
@property
|
||||
def tool_confirmation(self) -> ToolConfirmation | None:
|
||||
"""The tool confirmation of the current tool call."""
|
||||
return self._tool_confirmation
|
||||
|
||||
@tool_confirmation.setter
|
||||
def tool_confirmation(self, value: ToolConfirmation | None) -> None:
|
||||
"""Sets the tool confirmation of the current tool call."""
|
||||
self._tool_confirmation = value
|
||||
|
||||
@property
|
||||
@override
|
||||
def state(self) -> State:
|
||||
@@ -335,7 +346,8 @@ class Context(ReadonlyContext):
|
||||
|
||||
Args:
|
||||
events: Explicit events to add to memory.
|
||||
custom_metadata: Optional standard metadata for memory generation.
|
||||
custom_metadata: Optional metadata forwarded to the configured memory
|
||||
service. Supported keys are implementation-specific.
|
||||
|
||||
Raises:
|
||||
ValueError: If memory service is not available.
|
||||
@@ -352,6 +364,33 @@ class Context(ReadonlyContext):
|
||||
custom_metadata=custom_metadata,
|
||||
)
|
||||
|
||||
async def add_memory(
|
||||
self,
|
||||
*,
|
||||
memories: Sequence[MemoryEntry],
|
||||
custom_metadata: Mapping[str, object] | None = None,
|
||||
) -> None:
|
||||
"""Adds explicit memory items directly to the memory service.
|
||||
|
||||
Uses this callback's current session identifiers as memory scope.
|
||||
|
||||
Args:
|
||||
memories: Explicit memory items to add.
|
||||
custom_metadata: Optional metadata forwarded to the configured memory
|
||||
service. Supported keys are implementation-specific.
|
||||
|
||||
Raises:
|
||||
ValueError: If memory service is not available.
|
||||
"""
|
||||
if self._invocation_context.memory_service is None:
|
||||
raise ValueError("Cannot add memory: memory service is not available.")
|
||||
await self._invocation_context.memory_service.add_memory(
|
||||
app_name=self._invocation_context.session.app_name,
|
||||
user_id=self._invocation_context.session.user_id,
|
||||
memories=memories,
|
||||
custom_metadata=custom_metadata,
|
||||
)
|
||||
|
||||
async def search_memory(self, query: str) -> SearchMemoryResponse:
|
||||
"""Searches the memory of the current user.
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ class OAuth2Auth(BaseModelWithConfig):
|
||||
auth_code: Optional[str] = None
|
||||
access_token: Optional[str] = None
|
||||
refresh_token: Optional[str] = None
|
||||
id_token: Optional[str] = None
|
||||
expires_at: Optional[int] = None
|
||||
expires_in: Optional[int] = None
|
||||
audience: Optional[str] = None
|
||||
|
||||
@@ -107,8 +107,10 @@ def update_credential_with_tokens(
|
||||
auth_credential: The authentication credential to update.
|
||||
tokens: The OAuth2Token object containing new token information.
|
||||
"""
|
||||
if auth_credential.oauth2 and tokens:
|
||||
auth_credential.oauth2.access_token = tokens.get("access_token")
|
||||
auth_credential.oauth2.refresh_token = tokens.get("refresh_token")
|
||||
auth_credential.oauth2.id_token = tokens.get("id_token")
|
||||
auth_credential.oauth2.expires_at = (
|
||||
int(tokens.get("expires_at")) if tokens.get("expires_at") else None
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user