edsl 0.1.46__py3-none-any.whl → 0.1.48__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- edsl/__init__.py +44 -39
- edsl/__version__.py +1 -1
- edsl/agents/__init__.py +4 -2
- edsl/agents/{Agent.py → agent.py} +442 -152
- edsl/agents/{AgentList.py → agent_list.py} +220 -162
- edsl/agents/descriptors.py +46 -7
- edsl/{exceptions/agents.py → agents/exceptions.py} +3 -12
- edsl/base/__init__.py +75 -0
- edsl/base/base_class.py +1303 -0
- edsl/base/data_transfer_models.py +114 -0
- edsl/base/enums.py +215 -0
- edsl/base.py +8 -0
- edsl/buckets/__init__.py +25 -0
- edsl/buckets/bucket_collection.py +324 -0
- edsl/buckets/model_buckets.py +206 -0
- edsl/buckets/token_bucket.py +502 -0
- edsl/{jobs/buckets/TokenBucketAPI.py → buckets/token_bucket_api.py} +1 -1
- edsl/buckets/token_bucket_client.py +509 -0
- edsl/caching/__init__.py +20 -0
- edsl/caching/cache.py +814 -0
- edsl/caching/cache_entry.py +427 -0
- edsl/{data/CacheHandler.py → caching/cache_handler.py} +14 -15
- edsl/caching/exceptions.py +24 -0
- edsl/caching/orm.py +30 -0
- edsl/{data/RemoteCacheSync.py → caching/remote_cache_sync.py} +3 -3
- edsl/caching/sql_dict.py +441 -0
- edsl/config/__init__.py +8 -0
- edsl/config/config_class.py +177 -0
- edsl/config.py +4 -176
- edsl/conversation/Conversation.py +7 -7
- edsl/conversation/car_buying.py +4 -4
- edsl/conversation/chips.py +6 -6
- edsl/coop/__init__.py +25 -2
- edsl/coop/coop.py +430 -113
- edsl/coop/{ExpectedParrotKeyHandler.py → ep_key_handling.py} +86 -10
- edsl/coop/exceptions.py +62 -0
- edsl/coop/price_fetcher.py +126 -0
- edsl/coop/utils.py +89 -24
- edsl/data_transfer_models.py +5 -72
- edsl/dataset/__init__.py +10 -0
- edsl/{results/Dataset.py → dataset/dataset.py} +116 -36
- edsl/dataset/dataset_operations_mixin.py +1492 -0
- edsl/{results/DatasetTree.py → dataset/dataset_tree.py} +156 -75
- edsl/{results/TableDisplay.py → dataset/display/table_display.py} +18 -7
- edsl/{results → dataset/display}/table_renderers.py +58 -2
- edsl/{results → dataset}/file_exports.py +4 -5
- edsl/{results → dataset}/smart_objects.py +2 -2
- edsl/enums.py +5 -205
- edsl/inference_services/__init__.py +5 -0
- edsl/inference_services/{AvailableModelCacheHandler.py → available_model_cache_handler.py} +2 -3
- edsl/inference_services/{AvailableModelFetcher.py → available_model_fetcher.py} +8 -14
- edsl/inference_services/data_structures.py +3 -2
- edsl/{exceptions/inference_services.py → inference_services/exceptions.py} +1 -1
- edsl/inference_services/{InferenceServiceABC.py → inference_service_abc.py} +1 -1
- edsl/inference_services/{InferenceServicesCollection.py → inference_services_collection.py} +8 -7
- edsl/inference_services/registry.py +4 -41
- edsl/inference_services/{ServiceAvailability.py → service_availability.py} +5 -25
- edsl/inference_services/services/__init__.py +31 -0
- edsl/inference_services/{AnthropicService.py → services/anthropic_service.py} +3 -3
- edsl/inference_services/{AwsBedrock.py → services/aws_bedrock.py} +2 -2
- edsl/inference_services/{AzureAI.py → services/azure_ai.py} +2 -2
- edsl/inference_services/{DeepInfraService.py → services/deep_infra_service.py} +1 -3
- edsl/inference_services/{DeepSeekService.py → services/deep_seek_service.py} +2 -4
- edsl/inference_services/{GoogleService.py → services/google_service.py} +5 -4
- edsl/inference_services/{GroqService.py → services/groq_service.py} +1 -1
- edsl/inference_services/{MistralAIService.py → services/mistral_ai_service.py} +3 -3
- edsl/inference_services/{OllamaService.py → services/ollama_service.py} +1 -7
- edsl/inference_services/{OpenAIService.py → services/open_ai_service.py} +5 -6
- edsl/inference_services/{PerplexityService.py → services/perplexity_service.py} +12 -12
- edsl/inference_services/{TestService.py → services/test_service.py} +7 -6
- edsl/inference_services/{TogetherAIService.py → services/together_ai_service.py} +2 -6
- edsl/inference_services/{XAIService.py → services/xai_service.py} +1 -1
- edsl/inference_services/write_available.py +1 -2
- edsl/instructions/__init__.py +6 -0
- edsl/{surveys/instructions/Instruction.py → instructions/instruction.py} +11 -6
- edsl/{surveys/instructions/InstructionCollection.py → instructions/instruction_collection.py} +10 -5
- edsl/{surveys/InstructionHandler.py → instructions/instruction_handler.py} +3 -3
- edsl/{jobs/interviews → interviews}/ReportErrors.py +2 -2
- edsl/interviews/__init__.py +4 -0
- edsl/{jobs/AnswerQuestionFunctionConstructor.py → interviews/answering_function.py} +45 -18
- edsl/{jobs/interviews/InterviewExceptionEntry.py → interviews/exception_tracking.py} +107 -22
- edsl/interviews/interview.py +638 -0
- edsl/{jobs/interviews/InterviewStatusDictionary.py → interviews/interview_status_dictionary.py} +21 -12
- edsl/{jobs/interviews/InterviewStatusLog.py → interviews/interview_status_log.py} +16 -7
- edsl/{jobs/InterviewTaskManager.py → interviews/interview_task_manager.py} +12 -7
- edsl/{jobs/RequestTokenEstimator.py → interviews/request_token_estimator.py} +8 -3
- edsl/{jobs/interviews/InterviewStatistic.py → interviews/statistics.py} +36 -10
- edsl/invigilators/__init__.py +38 -0
- edsl/invigilators/invigilator_base.py +477 -0
- edsl/{agents/Invigilator.py → invigilators/invigilators.py} +263 -10
- edsl/invigilators/prompt_constructor.py +476 -0
- edsl/{agents → invigilators}/prompt_helpers.py +2 -1
- edsl/{agents/QuestionInstructionPromptBuilder.py → invigilators/question_instructions_prompt_builder.py} +18 -13
- edsl/{agents → invigilators}/question_option_processor.py +96 -21
- edsl/{agents/QuestionTemplateReplacementsBuilder.py → invigilators/question_template_replacements_builder.py} +64 -12
- edsl/jobs/__init__.py +7 -1
- edsl/jobs/async_interview_runner.py +99 -35
- edsl/jobs/check_survey_scenario_compatibility.py +7 -5
- edsl/jobs/data_structures.py +153 -22
- edsl/{exceptions/jobs.py → jobs/exceptions.py} +2 -1
- edsl/jobs/{FetchInvigilator.py → fetch_invigilator.py} +4 -4
- edsl/jobs/{loggers/HTMLTableJobLogger.py → html_table_job_logger.py} +6 -2
- edsl/jobs/{Jobs.py → jobs.py} +321 -155
- edsl/jobs/{JobsChecks.py → jobs_checks.py} +15 -7
- edsl/jobs/{JobsComponentConstructor.py → jobs_component_constructor.py} +20 -17
- edsl/jobs/{InterviewsConstructor.py → jobs_interview_constructor.py} +10 -5
- edsl/jobs/jobs_pricing_estimation.py +347 -0
- edsl/jobs/{JobsRemoteInferenceLogger.py → jobs_remote_inference_logger.py} +4 -3
- edsl/jobs/jobs_runner_asyncio.py +282 -0
- edsl/jobs/{JobsRemoteInferenceHandler.py → remote_inference.py} +19 -22
- edsl/jobs/results_exceptions_handler.py +2 -2
- edsl/key_management/__init__.py +28 -0
- edsl/key_management/key_lookup.py +161 -0
- edsl/{language_models/key_management/KeyLookupBuilder.py → key_management/key_lookup_builder.py} +118 -47
- edsl/key_management/key_lookup_collection.py +82 -0
- edsl/key_management/models.py +218 -0
- edsl/language_models/__init__.py +7 -2
- edsl/language_models/{ComputeCost.py → compute_cost.py} +18 -3
- edsl/{exceptions/language_models.py → language_models/exceptions.py} +2 -1
- edsl/language_models/language_model.py +1080 -0
- edsl/language_models/model.py +10 -25
- edsl/language_models/{ModelList.py → model_list.py} +9 -14
- edsl/language_models/{RawResponseHandler.py → raw_response_handler.py} +1 -1
- edsl/language_models/{RegisterLanguageModelsMeta.py → registry.py} +1 -1
- edsl/language_models/repair.py +4 -4
- edsl/language_models/utilities.py +4 -4
- edsl/notebooks/__init__.py +3 -1
- edsl/notebooks/{Notebook.py → notebook.py} +7 -8
- edsl/prompts/__init__.py +1 -1
- edsl/{exceptions/prompts.py → prompts/exceptions.py} +3 -1
- edsl/prompts/{Prompt.py → prompt.py} +101 -95
- edsl/questions/HTMLQuestion.py +1 -1
- edsl/questions/__init__.py +154 -25
- edsl/questions/answer_validator_mixin.py +1 -1
- edsl/questions/compose_questions.py +4 -3
- edsl/questions/derived/question_likert_five.py +166 -0
- edsl/questions/derived/{QuestionLinearScale.py → question_linear_scale.py} +4 -4
- edsl/questions/derived/{QuestionTopK.py → question_top_k.py} +4 -4
- edsl/questions/derived/{QuestionYesNo.py → question_yes_no.py} +4 -5
- edsl/questions/descriptors.py +24 -30
- edsl/questions/loop_processor.py +65 -19
- edsl/questions/question_base.py +881 -0
- edsl/questions/question_base_gen_mixin.py +15 -16
- edsl/questions/{QuestionBasePromptsMixin.py → question_base_prompts_mixin.py} +2 -2
- edsl/questions/{QuestionBudget.py → question_budget.py} +3 -4
- edsl/questions/{QuestionCheckBox.py → question_check_box.py} +16 -16
- edsl/questions/{QuestionDict.py → question_dict.py} +39 -5
- edsl/questions/{QuestionExtract.py → question_extract.py} +9 -9
- edsl/questions/question_free_text.py +282 -0
- edsl/questions/{QuestionFunctional.py → question_functional.py} +6 -5
- edsl/questions/{QuestionList.py → question_list.py} +6 -7
- edsl/questions/{QuestionMatrix.py → question_matrix.py} +6 -5
- edsl/questions/{QuestionMultipleChoice.py → question_multiple_choice.py} +126 -21
- edsl/questions/{QuestionNumerical.py → question_numerical.py} +5 -5
- edsl/questions/{QuestionRank.py → question_rank.py} +6 -6
- edsl/questions/question_registry.py +10 -16
- edsl/questions/register_questions_meta.py +8 -4
- edsl/questions/response_validator_abc.py +17 -16
- edsl/results/__init__.py +4 -1
- edsl/{exceptions/results.py → results/exceptions.py} +1 -1
- edsl/results/report.py +197 -0
- edsl/results/{Result.py → result.py} +131 -45
- edsl/results/{Results.py → results.py} +420 -216
- edsl/results/results_selector.py +344 -25
- edsl/scenarios/__init__.py +30 -3
- edsl/scenarios/{ConstructDownloadLink.py → construct_download_link.py} +7 -0
- edsl/scenarios/directory_scanner.py +156 -13
- edsl/scenarios/document_chunker.py +186 -0
- edsl/scenarios/exceptions.py +101 -0
- edsl/scenarios/file_methods.py +2 -3
- edsl/scenarios/file_store.py +755 -0
- edsl/scenarios/handlers/__init__.py +14 -14
- edsl/scenarios/handlers/{csv.py → csv_file_store.py} +1 -2
- edsl/scenarios/handlers/{docx.py → docx_file_store.py} +8 -7
- edsl/scenarios/handlers/{html.py → html_file_store.py} +1 -2
- edsl/scenarios/handlers/{jpeg.py → jpeg_file_store.py} +1 -1
- edsl/scenarios/handlers/{json.py → json_file_store.py} +1 -1
- edsl/scenarios/handlers/latex_file_store.py +5 -0
- edsl/scenarios/handlers/{md.py → md_file_store.py} +1 -1
- edsl/scenarios/handlers/{pdf.py → pdf_file_store.py} +2 -2
- edsl/scenarios/handlers/{png.py → png_file_store.py} +1 -1
- edsl/scenarios/handlers/{pptx.py → pptx_file_store.py} +8 -7
- edsl/scenarios/handlers/{py.py → py_file_store.py} +1 -3
- edsl/scenarios/handlers/{sql.py → sql_file_store.py} +2 -1
- edsl/scenarios/handlers/{sqlite.py → sqlite_file_store.py} +2 -3
- edsl/scenarios/handlers/{txt.py → txt_file_store.py} +1 -1
- edsl/scenarios/scenario.py +928 -0
- edsl/scenarios/scenario_join.py +18 -5
- edsl/scenarios/{ScenarioList.py → scenario_list.py} +424 -106
- edsl/scenarios/{ScenarioListPdfMixin.py → scenario_list_pdf_tools.py} +16 -15
- edsl/scenarios/scenario_selector.py +5 -1
- edsl/study/ObjectEntry.py +2 -2
- edsl/study/SnapShot.py +5 -5
- edsl/study/Study.py +20 -21
- edsl/study/__init__.py +6 -4
- edsl/surveys/__init__.py +7 -4
- edsl/surveys/dag/__init__.py +2 -0
- edsl/surveys/{ConstructDAG.py → dag/construct_dag.py} +3 -3
- edsl/surveys/{DAG.py → dag/dag.py} +13 -10
- edsl/surveys/descriptors.py +1 -1
- edsl/surveys/{EditSurvey.py → edit_survey.py} +9 -9
- edsl/{exceptions/surveys.py → surveys/exceptions.py} +1 -2
- edsl/surveys/memory/__init__.py +3 -0
- edsl/surveys/{MemoryPlan.py → memory/memory_plan.py} +10 -9
- edsl/surveys/rules/__init__.py +3 -0
- edsl/surveys/{Rule.py → rules/rule.py} +103 -43
- edsl/surveys/{RuleCollection.py → rules/rule_collection.py} +21 -30
- edsl/surveys/{RuleManager.py → rules/rule_manager.py} +19 -13
- edsl/surveys/survey.py +1743 -0
- edsl/surveys/{SurveyExportMixin.py → survey_export.py} +22 -27
- edsl/surveys/{SurveyFlowVisualization.py → survey_flow_visualization.py} +11 -2
- edsl/surveys/{Simulator.py → survey_simulator.py} +10 -3
- edsl/tasks/__init__.py +32 -0
- edsl/{jobs/tasks/QuestionTaskCreator.py → tasks/question_task_creator.py} +115 -57
- edsl/tasks/task_creators.py +135 -0
- edsl/{jobs/tasks/TaskHistory.py → tasks/task_history.py} +86 -47
- edsl/{jobs/tasks → tasks}/task_status_enum.py +91 -7
- edsl/tasks/task_status_log.py +85 -0
- edsl/tokens/__init__.py +2 -0
- edsl/tokens/interview_token_usage.py +53 -0
- edsl/utilities/PrettyList.py +1 -1
- edsl/utilities/SystemInfo.py +25 -22
- edsl/utilities/__init__.py +29 -21
- edsl/utilities/gcp_bucket/__init__.py +2 -0
- edsl/utilities/gcp_bucket/cloud_storage.py +99 -96
- edsl/utilities/interface.py +44 -536
- edsl/{results/MarkdownToPDF.py → utilities/markdown_to_pdf.py} +13 -5
- edsl/utilities/repair_functions.py +1 -1
- {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/METADATA +3 -2
- edsl-0.1.48.dist-info/RECORD +347 -0
- edsl/Base.py +0 -426
- edsl/BaseDiff.py +0 -260
- edsl/agents/InvigilatorBase.py +0 -260
- edsl/agents/PromptConstructor.py +0 -318
- edsl/auto/AutoStudy.py +0 -130
- edsl/auto/StageBase.py +0 -243
- edsl/auto/StageGenerateSurvey.py +0 -178
- edsl/auto/StageLabelQuestions.py +0 -125
- edsl/auto/StagePersona.py +0 -61
- edsl/auto/StagePersonaDimensionValueRanges.py +0 -88
- edsl/auto/StagePersonaDimensionValues.py +0 -74
- edsl/auto/StagePersonaDimensions.py +0 -69
- edsl/auto/StageQuestions.py +0 -74
- edsl/auto/SurveyCreatorPipeline.py +0 -21
- edsl/auto/utilities.py +0 -218
- edsl/base/Base.py +0 -279
- edsl/coop/PriceFetcher.py +0 -54
- edsl/data/Cache.py +0 -580
- edsl/data/CacheEntry.py +0 -230
- edsl/data/SQLiteDict.py +0 -292
- edsl/data/__init__.py +0 -5
- edsl/data/orm.py +0 -10
- edsl/exceptions/cache.py +0 -5
- edsl/exceptions/coop.py +0 -14
- edsl/exceptions/data.py +0 -14
- edsl/exceptions/scenarios.py +0 -29
- edsl/jobs/Answers.py +0 -43
- edsl/jobs/JobsPrompts.py +0 -354
- edsl/jobs/buckets/BucketCollection.py +0 -134
- edsl/jobs/buckets/ModelBuckets.py +0 -65
- edsl/jobs/buckets/TokenBucket.py +0 -283
- edsl/jobs/buckets/TokenBucketClient.py +0 -191
- edsl/jobs/interviews/Interview.py +0 -395
- edsl/jobs/interviews/InterviewExceptionCollection.py +0 -99
- edsl/jobs/interviews/InterviewStatisticsCollection.py +0 -25
- edsl/jobs/runners/JobsRunnerAsyncio.py +0 -163
- edsl/jobs/runners/JobsRunnerStatusData.py +0 -0
- edsl/jobs/tasks/TaskCreators.py +0 -64
- edsl/jobs/tasks/TaskStatusLog.py +0 -23
- edsl/jobs/tokens/InterviewTokenUsage.py +0 -27
- edsl/language_models/LanguageModel.py +0 -635
- edsl/language_models/ServiceDataSources.py +0 -0
- edsl/language_models/key_management/KeyLookup.py +0 -63
- edsl/language_models/key_management/KeyLookupCollection.py +0 -38
- edsl/language_models/key_management/models.py +0 -137
- edsl/questions/QuestionBase.py +0 -539
- edsl/questions/QuestionFreeText.py +0 -130
- edsl/questions/derived/QuestionLikertFive.py +0 -76
- edsl/results/DatasetExportMixin.py +0 -911
- edsl/results/ResultsExportMixin.py +0 -45
- edsl/results/TextEditor.py +0 -50
- edsl/results/results_fetch_mixin.py +0 -33
- edsl/results/results_tools_mixin.py +0 -98
- edsl/scenarios/DocumentChunker.py +0 -104
- edsl/scenarios/FileStore.py +0 -564
- edsl/scenarios/Scenario.py +0 -548
- edsl/scenarios/ScenarioHtmlMixin.py +0 -65
- edsl/scenarios/ScenarioListExportMixin.py +0 -45
- edsl/scenarios/handlers/latex.py +0 -5
- edsl/shared.py +0 -1
- edsl/surveys/Survey.py +0 -1306
- edsl/surveys/SurveyQualtricsImport.py +0 -284
- edsl/surveys/SurveyToApp.py +0 -141
- edsl/surveys/instructions/__init__.py +0 -0
- edsl/tools/__init__.py +0 -1
- edsl/tools/clusters.py +0 -192
- edsl/tools/embeddings.py +0 -27
- edsl/tools/embeddings_plotting.py +0 -118
- edsl/tools/plotting.py +0 -112
- edsl/tools/summarize.py +0 -18
- edsl/utilities/data/Registry.py +0 -6
- edsl/utilities/data/__init__.py +0 -1
- edsl/utilities/data/scooter_results.json +0 -1
- edsl-0.1.46.dist-info/RECORD +0 -366
- /edsl/coop/{CoopFunctionsMixin.py → coop_functions.py} +0 -0
- /edsl/{results → dataset/display}/CSSParameterizer.py +0 -0
- /edsl/{language_models/key_management → dataset/display}/__init__.py +0 -0
- /edsl/{results → dataset/display}/table_data_class.py +0 -0
- /edsl/{results → dataset/display}/table_display.css +0 -0
- /edsl/{results/ResultsGGMixin.py → dataset/r/ggplot.py} +0 -0
- /edsl/{results → dataset}/tree_explore.py +0 -0
- /edsl/{surveys/instructions/ChangeInstruction.py → instructions/change_instruction.py} +0 -0
- /edsl/{jobs/interviews → interviews}/interview_status_enum.py +0 -0
- /edsl/jobs/{runners/JobsRunnerStatus.py → jobs_runner_status.py} +0 -0
- /edsl/language_models/{PriceManager.py → price_manager.py} +0 -0
- /edsl/language_models/{fake_openai_call.py → unused/fake_openai_call.py} +0 -0
- /edsl/language_models/{fake_openai_service.py → unused/fake_openai_service.py} +0 -0
- /edsl/notebooks/{NotebookToLaTeX.py → notebook_to_latex.py} +0 -0
- /edsl/{exceptions/questions.py → questions/exceptions.py} +0 -0
- /edsl/questions/{SimpleAskMixin.py → simple_ask_mixin.py} +0 -0
- /edsl/surveys/{Memory.py → memory/memory.py} +0 -0
- /edsl/surveys/{MemoryManagement.py → memory/memory_management.py} +0 -0
- /edsl/surveys/{SurveyCSS.py → survey_css.py} +0 -0
- /edsl/{jobs/tokens/TokenUsage.py → tokens/token_usage.py} +0 -0
- /edsl/{results/MarkdownToDocx.py → utilities/markdown_to_docx.py} +0 -0
- /edsl/{TemplateLoader.py → utilities/template_loader.py} +0 -0
- {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/LICENSE +0 -0
- {edsl-0.1.46.dist-info → edsl-0.1.48.dist-info}/WHEEL +0 -0
@@ -1,9 +1,53 @@
|
|
1
|
-
"""
|
1
|
+
"""Agent module for creating and managing AI agents with traits and question-answering capabilities.
|
2
|
+
|
3
|
+
This module provides the Agent class, which represents an AI agent with customizable traits,
|
4
|
+
instructions, and optional direct question-answering methods. Agents are the primary entities
|
5
|
+
that answer questions in the EDSL framework, and they can be configured with:
|
6
|
+
|
7
|
+
- Traits: Key-value pairs representing agent attributes (age, occupation, preferences, etc.)
|
8
|
+
- Instructions: Directives for how the agent should answer questions
|
9
|
+
- Codebooks: Human-readable descriptions for traits used in prompts
|
10
|
+
- Dynamic traits: Functions that can modify traits based on questions
|
11
|
+
- Direct answering methods: Functions that can answer specific questions without using an LLM
|
12
|
+
|
13
|
+
Agents can be combined, modified, and used to answer various question types through different
|
14
|
+
invigilator mechanisms.
|
15
|
+
|
16
|
+
Codebook and Trait Rendering
|
17
|
+
---------------------------
|
18
|
+
One of the key features of the Agent class is its ability to use codebooks to improve
|
19
|
+
how traits are presented to language models. A codebook is a dictionary that maps trait keys
|
20
|
+
to human-readable descriptions:
|
21
|
+
|
22
|
+
```python
|
23
|
+
traits = {"age": 30, "occupation": "doctor"}
|
24
|
+
codebook = {"age": "Age in years", "occupation": "Current profession"}
|
25
|
+
agent = Agent(traits=traits, codebook=codebook)
|
26
|
+
```
|
27
|
+
|
28
|
+
When an agent with a codebook generates prompts, it will use these descriptions
|
29
|
+
instead of the raw trait keys, creating more natural and descriptive prompts:
|
30
|
+
|
31
|
+
Without codebook: "Your traits: {'age': 30, 'occupation': 'doctor'}"
|
32
|
+
With codebook:
|
33
|
+
```
|
34
|
+
Your traits:
|
35
|
+
Age in years: 30
|
36
|
+
Current profession: doctor
|
37
|
+
```
|
38
|
+
|
39
|
+
This approach helps language models better understand the traits and can lead to
|
40
|
+
more natural responses that properly interpret the agent's characteristics.
|
41
|
+
"""
|
2
42
|
|
3
43
|
from __future__ import annotations
|
4
44
|
import copy
|
5
45
|
import inspect
|
6
46
|
import types
|
47
|
+
import warnings
|
48
|
+
from uuid import uuid4
|
49
|
+
from contextlib import contextmanager
|
50
|
+
|
7
51
|
from typing import (
|
8
52
|
Callable,
|
9
53
|
Optional,
|
@@ -14,23 +58,21 @@ from typing import (
|
|
14
58
|
runtime_checkable,
|
15
59
|
TypeVar,
|
16
60
|
)
|
17
|
-
from contextlib import contextmanager
|
18
|
-
from dataclasses import dataclass
|
19
61
|
|
20
62
|
# Type variable for the Agent class
|
21
63
|
A = TypeVar("A", bound="Agent")
|
22
64
|
|
23
65
|
if TYPE_CHECKING:
|
24
|
-
from
|
25
|
-
from
|
26
|
-
from
|
27
|
-
from
|
28
|
-
from
|
29
|
-
from
|
30
|
-
from
|
31
|
-
from
|
32
|
-
from
|
33
|
-
from
|
66
|
+
from ..caching import Cache
|
67
|
+
from ..surveys import Survey
|
68
|
+
from ..scenarios import Scenario
|
69
|
+
from ..language_models import LanguageModel
|
70
|
+
from ..surveys.memory import MemoryPlan
|
71
|
+
from ..questions import QuestionBase
|
72
|
+
from ..invigilators import InvigilatorBase
|
73
|
+
from ..prompts import Prompt
|
74
|
+
from ..questions import QuestionBase
|
75
|
+
from ..key_management import KeyLookup
|
34
76
|
|
35
77
|
|
36
78
|
@runtime_checkable
|
@@ -40,52 +82,92 @@ class DirectAnswerMethod(Protocol):
|
|
40
82
|
def __call__(self, self_: A, question: QuestionBase, scenario: Scenario) -> Any: ...
|
41
83
|
|
42
84
|
|
43
|
-
from
|
85
|
+
from ..base import Base
|
86
|
+
from ..scenarios import Scenario
|
87
|
+
from ..questions import QuestionScenarioRenderError
|
88
|
+
from ..data_transfer_models import AgentResponseDict
|
89
|
+
from ..utilities import (
|
90
|
+
sync_wrapper,
|
91
|
+
create_restricted_function,
|
92
|
+
dict_hash,
|
93
|
+
remove_edsl_version,
|
94
|
+
)
|
44
95
|
|
45
|
-
from edsl.Base import Base
|
46
|
-
from edsl.exceptions.questions import QuestionScenarioRenderError
|
47
96
|
|
48
|
-
from
|
97
|
+
from .exceptions import (
|
49
98
|
AgentErrors,
|
50
99
|
AgentCombinationError,
|
51
100
|
AgentDirectAnswerFunctionError,
|
52
101
|
AgentDynamicTraitsFunctionError,
|
53
102
|
)
|
54
103
|
|
55
|
-
from
|
104
|
+
from .descriptors import (
|
56
105
|
TraitsDescriptor,
|
57
106
|
CodebookDescriptor,
|
58
107
|
InstructionDescriptor,
|
59
108
|
NameDescriptor,
|
60
109
|
)
|
61
|
-
from edsl.utilities.decorators import (
|
62
|
-
sync_wrapper,
|
63
|
-
)
|
64
|
-
from edsl.utilities.remove_edsl_version import remove_edsl_version
|
65
|
-
from edsl.data_transfer_models import AgentResponseDict
|
66
|
-
from edsl.utilities.restricted_python import create_restricted_function
|
67
|
-
|
68
|
-
from edsl.scenarios.Scenario import Scenario
|
69
110
|
|
70
111
|
|
71
112
|
class AgentTraits(Scenario):
|
72
|
-
"""A class representing the traits of an agent.
|
113
|
+
"""A class representing the traits of an agent.
|
114
|
+
|
115
|
+
AgentTraits inherits from Scenario to provide a structured way to store and
|
116
|
+
access agent characteristics. This allows agent traits to be handled consistently
|
117
|
+
throughout the EDSL framework, including for presentation in prompts.
|
118
|
+
|
119
|
+
Attributes:
|
120
|
+
data: Dictionary containing the agent's traits as key-value pairs
|
121
|
+
"""
|
73
122
|
|
74
123
|
def __repr__(self):
|
124
|
+
"""Generate a string representation of the agent traits.
|
125
|
+
|
126
|
+
Returns:
|
127
|
+
str: String representation of the agent traits dictionary
|
128
|
+
"""
|
75
129
|
return f"{self.data}"
|
76
130
|
|
77
131
|
|
78
132
|
class Agent(Base):
|
79
|
-
"""
|
133
|
+
"""A class representing an AI agent with customizable traits that can answer questions.
|
134
|
+
|
135
|
+
An Agent in EDSL represents an entity with specific characteristics (traits) that can
|
136
|
+
answer questions through various mechanisms. Agents can use language models to generate
|
137
|
+
responses based on their traits, directly answer questions through custom functions, or
|
138
|
+
dynamically adjust their traits based on the questions being asked.
|
139
|
+
|
140
|
+
Key capabilities:
|
141
|
+
- Store and manage agent characteristics (traits)
|
142
|
+
- Provide instructions on how the agent should answer
|
143
|
+
- Support for custom codebooks to provide human-readable trait descriptions
|
144
|
+
- Integration with multiple question types and language models
|
145
|
+
- Combine agents to create more complex personas
|
146
|
+
- Customize agent behavior through direct answering methods
|
147
|
+
|
148
|
+
Agents are used in conjunction with Questions, Scenarios, and Surveys to create
|
149
|
+
structured interactions with language models.
|
150
|
+
"""
|
80
151
|
|
81
152
|
__documentation__ = "https://docs.expectedparrot.com/en/latest/agents.html"
|
82
153
|
|
83
154
|
default_instruction = """You are answering questions as if you were a human. Do not break character."""
|
84
155
|
|
156
|
+
# Trait storage using descriptors for validation and management
|
85
157
|
_traits = TraitsDescriptor()
|
158
|
+
|
159
|
+
# Codebook maps trait keys to human-readable descriptions
|
160
|
+
# This improves prompt readability and LLM understanding
|
161
|
+
# Example: {'age': 'Age in years'} → "Age in years: 30" instead of "age: 30"
|
86
162
|
codebook = CodebookDescriptor()
|
163
|
+
|
164
|
+
# Instructions for how the agent should answer questions
|
87
165
|
instruction = InstructionDescriptor()
|
166
|
+
|
167
|
+
# Optional name identifier for the agent
|
88
168
|
name = NameDescriptor()
|
169
|
+
|
170
|
+
# Default values for function-related attributes
|
89
171
|
dynamic_traits_function_name = ""
|
90
172
|
answer_question_directly_function_name = ""
|
91
173
|
has_dynamic_traits_function = False
|
@@ -103,57 +185,67 @@ class Agent(Base):
|
|
103
185
|
answer_question_directly_source_code: Optional[str] = None,
|
104
186
|
answer_question_directly_function_name: Optional[str] = None,
|
105
187
|
):
|
106
|
-
"""Initialize a new instance
|
107
|
-
|
108
|
-
:
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
188
|
+
"""Initialize a new Agent instance with specified traits and capabilities.
|
189
|
+
|
190
|
+
Args:
|
191
|
+
traits: Dictionary of agent characteristics (e.g., {"age": 30, "occupation": "doctor"})
|
192
|
+
name: Optional name identifier for the agent
|
193
|
+
codebook: Dictionary mapping trait keys to human-readable descriptions for prompts.
|
194
|
+
This provides more descriptive labels for traits when rendering prompts.
|
195
|
+
For example, {'age': 'Age in years'} would display "Age in years: 30" instead of "age: 30".
|
196
|
+
instruction: Directive for how the agent should answer questions
|
197
|
+
traits_presentation_template: Jinja2 template for formatting traits in prompts
|
198
|
+
dynamic_traits_function: Function that can modify traits based on questions
|
199
|
+
dynamic_traits_function_source_code: Source code string for the dynamic traits function
|
200
|
+
dynamic_traits_function_name: Name of the dynamic traits function
|
201
|
+
answer_question_directly_source_code: Source code for direct question answering method
|
202
|
+
answer_question_directly_function_name: Name of the direct answering function
|
203
|
+
|
204
|
+
The Agent class brings together several key concepts:
|
205
|
+
|
206
|
+
Traits
|
207
|
+
------
|
208
|
+
Traits are key-value pairs that define an agent's characteristics. These are used
|
209
|
+
to construct a prompt that guides the language model on how to respond.
|
210
|
+
|
211
|
+
Example:
|
212
|
+
>>> a = Agent(traits={"age": 10, "hair": "brown", "height": 5.5})
|
126
213
|
>>> a.traits
|
127
214
|
{'age': 10, 'hair': 'brown', 'height': 5.5}
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
215
|
+
|
216
|
+
Traits Presentation
|
217
|
+
------------------
|
218
|
+
The traits_presentation_template controls how traits are formatted in prompts.
|
219
|
+
It uses Jinja2 templating to insert trait values.
|
220
|
+
|
221
|
+
Example:
|
222
|
+
>>> a = Agent(traits={"age": 10}, traits_presentation_template="I am a {{age}} year old.")
|
134
223
|
>>> repr(a.agent_persona)
|
135
224
|
'Prompt(text=\"""I am a {{age}} year old.\""")'
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
225
|
+
|
226
|
+
Codebooks
|
227
|
+
---------
|
228
|
+
Codebooks provide human-readable descriptions for traits in prompts.
|
229
|
+
|
230
|
+
Example:
|
141
231
|
>>> traits = {"age": 10, "hair": "brown", "height": 5.5}
|
142
232
|
>>> codebook = {'age': 'Their age is'}
|
143
|
-
>>> a = Agent(traits
|
233
|
+
>>> a = Agent(traits=traits, codebook=codebook,
|
234
|
+
... traits_presentation_template="This agent is Dave. {{codebook['age']}} {{age}}")
|
144
235
|
>>> d = a.traits | {'codebook': a.codebook}
|
145
236
|
>>> a.agent_persona.render(d)
|
146
237
|
Prompt(text=\"""This agent is Dave. Their age is 10\""")
|
147
|
-
|
238
|
+
|
148
239
|
Instructions
|
149
240
|
------------
|
150
|
-
|
151
|
-
|
241
|
+
Instructions guide how the agent should answer questions. If not provided,
|
242
|
+
a default instruction is used.
|
243
|
+
|
152
244
|
>>> Agent.default_instruction
|
153
245
|
'You are answering questions as if you were a human. Do not break character.'
|
154
|
-
|
155
|
-
|
156
|
-
|
246
|
+
|
247
|
+
For details on how these components are used to construct prompts, see
|
248
|
+
:py:class:`edsl.agents.Invigilator.InvigilatorBase`.
|
157
249
|
"""
|
158
250
|
self._initialize_basic_attributes(traits, name, codebook)
|
159
251
|
self._initialize_instruction(instruction)
|
@@ -170,13 +262,25 @@ class Agent(Base):
|
|
170
262
|
self.current_question = None
|
171
263
|
|
172
264
|
def _initialize_basic_attributes(self, traits, name, codebook) -> None:
|
173
|
-
"""Initialize the basic attributes of the agent.
|
265
|
+
"""Initialize the basic attributes of the agent.
|
266
|
+
|
267
|
+
Args:
|
268
|
+
traits: Dictionary of agent characteristics
|
269
|
+
name: Name identifier for the agent
|
270
|
+
codebook: Dictionary mapping trait keys to descriptions
|
271
|
+
"""
|
174
272
|
self.name = name
|
175
273
|
self._traits = AgentTraits(traits or dict())
|
176
274
|
self.codebook = codebook or dict()
|
177
275
|
|
178
276
|
def _initialize_instruction(self, instruction) -> None:
|
179
|
-
"""Initialize the instruction for
|
277
|
+
"""Initialize the instruction for how the agent should answer questions.
|
278
|
+
|
279
|
+
If no instruction is provided, uses the default instruction.
|
280
|
+
|
281
|
+
Args:
|
282
|
+
instruction: Directive for how the agent should answer questions
|
283
|
+
"""
|
180
284
|
if instruction is None:
|
181
285
|
self.instruction = self.default_instruction
|
182
286
|
self._instruction = self.default_instruction
|
@@ -189,13 +293,71 @@ class Agent(Base):
|
|
189
293
|
def _initialize_traits_presentation_template(
|
190
294
|
self, traits_presentation_template
|
191
295
|
) -> None:
|
192
|
-
"""Initialize the
|
296
|
+
"""Initialize the template for presenting agent traits in prompts.
|
297
|
+
|
298
|
+
This method sets up how the agent's traits will be formatted in language model prompts.
|
299
|
+
The template is a Jinja2 template string that can reference trait values and other
|
300
|
+
agent properties.
|
301
|
+
|
302
|
+
If no template is provided:
|
303
|
+
- If a codebook exists, the method creates a template that displays each trait with its
|
304
|
+
codebook description instead of the raw key names (e.g., "Age in years: 30" instead of "age: 30")
|
305
|
+
- Without a codebook, it uses a default template that displays all traits as a dictionary
|
306
|
+
|
307
|
+
Custom templates always take precedence over automatically generated ones, giving users
|
308
|
+
complete control over how traits are presented.
|
309
|
+
|
310
|
+
Args:
|
311
|
+
traits_presentation_template: Optional Jinja2 template string for formatting traits.
|
312
|
+
If not provided, a default template will be generated.
|
313
|
+
|
314
|
+
Examples:
|
315
|
+
With no template or codebook, traits are shown as a dictionary:
|
316
|
+
|
317
|
+
>>> agent = Agent(traits={"age": 25, "occupation": "engineer"})
|
318
|
+
>>> str(agent.prompt().text)
|
319
|
+
'Your traits: {\'age\': 25, \'occupation\': \'engineer\'}'
|
320
|
+
|
321
|
+
With a codebook but no custom template, traits are shown with descriptions:
|
322
|
+
|
323
|
+
>>> codebook = {"age": "Age in years", "occupation": "Current profession"}
|
324
|
+
>>> agent = Agent(traits={"age": 25, "occupation": "engineer"}, codebook=codebook)
|
325
|
+
>>> print(agent.prompt().text) # doctest: +NORMALIZE_WHITESPACE
|
326
|
+
Your traits:
|
327
|
+
Age in years: 25
|
328
|
+
Current profession: engineer
|
329
|
+
|
330
|
+
With a custom template, that format is used regardless of codebook:
|
331
|
+
|
332
|
+
>>> template = "Person: {{age}} years old, works as {{occupation}}"
|
333
|
+
>>> agent = Agent(traits={"age": 25, "occupation": "engineer"},
|
334
|
+
... codebook=codebook, traits_presentation_template=template)
|
335
|
+
>>> str(agent.prompt().text)
|
336
|
+
'Person: 25 years old, works as engineer'
|
337
|
+
"""
|
193
338
|
if traits_presentation_template is not None:
|
194
339
|
self._traits_presentation_template = traits_presentation_template
|
195
340
|
self.traits_presentation_template = traits_presentation_template
|
196
341
|
self.set_traits_presentation_template = True
|
197
342
|
else:
|
198
|
-
|
343
|
+
# Set the default template based on whether a codebook exists
|
344
|
+
if self.codebook:
|
345
|
+
# Create a template that uses the codebook descriptions
|
346
|
+
traits_lines = []
|
347
|
+
for trait_key in self.traits.keys():
|
348
|
+
if trait_key in self.codebook:
|
349
|
+
# Use codebook description if available
|
350
|
+
traits_lines.append(f"{self.codebook[trait_key]}: {{{{ {trait_key} }}}}")
|
351
|
+
else:
|
352
|
+
# Fall back to raw key for traits without codebook entries
|
353
|
+
traits_lines.append(f"{trait_key}: {{{{ {trait_key} }}}}")
|
354
|
+
|
355
|
+
# Join all trait lines with newlines
|
356
|
+
self.traits_presentation_template = "Your traits:\n" + "\n".join(traits_lines)
|
357
|
+
else:
|
358
|
+
# Use the standard dictionary format if no codebook
|
359
|
+
self.traits_presentation_template = "Your traits: {{traits}}"
|
360
|
+
|
199
361
|
self.set_traits_presentation_template = False
|
200
362
|
|
201
363
|
def _initialize_dynamic_traits_function(
|
@@ -204,7 +366,17 @@ class Agent(Base):
|
|
204
366
|
dynamic_traits_function_source_code,
|
205
367
|
dynamic_traits_function_name,
|
206
368
|
) -> None:
|
207
|
-
"""Initialize
|
369
|
+
"""Initialize a function that can dynamically modify agent traits based on questions.
|
370
|
+
|
371
|
+
This allows traits to change based on the question being asked, enabling
|
372
|
+
more sophisticated agent behaviors. The function can be provided directly
|
373
|
+
or as source code that will be compiled.
|
374
|
+
|
375
|
+
Args:
|
376
|
+
dynamic_traits_function: Function object that returns a dictionary of traits
|
377
|
+
dynamic_traits_function_source_code: Source code string for the function
|
378
|
+
dynamic_traits_function_name: Name to assign to the function
|
379
|
+
"""
|
208
380
|
# Deal with dynamic traits function
|
209
381
|
self.dynamic_traits_function = dynamic_traits_function
|
210
382
|
|
@@ -225,6 +397,16 @@ class Agent(Base):
|
|
225
397
|
answer_question_directly_source_code,
|
226
398
|
answer_question_directly_function_name,
|
227
399
|
) -> None:
|
400
|
+
"""Initialize a method for the agent to directly answer questions without using an LLM.
|
401
|
+
|
402
|
+
This allows creating agents that answer programmatically rather than through
|
403
|
+
language model generation. The direct answering method can be provided as
|
404
|
+
source code that will be compiled and bound to this agent instance.
|
405
|
+
|
406
|
+
Args:
|
407
|
+
answer_question_directly_source_code: Source code for the direct answering method
|
408
|
+
answer_question_directly_function_name: Name to assign to the method
|
409
|
+
"""
|
228
410
|
if answer_question_directly_source_code:
|
229
411
|
self.answer_question_directly_function_name = (
|
230
412
|
answer_question_directly_function_name
|
@@ -248,58 +430,161 @@ class Agent(Base):
|
|
248
430
|
self.set_traits_presentation_template = False
|
249
431
|
|
250
432
|
def duplicate(self) -> Agent:
|
251
|
-
"""
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
433
|
+
"""Create a deep copy of this agent with all its traits and capabilities.
|
434
|
+
|
435
|
+
This method creates a completely independent copy of the agent, including
|
436
|
+
all its traits, codebook, instructions, and special functions like dynamic
|
437
|
+
traits and direct answering methods.
|
438
|
+
|
439
|
+
Returns:
|
440
|
+
Agent: A new agent instance that is functionally identical to this one
|
441
|
+
|
442
|
+
Examples:
|
443
|
+
Create a duplicate agent and verify it's equal but not the same object:
|
444
|
+
|
445
|
+
>>> a = Agent(traits={"age": 10, "hair": "brown", "height": 5.5},
|
446
|
+
... codebook={'age': 'Their age is'})
|
447
|
+
>>> a2 = a.duplicate()
|
448
|
+
>>> a2 == a # Functionally equivalent
|
449
|
+
True
|
450
|
+
>>> id(a) == id(a2) # But different objects
|
451
|
+
False
|
452
|
+
|
453
|
+
Duplicating preserves direct answering methods:
|
454
|
+
|
455
|
+
>>> def f(self, question, scenario): return "I am a direct answer."
|
456
|
+
>>> a.add_direct_question_answering_method(f)
|
457
|
+
>>> hasattr(a, "answer_question_directly")
|
458
|
+
True
|
459
|
+
>>> a2 = a.duplicate()
|
460
|
+
>>> a2.answer_question_directly(None, None)
|
461
|
+
'I am a direct answer.'
|
462
|
+
|
463
|
+
Duplicating preserves custom instructions:
|
464
|
+
|
465
|
+
>>> a = Agent(traits={'age': 10}, instruction="Have fun!")
|
466
|
+
>>> a2 = a.duplicate()
|
467
|
+
>>> a2.instruction
|
468
|
+
'Have fun!'
|
271
469
|
"""
|
272
470
|
new_agent = Agent.from_dict(self.to_dict())
|
471
|
+
|
472
|
+
# Transfer direct answering method if present
|
273
473
|
if hasattr(self, "answer_question_directly"):
|
274
474
|
answer_question_directly = self.answer_question_directly
|
275
475
|
newf = lambda self, question, scenario: answer_question_directly(
|
276
476
|
question, scenario
|
277
477
|
)
|
278
478
|
new_agent.add_direct_question_answering_method(newf)
|
479
|
+
|
480
|
+
# Transfer dynamic traits function if present
|
279
481
|
if hasattr(self, "dynamic_traits_function"):
|
280
482
|
dynamic_traits_function = self.dynamic_traits_function
|
281
483
|
new_agent.dynamic_traits_function = dynamic_traits_function
|
484
|
+
|
282
485
|
return new_agent
|
283
486
|
|
284
487
|
@property
|
285
488
|
def agent_persona(self) -> Prompt:
|
286
|
-
"""
|
287
|
-
|
489
|
+
"""Get the agent's persona template as a Prompt object.
|
490
|
+
|
491
|
+
This property provides access to the template that formats the agent's traits
|
492
|
+
for presentation in prompts. The template is wrapped in a Prompt object
|
493
|
+
that supports rendering with variable substitution.
|
494
|
+
|
495
|
+
Returns:
|
496
|
+
Prompt: A prompt object containing the traits presentation template
|
497
|
+
"""
|
498
|
+
from ..prompts import Prompt
|
288
499
|
|
289
500
|
return Prompt(text=self.traits_presentation_template)
|
290
501
|
|
291
502
|
def prompt(self) -> str:
|
292
|
-
"""
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
503
|
+
"""Generate a formatted prompt containing the agent's traits.
|
504
|
+
|
505
|
+
This method renders the agent's traits presentation template with the
|
506
|
+
agent's traits and codebook, creating a formatted prompt that can be
|
507
|
+
used in language model requests.
|
508
|
+
|
509
|
+
The method is dynamic and responsive to changes in the agent's state:
|
510
|
+
|
511
|
+
1. If a custom template was explicitly set during initialization, it will be used
|
512
|
+
2. If using the default template and the codebook has been updated since
|
513
|
+
initialization, this method will recreate the template to reflect the current
|
514
|
+
codebook values
|
515
|
+
3. The template is rendered with access to all trait values, the complete traits
|
516
|
+
dictionary, and the codebook
|
517
|
+
|
518
|
+
The template rendering makes the following variables available:
|
519
|
+
- All individual trait keys (e.g., {{age}}, {{occupation}})
|
520
|
+
- The full traits dictionary as {{traits}}
|
521
|
+
- The codebook as {{codebook}}
|
522
|
+
|
523
|
+
Returns:
|
524
|
+
Prompt: A Prompt object containing the rendered template
|
525
|
+
|
526
|
+
Raises:
|
527
|
+
QuestionScenarioRenderError: If any template variables remain undefined
|
528
|
+
|
529
|
+
Examples:
|
530
|
+
Basic trait rendering without a codebook:
|
531
|
+
|
532
|
+
>>> agent = Agent(traits={"age": 10, "hair": "brown", "height": 5.5})
|
533
|
+
>>> agent.prompt()
|
534
|
+
Prompt(text=\"""Your traits: {'age': 10, 'hair': 'brown', 'height': 5.5}\""")
|
535
|
+
|
536
|
+
Trait rendering with a codebook (more readable format):
|
537
|
+
|
538
|
+
>>> codebook = {"age": "Age in years", "hair": "Hair color"}
|
539
|
+
>>> agent = Agent(traits={"age": 10, "hair": "brown"}, codebook=codebook)
|
540
|
+
>>> print(agent.prompt().text) # doctest: +NORMALIZE_WHITESPACE
|
541
|
+
Your traits:
|
542
|
+
Age in years: 10
|
543
|
+
Hair color: brown
|
544
|
+
|
545
|
+
Adding a codebook after initialization updates the rendering:
|
546
|
+
|
547
|
+
>>> agent = Agent(traits={"age": 30, "occupation": "doctor"})
|
548
|
+
>>> initial_prompt = agent.prompt()
|
549
|
+
>>> "Your traits: {" in initial_prompt.text
|
550
|
+
True
|
551
|
+
>>> agent.codebook = {"age": "Age", "occupation": "Profession"}
|
552
|
+
>>> updated_prompt = agent.prompt()
|
553
|
+
>>> "Age: 30" in updated_prompt.text
|
554
|
+
True
|
555
|
+
>>> "Profession: doctor" in updated_prompt.text
|
556
|
+
True
|
557
|
+
|
558
|
+
Custom templates can reference any trait directly:
|
559
|
+
|
560
|
+
>>> template = "Profile: {{age}} year old {{occupation}}"
|
561
|
+
>>> agent = Agent(traits={"age": 45, "occupation": "teacher"},
|
562
|
+
... traits_presentation_template=template)
|
563
|
+
>>> agent.prompt().text
|
564
|
+
'Profile: 45 year old teacher'
|
299
565
|
"""
|
566
|
+
# If using the default template and the codebook has been updated since initialization,
|
567
|
+
# recreate the template to use the current codebook
|
568
|
+
if not self.set_traits_presentation_template and self.codebook:
|
569
|
+
# Create a template that uses the codebook descriptions
|
570
|
+
traits_lines = []
|
571
|
+
for trait_key in self.traits.keys():
|
572
|
+
if trait_key in self.codebook:
|
573
|
+
# Use codebook description if available
|
574
|
+
traits_lines.append(f"{self.codebook[trait_key]}: {{{{ {trait_key} }}}}")
|
575
|
+
else:
|
576
|
+
# Fall back to raw key for traits without codebook entries
|
577
|
+
traits_lines.append(f"{trait_key}: {{{{ {trait_key} }}}}")
|
578
|
+
|
579
|
+
# Join all trait lines with newlines
|
580
|
+
self.traits_presentation_template = "Your traits:\n" + "\n".join(traits_lines)
|
581
|
+
|
582
|
+
# Create a dictionary with traits, a reference to all traits, and the codebook
|
300
583
|
replacement_dict = (
|
301
584
|
self.traits | {"traits": self.traits} | {"codebook": self.codebook}
|
302
585
|
)
|
586
|
+
|
587
|
+
# Check for any undefined variables in the template
|
303
588
|
if undefined := self.agent_persona.undefined_template_variables(
|
304
589
|
replacement_dict
|
305
590
|
):
|
@@ -310,28 +595,41 @@ class Agent(Base):
|
|
310
595
|
return self.agent_persona.render(replacement_dict)
|
311
596
|
|
312
597
|
def _check_dynamic_traits_function(self) -> None:
|
313
|
-
"""
|
314
|
-
|
315
|
-
This checks
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
598
|
+
"""Validate that the dynamic traits function has the correct signature.
|
599
|
+
|
600
|
+
This method checks if the dynamic traits function (if present) has the correct
|
601
|
+
parameter list. The function should either take no parameters or a single
|
602
|
+
parameter named 'question'.
|
603
|
+
|
604
|
+
Raises:
|
605
|
+
AgentDynamicTraitsFunctionError: If the function signature is invalid
|
606
|
+
|
607
|
+
Examples:
|
608
|
+
Valid function with 'question' parameter:
|
609
|
+
|
610
|
+
>>> def f(question): return {"age": 10, "hair": "brown", "height": 5.5}
|
611
|
+
>>> a = Agent(dynamic_traits_function=f)
|
612
|
+
>>> a._check_dynamic_traits_function()
|
613
|
+
|
614
|
+
Invalid function with extra parameters:
|
615
|
+
|
616
|
+
>>> def g(question, poo): return {"age": 10, "hair": "brown", "height": 5.5}
|
617
|
+
>>> a = Agent(dynamic_traits_function=g)
|
618
|
+
Traceback (most recent call last):
|
619
|
+
...
|
620
|
+
edsl.agents.exceptions.AgentDynamicTraitsFunctionError: ...
|
326
621
|
"""
|
327
622
|
if self.has_dynamic_traits_function:
|
328
623
|
sig = inspect.signature(self.dynamic_traits_function)
|
624
|
+
|
329
625
|
if "question" in sig.parameters:
|
626
|
+
# If it has 'question' parameter, it should be the only one
|
330
627
|
if len(sig.parameters) > 1:
|
331
628
|
raise AgentDynamicTraitsFunctionError(
|
332
629
|
message=f"The dynamic traits function {self.dynamic_traits_function} has too many parameters. It should only have one parameter: 'question'."
|
333
630
|
)
|
334
631
|
else:
|
632
|
+
# If it doesn't have 'question', it shouldn't have any parameters
|
335
633
|
if len(sig.parameters) > 0:
|
336
634
|
raise AgentDynamicTraitsFunctionError(
|
337
635
|
f"""The dynamic traits function {self.dynamic_traits_function} has too many parameters. It should have no parameters or
|
@@ -340,27 +638,33 @@ class Agent(Base):
|
|
340
638
|
|
341
639
|
@property
|
342
640
|
def traits(self) -> dict[str, str]:
|
343
|
-
"""
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
If
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
641
|
+
"""Get the agent's traits, potentially using dynamic generation.
|
642
|
+
|
643
|
+
This property provides access to the agent's traits, either from the stored
|
644
|
+
traits dictionary or by calling a dynamic traits function if one is defined.
|
645
|
+
If a dynamic traits function is used, it may take the current question as a
|
646
|
+
parameter to generate context-aware traits.
|
647
|
+
|
648
|
+
Returns:
|
649
|
+
dict: Dictionary of agent traits (key-value pairs)
|
650
|
+
|
651
|
+
Examples:
|
652
|
+
>>> a = Agent(traits={"age": 10, "hair": "brown", "height": 5.5})
|
653
|
+
>>> a.traits
|
654
|
+
{'age': 10, 'hair': 'brown', 'height': 5.5}
|
356
655
|
"""
|
357
656
|
if self.has_dynamic_traits_function:
|
657
|
+
# Check if the function expects a question parameter
|
358
658
|
sig = inspect.signature(self.dynamic_traits_function)
|
659
|
+
|
359
660
|
if "question" in sig.parameters:
|
661
|
+
# Call with the current question
|
360
662
|
return self.dynamic_traits_function(question=self.current_question)
|
361
663
|
else:
|
664
|
+
# Call without parameters
|
362
665
|
return self.dynamic_traits_function()
|
363
666
|
else:
|
667
|
+
# Return the stored traits
|
364
668
|
return dict(self._traits)
|
365
669
|
|
366
670
|
@contextmanager
|
@@ -383,8 +687,6 @@ class Agent(Base):
|
|
383
687
|
def traits(self, traits: dict[str, str]):
|
384
688
|
with self.modify_traits_context():
|
385
689
|
self._traits = traits
|
386
|
-
# self._check_before_modifying_traits()
|
387
|
-
# self._traits = AgentTraits(traits)
|
388
690
|
|
389
691
|
def rename(
|
390
692
|
self,
|
@@ -516,7 +818,6 @@ class Agent(Base):
|
|
516
818
|
'I am a direct answer.'
|
517
819
|
"""
|
518
820
|
if hasattr(self, "answer_question_directly"):
|
519
|
-
import warnings
|
520
821
|
|
521
822
|
warnings.warn(
|
522
823
|
"Warning: overwriting existing answer_question_directly method"
|
@@ -525,12 +826,6 @@ class Agent(Base):
|
|
525
826
|
self.validate_response = validate_response
|
526
827
|
self.translate_response = translate_response
|
527
828
|
|
528
|
-
# if not isinstance(method, DirectAnswerMethod):
|
529
|
-
# raise AgentDirectAnswerFunctionError(
|
530
|
-
# f"Method {method} does not match required signature. "
|
531
|
-
# "Must take (self, question, scenario) parameters."
|
532
|
-
# )
|
533
|
-
|
534
829
|
signature = inspect.signature(method)
|
535
830
|
for argument in ["question", "scenario", "self"]:
|
536
831
|
if argument not in signature.parameters:
|
@@ -568,9 +863,8 @@ class Agent(Base):
|
|
568
863
|
An invigator is an object that is responsible for administering a question to an agent and
|
569
864
|
recording the responses.
|
570
865
|
"""
|
571
|
-
from
|
572
|
-
|
573
|
-
from edsl.scenarios.Scenario import Scenario
|
866
|
+
from ..language_models import Model
|
867
|
+
from ..scenarios import Scenario
|
574
868
|
|
575
869
|
cache = cache
|
576
870
|
self.current_question = question
|
@@ -621,7 +915,7 @@ class Agent(Base):
|
|
621
915
|
|
622
916
|
>>> a = Agent(traits = {})
|
623
917
|
>>> a.add_direct_question_answering_method(lambda self, question, scenario: "I am a direct answer.")
|
624
|
-
>>> from edsl.questions
|
918
|
+
>>> from edsl.questions import QuestionFreeText
|
625
919
|
>>> q = QuestionFreeText.example()
|
626
920
|
>>> a.answer_question(question = q, cache = False).answer
|
627
921
|
'I am a direct answer.'
|
@@ -652,7 +946,7 @@ class Agent(Base):
|
|
652
946
|
This method returns the invigilator class that should be used to answer a question.
|
653
947
|
The invigilator class is determined by the type of question and the type of agent.
|
654
948
|
"""
|
655
|
-
from
|
949
|
+
from ..invigilators import (
|
656
950
|
InvigilatorHuman,
|
657
951
|
InvigilatorFunctional,
|
658
952
|
InvigilatorAI,
|
@@ -679,14 +973,14 @@ class Agent(Base):
|
|
679
973
|
key_lookup: Optional["KeyLookup"] = None,
|
680
974
|
) -> "InvigilatorBase":
|
681
975
|
"""Create an Invigilator."""
|
682
|
-
from
|
683
|
-
from
|
976
|
+
from ..language_models import Model
|
977
|
+
from ..scenarios import Scenario
|
684
978
|
|
685
979
|
model = model or Model()
|
686
980
|
scenario = scenario or Scenario()
|
687
981
|
|
688
982
|
if cache is None:
|
689
|
-
from
|
983
|
+
from ..caching import Cache
|
690
984
|
|
691
985
|
cache = Cache()
|
692
986
|
|
@@ -755,7 +1049,7 @@ class Agent(Base):
|
|
755
1049
|
>>> a1 + a1
|
756
1050
|
Traceback (most recent call last):
|
757
1051
|
...
|
758
|
-
edsl.exceptions.
|
1052
|
+
edsl.agents.exceptions.AgentCombinationError: The agents have overlapping traits: {'age'}.
|
759
1053
|
...
|
760
1054
|
>>> a1 = Agent(traits = {"age": 10}, codebook = {"age": "Their age is"})
|
761
1055
|
>>> a2 = Agent(traits = {"height": 5.5}, codebook = {"height": "Their height is"})
|
@@ -888,8 +1182,6 @@ class Agent(Base):
|
|
888
1182
|
>>> hash(Agent.example())
|
889
1183
|
2067581884874391607
|
890
1184
|
"""
|
891
|
-
from edsl.utilities.utilities import dict_hash
|
892
|
-
|
893
1185
|
return dict_hash(self.to_dict(add_edsl_version=False))
|
894
1186
|
|
895
1187
|
def to_dict(self, add_edsl_version=True) -> dict[str, Union[dict, bool]]:
|
@@ -1030,12 +1322,10 @@ class Agent(Base):
|
|
1030
1322
|
|
1031
1323
|
>>> a = Agent(traits = {"age": 10, "hair": "brown", "height": 5.5})
|
1032
1324
|
>>> print(a.code())
|
1033
|
-
from edsl.agents
|
1325
|
+
from edsl.agents import Agent
|
1034
1326
|
agent = Agent(traits={'age': 10, 'hair': 'brown', 'height': 5.5})
|
1035
1327
|
"""
|
1036
|
-
return (
|
1037
|
-
f"from edsl.agents.Agent import Agent\nagent = Agent(traits={self.traits})"
|
1038
|
-
)
|
1328
|
+
return f"from edsl.agents import Agent\nagent = Agent(traits={self.traits})"
|
1039
1329
|
|
1040
1330
|
|
1041
1331
|
def main():
|
@@ -1044,8 +1334,8 @@ def main():
|
|
1044
1334
|
|
1045
1335
|
WARNING: Consume API credits
|
1046
1336
|
"""
|
1047
|
-
from
|
1048
|
-
from
|
1337
|
+
from ..agents import Agent
|
1338
|
+
from ..questions import QuestionMultipleChoice
|
1049
1339
|
|
1050
1340
|
# a simple agent
|
1051
1341
|
agent = Agent(traits={"age": 10, "hair": "brown", "height": 5.5})
|