Skip to content

Commit bf06097

Browse files
authored
Merge branch 'strands-agents:main' into main
2 parents 7b42928 + 221d004 commit bf06097

File tree

14 files changed

+105
-79
lines changed

14 files changed

+105
-79
lines changed

.github/workflows/test-lint-pr.yml

Lines changed: 15 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,88 +6,49 @@ on:
66
types: [opened, synchronize, reopened, ready_for_review, review_requested, review_request_removed]
77
push:
88
branches: [ main ] # Also run on direct pushes to main
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
11+
cancel-in-progress: true
912

1013
jobs:
11-
check-approval:
12-
name: Check if PR has contributor approval
13-
runs-on: ubuntu-latest
14-
permissions:
15-
pull-requests: read
16-
# Skip this check for direct pushes to main
17-
if: github.event_name == 'pull_request'
18-
outputs:
19-
approved: ${{ steps.check-approval.outputs.approved }}
20-
steps:
21-
- name: Check if PR has been approved by a contributor
22-
id: check-approval
23-
uses: actions/github-script@v7
24-
with:
25-
script: |
26-
const APPROVED_ASSOCIATION = ['COLLABORATOR', 'CONTRIBUTOR', 'MEMBER', 'OWNER']
27-
const PR_AUTHOR_ASSOCIATION = context.payload.pull_request.author_association;
28-
const { data: reviews } = await github.rest.pulls.listReviews({
29-
owner: context.repo.owner,
30-
repo: context.repo.repo,
31-
pull_number: context.issue.number,
32-
});
33-
34-
const isApprovedContributor = APPROVED_ASSOCIATION.includes(PR_AUTHOR_ASSOCIATION);
35-
36-
// Check if any contributor has approved
37-
const isApproved = reviews.some(review =>
38-
review.state === 'APPROVED' && APPROVED_ASSOCIATION.includes(review.author_association)
39-
) || isApprovedContributor;
40-
41-
core.setOutput('approved', isApproved);
42-
43-
if (!isApproved) {
44-
core.notice('This PR does not have approval from a Contributor. Workflow will not run test jobs.');
45-
return false;
46-
}
47-
48-
return true;
49-
5014
unit-test:
5115
name: Unit Tests - Python ${{ matrix.python-version }} - ${{ matrix.os-name }}
52-
needs: check-approval
5316
permissions:
5417
contents: read
55-
# Only run if PR is approved or this is a direct push to main
56-
if: github.event_name == 'push' || needs.check-approval.outputs.approved == 'true'
5718
strategy:
5819
matrix:
5920
include:
6021
# Linux
6122
- os: ubuntu-latest
62-
os-name: linux
23+
os-name: 'linux'
6324
python-version: "3.10"
6425
- os: ubuntu-latest
65-
os-name: linux
26+
os-name: 'linux'
6627
python-version: "3.11"
6728
- os: ubuntu-latest
68-
os-name: linux
29+
os-name: 'linux'
6930
python-version: "3.12"
7031
- os: ubuntu-latest
71-
os-name: linux
32+
os-name: 'linux'
7233
python-version: "3.13"
7334
# Windows
7435
- os: windows-latest
75-
os-name: windows
36+
os-name: 'windows'
7637
python-version: "3.10"
7738
- os: windows-latest
78-
os-name: windows
39+
os-name: 'windows'
7940
python-version: "3.11"
8041
- os: windows-latest
81-
os-name: windows
42+
os-name: 'windows'
8243
python-version: "3.12"
8344
- os: windows-latest
84-
os-name: windows
45+
os-name: 'windows'
8546
python-version: "3.13"
86-
# MacOS - latest only; not enough runners for MacOS
47+
# MacOS - latest only; not enough runners for macOS
8748
- os: macos-latest
88-
os-name: macos
89-
python-version: "3.13"
90-
fail-fast: false
49+
os-name: 'macOS'
50+
python-version: "3.13"
51+
fail-fast: true
9152
runs-on: ${{ matrix.os }}
9253
env:
9354
LOG_LEVEL: DEBUG
@@ -108,14 +69,11 @@ jobs:
10869
id: tests
10970
run: hatch test tests --cover
11071
continue-on-error: false
111-
11272
lint:
11373
name: Lint
11474
runs-on: ubuntu-latest
115-
needs: check-approval
11675
permissions:
11776
contents: read
118-
if: github.event_name == 'push' || needs.check-approval.outputs.approved == 'true'
11977
steps:
12078
- name: Checkout code
12179
uses: actions/checkout@v4

