Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Record logs into step artifacts #1339

Merged
merged 46 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
79e5763
Record log entries into context
satansdeer Dec 6, 2024
c3cbf4b
Add log field to context
satansdeer Dec 6, 2024
f3debf5
Add SKYVERN_LOG artifact type
satansdeer Dec 6, 2024
c1e7ff6
Add skyvern log tab on the frontend
satansdeer Dec 6, 2024
b1f14c6
Add SkyvernLogEncoder to handle non-serializable objects
satansdeer Dec 6, 2024
b2cb9c4
Record logs to an artifact
satansdeer Dec 6, 2024
eb6fc3d
Record only logs corresponding to a step
satansdeer Dec 6, 2024
57cd772
Recover empty line
satansdeer Dec 6, 2024
419e12b
Add text log encoder
satansdeer Dec 9, 2024
ddbc340
re-enable upload block (#1324)
wintonzheng Dec 5, 2024
f313326
remove no latest screenshot error log (#1325)
LawyZheng Dec 5, 2024
9b35651
Put a guard in workflow save error detail (#1326)
wintonzheng Dec 5, 2024
2d90fed
urlencode download suffix (#1327)
LawyZheng Dec 5, 2024
be0e817
wait for downloads to be done (#1328)
LawyZheng Dec 5, 2024
5ee8c3b
Skyvern Forms UI (#1330)
wintonzheng Dec 5, 2024
f8a6b58
Fix a navigation bug with saved tasks (#1331)
wintonzheng Dec 5, 2024
8bf863c
workflow run block (#1332)
wintonzheng Dec 6, 2024
5d32d53
forloop metadata variables (#1334)
LawyZheng Dec 6, 2024
3c37a4a
auto prepend scheme to url (#1335)
LawyZheng Dec 6, 2024
1aa1146
rename GEMINI_FLUSH->GEMINI_FLASH (#1333)
nmfisher Dec 6, 2024
f3bb2fe
bump navigation max retry to 5 (#1336)
LawyZheng Dec 6, 2024
6606fba
add workflow_run_id column to artifacts + ObserverCruise and Observer…
wintonzheng Dec 6, 2024
aabc0fc
add observer cruise id to artifacts table (#1337)
wintonzheng Dec 6, 2024
b83c648
ObserverThought reproduce migration script (#1338)
wintonzheng Dec 6, 2024
40e9dd3
Import structlog
satansdeer Dec 11, 2024
5c8488d
Define build_log_uri method
satansdeer Dec 12, 2024
127b8a6
Merge main
satansdeer Dec 12, 2024
4742fc4
Extract save logs functions
satansdeer Dec 13, 2024
974b37e
Pass step_id to log artifact methods
satansdeer Dec 13, 2024
d48f792
Define get_artifact_by_entity_id
satansdeer Dec 13, 2024
f57c7b6
Reuse log artifacts when saving
satansdeer Dec 13, 2024
c8274c3
Introduce get artifacts by entity id handler
satansdeer Dec 13, 2024
3ff49cb
Get step artifacts using entity id handler
satansdeer Dec 13, 2024
c3ce7d3
Remove logs
satansdeer Dec 13, 2024
72b3f15
Record workflow run logs
satansdeer Dec 13, 2024
aac781f
Save task logs
satansdeer Dec 13, 2024
ff86b52
Merge branch 'main' into maksimi/record-logs
satansdeer Dec 13, 2024
a86d81d
Remove print
satansdeer Dec 13, 2024
0f0633f
Merge main
satansdeer Dec 16, 2024
727dd7e
Revert change to InvalidUrl type
satansdeer Dec 16, 2024
2312281
Add complete_action.action_order back
satansdeer Dec 16, 2024
d66e67b
Add with_skyvern_context decorator
satansdeer Dec 16, 2024
2cf5f69
Merge branch 'main' into shu/maksimi/record-logs-new
wintonzheng Dec 17, 2024
63829fa
address typing
wintonzheng Dec 17, 2024
9eda7ca
Add ENABLE_LOG_ARTIFACTS env variable; Disable log artifacts by default
satansdeer Dec 17, 2024
631f898
Pre-commit check
satansdeer Dec 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions skyvern-frontend/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const ArtifactType = {
LLMPrompt: "llm_prompt",
LLMRequest: "llm_request",
HTMLScrape: "html_scrape",
SkyvernLog: "skyvern_log",
} as const;

export type ArtifactType = (typeof ArtifactType)[keyof typeof ArtifactType];
Expand Down
8 changes: 8 additions & 0 deletions skyvern-frontend/src/routes/tasks/detail/StepArtifacts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ function StepArtifacts({ id, stepProps }: Props) {
(artifact) => artifact.artifact_type === ArtifactType.HTMLScrape,
);

const skyvernLog = artifacts?.filter(
(artifact) => artifact.artifact_type === ArtifactType.SkyvernLog,
);

return (
<Tabs
value={artifact}
Expand Down Expand Up @@ -108,6 +112,7 @@ function StepArtifacts({ id, stepProps }: Props) {
<TabsTrigger value="llm_response_parsed">Action List</TabsTrigger>
<TabsTrigger value="html_raw">HTML (Raw)</TabsTrigger>
<TabsTrigger value="llm_request">LLM Request (Raw)</TabsTrigger>
<TabsTrigger value="skyvern_log">Skyvern Log</TabsTrigger>
</TabsList>
<TabsContent value="info">
<div className="flex flex-col gap-6 p-4">
Expand Down Expand Up @@ -209,6 +214,9 @@ function StepArtifacts({ id, stepProps }: Props) {
<TabsContent value="llm_request">
{llmRequest ? <Artifact type="json" artifacts={llmRequest} /> : null}
</TabsContent>
<TabsContent value="skyvern_log">
{skyvernLog ? <Artifact type="json" artifacts={skyvernLog} /> : null}
</TabsContent>
</Tabs>
);
}
Expand Down
19 changes: 19 additions & 0 deletions skyvern/forge/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
from skyvern.webeye.browser_factory import BrowserState
from skyvern.webeye.scraper.scraper import ElementTreeFormat, ScrapedPage, scrape_website
from skyvern.webeye.utils.page import SkyvernFrame
from skyvern.forge.skyvern_log_encoder import SkyvernLogEncoder

LOG = structlog.get_logger()

Expand Down Expand Up @@ -1773,6 +1774,24 @@ async def update_step(
step_id=step.step_id,
diff=update_comparison,
)

try:
log = skyvern_context.current().log
current_step_log = [entry for entry in log if entry.get("step_id", "") == step.step_id]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this PR is a good start.

There are a couple of problems i'm seeing right now (not because of the code here) that we need keep iterating:

  • sometimes we don't have step_id in a log. example. TODO: We need to add step_id to all the logs
  • for each task in a workflow run, we don't set the task_id in the SkyvernContext before the task starts. this means many logs won't have task_id - if we want to store logs according to the "task_id" field in the json. TODO: set task_id in SkyvernContext before a task starts and reset it after a task ends (completed, failed, terminated, canceled)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: We need to add step_id to all the logs

@wintonzheng are we certain that each log must have a step_id?

From what I see in the logs when I ran tasks there are several logs in the beginning that don't belong to any step:

2024-12-09T11:09:09.220558Z [info     ] Created new task               organization_id=o_333794107196816612 proxy_location=RESIDENTIAL task_id=tsk_335262355392558810 url=https://www.nytimes.com/books/best-sellers
2024-12-09T11:09:09.220672Z [info     ] Executing task using background task executor task_id=tsk_335262355392558810
2024-12-09T11:09:09.266772Z [info     ] Creating browser state for task task_id=tsk_335262355392558810
2024-12-09T11:09:12.092991Z [info     ] browser console log is saved   log_path=./log/2024-12-09/95733df0-f2d8-428d-89ce-f71221f0cbe1.log
2024-12-09T11:09:12.326437Z [info     ] Trying to navigate to https://www.nytimes.com/books/best-sellers and waiting for 5 seconds. retry_time=0 url=https://www.nytimes.com/books/best-sellers
2024-12-09T11:09:14.148604Z [info     ] Page loading time              loading_time=1.8220469951629639 url=https://www.nytimes.com/books/best-sellers
2024-12-09T11:09:19.148456Z [info     ] Successfully went to https://www.nytimes.com/books/best-sellers retry_time=0 url=https://www.nytimes.com/books/best-sellers
2024-12-09T11:09:19.162015Z [info     ] Starting agent step            step_id=stp_335262355392558812 step_order=0 step_retry=0 task_id=tsk_335262355392558810

Here first 7 logs would be without a step because the first step is not created yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wintonzheng do I undestand correctly that we want to create task and step ids in advance?

This way the logs that are currently not associated with a step or task because step/task not created yet would be associated with the first task and first step?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@wintonzheng are we certain that each log must have a step_id?

no, step_id needs to be manually defined in LOG.info("xxx", step_id="stp_xxxx") nowadays. those logs you shared here don't belong to any step so it's okay many logs don't have step_id.

@wintonzheng do I undestand correctly that we want to create task and step ids in advance? This way the logs that are currently not associated with a step or task because step/task not created yet would be associated with the first task and first step?

yep, exactly what i mean. we need to backfill step_id and task_id in some logs.

log_json = json.dumps(current_step_log, cls=SkyvernLogEncoder, indent=2)
await app.ARTIFACT_MANAGER.create_artifact(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way we can also capture task and workflow level logs? Even if we don't render them in the UI today

We historically associate logs like that w/ the first step, although @wintonzheng is currently overhauling it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/Skyvern-AI/skyvern/pull/1345/files

here's the example of supporting artifact upload for observer thought.

going forward we can add support for task level and workflow run level artifacts

step=step,
artifact_type=ArtifactType.SKYVERN_LOG,
data=log_json.encode(),
)
except Exception:
LOG.error(
"Failed to record skyvern log after action",
task_id=step.task_id,
step_id=step.step_id,
exc_info=True,
)

return await app.DATABASE.update_step(
task_id=step.task_id,
step_id=step.step_id,
Expand Down
2 changes: 2 additions & 0 deletions skyvern/forge/sdk/artifact/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class ArtifactType(StrEnum):
RECORDING = "recording"
BROWSER_CONSOLE_LOG = "browser_console_log"

SKYVERN_LOG = "skyvern_log"

# DEPRECATED. pls use SCREENSHOT_LLM, SCREENSHOT_ACTION or SCREENSHOT_FINAL
SCREENSHOT = "screenshot"

Expand Down
1 change: 1 addition & 0 deletions skyvern/forge/sdk/artifact/storage/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
ArtifactType.SCREENSHOT_LLM: "png",
ArtifactType.SCREENSHOT_ACTION: "png",
ArtifactType.SCREENSHOT_FINAL: "png",
ArtifactType.SKYVERN_LOG: "json",
ArtifactType.LLM_PROMPT: "txt",
ArtifactType.LLM_REQUEST: "json",
ArtifactType.LLM_RESPONSE: "json",
Expand Down
1 change: 1 addition & 0 deletions skyvern/forge/sdk/core/skyvern_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class SkyvernContext:
workflow_run_id: str | None = None
max_steps_override: int | None = None
totp_codes: dict[str, str | None] = field(default_factory=dict)
log: list[dict] = field(default_factory=list)

def __repr__(self) -> str:
return f"SkyvernContext(request_id={self.request_id}, organization_id={self.organization_id}, task_id={self.task_id}, workflow_id={self.workflow_id}, workflow_run_id={self.workflow_run_id}, max_steps_override={self.max_steps_override})"
Expand Down
17 changes: 16 additions & 1 deletion skyvern/forge/sdk/forge_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ def add_kv_pairs_to_msg(logger: logging.Logger, method_name: str, event_dict: Ev
return event_dict


def skyvern_logs_processor(logger: logging.Logger, method_name: str, event_dict: EventDict) -> EventDict:
"""
A custom processor to add skyvern logs to the context
"""
if method_name not in ["info", "warning", "error", "critical", "exception"]:
return event_dict

context = skyvern_context.current()
if context:
log_entry = dict(event_dict)
context.log.append(log_entry)

return event_dict


def setup_logger() -> None:
"""
Setup the logger with the specified format
Expand Down Expand Up @@ -88,7 +103,7 @@ def setup_logger() -> None:
structlog.processors.format_exc_info,
]
+ additional_processors
+ [renderer],
+ [skyvern_logs_processor, renderer],
)
uvicorn_error = logging.getLogger("uvicorn.error")
uvicorn_error.disabled = True
Expand Down
22 changes: 22 additions & 0 deletions skyvern/forge/skyvern_log_encoder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import json
from typing import Any

class SkyvernLogEncoder(json.JSONEncoder):
"""Custom JSON encoder for Skyvern logs that handles non-serializable objects"""
def default(self, obj: Any) -> Any:
if hasattr(obj, 'model_dump'):
return obj.model_dump()

if hasattr(obj, '__dataclass_fields__'):
return {k: getattr(obj, k) for k in obj.__dataclass_fields__}

if hasattr(obj, '__dict__'):
return {
'type': obj.__class__.__name__,
'attributes': {k: v for k, v in obj.__dict__.items() if not k.startswith('_')}
}
# Handle other non-serializable objects
try:
return str(obj)
except Exception:
return f"<non-serializable-{obj.__class__.__name__}>"