-
Notifications
You must be signed in to change notification settings - Fork 147
feat(llm): add LLM profiles #795
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
Closed
Closed
Changes from all commits
Commits
Show all changes
83 commits
Select commit
Hold shift + click to select a range
3590c43
docs: LLM profiles design + example profile
openhands-agent 9b1e3db
llm: add profile_id field to LLM (profile filename identifier)\n\nCo-…
openhands-agent 21efefe
feat(llm): add ProfileManager and eagerly register profiles at conver…
openhands-agent 46ca1b7
chore: stop tracking local runtime and worktree files; add to .gitignore
openhands-agent 5efdaee
chore: only ignore bead databases
enyst 9cbf67f
test: cover llm profile manager
enyst dfab517
Update .gitignore
enyst 441eb25
Improve LLM profile manager persistence
enyst e7cd039
Add example for managing LLM profiles
enyst 269610a
Document plan for profile references
enyst d0ab952
Integrate profile-aware persistence
enyst f74d050
Simplify profile registration logging
enyst df308fb
Normalize inline_mode naming
enyst 4d293db
Simplify profile_id sync in ProfileManager
enyst 7d1a525
Rename profile sync helper
enyst ec45ed5
LLMRegistry handles profile management
enyst 1566df4
docs: clarify LLMRegistry profile guidance
enyst 8f8b5b9
refactor: rename profile persistence helpers
enyst a3efa6e
refactor: split profile transform helpers
enyst 17617aa
style: use f-strings in LLMRegistry logging
enyst 9134aa1
Update openhands/sdk/llm/llm_registry.py
enyst 36ab580
chore: stop tracking scripts/worktree.sh
enyst cea6a0d
Merge upstream main into agent-sdk-18-profile-manager
enyst 12eec55
fix: remove runtime llm switching
enyst 03b4600
style: use f-string for registry logging
enyst acf67e3
docs: expand LLM profile example
enyst 218728e
Refine LLM profile persistence
enyst 75e8ecd
Update LLM profile docs for usage_id semantics
enyst 8511524
Merge remote-tracking branch 'upstream/main' into agent-sdk-18-profil…
enyst 1f3adab
Merge branch 'main' into agent-sdk-18-profile-manager
enyst 96ba8e9
Merge branch 'main' into agent-sdk-18-profile-manager
enyst 142faee
fix LLM mutation for profiles to respect immutability; add docstring;…
enyst 82138dd
refactor: keep LLM profile expansion at persistence layer
enyst b6511a9
Merge branch 'main' of github.com:All-Hands-AI/agent-sdk into agent-s…
enyst f5404b6
fix: restore LLM profile validation behavior
enyst 85bc698
Merge branch 'main' into agent-sdk-18-profile-manager
enyst ba4bd50
harden profile handling
enyst 99a422c
Merge branch 'main' into agent-sdk-18-profile-manager
enyst 5c52fa5
Merge branch 'main' into agent-sdk-18-profile-manager
enyst b69db09
Merge branch 'main' into agent-sdk-18-profile-manager
enyst 5dc94c1
update to current state
enyst 69d3a7d
remove deprecated from llm
enyst 61f5b77
ruff
enyst 2381da7
restore gitignore
enyst b2f80d3
Delete .openhands/microagents/vscode.md
enyst 8a95dac
Merge branch 'main' into agent-sdk-18-profile-manager
enyst 0aa1164
Merge branch 'main' into agent-sdk-18-profile-manager
enyst 744f171
fix(llm): tolerate legacy profile fields
enyst 24d59bd
fix(llm): keep profile loading strict
enyst a4d6cd4
fix(llm): reduce profile side effects
enyst 075c9b2
test(utils): stabilize discriminated union suite
enyst ab3a265
single source of truth for persistence behavior
enyst 82549cc
Merge branch 'main' of github.com:OpenHands/software-agent-sdk into a…
enyst f400d7d
Update openhands-sdk/openhands/sdk/persistence/__init__.py
enyst a112ddc
feat(llm): save API keys in LLM profiles by default and set 0600 perm…
enyst 60bfbb2
Merge branch 'main' of github.com:OpenHands/software-agent-sdk into a…
enyst 0d01065
Merge branch 'agent-sdk-18-profile-manager' of github.com:OpenHands/s…
enyst bc94774
Merge branch 'main' into agent-sdk-18-profile-manager
enyst ad07b05
Merge branch 'main' into agent-sdk-18-profile-manager
enyst 2464633
Delete docs/llm_profiles.md
enyst db10002
Update openhands-sdk/openhands/sdk/llm/llm.py
enyst ce31e79
Merge branch 'main' into agent-sdk-18-profile-manager
enyst 1fe3929
Merge branch 'main' into agent-sdk-18-profile-manager
enyst 8625ff2
Merge remote-tracking branch 'upstream/main' into agent-sdk-18-profil…
enyst 95d94c3
Merge remote-tracking branch 'upstream/main' into agent-sdk-18-profil…
enyst 67ab2c0
ci: detect nested examples in docs check
enyst fab1d57
ci: fix nested examples regex
enyst 926fb90
ci(docs): clarify example skip rationale
enyst 5676592
Merge branch 'main' into agent-sdk-18-profile-manager
enyst b2ea371
Merge main into agent-sdk-18-profile-manager
enyst 2aa320d
Merge branch 'main' into agent-sdk-18-profile-manager
enyst b5a01ad
fix(llm): reject unknown fields when loading profiles
enyst 90257c5
Revert "fix(llm): reject unknown fields when loading profiles"
enyst 7a83b34
refactor(persistence): default to LLM profiles, drop inline env toggle
enyst 9530155
Update .gitignore
enyst 69e259b
chore(examples): make llm profiles example last
enyst c6f5db7
Update examples/01_standalone_sdk/34_llm_profiles.py
enyst 9ecab27
Merge branch 'main' into agent-sdk-18-profile-manager
xingyaoww cd3ab89
Merge branch 'main' into agent-sdk-18-profile-manager
xingyaoww 9859f21
chore(examples): inline llm profiles script body
enyst 23cb159
feat(llm): default profile persistence and drop inline key
enyst dcc83f5
Merge branch 'main' into agent-sdk-18-profile-manager
enyst 6807c99
test: fix workflow model resolver + session api key env
enyst File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| """Create and use an LLM profile with :class:`LLMRegistry`. | ||
|
|
||
| Run with:: | ||
|
|
||
| uv run python examples/01_standalone_sdk/34_llm_profiles.py | ||
|
|
||
| Profiles are stored under ``~/.openhands/llm-profiles/<name>.json`` by default. | ||
| Set ``LLM_PROFILE_NAME`` to pick a profile. | ||
|
|
||
| Notes on credentials: | ||
| - New profiles include API keys by default when saved | ||
| - To omit secrets on disk, pass include_secrets=False to LLMRegistry.save_profile | ||
| """ | ||
|
|
||
| import json | ||
| import os | ||
| from pathlib import Path | ||
|
|
||
| from pydantic import SecretStr | ||
|
|
||
| from openhands.sdk import ( | ||
| LLM, | ||
| Agent, | ||
| Conversation, | ||
| LLMRegistry, | ||
| Tool, | ||
| ) | ||
| from openhands.tools.terminal import TerminalTool | ||
|
|
||
|
|
||
| PROFILE_NAME = os.getenv("LLM_PROFILE_NAME", "gpt-5-mini") | ||
|
|
||
|
|
||
| def ensure_profile_exists(registry: LLMRegistry, name: str) -> None: | ||
| """Create a starter profile in the default directory when missing.""" | ||
|
|
||
| if name in registry.list_profiles(): | ||
| return | ||
|
|
||
| model = os.getenv("LLM_MODEL", "anthropic/claude-sonnet-4-5-20250929") | ||
| base_url = os.getenv("LLM_BASE_URL") | ||
| api_key = os.getenv("LLM_API_KEY") | ||
|
|
||
| profile_defaults = LLM( | ||
| usage_id="agent", | ||
| model=model, | ||
| base_url=base_url, | ||
| api_key=SecretStr(api_key) if api_key else None, | ||
| temperature=0.2, | ||
| max_output_tokens=4096, | ||
| ) | ||
| path = registry.save_profile(name, profile_defaults) | ||
| print(f"Created profile '{name}' at {path}") | ||
|
|
||
|
|
||
| def load_profile(registry: LLMRegistry, name: str) -> LLM: | ||
| llm = registry.load_profile(name) | ||
| # If profile was saved without secrets, allow providing API key via env var | ||
| if llm.api_key is None: | ||
| api_key = os.getenv("LLM_API_KEY") | ||
| if api_key: | ||
| llm = llm.model_copy(update={"api_key": SecretStr(api_key)}) | ||
| return llm | ||
|
|
||
|
|
||
| if __name__ == "__main__": # pragma: no cover | ||
| registry = LLMRegistry() | ||
| ensure_profile_exists(registry, PROFILE_NAME) | ||
|
|
||
| llm = load_profile(registry, PROFILE_NAME) | ||
|
|
||
| tools = [Tool(name=TerminalTool.name)] | ||
| agent = Agent(llm=llm, tools=tools) | ||
|
|
||
| workspace_dir = Path(os.getcwd()) | ||
| summary_path = workspace_dir / "summary_readme.md" | ||
| if summary_path.exists(): | ||
| summary_path.unlink() | ||
|
|
||
| persistence_root = workspace_dir / ".conversations_llm_profiles" | ||
| conversation = Conversation( | ||
| agent=agent, | ||
| workspace=str(workspace_dir), | ||
| persistence_dir=str(persistence_root), | ||
| visualizer=None, | ||
| ) | ||
|
|
||
| conversation.send_message( | ||
| "Read README.md in this workspace, create a concise summary in " | ||
| "summary_readme.md (overwrite it if it exists), and respond with " | ||
| "SUMMARY_READY when the file is written." | ||
| ) | ||
| conversation.run() | ||
|
|
||
| if summary_path.exists(): | ||
| print(f"summary_readme.md written to {summary_path}") | ||
| else: | ||
| print("summary_readme.md not found after first run") | ||
|
|
||
| conversation.send_message( | ||
| "Thanks! Delete summary_readme.md from the workspace and respond with " | ||
| "SUMMARY_REMOVED once it is gone." | ||
| ) | ||
| conversation.run() | ||
|
|
||
| if summary_path.exists(): | ||
| print("summary_readme.md still present after deletion request") | ||
| else: | ||
| print("summary_readme.md removed") | ||
|
|
||
| persistence_dir = conversation.state.persistence_dir | ||
| if persistence_dir is None: | ||
| raise RuntimeError("Conversation did not persist base state to disk") | ||
|
|
||
| base_state_path = Path(persistence_dir) / "base_state.json" | ||
| state_payload = json.loads(base_state_path.read_text()) | ||
| llm_entry = state_payload.get("agent", {}).get("llm", {}) | ||
| profile_in_state = llm_entry.get("profile_id") | ||
| print(f"Profile recorded in base_state.json: {profile_in_state}") | ||
| if profile_in_state != PROFILE_NAME: | ||
| print( | ||
| "Warning: profile_id in base_state.json does not match the profile " | ||
| "used at runtime." | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "model": "litellm_proxy/openai/gpt-5-mini", | ||
| "base_url": "https://llm-proxy.eval.all-hands.dev", | ||
| "temperature": 0.2, | ||
| "max_output_tokens": 4096, | ||
| "usage_id": "agent" | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.