CONTRIBUTING.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ hatch fmt --linter
9494
9595
If you're using an IDE like VS Code or PyCharm, consider configuring it to use these tools automatically.
9696
97+
For additional details on styling, please see our dedicated [Style Guide](./STYLE_GUIDE.md).
98+
9799
98100
## Contributing via Pull Requests
99101
Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that:
@@ -115,7 +117,7 @@ To send us a pull request, please:
115117
116118
117119
## Finding contributions to work on
118-
Looking at the existing issues is a great way to find something to contribute on.
120+
Looking at the existing issues is a great way to find something to contribute to.
119121
120122
You can check:
121123
- Our known bugs list in [Bug Reports](../../issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) for issues that need fixing

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ agent = Agent(model=bedrock_model)
118118
agent("Tell me about Agentic AI")
119119

120120
# Ollama
121-
ollama_modal = OllamaModel(
121+
ollama_model = OllamaModel(
122122
host="http://localhost:11434",
123123
model_id="llama3"
124124
)
125-
agent = Agent(model=ollama_modal)
125+
agent = Agent(model=ollama_model)
126126
agent("Tell me about Agentic AI")
127127

128128
# Llama API

STYLE_GUIDE.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Style Guide
2+
3+
## Overview
4+
5+
The Strands Agents style guide aims to establish consistent formatting, naming conventions, and structure across all code in the repository. We strive to make our code clean, readable, and maintainable.
6+
7+
Where possible, we will codify these style guidelines into our linting rules and pre-commit hooks to automate enforcement and reduce the manual review burden.
8+
9+
## Log Formatting
10+
11+
The format for Strands Agents logs is as follows:
12+
13+
```python
14+
logger.debug("field1=<%s>, field2=<%s>, ... | human readable message", field1, field2, ...)
15+
```
16+
17+
### Guidelines
18+
19+
1. **Context**:
20+
- Add context as `<FIELD>=<VALUE>` pairs at the beginning of the log
21+
- Many log services (CloudWatch, Splunk, etc.) look for these patterns to extract fields for searching
22+
- Use `,`'s to separate pairs
23+
- Enclose values in `<>` for readability
24+
- This is particularly helpful in displaying empty values (`field=` vs `field=<>`)
25+
- Use `%s` for string interpolation as recommended by Python logging
26+
- This is an optimization to skip string interpolation when the log level is not enabled
27+
28+
1. **Messages**:
29+
- Add human readable messages at the end of the log
30+
- Use lowercase for consistency
31+
- Avoid punctuation (periods, exclamation points, etc.) to reduce clutter
32+
- Keep messages concise and focused on a single statement
33+
- If multiple statements are needed, separate them with the pipe character (`|`)
34+
- Example: `"processing request | starting validation"`
35+
36+
### Examples
37+
38+
#### Good
39+
40+
```python
41+
logger.debug("user_id=<%s>, action=<%s> | user performed action", user_id, action)
42+
logger.info("request_id=<%s>, duration_ms=<%d> | request completed", request_id, duration)
43+
logger.warning("attempt=<%d>, max_attempts=<%d> | retry limit approaching", attempt, max_attempts)
44+
```
45+
46+
#### Poor
47+
48+
```python
49+
# Avoid: No structured fields, direct variable interpolation in message
50+
logger.debug(f"User {user_id} performed action {action}")
51+
52+
# Avoid: Inconsistent formatting, punctuation
53+
logger.info("Request completed in %d ms.", duration)
54+
55+
# Avoid: No separation between fields and message
56+
logger.warning("Retry limit approaching! attempt=%d max_attempts=%d", attempt, max_attempts)
57+
```
58+
59+
By following these log formatting guidelines, we ensure that logs are both human-readable and machine-parseable, making debugging and monitoring more efficient.

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "strands-agents"
7-
version = "0.1.2"
7+
version = "0.1.3"
88
description = "A model-driven approach to building AI agents in just a few lines of code"
99
readme = "README.md"
1010
requires-python = ">=3.10"
@@ -185,7 +185,9 @@ select = [
185185
"D", # pydocstyle
186186
"E", # pycodestyle
187187
"F", # pyflakes
188+
"G", # logging format
188189
"I", # isort
190+
"LOG", # logging
189191
]
190192

191193
[tool.ruff.lint.per-file-ignores]

src/strands/agent/agent.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
The Agent interface supports two complementary interaction patterns:
77
88
1. Natural language for conversation: `agent("Analyze this data")`
9-
2. Method-style for direct tool access: `agent.tool_name(param1="value")`
9+
2. Method-style for direct tool access: `agent.tool.tool_name(param1="value")`
1010
"""
1111

1212
import asyncio
@@ -515,7 +515,7 @@ def _record_tool_execution(
515515
"""
516516
# Create user message describing the tool call
517517
user_msg_content = [
518-
{"text": (f"agent.{tool['name']} direct tool call\nInput parameters: {json.dumps(tool['input'])}\n")}
518+
{"text": (f"agent.tool.{tool['name']} direct tool call.\nInput parameters: {json.dumps(tool['input'])}\n")}
519519
]
520520

521521
# Add override message if provided

src/strands/event_loop/error_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def handle_input_too_long_error(
7474
"""Handle 'Input is too long' errors by truncating tool results.
7575
7676
When a context window overflow exception occurs (input too long for the model), this function attempts to recover
77-
by finding and truncating the most recent tool results in the conversation history. If trunction is successful, the
77+
by finding and truncating the most recent tool results in the conversation history. If truncation is successful, the
7878
function will make a call to the event loop.
7979
8080
Args:

src/strands/models/bedrock.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def get_config(self) -> BedrockConfig:
132132
"""Get the current Bedrock Model configuration.
133133
134134
Returns:
135-
The Bedrok model configuration.
135+
The Bedrock model configuration.
136136
"""
137137
return self.config
138138

src/strands/models/litellm.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,8 @@ def stream(self, request: dict[str, Any]) -> Iterable[dict[str, Any]]:
334334

335335
yield {"chunk_type": "message_stop", "data": choice.finish_reason}
336336

337-
event = next(response)
338-
if hasattr(event, "usage"):
339-
yield {"chunk_type": "metadata", "data": event.usage}
337+
# Skip remaining events as we don't have use for anything except the final usage payload
338+
for event in response:
339+
_ = event
340+
341+
yield {"chunk_type": "metadata", "data": event.usage}

src/strands/telemetry/tracer.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def __init__(
125125
headers_dict[key.strip()] = value.strip()
126126
otlp_headers = headers_dict
127127
except Exception as e:
128-
logger.warning(f"error=<{e}> | failed to parse OTEL_EXPORTER_OTLP_HEADERS")
128+
logger.warning("error=<%s> | failed to parse OTEL_EXPORTER_OTLP_HEADERS", e)
129129

130130
self.service_name = service_name
131131
self.otlp_endpoint = otlp_endpoint
@@ -184,9 +184,9 @@ def _initialize_tracer(self) -> None:
184184

185185
batch_processor = BatchSpanProcessor(otlp_exporter)
186186
self.tracer_provider.add_span_processor(batch_processor)
187-
logger.info(f"endpoint=<{endpoint}> | OTLP exporter configured with endpoint")
187+
logger.info("endpoint=<%s> | OTLP exporter configured with endpoint", endpoint)
188188
except Exception as e:
189-
logger.error(f"error=<{e}> | Failed to configure OTLP exporter", exc_info=True)
189+
logger.exception("error=<%s> | Failed to configure OTLP exporter", e)
190190

191191
# Set as global tracer provider
192192
trace.set_tracer_provider(self.tracer_provider)
@@ -267,15 +267,15 @@ def _end_span(
267267
else:
268268
span.set_status(StatusCode.OK)
269269
except Exception as e:
270-
logger.warning(f"error=<{e}> | error while ending span", exc_info=True)
270+
logger.warning("error=<%s> | error while ending span", e, exc_info=True)
271271
finally:
272272
span.end()
273273
# Force flush to ensure spans are exported
274274
if self.tracer_provider:
275275
try:
276276
self.tracer_provider.force_flush()
277277
except Exception as e:
278-
logger.warning(f"error=<{e}> | failed to force flush tracer provider")
278+
logger.warning("error=<%s> | failed to force flush tracer provider", e)
279279

280280
def end_span_with_error(self, span: trace.Span, error_message: str, exception: Optional[Exception] = None) -> None:
281281
"""End a span with error status.

0 commit comments

Comments
 (0)